mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-29 04:33:04 +00:00
Merge branch 'master' into SpectreNIB
This commit is contained in:
commit
e69c777373
@ -24,6 +24,8 @@ namespace Activity {
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() {}
|
||||
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
|
@ -56,10 +56,10 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTy
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
|
||||
bool was_consumed = false;
|
||||
for(const auto &machine: machines_) {
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
|
||||
}
|
||||
return was_consumed;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed) final;
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
@ -322,8 +322,6 @@ struct MOS6526Storage {
|
||||
static constexpr int TestInputNow = 1 << 8;
|
||||
|
||||
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
|
||||
|
||||
bool active_ = false;
|
||||
} counter_[2];
|
||||
|
||||
static constexpr int InterruptInOne = 1 << 0;
|
||||
|
@ -46,19 +46,21 @@ class BusHandler {
|
||||
void perform_bus_cycle_phase2(const BusState &) {}
|
||||
};
|
||||
|
||||
enum Personality {
|
||||
enum class Personality {
|
||||
HD6845S, // Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length.
|
||||
// Considered exactly identical to the UM6845, so this enum covers both.
|
||||
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
|
||||
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
AMS40226 // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
AMS40226, // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
|
||||
|
||||
EGA, // Extended EGA-style CRTC; uses 16-bit addressing throughout.
|
||||
};
|
||||
|
||||
// https://www.pcjs.org/blog/2018/03/20/ advises that "the behavior of bits 5 and 6 [of register 10, the cursor start
|
||||
// register is really card specific".
|
||||
//
|
||||
// This enum captures those specifics.
|
||||
enum CursorType {
|
||||
enum class CursorType {
|
||||
/// No cursor signal is generated.
|
||||
None,
|
||||
/// MDA style: 00 => symmetric blinking; 01 or 10 => no blinking; 11 => short on, long off.
|
||||
@ -69,21 +71,20 @@ enum CursorType {
|
||||
|
||||
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
|
||||
|
||||
template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
template <class BusHandlerT, Personality personality, CursorType cursor_type> class CRTC6845 {
|
||||
public:
|
||||
|
||||
CRTC6845(Personality p, T &bus_handler) noexcept :
|
||||
personality_(p), bus_handler_(bus_handler), status_(0) {}
|
||||
CRTC6845(BusHandlerT &bus_handler) noexcept :
|
||||
bus_handler_(bus_handler), status_(0) {}
|
||||
|
||||
void select_register(uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
uint8_t get_status() {
|
||||
switch(personality_) {
|
||||
case UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
|
||||
case AMS40226: return get_register();
|
||||
default: return 0xff;
|
||||
switch(personality) {
|
||||
case Personality::UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
|
||||
case Personality::AMS40226: return get_register();
|
||||
default: return 0xff;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
@ -92,7 +93,7 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
if(selected_register_ == 31) status_ &= ~0x80;
|
||||
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
|
||||
|
||||
if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
|
||||
if(personality == Personality::UM6845R && selected_register_ == 31) return dummy_register_;
|
||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||
return registers_[selected_register_];
|
||||
}
|
||||
@ -100,11 +101,13 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
void set_register(uint8_t value) {
|
||||
static constexpr uint8_t masks[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
|
||||
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
|
||||
0xff, 0x1f, 0x7f, 0x1f,
|
||||
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
|
||||
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
|
||||
};
|
||||
|
||||
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
|
||||
if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
|
||||
if(selected_register_ == 8 && personality != Personality::UM6845R && personality != Personality::MC6845) {
|
||||
switch((value >> 4)&3) {
|
||||
default: display_skew_mask_ = 1; break;
|
||||
case 1: display_skew_mask_ = 2; break;
|
||||
@ -115,7 +118,7 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
if(selected_register_ < 16) {
|
||||
registers_[selected_register_] = value & masks[selected_register_];
|
||||
}
|
||||
if(selected_register_ == 31 && personality_ == UM6845R) {
|
||||
if(selected_register_ == 31 && personality == Personality::UM6845R) {
|
||||
dummy_register_ = value;
|
||||
}
|
||||
}
|
||||
@ -137,7 +140,7 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
}
|
||||
|
||||
perform_bus_cycle_phase1();
|
||||
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff;
|
||||
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & RefreshMask;
|
||||
|
||||
bus_state_.cursor = is_cursor_line_ &&
|
||||
bus_state_.refresh_address == (registers_[15] | (registers_[14] << 8));
|
||||
@ -162,9 +165,9 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
|
||||
// will end up counting as 16 as it won't be checked until after overflow.
|
||||
if(bus_state_.hsync) {
|
||||
switch(personality_) {
|
||||
case HD6845S:
|
||||
case UM6845R:
|
||||
switch(personality) {
|
||||
case Personality::HD6845S:
|
||||
case Personality::UM6845R:
|
||||
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
|
||||
hsync_counter_ = (hsync_counter_ + 1) & 15;
|
||||
break;
|
||||
@ -184,6 +187,8 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint16_t RefreshMask = (personality >= Personality::EGA) ? 0xffff : 0x3fff;
|
||||
|
||||
inline void perform_bus_cycle_phase1() {
|
||||
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
|
||||
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | unsigned(character_is_visible_);
|
||||
@ -207,9 +212,9 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
vsync_counter_ = (vsync_counter_ + 1) & 15;
|
||||
// On the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
|
||||
// always use a vertical sync count of 16.
|
||||
switch(personality_) {
|
||||
case HD6845S:
|
||||
case AMS40226:
|
||||
switch(personality) {
|
||||
case Personality::HD6845S:
|
||||
case Personality::AMS40226:
|
||||
bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
|
||||
break;
|
||||
default:
|
||||
@ -290,8 +295,7 @@ template <class T, CursorType cursor_type> class CRTC6845 {
|
||||
++bus_state_.field_count;
|
||||
}
|
||||
|
||||
Personality personality_;
|
||||
T &bus_handler_;
|
||||
BusHandlerT &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
||||
uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
@ -213,7 +213,7 @@ struct Line: public Command {
|
||||
}
|
||||
|
||||
private:
|
||||
int position_, numerator_, denominator_, duration_;
|
||||
int position_, numerator_, denominator_;
|
||||
};
|
||||
|
||||
// MARK: - Single pixel manipulation.
|
||||
|
@ -21,7 +21,7 @@ Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifie
|
||||
Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) :
|
||||
observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {}
|
||||
|
||||
bool Keyboard::set_key_pressed(Key key, char, bool is_pressed) {
|
||||
bool Keyboard::set_key_pressed(Key key, char, bool is_pressed, bool) {
|
||||
const size_t key_offset = size_t(key);
|
||||
if(key_offset >= key_states_.size()) {
|
||||
key_states_.resize(key_offset+1, false);
|
||||
|
@ -51,7 +51,7 @@ class Keyboard {
|
||||
// Host interface.
|
||||
|
||||
/// @returns @c true if the key press affects the machine; @c false otherwise.
|
||||
virtual bool set_key_pressed(Key key, char value, bool is_pressed);
|
||||
virtual bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat);
|
||||
virtual void reset_all_keys();
|
||||
|
||||
/// @returns a set of all Keys that this keyboard responds to.
|
||||
|
@ -225,9 +225,9 @@ class Chipset: private ClockingHint::Observer {
|
||||
uint16_t get_status();
|
||||
|
||||
private:
|
||||
uint16_t value = 0, reload = 0;
|
||||
uint16_t shift = 0, receive_shift = 0;
|
||||
uint16_t status;
|
||||
// uint16_t value = 0, reload = 0;
|
||||
// uint16_t shift = 0, receive_shift = 0;
|
||||
// uint16_t status;
|
||||
} serial_;
|
||||
|
||||
// MARK: - Pixel output.
|
||||
|
@ -96,21 +96,21 @@ class Keyboard {
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ShiftState {
|
||||
Shifting,
|
||||
AwaitingHandshake,
|
||||
Idle,
|
||||
} shift_state_ = ShiftState::Idle;
|
||||
// enum class ShiftState {
|
||||
// Shifting,
|
||||
// AwaitingHandshake,
|
||||
// Idle,
|
||||
// } shift_state_ = ShiftState::Idle;
|
||||
|
||||
enum class State {
|
||||
Startup,
|
||||
} state_ = State::Startup;
|
||||
// enum class State {
|
||||
// Startup,
|
||||
// } state_ = State::Startup;
|
||||
|
||||
int bit_phase_ = 0;
|
||||
uint32_t shift_sequence_ = 0;
|
||||
int bits_remaining_ = 0;
|
||||
// int bit_phase_ = 0;
|
||||
// uint32_t shift_sequence_ = 0;
|
||||
// int bits_remaining_ = 0;
|
||||
|
||||
uint8_t lines_ = 0;
|
||||
// uint8_t lines_ = 0;
|
||||
|
||||
Serial::Line<true> &output_;
|
||||
std::array<bool, 128> pressed_{};
|
||||
|
@ -583,7 +583,10 @@ class CRTCBusHandler {
|
||||
|
||||
InterruptTimer &interrupt_timer_;
|
||||
};
|
||||
using CRTC = Motorola::CRTC::CRTC6845<CRTCBusHandler, Motorola::CRTC::CursorType::None>;
|
||||
using CRTC = Motorola::CRTC::CRTC6845<
|
||||
CRTCBusHandler,
|
||||
Motorola::CRTC::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::None>;
|
||||
|
||||
/*!
|
||||
Holds and vends the current keyboard state, acting as the AY's port handler.
|
||||
@ -766,7 +769,7 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
z80_(*this),
|
||||
crtc_bus_handler_(ram_, interrupt_timer_),
|
||||
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
||||
crtc_(crtc_bus_handler_),
|
||||
i8255_port_handler_(key_state_, crtc_, ay_, tape_player_),
|
||||
i8255_(i8255_port_handler_),
|
||||
tape_player_(8000000),
|
||||
|
@ -276,10 +276,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
Keyboard(Processor *m6502) : m6502_(m6502) {}
|
||||
|
||||
void reset_all_keys() final {
|
||||
open_apple_is_pressed = closed_apple_is_pressed = control_is_pressed = shift_is_pressed = key_is_down = false;
|
||||
open_apple_is_pressed =
|
||||
closed_apple_is_pressed =
|
||||
control_is_pressed_ =
|
||||
shift_is_pressed_ =
|
||||
repeat_is_pressed_ =
|
||||
key_is_down_ =
|
||||
character_is_pressed_ = false;
|
||||
}
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed) final {
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final {
|
||||
if constexpr (!is_iie()) {
|
||||
if(is_repeat && !repeat_is_pressed_) return true;
|
||||
}
|
||||
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
switch(key) {
|
||||
case Key::Left: value = 0x08; break;
|
||||
@ -323,17 +333,27 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
case Key::LeftControl:
|
||||
control_is_pressed = is_pressed;
|
||||
control_is_pressed_ = is_pressed;
|
||||
return true;
|
||||
|
||||
case Key::LeftShift:
|
||||
case Key::RightShift:
|
||||
shift_is_pressed = is_pressed;
|
||||
shift_is_pressed_ = is_pressed;
|
||||
return true;
|
||||
|
||||
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
|
||||
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
|
||||
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
|
||||
case Key::F9: case Key::F10: case Key::F11:
|
||||
repeat_is_pressed_ = is_pressed;
|
||||
|
||||
if constexpr (!is_iie()) {
|
||||
if(is_pressed && (!is_repeat || character_is_pressed_)) {
|
||||
keyboard_input_ = uint8_t(last_pressed_character_ | 0x80);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case Key::F12:
|
||||
case Key::PrintScreen:
|
||||
case Key::ScrollLock:
|
||||
case Key::Pause:
|
||||
@ -356,10 +376,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
// Prior to the IIe, the keyboard could produce uppercase only.
|
||||
if(!is_iie()) value = char(toupper(value));
|
||||
|
||||
if(control_is_pressed && isalpha(value)) value &= 0xbf;
|
||||
if(control_is_pressed_ && isalpha(value)) value &= 0xbf;
|
||||
|
||||
// TODO: properly map IIe keys
|
||||
if(!is_iie() && shift_is_pressed) {
|
||||
if(!is_iie() && shift_is_pressed_) {
|
||||
switch(value) {
|
||||
case 0x27: value = 0x22; break; // ' -> "
|
||||
case 0x2c: value = 0x3c; break; // , -> <
|
||||
@ -383,11 +403,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
if(is_pressed) {
|
||||
keyboard_input = uint8_t(value | 0x80);
|
||||
key_is_down = true;
|
||||
last_pressed_character_ = value;
|
||||
character_is_pressed_ = true;
|
||||
keyboard_input_ = uint8_t(value | 0x80);
|
||||
key_is_down_ = true;
|
||||
} else {
|
||||
if((keyboard_input & 0x3f) == value) {
|
||||
key_is_down = false;
|
||||
if(value == last_pressed_character_) {
|
||||
character_is_pressed_ = false;
|
||||
}
|
||||
if((keyboard_input_ & 0x3f) == value) {
|
||||
key_is_down_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,24 +420,54 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
uint8_t get_keyboard_input() {
|
||||
if(string_serialiser) {
|
||||
return string_serialiser->head() | 0x80;
|
||||
if(string_serialiser_) {
|
||||
return string_serialiser_->head() | 0x80;
|
||||
} else {
|
||||
return keyboard_input;
|
||||
return keyboard_input_;
|
||||
}
|
||||
}
|
||||
|
||||
bool shift_is_pressed = false;
|
||||
bool control_is_pressed = false;
|
||||
void clear_keyboard_input() {
|
||||
keyboard_input_ &= 0x7f;
|
||||
if(string_serialiser_ && !string_serialiser_->advance()) {
|
||||
string_serialiser_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool get_key_is_down() {
|
||||
return key_is_down_;
|
||||
}
|
||||
|
||||
void set_string_serialiser(std::unique_ptr<Utility::StringSerialiser> &&serialiser) {
|
||||
string_serialiser_ = std::move(serialiser);
|
||||
}
|
||||
|
||||
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
|
||||
bool open_apple_is_pressed = false;
|
||||
bool closed_apple_is_pressed = false;
|
||||
uint8_t keyboard_input = 0x00;
|
||||
bool key_is_down = false;
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser;
|
||||
|
||||
private:
|
||||
Processor *const m6502_;
|
||||
// Current keyboard input register, as exposed to the programmer; on the IIe the programmer
|
||||
// can also poll for whether any key is currently down.
|
||||
uint8_t keyboard_input_ = 0x00;
|
||||
bool key_is_down_ = false;
|
||||
|
||||
// ASCII input state, referenced by the REPT key on models before the IIe.
|
||||
char last_pressed_character_ = 0;
|
||||
bool character_is_pressed_ = false;
|
||||
|
||||
// The repeat key itself.
|
||||
bool repeat_is_pressed_ = false;
|
||||
|
||||
// Modifier states.
|
||||
bool shift_is_pressed_ = false;
|
||||
bool control_is_pressed_ = false;
|
||||
|
||||
// A string serialiser for receiving copy and paste.
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
||||
|
||||
// 6502 connection, for applying the reset button.
|
||||
Processor *const m6502_;
|
||||
};
|
||||
Keyboard keyboard_;
|
||||
|
||||
@ -727,15 +782,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case 0xc010:
|
||||
keyboard_.keyboard_input &= 0x7f;
|
||||
if(keyboard_.string_serialiser) {
|
||||
if(!keyboard_.string_serialiser->advance())
|
||||
keyboard_.string_serialiser.reset();
|
||||
}
|
||||
keyboard_.clear_keyboard_input();
|
||||
|
||||
// On the IIe, reading C010 returns additional key info.
|
||||
if(is_iie() && isReadOperation(operation)) {
|
||||
*value = (keyboard_.key_is_down ? 0x80 : 0x00) | (keyboard_.keyboard_input & 0x7f);
|
||||
*value = (keyboard_.get_key_is_down() ? 0x80 : 0x00) | (keyboard_.get_keyboard_input() & 0x7f);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -879,7 +930,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) final {
|
||||
keyboard_.string_serialiser = std::make_unique<Utility::StringSerialiser>(string, true);
|
||||
keyboard_.set_string_serialiser(std::make_unique<Utility::StringSerialiser>(string, true));
|
||||
}
|
||||
|
||||
bool can_type(char c) const final {
|
||||
|
@ -39,7 +39,7 @@ template <typename TimeUnit> class VideoSwitches {
|
||||
set of potential flashing characters and alternate video modes.
|
||||
*/
|
||||
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
|
||||
character_zones_[0].xor_mask = 0;
|
||||
character_zones_[0].xor_mask = 0xff;
|
||||
character_zones_[0].address_mask = 0x3f;
|
||||
character_zones_[1].xor_mask = 0;
|
||||
character_zones_[1].address_mask = 0x3f;
|
||||
@ -49,7 +49,7 @@ template <typename TimeUnit> class VideoSwitches {
|
||||
character_zones_[3].address_mask = 0x3f;
|
||||
|
||||
if(is_iie) {
|
||||
character_zones_[0].xor_mask =
|
||||
character_zones_[1].xor_mask =
|
||||
character_zones_[2].xor_mask =
|
||||
character_zones_[3].xor_mask = 0xff;
|
||||
character_zones_[2].address_mask =
|
||||
@ -88,7 +88,7 @@ template <typename TimeUnit> class VideoSwitches {
|
||||
|
||||
if(alternative_character_set) {
|
||||
character_zones_[1].address_mask = 0xff;
|
||||
character_zones_[1].xor_mask = 0;
|
||||
character_zones_[1].xor_mask = 0xff;
|
||||
} else {
|
||||
character_zones_[1].address_mask = 0x3f;
|
||||
character_zones_[1].xor_mask = flash_mask();
|
||||
@ -286,7 +286,9 @@ template <typename TimeUnit> class VideoSwitches {
|
||||
// Update character set flashing; flashing is applied only when the alternative
|
||||
// character set is not selected.
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
character_zones_[1].xor_mask = flash_mask() * !internal_.alternative_character_set;
|
||||
if(!internal_.alternative_character_set) {
|
||||
character_zones_[1].xor_mask = flash_mask();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -38,7 +38,7 @@ class MemoryMap {
|
||||
|
||||
// Establish bank mapping.
|
||||
uint8_t next_region = 0;
|
||||
auto region = [&next_region, this]() -> uint8_t {
|
||||
auto region = [&]() -> uint8_t {
|
||||
assert(next_region != this->regions.size());
|
||||
return next_region++;
|
||||
};
|
||||
|
@ -75,12 +75,12 @@ class KeyboardMachine: public KeyActions {
|
||||
(i) if @c symbol can be typed and this is a key down, @c type_string it;
|
||||
(ii) if @c symbol cannot be typed, set @c key as @c is_pressed
|
||||
*/
|
||||
bool apply_key(Inputs::Keyboard::Key key, char symbol, bool is_pressed, bool map_logically) {
|
||||
bool apply_key(Inputs::Keyboard::Key key, char symbol, bool is_pressed, bool is_repeat, bool map_logically) {
|
||||
Inputs::Keyboard &keyboard = get_keyboard();
|
||||
|
||||
if(!map_logically) {
|
||||
// Try a regular keypress first, and stop if that works.
|
||||
if(keyboard.set_key_pressed(key, symbol, is_pressed)) {
|
||||
if(keyboard.set_key_pressed(key, symbol, is_pressed, is_repeat)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ class KeyboardMachine: public KeyActions {
|
||||
// That didn't work. Forward as a keypress. As, either:
|
||||
// (i) this is a key down, but doesn't have a symbol, or is an untypeable symbol; or
|
||||
// (ii) this is a key up, which it won't be an issue to miscommunicate.
|
||||
return keyboard.set_key_pressed(key, symbol, is_pressed);
|
||||
return keyboard.set_key_pressed(key, symbol, is_pressed, is_repeat);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ namespace PCCompatible {
|
||||
|
||||
class CGA {
|
||||
public:
|
||||
CGA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
|
||||
CGA() : crtc_(outputter_) {}
|
||||
|
||||
static constexpr uint32_t BaseAddress = 0xb'8000;
|
||||
static constexpr auto FontROM = ROM::Name::PCCompatibleCGAFont;
|
||||
@ -439,7 +439,10 @@ class CGA {
|
||||
return (source & 0x10) ? bright(result) : yellow_to_brown(result);
|
||||
}
|
||||
} outputter_;
|
||||
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
Motorola::CRTC::CRTC6845<
|
||||
CRTCOutputter,
|
||||
Motorola::CRTC::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
|
||||
int full_clock_ = 0;
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace PCCompatible {
|
||||
|
||||
class MDA {
|
||||
public:
|
||||
MDA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
|
||||
MDA() : crtc_(outputter_) {}
|
||||
|
||||
static constexpr uint32_t BaseAddress = 0xb'0000;
|
||||
static constexpr auto FontROM = ROM::Name::PCCompatibleMDAFont;
|
||||
@ -226,7 +226,10 @@ class MDA {
|
||||
|
||||
uint8_t control_ = 0;
|
||||
} outputter_;
|
||||
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
Motorola::CRTC::CRTC6845<
|
||||
CRTCOutputter,
|
||||
Motorola::CRTC::Personality::HD6845S,
|
||||
Motorola::CRTC::CursorType::MDA> crtc_;
|
||||
|
||||
int full_clock_ = 0;
|
||||
};
|
||||
|
@ -49,15 +49,12 @@
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
using VideoAdaptor = Analyser::Static::PCCompatible::Target::VideoAdaptor;
|
||||
using Target = Analyser::Static::PCCompatible::Target;
|
||||
|
||||
template <VideoAdaptor adaptor> struct Adaptor;
|
||||
template <> struct Adaptor<VideoAdaptor::MDA> {
|
||||
using type = MDA;
|
||||
};
|
||||
template <> struct Adaptor<VideoAdaptor::CGA> {
|
||||
using type = CGA;
|
||||
};
|
||||
// Map from members of the VideoAdaptor enum to concrete class types.
|
||||
template <Target::VideoAdaptor adaptor> struct Adaptor;
|
||||
template <> struct Adaptor<Target::VideoAdaptor::MDA> { using type = MDA; };
|
||||
template <> struct Adaptor<Target::VideoAdaptor::CGA> { using type = CGA; };
|
||||
|
||||
class FloppyController {
|
||||
public:
|
||||
@ -500,7 +497,7 @@ using PIT = i8253<false, PITObserver>;
|
||||
|
||||
class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
public:
|
||||
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, VideoAdaptor adaptor, int drive_count) :
|
||||
i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, Target::VideoAdaptor adaptor, int drive_count) :
|
||||
speaker_(speaker), keyboard_(keyboard) {
|
||||
// High switches:
|
||||
//
|
||||
@ -508,8 +505,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
// b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA)
|
||||
switch(adaptor) {
|
||||
default: break;
|
||||
case VideoAdaptor::MDA: high_switches_ |= 0b11; break;
|
||||
case VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns.
|
||||
case Target::VideoAdaptor::MDA: high_switches_ |= 0b11; break;
|
||||
case Target::VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns.
|
||||
}
|
||||
high_switches_ |= uint8_t(drive_count << 2);
|
||||
|
||||
@ -593,7 +590,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
};
|
||||
using PPI = Intel::i8255::i8255<i8255PortHandler>;
|
||||
|
||||
template <VideoAdaptor video>
|
||||
template <Target::VideoAdaptor video>
|
||||
class IO {
|
||||
public:
|
||||
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor<video>::type &card, FloppyController &fdc, RTC &rtc) :
|
||||
@ -601,7 +598,7 @@ class IO {
|
||||
|
||||
template <typename IntT> void out(uint16_t port, IntT value) {
|
||||
static constexpr uint16_t crtc_base =
|
||||
video == VideoAdaptor::MDA ? 0x03b0 : 0x03d0;
|
||||
video == Target::VideoAdaptor::MDA ? 0x03b0 : 0x03d0;
|
||||
|
||||
switch(port) {
|
||||
default:
|
||||
@ -772,13 +769,13 @@ class IO {
|
||||
case 0x03f5: return fdc_.read();
|
||||
|
||||
case 0x03b8:
|
||||
if constexpr (video == VideoAdaptor::MDA) {
|
||||
if constexpr (video == Target::VideoAdaptor::MDA) {
|
||||
return video_.template read<0x8>();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3da:
|
||||
if constexpr (video == VideoAdaptor::CGA) {
|
||||
if constexpr (video == Target::VideoAdaptor::CGA) {
|
||||
return video_.template read<0xa>();
|
||||
}
|
||||
break;
|
||||
@ -860,7 +857,7 @@ class FlowController {
|
||||
bool halted_ = false;
|
||||
};
|
||||
|
||||
template <VideoAdaptor video, bool turbo>
|
||||
template <Target::VideoAdaptor video>
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public MachineTypes::TimedMachine,
|
||||
@ -887,6 +884,9 @@ class ConcreteMachine:
|
||||
ppi_(ppi_handler_),
|
||||
context(pit_, dma_, ppi_, pic_, video_, fdc_, rtc_)
|
||||
{
|
||||
// Capture speed.
|
||||
speed_ = target.speed;
|
||||
|
||||
// Set up DMA source/target.
|
||||
dma_.set_memory(&context.memory);
|
||||
|
||||
@ -900,17 +900,21 @@ class ConcreteMachine:
|
||||
const auto tick = ROM::Name::PCCompatibleGLaTICK;
|
||||
const auto font = Video::FontROM;
|
||||
|
||||
ROM::Request request = ROM::Request(bios) && ROM::Request(tick) && ROM::Request(font);
|
||||
ROM::Request request = ROM::Request(bios) && ROM::Request(tick, true) && ROM::Request(font);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
// A BIOS is mandatory.
|
||||
const auto &bios_contents = roms.find(bios)->second;
|
||||
context.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
|
||||
|
||||
const auto &tick_contents = roms.find(tick)->second;
|
||||
context.memory.install(0xd'0000, tick_contents.data(), tick_contents.size());
|
||||
// If found, install GlaTICK at 0xd'0000.
|
||||
auto tick_contents = roms.find(tick);
|
||||
if(tick_contents != roms.end()) {
|
||||
context.memory.install(0xd'0000, tick_contents->second.data(), tick_contents->second.size());
|
||||
}
|
||||
|
||||
// Give the video card something to read from.
|
||||
const auto &font_contents = roms.find(font)->second;
|
||||
@ -926,15 +930,23 @@ class ConcreteMachine:
|
||||
|
||||
// MARK: - TimedMachine.
|
||||
void run_for(const Cycles duration) override {
|
||||
switch(speed_) {
|
||||
case Target::Speed::ApproximatelyOriginal: run_for<Target::Speed::ApproximatelyOriginal>(duration); break;
|
||||
case Target::Speed::Fast: run_for<Target::Speed::Fast>(duration); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <Target::Speed speed>
|
||||
void run_for(const Cycles duration) {
|
||||
const auto pit_ticks = duration.as<int>();
|
||||
|
||||
int ticks;
|
||||
if constexpr (!turbo) {
|
||||
if constexpr (speed == Target::Speed::Fast) {
|
||||
ticks = pit_ticks;
|
||||
} else {
|
||||
cpu_divisor_ += pit_ticks;
|
||||
ticks = cpu_divisor_ / 3;
|
||||
cpu_divisor_ %= 3;
|
||||
} else {
|
||||
ticks = pit_ticks;
|
||||
}
|
||||
|
||||
while(ticks--) {
|
||||
@ -948,7 +960,9 @@ class ConcreteMachine:
|
||||
pit_.run_for(1);
|
||||
++speaker_.cycles_since_update;
|
||||
|
||||
if constexpr (!turbo) {
|
||||
// For original speed, the CPU performs instructions at a 1/3rd divider of the PIT clock,
|
||||
// so run the PIT three times per 'tick'.
|
||||
if constexpr (speed == Target::Speed::ApproximatelyOriginal) {
|
||||
pit_.run_for(1);
|
||||
++speaker_.cycles_since_update;
|
||||
pit_.run_for(1);
|
||||
@ -958,7 +972,7 @@ class ConcreteMachine:
|
||||
//
|
||||
// Advance CRTC at a more approximate rate.
|
||||
//
|
||||
video_.run_for(turbo ? Cycles(1) : Cycles(3));
|
||||
video_.run_for(speed == Target::Speed::Fast ? Cycles(1) : Cycles(3));
|
||||
|
||||
//
|
||||
// Give the keyboard a notification of passing time; it's very approximately clocked,
|
||||
@ -992,8 +1006,8 @@ class ConcreteMachine:
|
||||
continue;
|
||||
}
|
||||
|
||||
if constexpr (turbo) {
|
||||
// There's no divider applied, so this makes for 2*PI = around 2.4 MIPS.
|
||||
if constexpr (speed == Target::Speed::Fast) {
|
||||
// There's no divider applied, so this makes for 2*PIT = around 2.4 MIPS.
|
||||
// That's broadly 80286 speed, if MIPS were a valid measure.
|
||||
perform_instruction();
|
||||
perform_instruction();
|
||||
@ -1176,6 +1190,7 @@ class ConcreteMachine:
|
||||
std::pair<int, InstructionSet::x86::Instruction<false>> decoded;
|
||||
|
||||
int cpu_divisor_ = 0;
|
||||
Target::Speed speed_{};
|
||||
};
|
||||
|
||||
|
||||
@ -1185,21 +1200,12 @@ using namespace PCCompatible;
|
||||
|
||||
// See header; constructs and returns an instance of the Amstrad CPC.
|
||||
Machine *Machine::PCCompatible(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::PCCompatible::Target;
|
||||
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
||||
|
||||
if(pc_target->speed == Target::Speed::Fast) {
|
||||
switch(pc_target->adaptor) {
|
||||
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA, true>(*pc_target, rom_fetcher);
|
||||
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA, true>(*pc_target, rom_fetcher);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
switch(pc_target->adaptor) {
|
||||
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA, false>(*pc_target, rom_fetcher);
|
||||
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA, false>(*pc_target, rom_fetcher);
|
||||
default: return nullptr;
|
||||
}
|
||||
switch(pc_target->adaptor) {
|
||||
case Target::VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<Target::VideoAdaptor::MDA>(*pc_target, rom_fetcher);
|
||||
case Target::VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<Target::VideoAdaptor::CGA>(*pc_target, rom_fetcher);
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -581,6 +581,13 @@ Description::Description(Name name) {
|
||||
case Name::PCCompatibleMDAFont:
|
||||
*this = Description(name, "PCCompatible", "IBM's MDA font", "EUMDA9.F14", 14 * 256, 0x7754882au);
|
||||
break;
|
||||
case Name::PCCompatibleEGABIOS:
|
||||
*this = Description(name, "PCCompatible", "IBM's EGA BIOS", "ibm_6277356_ega_card_u44_27128.bin", 16 * 1024, 0x2f2fbc40u);
|
||||
break;
|
||||
case Name::PCCompatibleVGABIOS:
|
||||
*this = Description(name, "PCCompatible", "IBM's VGA BIOS", "ibm_vga.bin", 32 * 1024, 0x03b3f90du);
|
||||
break;
|
||||
|
||||
|
||||
// TODO: CRCs below are incomplete, at best.
|
||||
case Name::MSXGenericBIOS: *this = Description(name, "MSX", "a generix MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break;
|
||||
|
@ -135,8 +135,11 @@ enum Name {
|
||||
PCCompatibleGLaBIOS,
|
||||
PCCompatibleGLaTICK,
|
||||
PCCompatiblePhoenix80286BIOS,
|
||||
|
||||
PCCompatibleMDAFont,
|
||||
PCCompatibleCGAFont,
|
||||
PCCompatibleEGABIOS,
|
||||
PCCompatibleVGABIOS,
|
||||
|
||||
// Sinclair QL.
|
||||
SinclairQLJS,
|
||||
|
@ -6592,6 +6592,7 @@
|
||||
"$(inherited)",
|
||||
IGNORE_APPLE,
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(USER_LIBRARY_DIR)/Frameworks/SDL2.framework/Headers";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(USER_LIBRARY_DIR)/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -6613,6 +6614,7 @@
|
||||
NDEBUG,
|
||||
IGNORE_APPLE,
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(USER_LIBRARY_DIR)/Frameworks/SDL2.framework/Headers";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(USER_LIBRARY_DIR)/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
@ -345,14 +345,14 @@ class MachineDocument:
|
||||
/// Forwards key down events directly to the machine.
|
||||
func keyDown(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
|
||||
machine.setKey(event.keyCode, characters: event.characters, isPressed: true, isRepeat: event.isARepeat)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards key up events directly to the machine.
|
||||
func keyUp(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
|
||||
machine.setKey(event.keyCode, characters: event.characters, isPressed: false, isRepeat: false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,19 +361,19 @@ class MachineDocument:
|
||||
if let machine = self.machine {
|
||||
if newModifiers.modifierFlags.contains(.shift) != shiftIsDown {
|
||||
shiftIsDown = newModifiers.modifierFlags.contains(.shift)
|
||||
machine.setKey(VK_Shift, characters: nil, isPressed: shiftIsDown)
|
||||
machine.setKey(VK_Shift, characters: nil, isPressed: shiftIsDown, isRepeat: false)
|
||||
}
|
||||
if newModifiers.modifierFlags.contains(.control) != controlIsDown {
|
||||
controlIsDown = newModifiers.modifierFlags.contains(.control)
|
||||
machine.setKey(VK_Control, characters: nil, isPressed: controlIsDown)
|
||||
machine.setKey(VK_Control, characters: nil, isPressed: controlIsDown, isRepeat: false)
|
||||
}
|
||||
if newModifiers.modifierFlags.contains(.command) != commandIsDown {
|
||||
commandIsDown = newModifiers.modifierFlags.contains(.command)
|
||||
machine.setKey(VK_Command, characters: nil, isPressed: commandIsDown)
|
||||
machine.setKey(VK_Command, characters: nil, isPressed: commandIsDown, isRepeat: false)
|
||||
}
|
||||
if newModifiers.modifierFlags.contains(.option) != optionIsDown {
|
||||
optionIsDown = newModifiers.modifierFlags.contains(.option)
|
||||
machine.setKey(VK_Option, characters: nil, isPressed: optionIsDown)
|
||||
machine.setKey(VK_Option, characters: nil, isPressed: optionIsDown, isRepeat: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -769,11 +769,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>23.12.26</string>
|
||||
<string>23.12.28</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>23.12.26</string>
|
||||
<string>23.12.28</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.entertainment</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
@ -67,7 +67,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
|
||||
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
|
||||
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed isRepeat:(BOOL)isRepeat;
|
||||
- (void)clearAllKeys;
|
||||
|
||||
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed;
|
||||
|
@ -366,7 +366,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
[self updateJoystickTimer];
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed isRepeat:(BOOL)isRepeat {
|
||||
[self applyInputEvent:^{
|
||||
auto keyboard_machine = self->_machine->keyboard_machine();
|
||||
if(keyboard_machine && (self.inputMode != CSMachineKeyboardInputModeJoystick || !keyboard_machine->get_keyboard().is_exclusive())) {
|
||||
@ -437,7 +437,13 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
if(keyboard_machine->apply_key(mapped_key, pressedKey, isPressed, self.inputMode == CSMachineKeyboardInputModeKeyboardLogical)) {
|
||||
if(keyboard_machine->apply_key(
|
||||
mapped_key,
|
||||
pressedKey,
|
||||
isPressed,
|
||||
isRepeat,
|
||||
self.inputMode == CSMachineKeyboardInputModeKeyboardLogical)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
QT += core gui multimedia widgets
|
||||
greaterThan(5, QT_MAJOR_VERSION) QT += openglwidgets
|
||||
|
||||
# Be specific about C++17 but also try the vaguer C++1z for older
|
||||
# versions of Qt.
|
||||
@ -24,10 +25,10 @@ DEFINES += TARGET_QT
|
||||
DEFINES += IGNORE_APPLE
|
||||
QMAKE_CXXFLAGS_RELEASE += -DNDEBUG
|
||||
|
||||
# Generate warnings for any use of APIs deprecated prior to Qt 6.0.0.
|
||||
# Development was performed against Qt 5.14.
|
||||
# Generate warnings for any use of APIs deprecated prior to Qt 7.0.0.
|
||||
# Development was performed against Qt 6.6.1 and Qt 5.15.2
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x070000
|
||||
|
||||
SRC = $$PWD/../..
|
||||
|
||||
|
@ -2,11 +2,17 @@
|
||||
#include "settings.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QAudioDevice>
|
||||
#include <QMediaDevices>
|
||||
#endif
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
@ -314,24 +320,44 @@ void MainWindow::launchMachine() {
|
||||
static constexpr size_t samplesPerBuffer = 256; // TODO: select this dynamically.
|
||||
const auto speaker = audio_producer->get_speaker();
|
||||
if(speaker) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QAudioDevice device(QMediaDevices::defaultAudioOutput());
|
||||
if(true) { // TODO: how to check that audio output is available in Qt6?
|
||||
QAudioFormat idealFormat = device.preferredFormat();
|
||||
#else
|
||||
const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();
|
||||
if(!defaultDeviceInfo.isNull()) {
|
||||
QAudioFormat idealFormat = defaultDeviceInfo.preferredFormat();
|
||||
#endif
|
||||
|
||||
// Use the ideal format's sample rate, provide stereo as long as at least two channels
|
||||
// are available, and — at least for now — assume a good buffer size.
|
||||
audioIsStereo = (idealFormat.channelCount() > 1) && speaker->get_is_stereo();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
audioIs8bit = idealFormat.sampleFormat() == QAudioFormat::UInt8;
|
||||
#else
|
||||
audioIs8bit = idealFormat.sampleSize() < 16;
|
||||
#endif
|
||||
|
||||
idealFormat.setChannelCount(1 + int(audioIsStereo));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
idealFormat.setSampleFormat(audioIs8bit ? QAudioFormat::UInt8 : QAudioFormat::Int16);
|
||||
#else
|
||||
idealFormat.setSampleSize(audioIs8bit ? 8 : 16);
|
||||
#endif
|
||||
|
||||
speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo);
|
||||
speaker->set_delegate(this);
|
||||
|
||||
audioThread.start();
|
||||
audioThread.performAsync([this, idealFormat] {
|
||||
audioThread.performAsync([&] {
|
||||
// Create an audio output.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
audioOutput = std::make_unique<QAudioSink>(device, idealFormat);
|
||||
#else
|
||||
audioOutput = std::make_unique<QAudioOutput>(idealFormat);
|
||||
#endif
|
||||
|
||||
// Start the output. The additional `audioBuffer` is meant to minimise latency,
|
||||
// believe it or not, given Qt's semantics.
|
||||
@ -373,13 +399,17 @@ void MainWindow::launchMachine() {
|
||||
QAction *const asKeyboardAction = new QAction(tr("Use Keyboard as Keyboard"), this);
|
||||
asKeyboardAction->setCheckable(true);
|
||||
asKeyboardAction->setChecked(true);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
asKeyboardAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K));
|
||||
#endif
|
||||
inputMenu->addAction(asKeyboardAction);
|
||||
|
||||
QAction *const asJoystickAction = new QAction(tr("Use Keyboard as Joystick"), this);
|
||||
asJoystickAction->setCheckable(true);
|
||||
asJoystickAction->setChecked(false);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
asJoystickAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J));
|
||||
#endif
|
||||
inputMenu->addAction(asJoystickAction);
|
||||
|
||||
connect(asKeyboardAction, &QAction::triggered, this, [=] {
|
||||
@ -755,12 +785,13 @@ void MainWindow::dropEvent(QDropEvent* event) {
|
||||
|
||||
QString unusedRoms;
|
||||
for(const auto &url: event->mimeData()->urls()) {
|
||||
const char *const name = url.toLocalFile().toUtf8();
|
||||
FILE *const file = fopen(name, "rb");
|
||||
const std::string name = url.toLocalFile().toStdString();
|
||||
FILE *const file = fopen(name.c_str(), "rb");
|
||||
if(!file) continue;
|
||||
const auto contents = fileContentsAndClose(file);
|
||||
if(!contents) continue;
|
||||
|
||||
|
||||
CRC::CRC32 generator;
|
||||
const uint32_t crc = generator.compute_crc(*contents);
|
||||
|
||||
@ -889,7 +920,7 @@ bool MainWindow::processEvent(QKeyEvent *event) {
|
||||
if(!keyboardMachine) return true;
|
||||
|
||||
auto &keyboard = keyboardMachine->get_keyboard();
|
||||
keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed);
|
||||
keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed, event->isAutoRepeat());
|
||||
if(keyboard.is_exclusive() || keyboard.observed_keys().find(*key) != keyboard.observed_keys().end()) {
|
||||
return false;
|
||||
}
|
||||
@ -962,6 +993,7 @@ void MainWindow::setButtonPressed(int index, bool isPressed) {
|
||||
#include "../../Analyser/Static/Macintosh/Target.hpp"
|
||||
#include "../../Analyser/Static/MSX/Target.hpp"
|
||||
#include "../../Analyser/Static/Oric/Target.hpp"
|
||||
#include "../../Analyser/Static/PCCompatible/Target.hpp"
|
||||
#include "../../Analyser/Static/ZX8081/Target.hpp"
|
||||
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
|
||||
|
||||
@ -984,6 +1016,7 @@ void MainWindow::startMachine() {
|
||||
TEST(macintosh);
|
||||
TEST(msx);
|
||||
TEST(oric);
|
||||
TEST(pc);
|
||||
TEST(spectrum);
|
||||
TEST(vic20);
|
||||
TEST(zx80);
|
||||
@ -1182,6 +1215,23 @@ void MainWindow::start_oric() {
|
||||
launchTarget(std::move(target));
|
||||
}
|
||||
|
||||
void MainWindow::start_pc() {
|
||||
using Target = Analyser::Static::PCCompatible::Target;
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
switch(ui->pcSpeedComboBox->currentIndex()) {
|
||||
default: target->speed = Target::Speed::ApproximatelyOriginal; break;
|
||||
case 1: target->speed = Target::Speed::Fast; break;
|
||||
}
|
||||
|
||||
switch(ui->pcVideoAdaptorComboBox->currentIndex()) {
|
||||
default: target->adaptor = Target::VideoAdaptor::MDA; break;
|
||||
case 1: target->adaptor = Target::VideoAdaptor::CGA; break;
|
||||
}
|
||||
|
||||
launchTarget(std::move(target));
|
||||
}
|
||||
|
||||
void MainWindow::start_spectrum() {
|
||||
using Target = Analyser::Static::ZXSpectrum::Target;
|
||||
auto target = std::make_unique<Target>();
|
||||
|
@ -1,12 +1,18 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QAudioOutput>
|
||||
#include <QtGlobal>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QAudioSink>
|
||||
#else
|
||||
#include <QAudioOutput>
|
||||
#endif
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include "audiobuffer.h"
|
||||
#include "timer.h"
|
||||
@ -71,7 +77,11 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
std::unique_ptr<Machine::DynamicMachine> machine;
|
||||
std::mutex machineMutex;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
std::unique_ptr<QAudioSink> audioOutput;
|
||||
#else
|
||||
std::unique_ptr<QAudioOutput> audioOutput;
|
||||
#endif
|
||||
bool audioIs8bit = false, audioIsStereo = false;
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override;
|
||||
AudioBuffer audioBuffer;
|
||||
@ -96,6 +106,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
void start_macintosh();
|
||||
void start_msx();
|
||||
void start_oric();
|
||||
void start_pc();
|
||||
void start_spectrum();
|
||||
void start_vic20();
|
||||
void start_zx80();
|
||||
|
@ -852,6 +852,76 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="pcTab">
|
||||
<attribute name="title">
|
||||
<string>PC Compatible</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Video Adaptor:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="pcVideoAdaptorComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>MDA</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>CGA</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Speed:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="pcSpeedComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Similar to Original</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Turbo</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="vic20Tab">
|
||||
<attribute name="title">
|
||||
<string>Vic-20</string>
|
||||
@ -1170,9 +1240,6 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="missingROMsBox">
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <QApplication>
|
||||
#include <QCursor>
|
||||
#include <QDebug>
|
||||
#include <QDesktopWidget>
|
||||
#include <QGuiApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
@ -30,10 +29,7 @@ void ScanTargetWidget::paintGL() {
|
||||
requestedRedrawTime = 0;
|
||||
}
|
||||
|
||||
// TODO: if Qt 5.14 can be guaranteed, just use window()->screen().
|
||||
const auto screenNumber = QApplication::desktop()->screenNumber(this);
|
||||
QScreen *const screen = QGuiApplication::screens()[screenNumber];
|
||||
|
||||
QScreen *const screen = window()->screen();
|
||||
const float newOutputScale = float(screen->devicePixelRatio());
|
||||
if(outputScale != newOutputScale) {
|
||||
outputScale = newOutputScale;
|
||||
|
@ -976,9 +976,11 @@ int main(int argc, char *argv[]) {
|
||||
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
|
||||
SDL_Keycode keycode = SDLK_UNKNOWN;
|
||||
bool is_down = true;
|
||||
bool repeat = false;
|
||||
|
||||
KeyPress(uint32_t timestamp, const char *text) : timestamp(timestamp), input(text) {}
|
||||
KeyPress(uint32_t timestamp, SDL_Scancode scancode, SDL_Keycode keycode, bool is_down) : timestamp(timestamp), scancode(scancode), keycode(keycode), is_down(is_down) {}
|
||||
KeyPress(uint32_t timestamp, SDL_Scancode scancode, SDL_Keycode keycode, bool is_down, bool repeat) :
|
||||
timestamp(timestamp), scancode(scancode), keycode(keycode), is_down(is_down), repeat(repeat) {}
|
||||
KeyPress() {}
|
||||
};
|
||||
std::vector<KeyPress> keypresses;
|
||||
@ -1129,7 +1131,12 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
keypresses.emplace_back(event.text.timestamp, event.key.keysym.scancode, event.key.keysym.sym, event.type == SDL_KEYDOWN);
|
||||
keypresses.emplace_back(
|
||||
event.text.timestamp,
|
||||
event.key.keysym.scancode,
|
||||
event.key.keysym.sym,
|
||||
event.type == SDL_KEYDOWN,
|
||||
event.key.repeat);
|
||||
} break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
@ -1214,14 +1221,14 @@ int main(int argc, char *argv[]) {
|
||||
// is sufficiently untested on SDL, and somewhat too reliant on empirical timestamp behaviour,
|
||||
// for it to be trustworthy enough otherwise to expose.
|
||||
if(logical_keyboard) {
|
||||
if(keyboard_machine->apply_key(key, keypress.input.size() ? keypress.input[0] : 0, keypress.is_down, logical_keyboard)) {
|
||||
if(keyboard_machine->apply_key(key, keypress.input.size() ? keypress.input[0] : 0, keypress.is_down, keypress.repeat, logical_keyboard)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// This is a slightly terrible way of obtaining a symbol for the key, e.g. for letters it will always return
|
||||
// the capital letter version, at least empirically. But it'll have to do for now.
|
||||
const char *key_name = SDL_GetKeyName(keypress.keycode);
|
||||
if(keyboard_machine->get_keyboard().set_key_pressed(key, (strlen(key_name) == 1) ? key_name[0] : 0, keypress.is_down)) {
|
||||
if(keyboard_machine->get_keyboard().set_key_pressed(key, (strlen(key_name) == 1) ? key_name[0] : 0, keypress.is_down, keypress.repeat)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,24 @@
|
||||
Expected files:
|
||||
|
||||
GLABIOS_0.2.5_8T.ROM — the 8088 GLaBIOS ROM.
|
||||
GLaTICK_0.8.5_AT.ROM — the GLaBIOS AT RTC option ROM.
|
||||
Phoenix 80286 ROM BIOS Version 3.05.bin — Phoenix's 80286 AT-clone BIOS.
|
||||
EUMDA9.F14 — a dump of the MDA font.
|
||||
CGA.F08 — a dump of the CGA font.
|
||||
For XT-class emulation:
|
||||
|
||||
GLABIOS_0.2.5_8T.ROM — the 8088 GLaBIOS ROM.
|
||||
GLaTICK_0.8.5_AT.ROM (optionally) — the GLaBIOS AT RTC option ROM.
|
||||
|
||||
For specific video cards:
|
||||
|
||||
EUMDA9.F14 — the MDA font.
|
||||
CGA.F08 — the CGA font.
|
||||
|
||||
In the future:
|
||||
|
||||
Phoenix 80286 ROM BIOS Version 3.05.bin — Phoenix's 80286 AT-clone BIOS.
|
||||
ibm_vga.bin — the VGA BIOS.
|
||||
ibm_6277356_ega_card_u44_27128.bin — the EGA BIOS.
|
||||
|
||||
|
||||
GLaBIOS is an open-source GPLv3 alternative BIOS for XT clones, available from https://glabios.org/
|
||||
|
||||
GLaTICK is a real-time clock option ROM, also available from available from https://glabios.org/
|
||||
|
||||
The MDA and CGA fonts are in the form offered at https://github.com/viler-int10h/vga-text-mode-fonts i.e. it's 256 lots of 14 bytes, the first 14 being the content of character 0, the next 14 being the content of character 1, etc.
|
||||
The MDA and CGA fonts are in the form offered at https://github.com/viler-int10h/vga-text-mode-fonts i.e. the MDA font is 256 lots of 14 bytes, the first 14 being the content of character 0, the next 14 being the content of character 1, etc; the CGA font is 256 lots of 8 bytes with a corresponding arrangement.
|
@ -44,6 +44,7 @@ const Time FMBitLength = Time(1, 50000);
|
||||
|
||||
constexpr Time bit_length(Density density) {
|
||||
switch(density) {
|
||||
default:
|
||||
case Density::Single: return FMBitLength;
|
||||
case Density::Double: return MFMBitLength;
|
||||
case Density::High: return HDMFMBitLength;
|
||||
|
@ -382,6 +382,7 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::TrackWithSectors(
|
||||
std::optional<uint8_t> sector_gap_filler_byte
|
||||
) {
|
||||
switch(density) {
|
||||
default:
|
||||
case Density::Single: return TTrackWithSectors<Density::Single>(sectors, sector_gap_length, sector_gap_filler_byte);
|
||||
case Density::Double: return TTrackWithSectors<Density::Double>(sectors, sector_gap_length, sector_gap_filler_byte);
|
||||
case Density::High: return TTrackWithSectors<Density::High>(sectors, sector_gap_length, sector_gap_filler_byte);
|
||||
|
@ -33,7 +33,6 @@ void Parser::install_track(const Storage::Disk::Track::Address &address) {
|
||||
std::map<int, Storage::Encodings::MFM::Sector> sectors_by_id;
|
||||
if(density_) {
|
||||
append(parse_track(*track, *density_), sectors_by_id);
|
||||
return;
|
||||
} else {
|
||||
// Just try all three in succession.
|
||||
append(parse_track(*track, Density::Single), sectors_by_id);
|
||||
|
Loading…
x
Reference in New Issue
Block a user