diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 7fe020af2..e2c2e2823 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -138,7 +138,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format( "hfe", result.disks, Disk::DiskImageHolder, - TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) + TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // HFE (TODO: switch to AllDisk once the MSX stops being so greedy) Format("img", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) Format("image", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) diff --git a/Machines/Apple/AppleIIgs/Video.cpp b/Machines/Apple/AppleIIgs/Video.cpp index 094610c20..98ab09c04 100644 --- a/Machines/Apple/AppleIIgs/Video.cpp +++ b/Machines/Apple/AppleIIgs/Video.cpp @@ -113,6 +113,10 @@ Video::Video() : crt_.set_display_type(Outputs::Display::DisplayType::RGB); crt_.set_visible_area(Outputs::Display::Rect(0.097f, 0.1f, 0.85f, 0.85f)); + // Reduce the initial bounce by cueing up the part of the frame that initial drawing actually + // starts with. More or less. + crt_.output_blank(228*63*2); + // Establish the shift lookup table for NTSC -> RGB output. for(size_t c = 0; c < sizeof(ntsc_delay_lookup_) / sizeof(*ntsc_delay_lookup_); c++) { const auto old_delay = c >> 2; diff --git a/Machines/Sinclair/Keyboard/Keyboard.cpp b/Machines/Sinclair/Keyboard/Keyboard.cpp index 3677adaca..686179835 100644 --- a/Machines/Sinclair/Keyboard/Keyboard.cpp +++ b/Machines/Sinclair/Keyboard/Keyboard.cpp @@ -68,10 +68,76 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { CharacterMapper::CharacterMapper(Machine machine) : machine_(machine) {} const uint16_t *CharacterMapper::sequence_for_character(char character) const { -#define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} -#define SHIFT(...) {KeyShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} -#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped} - static KeySequence zx81_key_sequences[] = { +#define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +#define SHIFT(...) {KeyShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +#define SYMSHIFT(...) {KeySymbolShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped} + constexpr KeySequence spectrum_key_sequences[] = { + /* NUL */ X, /* SOH */ X, + /* STX */ X, /* ETX */ X, + /* EOT */ X, /* ENQ */ X, + /* ACK */ X, /* BEL */ X, + /* BS */ SHIFT(Key0), /* HT */ X, + /* LF */ KEYS(KeyEnter), /* VT */ X, + /* FF */ X, /* CR */ KEYS(KeyEnter), + /* SO */ X, /* SI */ X, + /* DLE */ X, /* DC1 */ X, + /* DC2 */ X, /* DC3 */ X, + /* DC4 */ X, /* NAK */ X, + /* SYN */ X, /* ETB */ X, + /* CAN */ X, /* EM */ X, + /* SUB */ X, /* ESC */ X, + /* FS */ X, /* GS */ X, + /* RS */ X, /* US */ X, + /* space */ KEYS(KeySpace), /* ! */ SYMSHIFT(Key1), + /* " */ SYMSHIFT(KeyP), /* # */ SYMSHIFT(Key3), + /* $ */ SYMSHIFT(Key4), /* % */ SYMSHIFT(Key5), + /* & */ SYMSHIFT(Key6), /* ' */ SYMSHIFT(Key7), + /* ( */ SYMSHIFT(Key8), /* ) */ SYMSHIFT(Key9), + /* * */ SYMSHIFT(KeyB), /* + */ SYMSHIFT(KeyK), + /* , */ SYMSHIFT(KeyN), /* - */ SYMSHIFT(KeyJ), + /* . */ SYMSHIFT(KeyM), /* / */ SYMSHIFT(KeyV), + /* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1), + /* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3), + /* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5), + /* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7), + /* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9), + /* : */ SYMSHIFT(KeyZ), /* ; */ SYMSHIFT(KeyO), + /* < */ SYMSHIFT(KeyR), /* = */ SYMSHIFT(KeyL), + /* > */ SYMSHIFT(KeyT), /* ? */ SYMSHIFT(KeyC), + /* @ */ SYMSHIFT(Key2), /* A */ SHIFT(KeyA), + /* B */ SHIFT(KeyB), /* C */ SHIFT(KeyC), + /* D */ SHIFT(KeyD), /* E */ SHIFT(KeyE), + /* F */ SHIFT(KeyF), /* G */ SHIFT(KeyG), + /* H */ SHIFT(KeyH), /* I */ SHIFT(KeyI), + /* J */ SHIFT(KeyJ), /* K */ SHIFT(KeyK), + /* L */ SHIFT(KeyL), /* M */ SHIFT(KeyM), + /* N */ SHIFT(KeyN), /* O */ SHIFT(KeyO), + /* P */ SHIFT(KeyP), /* Q */ SHIFT(KeyQ), + /* R */ SHIFT(KeyR), /* S */ SHIFT(KeyS), + /* T */ SHIFT(KeyT), /* U */ SHIFT(KeyU), + /* V */ SHIFT(KeyV), /* W */ SHIFT(KeyW), + /* X */ SHIFT(KeyX), /* Y */ SHIFT(KeyY), + /* Z */ SHIFT(KeyZ), /* [ */ X, + /* \ */ X, /* ] */ X, + /* ^ */ SYMSHIFT(KeyH), /* _ */ SYMSHIFT(Key0), + /* ` */ X, /* a */ KEYS(KeyA), + /* b */ KEYS(KeyB), /* c */ KEYS(KeyC), + /* d */ KEYS(KeyD), /* e */ KEYS(KeyE), + /* f */ KEYS(KeyF), /* g */ KEYS(KeyG), + /* h */ KEYS(KeyH), /* i */ KEYS(KeyI), + /* j */ KEYS(KeyJ), /* k */ KEYS(KeyK), + /* l */ KEYS(KeyL), /* m */ KEYS(KeyM), + /* n */ KEYS(KeyN), /* o */ KEYS(KeyO), + /* p */ KEYS(KeyP), /* q */ KEYS(KeyQ), + /* r */ KEYS(KeyR), /* s */ KEYS(KeyS), + /* t */ KEYS(KeyT), /* u */ KEYS(KeyU), + /* v */ KEYS(KeyV), /* w */ KEYS(KeyW), + /* x */ KEYS(KeyX), /* y */ KEYS(KeyY), + /* z */ KEYS(KeyZ), + }; + + constexpr KeySequence zx81_key_sequences[] = { /* NUL */ X, /* SOH */ X, /* STX */ X, /* ETX */ X, /* EOT */ X, /* ENQ */ X, @@ -204,20 +270,23 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { }; #undef KEYS #undef SHIFT +#undef SYMSHIFT #undef X switch(machine_) { - case Machine::ZX81: - case Machine::ZXSpectrum: // TODO: some differences exist for the Spectrum. - return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character); - case Machine::ZX80: return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character); + + case Machine::ZX81: + return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character); + + case Machine::ZXSpectrum: + return table_lookup_sequence_for_character(spectrum_key_sequences, sizeof(zx81_key_sequences), character); } } bool CharacterMapper::needs_pause_after_key(uint16_t key) const { - return key != KeyShift; + return key != KeyShift && !(machine_ == Machine::ZXSpectrum && key == KeySymbolShift); } Keyboard::Keyboard(Machine machine) : machine_(machine) { diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 2ac8a36b6..e278997cd 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -323,11 +323,11 @@ template class Video { if(line >= 192) return 0xff; const int time_into_line = time_into_frame_ % timings.cycles_per_line; - if(time_into_line >= 256 || (time_into_line&4)) { + if(time_into_line >= 256 || (time_into_line&8)) { return 0xff; } - return last_fetches_[time_into_line & 3]; + return last_fetches_[(time_into_line >> 1) & 3]; } /*! diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 4bb9ec581..5aafebbb1 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -12,6 +12,7 @@ #define LOG_PREFIX "[Spectrum] " +#include "../../../Activity/Source.hpp" #include "../../MachineTypes.hpp" #include "../../../Processors/Z80/Z80.hpp" @@ -34,6 +35,7 @@ #include "../../../Analyser/Static/ZXSpectrum/Target.hpp" #include "../../Utility/MemoryFuzzer.hpp" +#include "../../Utility/Typer.hpp" #include "../../../ClockReceiver/JustInTime.hpp" @@ -45,17 +47,23 @@ namespace Sinclair { namespace ZXSpectrum { using Model = Analyser::Static::ZXSpectrum::Target::Model; +using CharacterMapper = Sinclair::ZX::Keyboard::CharacterMapper; + template class ConcreteMachine: + public Activity::Source, + public ClockingHint::Observer, public Configurable::Device, + public CPU::Z80::BusHandler, public Machine, public MachineTypes::AudioProducer, public MachineTypes::MappedKeyboardMachine, public MachineTypes::MediaTarget, public MachineTypes::ScanProducer, public MachineTypes::TimedMachine, - public CPU::Z80::BusHandler { + public Utility::TypeRecipient { public: ConcreteMachine(const Analyser::Static::ZXSpectrum::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + Utility::TypeRecipient(Sinclair::ZX::Keyboard::Machine::ZXSpectrum), z80_(*this), ay_(GI::AY38910::Personality::AY38910, audio_queue_), audio_toggle_(audio_queue_), @@ -76,6 +84,10 @@ template class ConcreteMachine: if(!roms[0]) throw ROMMachine::Error::MissingROMs; memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size())); + // Register for sleeping notifications. + fdc_->set_clocking_hint_observer(this); + tape_player_.set_clocking_hint_observer(this); + // Set up initial memory map. update_memory_map(); set_video_address(); @@ -127,7 +139,7 @@ template class ConcreteMachine: return Plus3ClockRate; } - // MARK: - TimedMachine + // MARK: - TimedMachine. void run_for(const Cycles cycles) override { z80_.run_for(cycles); @@ -154,7 +166,7 @@ template class ConcreteMachine: } } - // MARK: - ScanProducer + // MARK: - ScanProducer. void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { video_->set_scan_target(scan_target); @@ -168,7 +180,7 @@ template class ConcreteMachine: video_->set_display_type(display_type); } - // MARK: - BusHandler + // MARK: - BusHandler. forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { using PartialMachineCycle = CPU::Z80::PartialMachineCycle; @@ -317,6 +329,12 @@ template class ConcreteMachine: *cycle.value &= GI::AY38910::Utility::read_data(ay_); } + // Check for a floating bus read; these are particularly arcane + // on the +2a/+3. See footnote to https://spectrumforeveryone.com/technical/memory-contention-floating-bus/ + if((address & 0xf003) == 0x0001) { + *cycle.value &= video_->get_current_fetch(); + } + if constexpr (model == Model::Plus3) { switch(address) { default: break; @@ -341,8 +359,7 @@ template class ConcreteMachine: z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); } - // TODO: sleeping support here. - tape_player_.run_for(duration.as_integral()); + if(!tape_player_is_sleeping_) tape_player_.run_for(duration.as_integral()); // Update automatic tape motor control, if enabled; if it's been // 3 seconds since software last possibly polled the tape, stop it. @@ -356,26 +373,36 @@ template class ConcreteMachine: } if constexpr (model == Model::Plus3) { - fdc_ += Cycles(duration.as_integral()); + if(!fdc_is_sleeping_) fdc_ += Cycles(duration.as_integral()); } + + if(typer_) typer_->run_for(duration); + } + + void type_string(const std::string &string) override { + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) const override { + return Utility::TypeRecipient::can_type(c); } public: - // MARK: - Typer -// HalfCycles get_typer_delay(const std::string &) const final { -// return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); -// } -// -// HalfCycles get_typer_frequency() const final { -// return Cycles(146'250); -// } + // MARK: - Typer. + HalfCycles get_typer_delay(const std::string &) const override { + return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); + } + + HalfCycles get_typer_frequency() const override{ + return Cycles(70'908); + } KeyboardMapper *get_keyboard_mapper() override { return &keyboard_mapper_; } - // MARK: - Keyboard + // MARK: - Keyboard. void set_key_state(uint16_t key, bool is_pressed) override { keyboard_.set_key_state(key, is_pressed); } @@ -407,7 +434,14 @@ template class ConcreteMachine: return !media.tapes.empty() || (!media.disks.empty() && model == Model::Plus3); } - // MARK: - Tape control + // MARK: - ClockingHint::Observer. + + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) override { + fdc_is_sleeping_ = fdc_.last_valid()->preferred_clocking() == ClockingHint::Preference::None; + tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; + } + + // MARK: - Tape control. void set_use_automatic_tape_motor_control(bool enabled) { use_automatic_tape_motor_control_ = enabled; @@ -447,6 +481,12 @@ template class ConcreteMachine: return &speaker_; } + // MARK: - Activity Source. + void set_activity_observer(Activity::Observer *observer) override { + if constexpr (model == Model::Plus3) fdc_->set_activity_observer(observer); + tape_player_.set_activity_observer(observer); + } + private: CPU::Z80::Processor z80_; @@ -560,6 +600,7 @@ template class ConcreteMachine: // MARK: - Tape. Storage::Tape::BinaryTapePlayer tape_player_; + bool tape_player_is_sleeping_ = false; bool use_automatic_tape_motor_control_ = true; HalfCycles cycles_since_tape_input_read_; @@ -614,6 +655,7 @@ template class ConcreteMachine: // MARK: - Disc. JustInTimeActor fdc_; + bool fdc_is_sleeping_ = false; // MARK: - Automatic startup. Cycles duration_to_press_enter_; diff --git a/Machines/Utility/Typer.cpp b/Machines/Utility/Typer.cpp index 4470f97c2..9706d85bf 100644 --- a/Machines/Utility/Typer.cpp +++ b/Machines/Utility/Typer.cpp @@ -134,7 +134,7 @@ bool Typer::type_next_character() { // MARK: - Character mapper -uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, std::size_t length, char character) const { +const uint16_t *CharacterMapper::table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const { std::size_t ucharacter = size_t((unsigned char)character); if(ucharacter >= (length / sizeof(KeySequence))) return nullptr; if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr; diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index d6bdc80f7..0e0a15b3a 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -48,7 +48,7 @@ class CharacterMapper { with @c length entries, returns the sequence for character @c character if it exists; otherwise returns @c nullptr. */ - uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, std::size_t length, char character) const; + const uint16_t *table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const; }; /*!