1
0
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:
Thomas Harte 2024-01-02 09:17:37 -05:00
commit e69c777373
35 changed files with 400 additions and 167 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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};

View File

@ -213,7 +213,7 @@ struct Line: public Command {
}
private:
int position_, numerator_, denominator_, duration_;
int position_, numerator_, denominator_;
};
// MARK: - Single pixel manipulation.

View File

@ -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);

View File

@ -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.

View File

@ -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.

View File

@ -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_{};

View File

@ -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),

View File

@ -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 {

View File

@ -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:

View File

@ -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++;
};

View File

@ -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);
}
}
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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;
}
}

View File

@ -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;

View File

@ -135,8 +135,11 @@ enum Name {
PCCompatibleGLaBIOS,
PCCompatibleGLaTICK,
PCCompatiblePhoenix80286BIOS,
PCCompatibleMDAFont,
PCCompatibleCGAFont,
PCCompatibleEGABIOS,
PCCompatibleVGABIOS,
// Sinclair QL.
SinclairQLJS,

View File

@ -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;

View File

@ -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)
}
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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/../..

View File

@ -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>();

View File

@ -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();

View File

@ -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>

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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);

View File

@ -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);