From ce5aae3f7da67eedef681eb87d19ba5058acb1c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Wed, 4 Dec 2024 22:29:08 -0500 Subject: [PATCH 1/2] Adjust more dangling indentation changes. --- Analyser/Static/Commodore/Disk.cpp | 4 +- .../Apple/AppleII/AuxiliaryMemorySwitches.hpp | 450 ++--- Machines/Apple/AppleIIgs/ADB.hpp | 86 +- Machines/Apple/AppleIIgs/AppleIIgs.cpp | 1618 ++++++++--------- Machines/Apple/AppleIIgs/MemoryMap.hpp | 216 +-- Machines/Apple/AppleIIgs/Sound.hpp | 138 +- Machines/Apple/AppleIIgs/Video.hpp | 430 ++--- Machines/Apple/Macintosh/Audio.hpp | 88 +- .../Apple/Macintosh/DriveSpeedAccumulator.hpp | 38 +- Machines/Apple/Macintosh/Keyboard.hpp | 368 ++-- Machines/Apple/Macintosh/Macintosh.cpp | 1382 +++++++------- Machines/Apple/Macintosh/Video.hpp | 112 +- Machines/Atari/2600/Atari2600.cpp | 272 +-- Machines/Atari/ST/AtariST.cpp | 1190 ++++++------ Machines/Atari/ST/DMAController.hpp | 134 +- Machines/Atari/ST/IntelligentKeyboard.hpp | 264 +-- Machines/Atari/ST/Video.hpp | 400 ++-- .../1540/Implementation/C1540Base.hpp | 128 +- Machines/Utility/ROMCatalogue.hpp | 100 +- Machines/Utility/StringSerialiser.hpp | 14 +- Machines/Utility/TypedDynamicMachine.hpp | 70 +- Machines/Utility/Typer.hpp | 120 +- Numeric/CRC.hpp | 127 +- Numeric/LFSR.hpp | 58 +- Numeric/NumericCoder.hpp | 70 +- Outputs/CRT/CRT.hpp | 502 ++--- Outputs/CRT/Internals/Flywheel.hpp | 38 +- Outputs/DisplayMetrics.hpp | 44 +- Outputs/Log.hpp | 40 +- Outputs/OpenGL/Primitives/Rectangle.hpp | 26 +- Outputs/OpenGL/Primitives/TextureTarget.hpp | 104 +- Outputs/OpenGL/ScanTarget.hpp | 186 +- Outputs/ScanTargets/BufferingScanTarget.hpp | 392 ++-- .../Speaker/Implementation/BufferSource.hpp | 80 +- .../Speaker/Implementation/CompoundSource.hpp | 237 +-- .../Speaker/Implementation/LowpassSpeaker.hpp | 654 +++---- Outputs/Speaker/Speaker.hpp | 220 +-- Processors/6502/6502.hpp | 34 +- Processors/6502/AllRAM/6502AllRAM.cpp | 198 +- Processors/65816/65816.hpp | 80 +- .../65816/Implementation/65816Storage.cpp | 2 +- .../65816/Implementation/65816Storage.hpp | 18 +- Processors/68000/68000.hpp | 86 +- Processors/AllRAMProcessor.hpp | 42 +- Processors/Z80/Z80.hpp | 198 +- 45 files changed, 5530 insertions(+), 5528 deletions(-) diff --git a/Analyser/Static/Commodore/Disk.cpp b/Analyser/Static/Commodore/Disk.cpp index 0551fd997..658b0e938 100644 --- a/Analyser/Static/Commodore/Disk.cpp +++ b/Analyser/Static/Commodore/Disk.cpp @@ -70,7 +70,7 @@ private: uint8_t track_; std::shared_ptr<Sector> sector_cache_[65536]; - void process_input_bit(const int value) { + void process_input_bit(const int value) override { shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff; bit_count_++; } @@ -107,7 +107,7 @@ private: } } - void process_index_hole() { + void process_index_hole() override { index_count_++; } diff --git a/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp b/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp index f4abba555..1d2dab9bb 100644 --- a/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp +++ b/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp @@ -24,261 +24,261 @@ namespace Apple::II { Implementation observation: as implemented on the IIe, the zero page setting also affects what happens in the language card area. */ template <typename Machine> class AuxiliaryMemorySwitches { - public: - static constexpr bool Auxiliary = true; - static constexpr bool Main = false; - static constexpr bool ROM = true; - static constexpr bool Card = false; +public: + static constexpr bool Auxiliary = true; + static constexpr bool Main = false; + static constexpr bool ROM = true; + static constexpr bool Card = false; - /// Describes banking state between $0200 and $BFFF. - struct MainState { - struct Region { - /// @c true indicates auxiliary memory should be read from; @c false indicates main. - bool read = false; - /// @c true indicates auxiliary memory should be written to; @c false indicates main. - bool write = false; - }; - - /// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF. - Region base; - /// Describes banking state in the range $0400–$07FF. - Region region_04_08; - /// Describes banking state in the range $2000–$3FFF. - Region region_20_40; - - bool operator != (const MainState &rhs) const { - return - base.read != rhs.base.read || base.write != rhs.base.write || - region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write || - region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write; - } + /// Describes banking state between $0200 and $BFFF. + struct MainState { + struct Region { + /// @c true indicates auxiliary memory should be read from; @c false indicates main. + bool read = false; + /// @c true indicates auxiliary memory should be written to; @c false indicates main. + bool write = false; }; - /// Describes banking state between $C100 and $Cfff. - struct CardState { - /// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses. - bool region_C1_C3 = false; - /// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses. - bool region_C3 = false; - /// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses. - bool region_C4_C8 = false; - /// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses. - bool region_C8_D0 = false; + /// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF. + Region base; + /// Describes banking state in the range $0400–$07FF. + Region region_04_08; + /// Describes banking state in the range $2000–$3FFF. + Region region_20_40; - bool operator != (const CardState &rhs) const { - return - region_C1_C3 != rhs.region_C1_C3 || - region_C3 != rhs.region_C3 || - region_C4_C8 != rhs.region_C4_C8 || - region_C8_D0 != rhs.region_C8_D0; - } - }; - - /// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory. - using ZeroState = bool; - - /// Returns raw switch state for all switches that affect banking, even if they're logically video switches. - struct SwitchState { - bool read_auxiliary_memory = false; - bool write_auxiliary_memory = false; - - bool internal_CX_rom = false; - bool slot_C3_rom = false; - bool internal_C8_rom = false; - - bool store_80 = false; - bool alternative_zero_page = false; - bool video_page_2 = false; - bool high_resolution = false; - - void reset() { - *this = SwitchState(); - } - }; - - AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {} - - /// Used by an owner to forward, at least, any access in the range $C000 to $C00B, - /// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address. - void access(uint16_t address, bool is_read) { - if(address >= 0xc300 && address < 0xd000) { - switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom; - switches_.internal_C8_rom &= (address != 0xcfff); - set_card_paging(); - return; - } - - if(address < 0xc000 || address >= 0xc058) return; - - switch(address) { - default: break; - - case 0xc000: case 0xc001: - if(!is_read) { - switches_.store_80 = address & 1; - set_main_paging(); - } - break; - - case 0xc002: case 0xc003: - if(!is_read) { - switches_.read_auxiliary_memory = address & 1; - set_main_paging(); - } - break; - - case 0xc004: case 0xc005: - if(!is_read) { - switches_.write_auxiliary_memory = address & 1; - set_main_paging(); - } - break; - - case 0xc006: case 0xc007: - if(!is_read) { - switches_.internal_CX_rom = address & 1; - set_card_paging(); - } - break; - - case 0xc008: case 0xc009: { - const bool alternative_zero_page = address & 1; - if(!is_read && switches_.alternative_zero_page != alternative_zero_page) { - switches_.alternative_zero_page = alternative_zero_page; - set_zero_page_paging(); - } - } break; - - case 0xc00a: case 0xc00b: - if(!is_read) { - switches_.slot_C3_rom = address & 1; - set_card_paging(); - } - break; - - case 0xc054: case 0xc055: - switches_.video_page_2 = address & 1; - set_main_paging(); - break; - - case 0xc056: case 0xc057: - switches_.high_resolution = address & 1; - set_main_paging(); - break; - } - } - - /// Provides part of the IIgs interface. - void set_state(uint8_t value) { - // b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around] - // b6: 1 => text page 2 is selected; 0 => text page 1. - // b5: 1 => auxiliary RAM bank is selected for reads; 0 => main. - // b4: 1 => auxiliary RAM bank is selected for writes; 0 => main. - // b0: 1 => the internal ROM is selected for C800+; 0 => card ROM. - switches_.alternative_zero_page = value & 0x80; - switches_.video_page_2 = value & 0x40; - switches_.read_auxiliary_memory = value & 0x20; - switches_.write_auxiliary_memory = value & 0x10; - switches_.internal_CX_rom = value & 0x01; - - set_main_paging(); - set_zero_page_paging(); - set_card_paging(); - } - - uint8_t get_state() const { + bool operator != (const MainState &rhs) const { return - (switches_.alternative_zero_page ? 0x80 : 0x00) | - (switches_.video_page_2 ? 0x40 : 0x00) | - (switches_.read_auxiliary_memory ? 0x20 : 0x00) | - (switches_.write_auxiliary_memory ? 0x10 : 0x00) | - (switches_.internal_CX_rom ? 0x01 : 0x00); + base.read != rhs.base.read || base.write != rhs.base.write || + region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write || + region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write; } + }; - const MainState &main_state() const { - return main_state_; - } + /// Describes banking state between $C100 and $Cfff. + struct CardState { + /// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses. + bool region_C1_C3 = false; + /// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses. + bool region_C3 = false; + /// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses. + bool region_C4_C8 = false; + /// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses. + bool region_C8_D0 = false; - const CardState &card_state() const { - return card_state_; + bool operator != (const CardState &rhs) const { + return + region_C1_C3 != rhs.region_C1_C3 || + region_C3 != rhs.region_C3 || + region_C4_C8 != rhs.region_C4_C8 || + region_C8_D0 != rhs.region_C8_D0; } + }; - /// @returns @c true if the alternative zero page should be used; @c false otherwise. - ZeroState zero_state() const { - return switches_.alternative_zero_page; - } + /// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory. + using ZeroState = bool; - const SwitchState switches() const { - return switches_; - } + /// Returns raw switch state for all switches that affect banking, even if they're logically video switches. + struct SwitchState { + bool read_auxiliary_memory = false; + bool write_auxiliary_memory = false; + + bool internal_CX_rom = false; + bool slot_C3_rom = false; + bool internal_C8_rom = false; + + bool store_80 = false; + bool alternative_zero_page = false; + bool video_page_2 = false; + bool high_resolution = false; void reset() { - switches_.reset(); + *this = SwitchState(); + } + }; - set_main_paging(); - set_zero_page_paging(); + AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {} + + /// Used by an owner to forward, at least, any access in the range $C000 to $C00B, + /// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address. + void access(uint16_t address, bool is_read) { + if(address >= 0xc300 && address < 0xd000) { + switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom; + switches_.internal_C8_rom &= (address != 0xcfff); set_card_paging(); + return; } - private: - Machine &machine_; - SwitchState switches_; + if(address < 0xc000 || address >= 0xc058) return; - MainState main_state_; - void set_main_paging() { - const auto previous_state = main_state_; + switch(address) { + default: break; - // The two appropriately named switches provide the base case. - main_state_.base.read = switches_.read_auxiliary_memory; - main_state_.base.write = switches_.write_auxiliary_memory; - - if(switches_.store_80) { - // If store 80 is set, use the page 2 flag for the lower carve out; - // if both store 80 and high resolution are set, use the page 2 flag for both carve outs. - main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2; - - if(switches_.high_resolution) { - main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2; - } else { - main_state_.region_20_40 = main_state_.base; + case 0xc000: case 0xc001: + if(!is_read) { + switches_.store_80 = address & 1; + set_main_paging(); } + break; + + case 0xc002: case 0xc003: + if(!is_read) { + switches_.read_auxiliary_memory = address & 1; + set_main_paging(); + } + break; + + case 0xc004: case 0xc005: + if(!is_read) { + switches_.write_auxiliary_memory = address & 1; + set_main_paging(); + } + break; + + case 0xc006: case 0xc007: + if(!is_read) { + switches_.internal_CX_rom = address & 1; + set_card_paging(); + } + break; + + case 0xc008: case 0xc009: { + const bool alternative_zero_page = address & 1; + if(!is_read && switches_.alternative_zero_page != alternative_zero_page) { + switches_.alternative_zero_page = alternative_zero_page; + set_zero_page_paging(); + } + } break; + + case 0xc00a: case 0xc00b: + if(!is_read) { + switches_.slot_C3_rom = address & 1; + set_card_paging(); + } + break; + + case 0xc054: case 0xc055: + switches_.video_page_2 = address & 1; + set_main_paging(); + break; + + case 0xc056: case 0xc057: + switches_.high_resolution = address & 1; + set_main_paging(); + break; + } + } + + /// Provides part of the IIgs interface. + void set_state(uint8_t value) { + // b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around] + // b6: 1 => text page 2 is selected; 0 => text page 1. + // b5: 1 => auxiliary RAM bank is selected for reads; 0 => main. + // b4: 1 => auxiliary RAM bank is selected for writes; 0 => main. + // b0: 1 => the internal ROM is selected for C800+; 0 => card ROM. + switches_.alternative_zero_page = value & 0x80; + switches_.video_page_2 = value & 0x40; + switches_.read_auxiliary_memory = value & 0x20; + switches_.write_auxiliary_memory = value & 0x10; + switches_.internal_CX_rom = value & 0x01; + + set_main_paging(); + set_zero_page_paging(); + set_card_paging(); + } + + uint8_t get_state() const { + return + (switches_.alternative_zero_page ? 0x80 : 0x00) | + (switches_.video_page_2 ? 0x40 : 0x00) | + (switches_.read_auxiliary_memory ? 0x20 : 0x00) | + (switches_.write_auxiliary_memory ? 0x10 : 0x00) | + (switches_.internal_CX_rom ? 0x01 : 0x00); + } + + const MainState &main_state() const { + return main_state_; + } + + const CardState &card_state() const { + return card_state_; + } + + /// @returns @c true if the alternative zero page should be used; @c false otherwise. + ZeroState zero_state() const { + return switches_.alternative_zero_page; + } + + const SwitchState switches() const { + return switches_; + } + + void reset() { + switches_.reset(); + + set_main_paging(); + set_zero_page_paging(); + set_card_paging(); + } + +private: + Machine &machine_; + SwitchState switches_; + + MainState main_state_; + void set_main_paging() { + const auto previous_state = main_state_; + + // The two appropriately named switches provide the base case. + main_state_.base.read = switches_.read_auxiliary_memory; + main_state_.base.write = switches_.write_auxiliary_memory; + + if(switches_.store_80) { + // If store 80 is set, use the page 2 flag for the lower carve out; + // if both store 80 and high resolution are set, use the page 2 flag for both carve outs. + main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2; + + if(switches_.high_resolution) { + main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2; } else { - main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base; - } - - if(previous_state != main_state_) { - machine_.template set_paging<PagingType::Main>(); + main_state_.region_20_40 = main_state_.base; } + } else { + main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base; } - CardState card_state_; - void set_card_paging() { - const auto previous_state = card_state_; + if(previous_state != main_state_) { + machine_.template set_paging<PagingType::Main>(); + } + } - // By default apply the CX switch through to $C7FF. - card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom; + CardState card_state_; + void set_card_paging() { + const auto previous_state = card_state_; - // Allow the C3 region to be switched to internal ROM in isolation even if the rest of the - // first half of the CX region is diabled, if its specific switch is also disabled. - if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) { - card_state_.region_C3 = true; - } else { - card_state_.region_C3 = card_state_.region_C1_C3; - } + // By default apply the CX switch through to $C7FF. + card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom; - // Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation. - card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom; - - if(previous_state != card_state_) { - machine_.template set_paging<PagingType::CardArea>(); - } + // Allow the C3 region to be switched to internal ROM in isolation even if the rest of the + // first half of the CX region is diabled, if its specific switch is also disabled. + if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) { + card_state_.region_C3 = true; + } else { + card_state_.region_C3 = card_state_.region_C1_C3; } - void set_zero_page_paging() { - // Believe it or not, the zero page is just set or cleared by a single flag. - // As though life were rational. - machine_.template set_paging<PagingType::ZeroPage>(); + // Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation. + card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom; + + if(previous_state != card_state_) { + machine_.template set_paging<PagingType::CardArea>(); } + } + + void set_zero_page_paging() { + // Believe it or not, the zero page is just set or cleared by a single flag. + // As though life were rational. + machine_.template set_paging<PagingType::ZeroPage>(); + } }; } diff --git a/Machines/Apple/AppleIIgs/ADB.hpp b/Machines/Apple/AppleIIgs/ADB.hpp index 9669c0f19..125687789 100644 --- a/Machines/Apple/AppleIIgs/ADB.hpp +++ b/Machines/Apple/AppleIIgs/ADB.hpp @@ -19,67 +19,67 @@ namespace Apple::IIgs::ADB { class GLU: public InstructionSet::M50740::PortHandler { - public: - GLU(); +public: + GLU(); - uint8_t get_keyboard_data(); - uint8_t get_mouse_data(); - uint8_t get_modifier_status(); - uint8_t get_any_key_down(); + uint8_t get_keyboard_data(); + uint8_t get_mouse_data(); + uint8_t get_modifier_status(); + uint8_t get_any_key_down(); - uint8_t get_data(); - uint8_t get_status(); + uint8_t get_data(); + uint8_t get_status(); - void set_command(uint8_t); - void set_status(uint8_t); - void clear_key_strobe(); + void set_command(uint8_t); + void set_status(uint8_t); + void clear_key_strobe(); - void set_microcontroller_rom(const std::vector<uint8_t> &rom); + void set_microcontroller_rom(const std::vector<uint8_t> &rom); - void run_for(Cycles cycles); + void run_for(Cycles cycles); - bool get_command_button() const; - bool get_option_button() const; + bool get_command_button() const; + bool get_option_button() const; - void set_vertical_blank(bool); - bool get_vertical_blank() { - return vertical_blank_; - } + void set_vertical_blank(bool); + bool get_vertical_blank() { + return vertical_blank_; + } - Apple::ADB::Keyboard &keyboard() { - return keyboard_; - } - Inputs::Mouse &get_mouse() { - return mouse_; - } + Apple::ADB::Keyboard &keyboard() { + return keyboard_; + } + Inputs::Mouse &get_mouse() { + return mouse_; + } - private: - InstructionSet::M50740::Executor executor_; +private: + InstructionSet::M50740::Executor executor_; - void run_ports_for(Cycles) override; - void set_port_output(int port, uint8_t value) override; - uint8_t get_port_input(int port) override; + void run_ports_for(Cycles) override; + void set_port_output(int port, uint8_t value) override; + uint8_t get_port_input(int port) override; - uint8_t registers_[16]{}; + uint8_t registers_[16]{}; - uint8_t register_address_; - uint8_t register_latch_ = 0xff; - bool register_strobe_ = false; + uint8_t register_address_; + uint8_t register_latch_ = 0xff; + bool register_strobe_ = false; - uint8_t status_ = 0x00; + uint8_t status_ = 0x00; - Apple::ADB::Bus bus_; - size_t controller_id_; + Apple::ADB::Bus bus_; + size_t controller_id_; - uint8_t modifier_state_ = 0; + uint8_t modifier_state_ = 0; - bool vertical_blank_ = false; + bool vertical_blank_ = false; - int visible_mouse_register_ = 2; + int visible_mouse_register_ = 2; - // For now, attach only a keyboard and mouse. - Apple::ADB::Mouse mouse_; - Apple::ADB::Keyboard keyboard_; + // For now, attach only a keyboard and mouse. + Apple::ADB::Mouse mouse_; + Apple::ADB::Keyboard keyboard_; }; } diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index a0c4ac564..674905f07 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -88,921 +88,921 @@ class ConcreteMachine: public MachineTypes::TimedMachine, public CPU::MOS6502Esque::BusHandler<uint32_t> { - public: - ConcreteMachine(const Analyser::Static::AppleIIgs::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - m65816_(*this), - memory_(target.model >= Analyser::Static::AppleIIgs::Target::Model::ROM03), - iwm_(CLOCK_RATE / 2), - drives35_{ - {CLOCK_RATE / 2, true}, - {CLOCK_RATE / 2, true} - }, - drives525_{ - {CLOCK_RATE / 2}, - {CLOCK_RATE / 2} - }, - sound_glu_(audio_queue_), - audio_toggle_(audio_queue_), - mixer_(sound_glu_, audio_toggle_), - speaker_(mixer_) { +public: + ConcreteMachine(const Analyser::Static::AppleIIgs::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + m65816_(*this), + memory_(target.model >= Analyser::Static::AppleIIgs::Target::Model::ROM03), + iwm_(CLOCK_RATE / 2), + drives35_{ + {CLOCK_RATE / 2, true}, + {CLOCK_RATE / 2, true} + }, + drives525_{ + {CLOCK_RATE / 2}, + {CLOCK_RATE / 2} + }, + sound_glu_(audio_queue_), + audio_toggle_(audio_queue_), + mixer_(sound_glu_, audio_toggle_), + speaker_(mixer_) { - set_clock_rate(double(CLOCK_RATE)); - speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider)); - clock_.ClockStorage::set_data(std::begin(default_bram), std::end(default_bram)); + set_clock_rate(double(CLOCK_RATE)); + speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider)); + clock_.ClockStorage::set_data(std::begin(default_bram), std::end(default_bram)); - using Target = Analyser::Static::AppleIIgs::Target; - ROM::Name system; - switch(target.model) { - case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break; - case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break; - default: system = ROM::Name::AppleIIgsROM03; break; - } - constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter; - constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03; + using Target = Analyser::Static::AppleIIgs::Target; + ROM::Name system; + switch(target.model) { + case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break; + case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break; + default: system = ROM::Name::AppleIIgsROM03; break; + } + constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter; + constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03; - ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - rom_ = roms.find(system)->second; - video_->set_character_rom(roms.find(characters)->second); - adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second); + ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + rom_ = roms.find(system)->second; + video_->set_character_rom(roms.find(characters)->second); + adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second); - // Run only the currently-interesting self test. -// rom_[0x36402] = 2; -// rom_[0x36403] = 0x7c; // ROM_CHECKSUM [working, when hacks like this are removed] -// rom_[0x36404] = 0x6c; + // Run only the currently-interesting self test. +// rom_[0x36402] = 2; +// rom_[0x36403] = 0x7c; // ROM_CHECKSUM [working, when hacks like this are removed] +// rom_[0x36404] = 0x6c; +// +// rom_[0x36403] = 0x82; // MOVIRAM [working] +// rom_[0x36404] = 0x67; +// +// rom_[0x36403] = 0x2c; // SOFT_SW [working] +// rom_[0x36404] = 0x6a; +// +// rom_[0x36403] = 0xe8; // RAM_ADDR [working] +// rom_[0x36404] = 0x6f; +// +// rom_[0x36403] = 0xc7; // FPI_SPEED [working] +// rom_[0x36404] = 0x6a; +// +// rom_[0x36403] = 0xd7; // SER_TST [broken] +// rom_[0x36404] = 0x68; +// +// rom_[0x36403] = 0xdc; // CLOCK [broken] +// rom_[0x36404] = 0x6c; +// +// rom_[0x36403] = 0x1b; // BAT_RAM [broken] +// rom_[0x36404] = 0x6e; +// +// rom_[0x36403] = 0x11; // FDB (/ADB?) [broken] +// rom_[0x36404] = 0x6f; +// +// rom_[0x36403] = 0x41; // SHADOW_TST [working] +// rom_[0x36404] = 0x6d; +// +// rom_[0x36403] = 0x09; // CUSTOM_IRQ [broken?] +// rom_[0x36404] = 0x6b; +// +// rom_[0x36403] = 0xf4; // DOC_EXEC +// rom_[0x36404] = 0x70; +// +// rom_[0x36403] = 0xab; // ECT_SEQ +// rom_[0x36404] = 0x64; +// +// rom_[0xfc146f] = rom_[0xfc1470] = 0xea; -// rom_[0x36403] = 0x82; // MOVIRAM [working] -// rom_[0x36404] = 0x67; + size_t ram_size = 0; + switch(target.memory_model) { + case Target::MemoryModel::TwoHundredAndFiftySixKB: + ram_size = 256; + break; -// rom_[0x36403] = 0x2c; // SOFT_SW [working] -// rom_[0x36404] = 0x6a; + case Target::MemoryModel::OneMB: + ram_size = 128 + 1024; + break; -// rom_[0x36403] = 0xe8; // RAM_ADDR [working] -// rom_[0x36404] = 0x6f; + case Target::MemoryModel::EightMB: + ram_size = 128 + 8 * 1024; + break; + } + ram_.resize(ram_size * 1024); -// rom_[0x36403] = 0xc7; // FPI_SPEED [working] -// rom_[0x36404] = 0x6a; + memory_.set_storage(ram_, rom_); + video_->set_internal_ram(&ram_[ram_.size() - 128*1024]); -// rom_[0x36403] = 0xd7; // SER_TST [broken] -// rom_[0x36404] = 0x68; + // Attach drives to the IWM. + iwm_->set_drive(0, &drives35_[0]); + iwm_->set_drive(1, &drives35_[1]); -// rom_[0x36403] = 0xdc; // CLOCK [broken] -// rom_[0x36404] = 0x6c; + // Randomise RAM contents. + Memory::Fuzz(ram_); -// rom_[0x36403] = 0x1b; // BAT_RAM [broken] -// rom_[0x36404] = 0x6e; - -// rom_[0x36403] = 0x11; // FDB (/ADB?) [broken] -// rom_[0x36404] = 0x6f; - -// rom_[0x36403] = 0x41; // SHADOW_TST [working] -// rom_[0x36404] = 0x6d; - -// rom_[0x36403] = 0x09; // CUSTOM_IRQ [broken?] -// rom_[0x36404] = 0x6b; - -// rom_[0x36403] = 0xf4; // DOC_EXEC -// rom_[0x36404] = 0x70; - -// rom_[0x36403] = 0xab; // ECT_SEQ -// rom_[0x36404] = 0x64; - -// rom_[0xfc146f] = rom_[0xfc1470] = 0xea; - - size_t ram_size = 0; - switch(target.memory_model) { - case Target::MemoryModel::TwoHundredAndFiftySixKB: - ram_size = 256; - break; - - case Target::MemoryModel::OneMB: - ram_size = 128 + 1024; - break; - - case Target::MemoryModel::EightMB: - ram_size = 128 + 8 * 1024; - break; - } - ram_.resize(ram_size * 1024); - - memory_.set_storage(ram_, rom_); - video_->set_internal_ram(&ram_[ram_.size() - 128*1024]); - - // Attach drives to the IWM. - iwm_->set_drive(0, &drives35_[0]); - iwm_->set_drive(1, &drives35_[1]); - - // Randomise RAM contents. - Memory::Fuzz(ram_); - - // Prior to ROM03 there's no power-on bit. - if(target.model != Target::Model::ROM03) { - speed_register_ &= ~0x40; - } - - // Sync up initial values. - memory_.set_speed_register(speed_register_ ^ 0x80); - - insert_media(target.media); + // Prior to ROM03 there's no power-on bit. + if(target.model != Target::Model::ROM03) { + speed_register_ &= ~0x40; } - ~ConcreteMachine() { - audio_queue_.flush(); + // Sync up initial values. + memory_.set_speed_register(speed_register_ ^ 0x80); + + insert_media(target.media); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + void run_for(const Cycles cycles) override { + m65816_.run_for(cycles); + } + + void flush_output(int outputs) final { + iwm_.flush(); + adb_glu_.flush(); + + if(outputs & Output::Video) { + video_.flush(); } - - void run_for(const Cycles cycles) override { - m65816_.run_for(cycles); + if(outputs & Output::Audio) { + AudioUpdater updater(this); + audio_queue_.perform(); } + } - void flush_output(int outputs) final { - iwm_.flush(); - adb_glu_.flush(); + void set_scan_target(Outputs::Display::ScanTarget *target) override { + video_->set_scan_target(target); + } - if(outputs & Output::Video) { - video_.flush(); - } - if(outputs & Output::Audio) { - AudioUpdater updater(this); - audio_queue_.perform(); + Outputs::Display::ScanStatus get_scaled_scan_status() const override { + return video_->get_scaled_scan_status() * 2.0f; // TODO: expose multiplier and divider via the JustInTime template? + } + + void set_display_type(Outputs::Display::DisplayType display_type) final { + video_->set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return video_->get_display_type(); + } + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + // MARK: MediaTarget. + bool insert_media(const Analyser::Static::Media &media) final { + if(!media.disks.empty()) { + const auto disk = media.disks[0]; + if(disk->get_maximum_head_position().as_int() > 35) { + drives35_[0].set_disk(media.disks[0]); + } else { + drives525_[0].set_disk(media.disks[0]); } } + return true; + } - void set_scan_target(Outputs::Display::ScanTarget *target) override { - video_->set_scan_target(target); - } + // MARK: Activity::Source + void set_activity_observer(Activity::Observer *observer) final { + drives35_[0].set_activity_observer(observer, "First 3.5\" Drive", true); + drives35_[1].set_activity_observer(observer, "Second 3.5\" Drive", true); + drives525_[0].set_activity_observer(observer, "First 5.25\" Drive", true); + drives525_[1].set_activity_observer(observer, "Second 5.25\" Drive", true); + } - Outputs::Display::ScanStatus get_scaled_scan_status() const override { - return video_->get_scaled_scan_status() * 2.0f; // TODO: expose multiplier and divider via the JustInTime template? - } + // MARK: BusHandler. + forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { + const auto ®ion = memory_.region(address); + bool is_1Mhz = false; - void set_display_type(Outputs::Display::DisplayType display_type) final { - video_->set_display_type(display_type); - } + if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) { + // I think vector pulls always go to ROM? + // That's slightly implied in the documentation, and doing so makes GS/OS boot, so... + // TODO: but is my guess above re: not doing that if IOLC shadowing is disabled correct? + assert(address <= 0xffff && address >= 0xffe4); + *value = rom_[rom_.size() - 65536 + address]; + } else if(region.flags & MemoryMap::Region::IsIO) { + // Ensure classic auxiliary and language card accesses have effect. + const bool is_read = isReadOperation(operation); + memory_.access(uint16_t(address), is_read); - Outputs::Display::DisplayType get_display_type() const final { - return video_->get_display_type(); - } - - Outputs::Speaker::Speaker *get_speaker() final { - return &speaker_; - } - - // MARK: MediaTarget. - bool insert_media(const Analyser::Static::Media &media) final { - if(!media.disks.empty()) { - const auto disk = media.disks[0]; - if(disk->get_maximum_head_position().as_int() > 35) { - drives35_[0].set_disk(media.disks[0]); - } else { - drives525_[0].set_disk(media.disks[0]); - } - } - return true; - } - - // MARK: Activity::Source - void set_activity_observer(Activity::Observer *observer) final { - drives35_[0].set_activity_observer(observer, "First 3.5\" Drive", true); - drives35_[1].set_activity_observer(observer, "Second 3.5\" Drive", true); - drives525_[0].set_activity_observer(observer, "First 5.25\" Drive", true); - drives525_[1].set_activity_observer(observer, "Second 5.25\" Drive", true); - } - - // MARK: BusHandler. - forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { - const auto ®ion = memory_.region(address); - bool is_1Mhz = false; - - if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) { - // I think vector pulls always go to ROM? - // That's slightly implied in the documentation, and doing so makes GS/OS boot, so... - // TODO: but is my guess above re: not doing that if IOLC shadowing is disabled correct? - assert(address <= 0xffff && address >= 0xffe4); - *value = rom_[rom_.size() - 65536 + address]; - } else if(region.flags & MemoryMap::Region::IsIO) { - // Ensure classic auxiliary and language card accesses have effect. - const bool is_read = isReadOperation(operation); - memory_.access(uint16_t(address), is_read); - - const auto address_suffix = address & 0xffff; - assert(address_suffix >= 0xc000 && address_suffix < 0xd000); + const auto address_suffix = address & 0xffff; + assert(address_suffix >= 0xc000 && address_suffix < 0xd000); #define ReadWrite(x) (x) | (is_read * 0x10000) #define Read(x) (x) | 0x10000 #define Write(x) (x) - switch(ReadWrite(address_suffix)) { + switch(ReadWrite(address_suffix)) { - // New video register. - case Read(0xc029): - *value = video_->get_new_video(); - break; - case Write(0xc029): - video_->set_new_video(*value); - assert(*value & 1); + // New video register. + case Read(0xc029): + *value = video_->get_new_video(); + break; + case Write(0xc029): + video_->set_new_video(*value); + assert(*value & 1); - // TODO: I think bits 7 and 0 might also affect the memory map. - // The descripton isn't especially clear — P.90 of the Hardware Reference. - // Revisit if necessary. - break; + // TODO: I think bits 7 and 0 might also affect the memory map. + // The descripton isn't especially clear — P.90 of the Hardware Reference. + // Revisit if necessary. + break; - // Video [and clock] interrupt register. - case Read(0xc023): - *value = video_->get_interrupt_register(); - break; - case Write(0xc023): - video_->set_interrupt_register(*value); - break; + // Video [and clock] interrupt register. + case Read(0xc023): + *value = video_->get_interrupt_register(); + break; + case Write(0xc023): + video_->set_interrupt_register(*value); + break; - // Video interrupt-clear register. - case Write(0xc032): - video_->clear_interrupts(*value); - break; - case Read(0xc032): - // TODO: this seems to be undocumented, but used. What value is likely? - *value = 0xff; - break; + // Video interrupt-clear register. + case Write(0xc032): + video_->clear_interrupts(*value); + break; + case Read(0xc032): + // TODO: this seems to be undocumented, but used. What value is likely? + *value = 0xff; + break; - // Shadow register. - case Read(0xc035): - *value = memory_.get_shadow_register(); - break; - case Write(0xc035): - memory_.set_shadow_register(*value); - break; + // Shadow register. + case Read(0xc035): + *value = memory_.get_shadow_register(); + break; + case Write(0xc035): + memory_.set_shadow_register(*value); + break; - // Clock data. - case Read(0xc033): - *value = clock_.get_data(); - break; - case Write(0xc033): - clock_.set_data(*value); - break; + // Clock data. + case Read(0xc033): + *value = clock_.get_data(); + break; + case Write(0xc033): + clock_.set_data(*value); + break; - // Clock and border control. - case Read(0xc034): - *value = (clock_.get_control() & 0xf0) | (video_.last_valid()->get_border_colour() & 0x0f); - break; - case Write(0xc034): - clock_.set_control(*value); - video_->set_border_colour(*value); - break; + // Clock and border control. + case Read(0xc034): + *value = (clock_.get_control() & 0xf0) | (video_.last_valid()->get_border_colour() & 0x0f); + break; + case Write(0xc034): + clock_.set_control(*value); + video_->set_border_colour(*value); + break; - // Colour text control. - case Write(0xc022): - video_->set_text_colour(*value); - break; - case Read(0xc022): - *value = video_.last_valid()->get_text_colour(); - break; + // Colour text control. + case Write(0xc022): + video_->set_text_colour(*value); + break; + case Read(0xc022): + *value = video_.last_valid()->get_text_colour(); + break; - // Speed register. - case Read(0xc036): - *value = speed_register_ ^ 0x80; - break; - case Write(0xc036): - // b7: 1 => operate at 2.8Mhz; 0 => 1Mhz. - // b6: power-on status; 1 => system has been turned on by the power switch (TODO: what clears this?) - // b5: reserved - // b4: [bank shadowing bit; cf. the memory map] - // b0–3: motor on-off speed detectors; - // 1 => switch to 1Mhz if motor is on; 0 => don't; - // b0 = slot 4 (i.e. watches addresses c0c9, c0c8) - // b1 = slot 5 (i.e. c0d9, c0d8) - // b2 = slot 6 (i.e. c0e9, c0e8) - // b3 = slot 7 (i.e. c0f9, c0f8) - memory_.set_speed_register(*value); - speed_register_ = *value ^ 0x80; - break; + // Speed register. + case Read(0xc036): + *value = speed_register_ ^ 0x80; + break; + case Write(0xc036): + // b7: 1 => operate at 2.8Mhz; 0 => 1Mhz. + // b6: power-on status; 1 => system has been turned on by the power switch (TODO: what clears this?) + // b5: reserved + // b4: [bank shadowing bit; cf. the memory map] + // b0–3: motor on-off speed detectors; + // 1 => switch to 1Mhz if motor is on; 0 => don't; + // b0 = slot 4 (i.e. watches addresses c0c9, c0c8) + // b1 = slot 5 (i.e. c0d9, c0d8) + // b2 = slot 6 (i.e. c0e9, c0e8) + // b3 = slot 7 (i.e. c0f9, c0f8) + memory_.set_speed_register(*value); + speed_register_ = *value ^ 0x80; + break; - // [Memory] State register. - case Read(0xc068): - *value = memory_.get_state_register(); - break; - case Write(0xc068): - memory_.set_state_register(*value); - video_->set_page2(*value & 0x40); - break; + // [Memory] State register. + case Read(0xc068): + *value = memory_.get_state_register(); + break; + case Write(0xc068): + memory_.set_state_register(*value); + video_->set_page2(*value & 0x40); + break; - case Read(0xc069): - case Write(0xc069): - // Swallow silently; often hit as a side effect of a 16-bit write to 0xc068. - break; + case Read(0xc069): + case Write(0xc069): + // Swallow silently; often hit as a side effect of a 16-bit write to 0xc068. + break; - // Various independent memory switch reads [TODO: does the IIe-style keyboard provide the low seven?]. + // Various independent memory switch reads [TODO: does the IIe-style keyboard provide the low seven?]. #define SwitchRead(s) *value = memory_.s ? 0x80 : 0x00; is_1Mhz = true; #define LanguageRead(s) SwitchRead(language_card_switches().state().s) #define AuxiliaryRead(s) SwitchRead(auxiliary_switches().switches().s) #define VideoRead(s) *value = video_.last_valid()->s ? 0x80 : 0x00; is_1Mhz = true; - case Read(0xc011): LanguageRead(bank2); break; - case Read(0xc012): LanguageRead(read); break; - case Read(0xc013): AuxiliaryRead(read_auxiliary_memory); break; - case Read(0xc014): AuxiliaryRead(write_auxiliary_memory); break; - case Read(0xc015): AuxiliaryRead(internal_CX_rom); break; - case Read(0xc016): AuxiliaryRead(alternative_zero_page); break; - case Read(0xc017): AuxiliaryRead(slot_C3_rom); break; - case Read(0xc018): VideoRead(get_80_store()); break; - case Read(0xc019): - VideoRead(get_is_vertical_blank(video_.time_since_flush())); - break; - case Read(0xc01a): VideoRead(get_text()); break; - case Read(0xc01b): VideoRead(get_mixed()); break; - case Read(0xc01c): VideoRead(get_page2()); break; - case Read(0xc01d): VideoRead(get_high_resolution()); break; - case Read(0xc01e): VideoRead(get_alternative_character_set()); break; - case Read(0xc01f): VideoRead(get_80_columns()); break; + case Read(0xc011): LanguageRead(bank2); break; + case Read(0xc012): LanguageRead(read); break; + case Read(0xc013): AuxiliaryRead(read_auxiliary_memory); break; + case Read(0xc014): AuxiliaryRead(write_auxiliary_memory); break; + case Read(0xc015): AuxiliaryRead(internal_CX_rom); break; + case Read(0xc016): AuxiliaryRead(alternative_zero_page); break; + case Read(0xc017): AuxiliaryRead(slot_C3_rom); break; + case Read(0xc018): VideoRead(get_80_store()); break; + case Read(0xc019): + VideoRead(get_is_vertical_blank(video_.time_since_flush())); + break; + case Read(0xc01a): VideoRead(get_text()); break; + case Read(0xc01b): VideoRead(get_mixed()); break; + case Read(0xc01c): VideoRead(get_page2()); break; + case Read(0xc01d): VideoRead(get_high_resolution()); break; + case Read(0xc01e): VideoRead(get_alternative_character_set()); break; + case Read(0xc01f): VideoRead(get_80_columns()); break; #undef VideoRead #undef AuxiliaryRead #undef LanguageRead #undef SwitchRead - // Video switches (and annunciators). - case Read(0xc050): case Read(0xc051): - case Write(0xc050): case Write(0xc051): - video_->set_text(address & 1); - is_1Mhz = true; - break; - case Read(0xc052): case Read(0xc053): - case Write(0xc052): case Write(0xc053): - video_->set_mixed(address & 1); - is_1Mhz = true; - break; - case Read(0xc054): case Read(0xc055): - case Write(0xc054): case Write(0xc055): - video_->set_page2(address & 1); - is_1Mhz = true; - break; - case Read(0xc056): case Read(0xc057): - case Write(0xc056): case Write(0xc057): - video_->set_high_resolution(address&1); - is_1Mhz = true; - break; - case Read(0xc058): case Read(0xc059): - case Write(0xc058): case Write(0xc059): - case Read(0xc05a): case Read(0xc05b): - case Write(0xc05a): case Write(0xc05b): - case Read(0xc05c): case Read(0xc05d): - case Write(0xc05c): case Write(0xc05d): - // Annunciators 0, 1 and 2. - is_1Mhz = true; - break; - case Read(0xc05e): case Read(0xc05f): - case Write(0xc05e): case Write(0xc05f): - video_->set_annunciator_3(!(address&1)); - is_1Mhz = true; - break; - case Write(0xc000): case Write(0xc001): - video_->set_80_store(address & 1); - is_1Mhz = true; - break; - case Write(0xc00c): case Write(0xc00d): - video_->set_80_columns(address & 1); - is_1Mhz = true; - break; - case Write(0xc00e): case Write(0xc00f): - video_->set_alternative_character_set(address & 1); - is_1Mhz = true; - break; + // Video switches (and annunciators). + case Read(0xc050): case Read(0xc051): + case Write(0xc050): case Write(0xc051): + video_->set_text(address & 1); + is_1Mhz = true; + break; + case Read(0xc052): case Read(0xc053): + case Write(0xc052): case Write(0xc053): + video_->set_mixed(address & 1); + is_1Mhz = true; + break; + case Read(0xc054): case Read(0xc055): + case Write(0xc054): case Write(0xc055): + video_->set_page2(address & 1); + is_1Mhz = true; + break; + case Read(0xc056): case Read(0xc057): + case Write(0xc056): case Write(0xc057): + video_->set_high_resolution(address&1); + is_1Mhz = true; + break; + case Read(0xc058): case Read(0xc059): + case Write(0xc058): case Write(0xc059): + case Read(0xc05a): case Read(0xc05b): + case Write(0xc05a): case Write(0xc05b): + case Read(0xc05c): case Read(0xc05d): + case Write(0xc05c): case Write(0xc05d): + // Annunciators 0, 1 and 2. + is_1Mhz = true; + break; + case Read(0xc05e): case Read(0xc05f): + case Write(0xc05e): case Write(0xc05f): + video_->set_annunciator_3(!(address&1)); + is_1Mhz = true; + break; + case Write(0xc000): case Write(0xc001): + video_->set_80_store(address & 1); + is_1Mhz = true; + break; + case Write(0xc00c): case Write(0xc00d): + video_->set_80_columns(address & 1); + is_1Mhz = true; + break; + case Write(0xc00e): case Write(0xc00f): + video_->set_alternative_character_set(address & 1); + is_1Mhz = true; + break; - // ADB and keyboard. - case Read(0xc000): - *value = adb_glu_->get_keyboard_data(); - break; - case Read(0xc010): - *value = adb_glu_->get_any_key_down() ? 0x80 : 0x00; - [[fallthrough]]; - case Write(0xc010): - adb_glu_->clear_key_strobe(); - break; + // ADB and keyboard. + case Read(0xc000): + *value = adb_glu_->get_keyboard_data(); + break; + case Read(0xc010): + *value = adb_glu_->get_any_key_down() ? 0x80 : 0x00; + [[fallthrough]]; + case Write(0xc010): + adb_glu_->clear_key_strobe(); + break; - case Read(0xc024): - *value = adb_glu_->get_mouse_data(); - break; - case Read(0xc025): - *value = adb_glu_->get_modifier_status(); - break; - case Read(0xc026): - *value = adb_glu_->get_data(); - break; - case Write(0xc026): - adb_glu_->set_command(*value); - break; - case Read(0xc027): - *value = adb_glu_->get_status(); - break; - case Write(0xc027): - adb_glu_->set_status(*value); - break; + case Read(0xc024): + *value = adb_glu_->get_mouse_data(); + break; + case Read(0xc025): + *value = adb_glu_->get_modifier_status(); + break; + case Read(0xc026): + *value = adb_glu_->get_data(); + break; + case Write(0xc026): + adb_glu_->set_command(*value); + break; + case Read(0xc027): + *value = adb_glu_->get_status(); + break; + case Write(0xc027): + adb_glu_->set_status(*value); + break; - // The SCC. - case Read(0xc038): case Read(0xc039): case Read(0xc03a): case Read(0xc03b): - *value = scc_.read(int(address)); - break; - case Write(0xc038): case Write(0xc039): case Write(0xc03a): case Write(0xc03b): - scc_.write(int(address), *value); - break; + // The SCC. + case Read(0xc038): case Read(0xc039): case Read(0xc03a): case Read(0xc03b): + *value = scc_.read(int(address)); + break; + case Write(0xc038): case Write(0xc039): case Write(0xc03a): case Write(0xc03b): + scc_.write(int(address), *value); + break; - // The audio GLU. - case Read(0xc03c): { - AudioUpdater updater(this); - *value = sound_glu_.get_control(); - } break; - case Write(0xc03c): { - AudioUpdater updater(this); - sound_glu_.set_control(*value); - } break; - case Read(0xc03d): { - AudioUpdater updater(this); - *value = sound_glu_.get_data(); - } break; - case Write(0xc03d): { - AudioUpdater updater(this); - sound_glu_.set_data(*value); - } break; - case Read(0xc03e): { - AudioUpdater updater(this); - *value = sound_glu_.get_address_low(); - } break; - case Write(0xc03e): { - AudioUpdater updater(this); - sound_glu_.set_address_low(*value); - } break; - case Read(0xc03f): { - AudioUpdater updater(this); - *value = sound_glu_.get_address_high(); - } break; - case Write(0xc03f): { - AudioUpdater updater(this); - sound_glu_.set_address_high(*value); - } break; + // The audio GLU. + case Read(0xc03c): { + AudioUpdater updater(this); + *value = sound_glu_.get_control(); + } break; + case Write(0xc03c): { + AudioUpdater updater(this); + sound_glu_.set_control(*value); + } break; + case Read(0xc03d): { + AudioUpdater updater(this); + *value = sound_glu_.get_data(); + } break; + case Write(0xc03d): { + AudioUpdater updater(this); + sound_glu_.set_data(*value); + } break; + case Read(0xc03e): { + AudioUpdater updater(this); + *value = sound_glu_.get_address_low(); + } break; + case Write(0xc03e): { + AudioUpdater updater(this); + sound_glu_.set_address_low(*value); + } break; + case Read(0xc03f): { + AudioUpdater updater(this); + *value = sound_glu_.get_address_high(); + } break; + case Write(0xc03f): { + AudioUpdater updater(this); + sound_glu_.set_address_high(*value); + } break; - // These were all dealt with by the call to memory_.access. - // TODO: subject to read data? Does vapour lock apply? - case Read(0xc002): case Read(0xc003): case Read(0xc004): case Read(0xc005): - case Read(0xc006): case Read(0xc007): case Read(0xc008): case Read(0xc009): case Read(0xc00a): case Read(0xc00b): - *value = 0xff; - break; - case Write(0xc002): case Write(0xc003): case Write(0xc004): case Write(0xc005): - case Write(0xc006): case Write(0xc007): case Write(0xc008): case Write(0xc009): case Write(0xc00a): case Write(0xc00b): - break; + // These were all dealt with by the call to memory_.access. + // TODO: subject to read data? Does vapour lock apply? + case Read(0xc002): case Read(0xc003): case Read(0xc004): case Read(0xc005): + case Read(0xc006): case Read(0xc007): case Read(0xc008): case Read(0xc009): case Read(0xc00a): case Read(0xc00b): + *value = 0xff; + break; + case Write(0xc002): case Write(0xc003): case Write(0xc004): case Write(0xc005): + case Write(0xc006): case Write(0xc007): case Write(0xc008): case Write(0xc009): case Write(0xc00a): case Write(0xc00b): + break; - // Interrupt ROM addresses; Cf. P25 of the Hardware Reference. - case Read(0xc071): case Read(0xc072): case Read(0xc073): - case Read(0xc074): case Read(0xc075): case Read(0xc076): case Read(0xc077): - case Read(0xc078): case Read(0xc079): case Read(0xc07a): case Read(0xc07b): - case Read(0xc07c): case Read(0xc07d): case Read(0xc07e): case Read(0xc07f): - *value = rom_[rom_.size() - 65536 + address_suffix]; - break; + // Interrupt ROM addresses; Cf. P25 of the Hardware Reference. + case Read(0xc071): case Read(0xc072): case Read(0xc073): + case Read(0xc074): case Read(0xc075): case Read(0xc076): case Read(0xc077): + case Read(0xc078): case Read(0xc079): case Read(0xc07a): case Read(0xc07b): + case Read(0xc07c): case Read(0xc07d): case Read(0xc07e): case Read(0xc07f): + *value = rom_[rom_.size() - 65536 + address_suffix]; + break; + // Analogue inputs. + case Read(0xc061): + *value = (adb_glu_->get_command_button() || joysticks_.button(0)) ? 0x80 : 0x00; + is_1Mhz = true; + break; + + case Read(0xc062): + *value = (adb_glu_->get_option_button() || joysticks_.button(1)) ? 0x80 : 0x00; + is_1Mhz = true; + break; + + case Read(0xc063): + *value = joysticks_.button(2) ? 0x80 : 0x00; + is_1Mhz = true; + break; + + case Read(0xc064): + case Read(0xc065): + case Read(0xc066): + case Read(0xc067): { // Analogue inputs. - case Read(0xc061): - *value = (adb_glu_->get_command_button() || joysticks_.button(0)) ? 0x80 : 0x00; - is_1Mhz = true; - break; + const size_t input = address_suffix - 0xc064; + *value = joysticks_.analogue_channel_is_discharged(input) ? 0x00 : 0x80; + is_1Mhz = true; + } break; - case Read(0xc062): - *value = (adb_glu_->get_option_button() || joysticks_.button(1)) ? 0x80 : 0x00; - is_1Mhz = true; - break; + case Read(0xc070): case Write(0xc070): + joysticks_.access_c070(); + is_1Mhz = true; + break; - case Read(0xc063): - *value = joysticks_.button(2) ? 0x80 : 0x00; - is_1Mhz = true; - break; + // Monochome/colour register. + case Read(0xc021): + // "Uses bit 7 to determine whether composite output is colour 9) or gray scale (1)." + *value = video_.last_valid()->get_composite_is_colour() ? 0x00 : 0x80; + break; + case Write(0xc021): + video_->set_composite_is_colour(!(*value & 0x80)); + break; - case Read(0xc064): - case Read(0xc065): - case Read(0xc066): - case Read(0xc067): { - // Analogue inputs. - const size_t input = address_suffix - 0xc064; - *value = joysticks_.analogue_channel_is_discharged(input) ? 0x00 : 0x80; - is_1Mhz = true; - } break; + case Read(0xc02e): + *value = video_.last_valid()->get_vertical_counter(video_.time_since_flush()); + is_1Mhz = true; + break; + case Read(0xc02f): + *value = video_.last_valid()->get_horizontal_counter(video_.time_since_flush()); + is_1Mhz = true; + break; - case Read(0xc070): case Write(0xc070): - joysticks_.access_c070(); - is_1Mhz = true; - break; + // C037 seems to be just a full-speed storage register. + case Read(0xc037): + *value = c037_; + break; + case Write(0xc037): + c037_ = *value; + break; - // Monochome/colour register. - case Read(0xc021): - // "Uses bit 7 to determine whether composite output is colour 9) or gray scale (1)." - *value = video_.last_valid()->get_composite_is_colour() ? 0x00 : 0x80; - break; - case Write(0xc021): - video_->set_composite_is_colour(!(*value & 0x80)); - break; + case Read(0xc041): + *value = megaii_interrupt_mask_; + is_1Mhz = true; + break; + case Write(0xc041): + megaii_interrupt_mask_ = *value; + video_->set_megaii_interrupts_enabled(*value); + is_1Mhz = true; + break; + case Read(0xc044): + // MMDELTAX byte. + *value = 0; + is_1Mhz = true; + break; + case Read(0xc045): + // MMDELTAY byte. + *value = 0; + is_1Mhz = true; + break; + case Read(0xc046): + *value = video_->get_megaii_interrupt_status(); + is_1Mhz = true; + break; + case Read(0xc047): case Write(0xc047): + video_->clear_megaii_interrupts(); + is_1Mhz = true; + break; + case Read(0xc048): case Write(0xc048): + // No-op: Clear Mega II mouse interrupt flags + is_1Mhz = true; + break; - case Read(0xc02e): - *value = video_.last_valid()->get_vertical_counter(video_.time_since_flush()); - is_1Mhz = true; - break; - case Read(0xc02f): - *value = video_.last_valid()->get_horizontal_counter(video_.time_since_flush()); - is_1Mhz = true; - break; + // Language select. + // b7, b6, b5: character generator language select; + // b4: NTSC/PAL (0 = NTC); + // b3: language select — primary or secondary. + case Read(0xc02b): + *value = language_; + break; + case Write(0xc02b): + language_ = *value; + break; - // C037 seems to be just a full-speed storage register. - case Read(0xc037): - *value = c037_; - break; - case Write(0xc037): - c037_ = *value; - break; + // TODO: 0xc02c is "Addr for tst mode read of character ROM". So it reads... what? - case Read(0xc041): - *value = megaii_interrupt_mask_; - is_1Mhz = true; - break; - case Write(0xc041): - megaii_interrupt_mask_ = *value; - video_->set_megaii_interrupts_enabled(*value); - is_1Mhz = true; - break; - case Read(0xc044): - // MMDELTAX byte. - *value = 0; - is_1Mhz = true; - break; - case Read(0xc045): - // MMDELTAY byte. - *value = 0; - is_1Mhz = true; - break; - case Read(0xc046): - *value = video_->get_megaii_interrupt_status(); - is_1Mhz = true; - break; - case Read(0xc047): case Write(0xc047): - video_->clear_megaii_interrupts(); - is_1Mhz = true; - break; - case Read(0xc048): case Write(0xc048): - // No-op: Clear Mega II mouse interrupt flags - is_1Mhz = true; - break; + // Slot select. + case Read(0xc02d): + // b7: 0 = internal ROM code for slot 7; + // b6: 0 = internal ROM code for slot 6; + // b5: 0 = internal ROM code for slot 5; + // b4: 0 = internal ROM code for slot 4; + // b3: reserved; + // b2: internal ROM code for slot 2; + // b1: internal ROM code for slot 1; + // b0: reserved. + *value = card_mask_; + break; + case Write(0xc02d): + card_mask_ = *value; + break; - // Language select. - // b7, b6, b5: character generator language select; - // b4: NTSC/PAL (0 = NTC); - // b3: language select — primary or secondary. - case Read(0xc02b): - *value = language_; - break; - case Write(0xc02b): - language_ = *value; - break; + case Read(0xc030): case Write(0xc030): { + AudioUpdater updater(this); + audio_toggle_.set_output(!audio_toggle_.get_output()); + } break; - // TODO: 0xc02c is "Addr for tst mode read of character ROM". So it reads... what? + // 'Test Mode', whatever that is (?) + case Read(0xc06e): case Read(0xc06f): + case Write(0xc06e): case Write(0xc06f): + test_mode_ = address & 1; + break; + case Read(0xc06d): + *value = test_mode_ * 0x80; + break; - // Slot select. - case Read(0xc02d): - // b7: 0 = internal ROM code for slot 7; - // b6: 0 = internal ROM code for slot 6; - // b5: 0 = internal ROM code for slot 5; - // b4: 0 = internal ROM code for slot 4; - // b3: reserved; - // b2: internal ROM code for slot 2; - // b1: internal ROM code for slot 1; - // b0: reserved. - *value = card_mask_; - break; - case Write(0xc02d): - card_mask_ = *value; - break; + // Disk drive controls additional to the IWM. + case Read(0xc031): + *value = disk_select_; + break; + case Write(0xc031): + // b7: 0 = use head 0; 1 = use head 1. + // b6: 0 = use 5.25" disks; 1 = use 3.5". + disk_select_ = *value; + iwm_->set_select(*value & 0x80); - case Read(0xc030): case Write(0xc030): { - AudioUpdater updater(this); - audio_toggle_.set_output(!audio_toggle_.get_output()); - } break; + // Presumably bit 6 selects between two 5.25" drives rather than the two 3.5"? + if(*value & 0x40) { + iwm_->set_drive(0, &drives35_[0]); + iwm_->set_drive(1, &drives35_[1]); + } else { + iwm_->set_drive(0, &drives525_[0]); + iwm_->set_drive(1, &drives525_[1]); + } + break; - // 'Test Mode', whatever that is (?) - case Read(0xc06e): case Read(0xc06f): - case Write(0xc06e): case Write(0xc06f): - test_mode_ = address & 1; - break; - case Read(0xc06d): - *value = test_mode_ * 0x80; - break; + // Addresses on other Apple II devices which do nothing on the GS. + case Read(0xc020): case Write(0xc020): // Reserved for future system expansion. + case Read(0xc028): case Write(0xc028): // ROMBANK; "not used in Apple IIGS". + case Read(0xc02a): case Write(0xc02a): // Reserved for future system expansion. + case Read(0xc040): case Write(0xc040): // Reserved for future system expansion. + case Read(0xc042): case Write(0xc042): // Reserved for future system expansion. + case Read(0xc043): case Write(0xc043): // Reserved for future system expansion. + case Read(0xc049): case Write(0xc049): // Reserved for future system expansion. + case Read(0xc04a): case Write(0xc04a): // Reserved for future system expansion. + case Read(0xc04b): case Write(0xc04b): // Reserved for future system expansion. + case Read(0xc04c): case Write(0xc04c): // Reserved for future system expansion. + case Read(0xc04d): case Write(0xc04d): // Reserved for future system expansion. + case Read(0xc04e): case Write(0xc04e): // Reserved for future system expansion. + case Read(0xc04f): case Write(0xc04f): // Reserved for future system expansion. + case Read(0xc06b): case Write(0xc06b): // Reserved for future system expansion. + case Read(0xc06c): case Write(0xc06c): // Reserved for future system expansion. + case Write(0xc07e): + break; - // Disk drive controls additional to the IWM. - case Read(0xc031): - *value = disk_select_; - break; - case Write(0xc031): - // b7: 0 = use head 0; 1 = use head 1. - // b6: 0 = use 5.25" disks; 1 = use 3.5". - disk_select_ = *value; - iwm_->set_select(*value & 0x80); + default: + // Update motor mask bits. + switch(address_suffix) { + case 0xc0c8: motor_flags_ &= ~0x01; break; + case 0xc0c9: motor_flags_ |= 0x01; break; + case 0xc0d8: motor_flags_ &= ~0x02; break; + case 0xc0d9: motor_flags_ |= 0x02; break; + case 0xc0e8: motor_flags_ &= ~0x04; break; + case 0xc0e9: motor_flags_ |= 0x04; break; + case 0xc0f8: motor_flags_ &= ~0x08; break; + case 0xc0f9: motor_flags_ |= 0x08; break; + } - // Presumably bit 6 selects between two 5.25" drives rather than the two 3.5"? - if(*value & 0x40) { - iwm_->set_drive(0, &drives35_[0]); - iwm_->set_drive(1, &drives35_[1]); + // Check for a card access. + if(address_suffix >= 0xc080 && address_suffix < 0xc800) { + // This is an abridged version of the similar code in AppleII.cpp from + // line 653; it would be good to factor that out and support cards here. + // For now just either supply the internal ROM or nothing as per the + // current card mask. + + size_t card_number = 0; + if(address_suffix >= 0xc100) { + /* + Decode the area conventionally used by cards for ROMs: + 0xCn00 to 0xCnff: card n. + */ + card_number = (address_suffix - 0xc000) >> 8; } else { - iwm_->set_drive(0, &drives525_[0]); - iwm_->set_drive(1, &drives525_[1]); - } - break; - - // Addresses on other Apple II devices which do nothing on the GS. - case Read(0xc020): case Write(0xc020): // Reserved for future system expansion. - case Read(0xc028): case Write(0xc028): // ROMBANK; "not used in Apple IIGS". - case Read(0xc02a): case Write(0xc02a): // Reserved for future system expansion. - case Read(0xc040): case Write(0xc040): // Reserved for future system expansion. - case Read(0xc042): case Write(0xc042): // Reserved for future system expansion. - case Read(0xc043): case Write(0xc043): // Reserved for future system expansion. - case Read(0xc049): case Write(0xc049): // Reserved for future system expansion. - case Read(0xc04a): case Write(0xc04a): // Reserved for future system expansion. - case Read(0xc04b): case Write(0xc04b): // Reserved for future system expansion. - case Read(0xc04c): case Write(0xc04c): // Reserved for future system expansion. - case Read(0xc04d): case Write(0xc04d): // Reserved for future system expansion. - case Read(0xc04e): case Write(0xc04e): // Reserved for future system expansion. - case Read(0xc04f): case Write(0xc04f): // Reserved for future system expansion. - case Read(0xc06b): case Write(0xc06b): // Reserved for future system expansion. - case Read(0xc06c): case Write(0xc06c): // Reserved for future system expansion. - case Write(0xc07e): - break; - - default: - // Update motor mask bits. - switch(address_suffix) { - case 0xc0c8: motor_flags_ &= ~0x01; break; - case 0xc0c9: motor_flags_ |= 0x01; break; - case 0xc0d8: motor_flags_ &= ~0x02; break; - case 0xc0d9: motor_flags_ |= 0x02; break; - case 0xc0e8: motor_flags_ &= ~0x04; break; - case 0xc0e9: motor_flags_ |= 0x04; break; - case 0xc0f8: motor_flags_ &= ~0x08; break; - case 0xc0f9: motor_flags_ |= 0x08; break; + /* + Decode the area conventionally used by cards for registers: + C0n0 to C0nF: card n - 8. + */ + card_number = (address_suffix - 0xc080) >> 4; } - // Check for a card access. - if(address_suffix >= 0xc080 && address_suffix < 0xc800) { - // This is an abridged version of the similar code in AppleII.cpp from - // line 653; it would be good to factor that out and support cards here. - // For now just either supply the internal ROM or nothing as per the - // current card mask. - - size_t card_number = 0; - if(address_suffix >= 0xc100) { - /* - Decode the area conventionally used by cards for ROMs: - 0xCn00 to 0xCnff: card n. - */ - card_number = (address_suffix - 0xc000) >> 8; - } else { - /* - Decode the area conventionally used by cards for registers: - C0n0 to C0nF: card n - 8. - */ - card_number = (address_suffix - 0xc080) >> 4; + const uint8_t permitted_card_mask_ = card_mask_ & 0xf6; + if(permitted_card_mask_ & (1 << card_number)) { + // TODO: Access an actual card. + assert(operation != CPU::WDC65816::BusOperation::ReadOpcode); + if(is_read) { + *value = 0xff; } + } else { + switch(address_suffix) { + default: + // Temporary: log _potential_ mistakes. + if((address_suffix < 0xc100 && address_suffix >= 0xc090) || (address_suffix < 0xc080)) { + printf("Internal card-area access: %04x\n", address_suffix); + } + if(is_read) { + *value = rom_[rom_.size() - 65536 + address_suffix]; + } + break; - const uint8_t permitted_card_mask_ = card_mask_ & 0xf6; - if(permitted_card_mask_ & (1 << card_number)) { - // TODO: Access an actual card. - assert(operation != CPU::WDC65816::BusOperation::ReadOpcode); - if(is_read) { - *value = 0xff; - } - } else { - switch(address_suffix) { - default: - // Temporary: log _potential_ mistakes. - if((address_suffix < 0xc100 && address_suffix >= 0xc090) || (address_suffix < 0xc080)) { - printf("Internal card-area access: %04x\n", address_suffix); - } - if(is_read) { - *value = rom_[rom_.size() - 65536 + address_suffix]; - } - break; - - // IWM. - case 0xc0e0: case 0xc0e1: case 0xc0e2: case 0xc0e3: - case 0xc0e4: case 0xc0e5: case 0xc0e6: case 0xc0e7: - case 0xc0e8: case 0xc0e9: case 0xc0ea: case 0xc0eb: - case 0xc0ec: case 0xc0ed: case 0xc0ee: case 0xc0ef: - if(is_read) { - *value = iwm_->read(int(address_suffix)); - } else { - iwm_->write(int(address_suffix), *value); - } - break; - } + // IWM. + case 0xc0e0: case 0xc0e1: case 0xc0e2: case 0xc0e3: + case 0xc0e4: case 0xc0e5: case 0xc0e6: case 0xc0e7: + case 0xc0e8: case 0xc0e9: case 0xc0ea: case 0xc0eb: + case 0xc0ec: case 0xc0ed: case 0xc0ee: case 0xc0ef: + if(is_read) { + *value = iwm_->read(int(address_suffix)); + } else { + iwm_->write(int(address_suffix), *value); + } + break; } + } #undef ReadWrite #undef Read #undef Write - } else { - // Access the internal ROM. - // - // TODO: should probably occur only if there was a preceding access to a built-in - // card ROM? - if(is_read) { - *value = rom_[rom_.size() - 65536 + address_suffix]; - } - - if(address_suffix < 0xc080) { - // TODO: all other IO accesses. - printf("Unhandled IO %s: %04x\n", is_read ? "read" : "write", address_suffix); - } + } else { + // Access the internal ROM. + // + // TODO: should probably occur only if there was a preceding access to a built-in + // card ROM? + if(is_read) { + *value = rom_[rom_.size() - 65536 + address_suffix]; } - } - } else { - // For debugging purposes; if execution heads off into an unmapped page then - // it's pretty certain that my 65816 still has issues. - assert(operation != CPU::WDC65816::BusOperation::ReadOpcode || region.read); - is_1Mhz = region.flags & MemoryMap::Region::Is1Mhz; - if(isReadOperation(operation)) { - *value = memory_.read(region, address); - } else { - // Shadowed writes also occur "at 1Mhz". - // TODO: this is probably an approximation. I'm assuming that there's the ability asynchronously to post - // both a 1Mhz cycle and a 2.8Mhz cycle and since the latter always fits into the former, this is sufficiently - // descriptive. I suspect this isn't true as it wouldn't explain the speed boost that Wolfenstein and others - // get by adding periodic NOPs within their copy-to-shadow step. - // - // Maybe the interaction with 2.8Mhz refresh isn't as straightforward as I think? - const bool is_shadowed = memory_.is_shadowed(region, address); - is_1Mhz |= is_shadowed; - - // Use a very broad test for flushing video: any write to $e0 or $e1, or any write that is shadowed. - // TODO: at least restrict the e0/e1 test to possible video buffers! - if((address >= 0xe0'0400 && address < 0xe1'a000) || is_shadowed) { - video_.flush(); + if(address_suffix < 0xc080) { + // TODO: all other IO accesses. + printf("Unhandled IO %s: %04x\n", is_read ? "read" : "write", address_suffix); + } } - - memory_.write(region, address, *value); - } } + } else { + // For debugging purposes; if execution heads off into an unmapped page then + // it's pretty certain that my 65816 still has issues. + assert(operation != CPU::WDC65816::BusOperation::ReadOpcode || region.read); + is_1Mhz = region.flags & MemoryMap::Region::Is1Mhz; - Cycles duration; - - // In preparation for this test: the top bit of speed_register_ has been inverted, - // so 1 => 1Mhz, 0 => 2.8Mhz, and motor_flags_ always has that bit set. - if(is_1Mhz || (speed_register_ & motor_flags_)) { - // TODO: this is very implicitly linked to the video timing; make that overt somehow. Even if it's just with a redundant video setter at construction. - const int current_length = 14 + 2*(slow_access_phase_ / 896); // Length of cycle currently ongoing. - const int phase_adjust = (current_length - slow_access_phase_%14)%current_length; // Amount of time to expand waiting until end of cycle, if not actually at start. - const int access_phase = (slow_access_phase_ + phase_adjust)%912; // Phase at which access will begin. - const int next_length = 14 + 2*(access_phase / 896); // Length of cycle that this access will occur within. - duration = Cycles(next_length + phase_adjust); + if(isReadOperation(operation)) { + *value = memory_.read(region, address); } else { - // Clues as to 'fast' refresh timing: + // Shadowed writes also occur "at 1Mhz". + // TODO: this is probably an approximation. I'm assuming that there's the ability asynchronously to post + // both a 1Mhz cycle and a 2.8Mhz cycle and since the latter always fits into the former, this is sufficiently + // descriptive. I suspect this isn't true as it wouldn't explain the speed boost that Wolfenstein and others + // get by adding periodic NOPs within their copy-to-shadow step. // - // (i) "The time required for the refresh cycles reduces the effective - // processor speed for programs in RAM by about 8 percent."; - // (ii) "These cycles occur approximately every 3.5 microseconds" - // - // 3.5µs @ 14,318,180Hz => one every 50.11 cycles. Safe to assume every 10th fast cycle - // is refresh? That feels like a lot. - // - // (and the IIgs is smart enough that refresh is applicable only to RAM accesses). - const int phase_adjust = (5 - fast_access_phase_%5)%5; - const int refresh = (fast_access_phase_ / 45) * bool(region.write) * 5; - duration = Cycles(5 + phase_adjust + refresh); - } - // TODO: lookup tables to avoid the above? LCM of the two phases is 22,800 so probably 912+50 bytes plus two counters. - fast_access_phase_ = (fast_access_phase_ + duration.as<int>()) % 50; - slow_access_phase_ = (slow_access_phase_ + duration.as<int>()) % 912; + // Maybe the interaction with 2.8Mhz refresh isn't as straightforward as I think? + const bool is_shadowed = memory_.is_shadowed(region, address); + is_1Mhz |= is_shadowed; - // Propagate time far and wide. - cycles_since_clock_tick_ += duration; - auto ticks = cycles_since_clock_tick_.divide(Cycles(CLOCK_RATE)).as_integral(); - while(ticks--) { - clock_.update(); - video_.last_valid()->notify_clock_tick(); // The video controller marshalls the one-second interrupt. - // TODO: I think I may have made a false assumption here; does - // the VGC have an independent 1-second interrupt? - update_interrupts(); - } - - video_ += duration; - iwm_ += duration; - cycles_since_audio_update_ += duration; - adb_glu_ += duration; - - if(cycles_since_audio_update_ >= cycles_until_audio_event_) { - AudioUpdater updater(this); - update_interrupts(); - } - if(video_.did_flush()) { - update_interrupts(); - - const bool is_vertical_blank = video_.last_valid()->get_is_vertical_blank(video_.time_since_flush()); - if(is_vertical_blank != adb_glu_.last_valid()->get_vertical_blank()) { - adb_glu_->set_vertical_blank(is_vertical_blank); + // Use a very broad test for flushing video: any write to $e0 or $e1, or any write that is shadowed. + // TODO: at least restrict the e0/e1 test to possible video buffers! + if((address >= 0xe0'0400 && address < 0xe1'a000) || is_shadowed) { + video_.flush(); } + + memory_.write(region, address, *value); } - - joysticks_.update_charge(duration.as<float>() / 14.0f); - - return duration; } - void update_interrupts() { - // Update the interrupt line. - // TODO: add ADB controller as event source. - m65816_.set_irq_line(video_.last_valid()->get_interrupt_line() || sound_glu_.get_interrupt_line()); + Cycles duration; + + // In preparation for this test: the top bit of speed_register_ has been inverted, + // so 1 => 1Mhz, 0 => 2.8Mhz, and motor_flags_ always has that bit set. + if(is_1Mhz || (speed_register_ & motor_flags_)) { + // TODO: this is very implicitly linked to the video timing; make that overt somehow. Even if it's just with a redundant video setter at construction. + const int current_length = 14 + 2*(slow_access_phase_ / 896); // Length of cycle currently ongoing. + const int phase_adjust = (current_length - slow_access_phase_%14)%current_length; // Amount of time to expand waiting until end of cycle, if not actually at start. + const int access_phase = (slow_access_phase_ + phase_adjust)%912; // Phase at which access will begin. + const int next_length = 14 + 2*(access_phase / 896); // Length of cycle that this access will occur within. + duration = Cycles(next_length + phase_adjust); + } else { + // Clues as to 'fast' refresh timing: + // + // (i) "The time required for the refresh cycles reduces the effective + // processor speed for programs in RAM by about 8 percent."; + // (ii) "These cycles occur approximately every 3.5 microseconds" + // + // 3.5µs @ 14,318,180Hz => one every 50.11 cycles. Safe to assume every 10th fast cycle + // is refresh? That feels like a lot. + // + // (and the IIgs is smart enough that refresh is applicable only to RAM accesses). + const int phase_adjust = (5 - fast_access_phase_%5)%5; + const int refresh = (fast_access_phase_ / 45) * bool(region.write) * 5; + duration = Cycles(5 + phase_adjust + refresh); + } + // TODO: lookup tables to avoid the above? LCM of the two phases is 22,800 so probably 912+50 bytes plus two counters. + fast_access_phase_ = (fast_access_phase_ + duration.as<int>()) % 50; + slow_access_phase_ = (slow_access_phase_ + duration.as<int>()) % 912; + + // Propagate time far and wide. + cycles_since_clock_tick_ += duration; + auto ticks = cycles_since_clock_tick_.divide(Cycles(CLOCK_RATE)).as_integral(); + while(ticks--) { + clock_.update(); + video_.last_valid()->notify_clock_tick(); // The video controller marshalls the one-second interrupt. + // TODO: I think I may have made a false assumption here; does + // the VGC have an independent 1-second interrupt? + update_interrupts(); } - // MARK: - Input. - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; + video_ += duration; + iwm_ += duration; + cycles_since_audio_update_ += duration; + adb_glu_ += duration; + + if(cycles_since_audio_update_ >= cycles_until_audio_event_) { + AudioUpdater updater(this); + update_interrupts(); + } + if(video_.did_flush()) { + update_interrupts(); + + const bool is_vertical_blank = video_.last_valid()->get_is_vertical_blank(video_.time_since_flush()); + if(is_vertical_blank != adb_glu_.last_valid()->get_vertical_blank()) { + adb_glu_->set_vertical_blank(is_vertical_blank); + } } - void set_key_state(uint16_t key, bool is_pressed) final { - adb_glu_.last_valid()->keyboard().set_key_pressed(Apple::ADB::Key(key), is_pressed); - } + joysticks_.update_charge(duration.as<float>() / 14.0f); - void clear_all_keys() final { - adb_glu_.last_valid()->keyboard().clear_all_keys(); - } + return duration; + } - Inputs::Mouse &get_mouse() final { - return adb_glu_.last_valid()->get_mouse(); - } + void update_interrupts() { + // Update the interrupt line. + // TODO: add ADB controller as event source. + m65816_.set_irq_line(video_.last_valid()->get_interrupt_line() || sound_glu_.get_interrupt_line()); + } - const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final { - return joysticks_.get_joysticks(); - } + // MARK: - Input. + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + void set_key_state(uint16_t key, bool is_pressed) final { + adb_glu_.last_valid()->keyboard().set_key_pressed(Apple::ADB::Key(key), is_pressed); + } + + void clear_all_keys() final { + adb_glu_.last_valid()->keyboard().clear_all_keys(); + } + + Inputs::Mouse &get_mouse() final { + return adb_glu_.last_valid()->get_mouse(); + } + + const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final { + return joysticks_.get_joysticks(); + } + +private: + CPU::WDC65816::Processor<ConcreteMachine, false> m65816_; + MemoryMap memory_; + + // MARK: - Timing. + + int fast_access_phase_ = 0; + int slow_access_phase_ = 0; + + uint8_t speed_register_ = 0x40; // i.e. Power-on status. (TODO: only if ROM03?) + uint8_t motor_flags_ = 0x80; + + // MARK: - Memory storage. + + std::vector<uint8_t> ram_; + std::vector<uint8_t> rom_; + uint8_t c037_ = 0; + + // MARK: - Other components. + + Apple::Clock::ParallelClock clock_; + JustInTimeActor<Apple::IIgs::Video::Video, Cycles, 1, 2> video_; // i.e. run video at 7Mhz. + JustInTimeActor<Apple::IIgs::ADB::GLU, Cycles, 1, 4> adb_glu_; // i.e. 3,579,545Mhz. + Zilog::SCC::z8530 scc_; + JustInTimeActor<Apple::IWM, Cycles, 1, 2> iwm_; + Cycles cycles_since_clock_tick_; + Apple::Macintosh::DoubleDensityDrive drives35_[2]; + Apple::Disk::DiskIIDrive drives525_[2]; + + // The audio parts. + Concurrency::AsyncTaskQueue<false> audio_queue_; + Apple::IIgs::Sound::GLU sound_glu_; + Audio::Toggle audio_toggle_; + using AudioSource = Outputs::Speaker::CompoundSource<Apple::IIgs::Sound::GLU, Audio::Toggle>; + AudioSource mixer_; + Outputs::Speaker::PullLowpass<AudioSource> speaker_; + Cycles cycles_since_audio_update_; + Cycles cycles_until_audio_event_; + static constexpr int audio_divider = 16; + void update_audio() { + const auto divided_cycles = cycles_since_audio_update_.divide(Cycles(audio_divider)); + sound_glu_.run_for(divided_cycles); + speaker_.run_for(audio_queue_, divided_cycles); + } + class AudioUpdater { + public: + AudioUpdater(ConcreteMachine *machine) : machine_(machine) { + machine_->update_audio(); + } + ~AudioUpdater() { + machine_->cycles_until_audio_event_ = machine_->sound_glu_.next_sequence_point(); + } private: - CPU::WDC65816::Processor<ConcreteMachine, false> m65816_; - MemoryMap memory_; + ConcreteMachine *machine_; + }; + friend AudioUpdater; - // MARK: - Timing. + // MARK: - Keyboard and joystick. + Apple::ADB::KeyboardMapper keyboard_mapper_; + Apple::II::JoystickPair joysticks_; - int fast_access_phase_ = 0; - int slow_access_phase_ = 0; + // MARK: - Cards. - uint8_t speed_register_ = 0x40; // i.e. Power-on status. (TODO: only if ROM03?) - uint8_t motor_flags_ = 0x80; + // TODO: most of cards. + uint8_t card_mask_ = 0x00; - // MARK: - Memory storage. + bool test_mode_ = false; + uint8_t language_ = 0; + uint8_t disk_select_ = 0; - std::vector<uint8_t> ram_; - std::vector<uint8_t> rom_; - uint8_t c037_ = 0; - - // MARK: - Other components. - - Apple::Clock::ParallelClock clock_; - JustInTimeActor<Apple::IIgs::Video::Video, Cycles, 1, 2> video_; // i.e. run video at 7Mhz. - JustInTimeActor<Apple::IIgs::ADB::GLU, Cycles, 1, 4> adb_glu_; // i.e. 3,579,545Mhz. - Zilog::SCC::z8530 scc_; - JustInTimeActor<Apple::IWM, Cycles, 1, 2> iwm_; - Cycles cycles_since_clock_tick_; - Apple::Macintosh::DoubleDensityDrive drives35_[2]; - Apple::Disk::DiskIIDrive drives525_[2]; - - // The audio parts. - Concurrency::AsyncTaskQueue<false> audio_queue_; - Apple::IIgs::Sound::GLU sound_glu_; - Audio::Toggle audio_toggle_; - using AudioSource = Outputs::Speaker::CompoundSource<Apple::IIgs::Sound::GLU, Audio::Toggle>; - AudioSource mixer_; - Outputs::Speaker::PullLowpass<AudioSource> speaker_; - Cycles cycles_since_audio_update_; - Cycles cycles_until_audio_event_; - static constexpr int audio_divider = 16; - void update_audio() { - const auto divided_cycles = cycles_since_audio_update_.divide(Cycles(audio_divider)); - sound_glu_.run_for(divided_cycles); - speaker_.run_for(audio_queue_, divided_cycles); - } - class AudioUpdater { - public: - AudioUpdater(ConcreteMachine *machine) : machine_(machine) { - machine_->update_audio(); - } - ~AudioUpdater() { - machine_->cycles_until_audio_event_ = machine_->sound_glu_.next_sequence_point(); - } - private: - ConcreteMachine *machine_; - }; - friend AudioUpdater; - - // MARK: - Keyboard and joystick. - Apple::ADB::KeyboardMapper keyboard_mapper_; - Apple::II::JoystickPair joysticks_; - - // MARK: - Cards. - - // TODO: most of cards. - uint8_t card_mask_ = 0x00; - - bool test_mode_ = false; - uint8_t language_ = 0; - uint8_t disk_select_ = 0; - - uint8_t megaii_interrupt_mask_ = 0; + uint8_t megaii_interrupt_mask_ = 0; }; } diff --git a/Machines/Apple/AppleIIgs/MemoryMap.hpp b/Machines/Apple/AppleIIgs/MemoryMap.hpp index 363758475..94fef9c05 100644 --- a/Machines/Apple/AppleIIgs/MemoryMap.hpp +++ b/Machines/Apple/AppleIIgs/MemoryMap.hpp @@ -20,144 +20,144 @@ namespace Apple::IIgs { class MemoryMap { - public: - // MARK: - Initial construction and configuration. +public: + // MARK: - Initial construction and configuration. - MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) { - setup_shadow_maps(is_rom03); - } + MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) { + setup_shadow_maps(is_rom03); + } - /// Sets the ROM and RAM storage underlying this MemoryMap. - void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom); + /// Sets the ROM and RAM storage underlying this MemoryMap. + void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom); - // MARK: - Live bus access notifications and register access. + // MARK: - Live bus access notifications and register access. - void set_shadow_register(uint8_t value); - uint8_t get_shadow_register() const; + void set_shadow_register(uint8_t value); + uint8_t get_shadow_register() const; - void set_speed_register(uint8_t value); + void set_speed_register(uint8_t value); - void set_state_register(uint8_t value); - uint8_t get_state_register() const; + void set_state_register(uint8_t value); + uint8_t get_state_register() const; - void access(uint16_t address, bool is_read); + void access(uint16_t address, bool is_read); - using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>; - const AuxiliaryMemorySwitches &auxiliary_switches() const { - return auxiliary_switches_; - } + using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>; + const AuxiliaryMemorySwitches &auxiliary_switches() const { + return auxiliary_switches_; + } - using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>; - const LanguageCardSwitches &language_card_switches() const { - return language_card_; - } + using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>; + const LanguageCardSwitches &language_card_switches() const { + return language_card_; + } - // MARK: - Accessors for reading and writing RAM. + // MARK: - Accessors for reading and writing RAM. - struct Region { - uint8_t *write = nullptr; - const uint8_t *read = nullptr; - uint8_t flags = 0; + struct Region { + uint8_t *write = nullptr; + const uint8_t *read = nullptr; + uint8_t flags = 0; - enum Flag: uint8_t { - Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock. - IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc. - }; + enum Flag: uint8_t { + Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock. + IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc. }; + }; - const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; } - uint8_t read(const Region ®ion, uint32_t address) const { - return region.read ? region.read[address] : 0xff; + const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; } + uint8_t read(const Region ®ion, uint32_t address) const { + return region.read ? region.read[address] : 0xff; + } + + bool is_shadowed(const Region ®ion, uint32_t address) const { + // ROM is never shadowed. + if(!region.write) { + return false; } - bool is_shadowed(const Region ®ion, uint32_t address) const { - // ROM is never shadowed. - if(!region.write) { - return false; - } - - const auto physical = physical_address(region, address); - assert(physical <= 0xff'ffff); - return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17]; - } - void write(const Region ®ion, uint32_t address, uint8_t value) { - if(!region.write) { - return; - } - - // Write once. - region.write[address] = value; - - // Write again, either to the same place (if unshadowed) or to the shadow destination. - static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff}; - const bool shadowed = is_shadowed(region, address); - shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value; + const auto physical = physical_address(region, address); + assert(physical <= 0xff'ffff); + return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17]; + } + void write(const Region ®ion, uint32_t address, uint8_t value) { + if(!region.write) { + return; } - // The objective is to support shadowing: - // 1. without storing a whole extra pointer, and such that the shadowing flags - // are orthogonal to the current auxiliary memory settings; - // 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and - // 3. to do so without introducing too much in the way of branching. - // - // Hence the implemented solution: if shadowing is enabled then use the distance from the start of - // physical RAM modulo 128k indexed into the bank $e0/$e1 RAM. - // - // With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch - // even on that. + // Write once. + region.write[address] = value; - private: - AuxiliaryMemorySwitches auxiliary_switches_; - LanguageCardSwitches language_card_; - friend AuxiliaryMemorySwitches; - friend LanguageCardSwitches; + // Write again, either to the same place (if unshadowed) or to the shadow destination. + static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff}; + const bool shadowed = is_shadowed(region, address); + shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value; + } - uint8_t shadow_register_ = 0x00; - uint8_t speed_register_ = 0x00; + // The objective is to support shadowing: + // 1. without storing a whole extra pointer, and such that the shadowing flags + // are orthogonal to the current auxiliary memory settings; + // 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and + // 3. to do so without introducing too much in the way of branching. + // + // Hence the implemented solution: if shadowing is enabled then use the distance from the start of + // physical RAM modulo 128k indexed into the bank $e0/$e1 RAM. + // + // With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch + // even on that. - // MARK: - Banking. +private: + AuxiliaryMemorySwitches auxiliary_switches_; + LanguageCardSwitches language_card_; + friend AuxiliaryMemorySwitches; + friend LanguageCardSwitches; - void assert_is_region(uint8_t start, uint8_t end); - template <int type> void set_paging(); + uint8_t shadow_register_ = 0x00; + uint8_t speed_register_ = 0x00; - uint8_t *ram_base_ = nullptr; + // MARK: - Banking. - // Memory layout here is done via double indirection; the main loop should: - // (i) use the top two bytes of the address to get an index from region_map; and - // (ii) use that to index the memory_regions table. - // - // Pointers are eight bytes at the time of writing, so the extra level of indirection - // reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb. - std::array<uint8_t, 65536> region_map_{}; - std::array<Region, 40> regions_; // An assert above ensures that this is large enough; there's no - // doctrinal reason for it to be whatever size it is now, just - // adjust as required. + void assert_is_region(uint8_t start, uint8_t end); + template <int type> void set_paging(); - std::size_t physical_address(const Region ®ion, uint32_t address) const { - return std::size_t(®ion.write[address] - ram_base_); - } + uint8_t *ram_base_ = nullptr; - // MARK: - Shadowing + // Memory layout here is done via double indirection; the main loop should: + // (i) use the top two bytes of the address to get an index from region_map; and + // (ii) use that to index the memory_regions table. + // + // Pointers are eight bytes at the time of writing, so the extra level of indirection + // reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb. + std::array<uint8_t, 65536> region_map_{}; + std::array<Region, 40> regions_; // An assert above ensures that this is large enough; there's no + // doctrinal reason for it to be whatever size it is now, just + // adjust as required. - // Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation - // beyond the first 64 bits at the time of writing, alas, so these are generated at runtime. - std::bitset<128> shadow_text1_; - std::bitset<128> shadow_text2_; - std::bitset<128> shadow_highres1_, shadow_highres1_aux_; - std::bitset<128> shadow_highres2_, shadow_highres2_aux_; - std::bitset<128> shadow_superhighres_; - void setup_shadow_maps(bool is_rom03); - void set_shadowing(); + std::size_t physical_address(const Region ®ion, uint32_t address) const { + return std::size_t(®ion.write[address] - ram_base_); + } - uint8_t *shadow_base_[2] = {nullptr, nullptr}; + // MARK: - Shadowing - // Divide the final 128kb of memory into 1kb chunks and flag to indicate whether - // each is a potential destination for shadowing. - std::bitset<128> shadow_pages_{}; + // Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation + // beyond the first 64 bits at the time of writing, alas, so these are generated at runtime. + std::bitset<128> shadow_text1_; + std::bitset<128> shadow_text2_; + std::bitset<128> shadow_highres1_, shadow_highres1_aux_; + std::bitset<128> shadow_highres2_, shadow_highres2_aux_; + std::bitset<128> shadow_superhighres_; + void setup_shadow_maps(bool is_rom03); + void set_shadowing(); - // Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether - // each is a potential source of shadowing. - std::bitset<128> shadow_banks_{}; + uint8_t *shadow_base_[2] = {nullptr, nullptr}; + + // Divide the final 128kb of memory into 1kb chunks and flag to indicate whether + // each is a potential destination for shadowing. + std::bitset<128> shadow_pages_{}; + + // Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether + // each is a potential source of shadowing. + std::bitset<128> shadow_banks_{}; }; } diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index 711873cbd..b217a59dc 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -17,90 +17,90 @@ namespace Apple::IIgs::Sound { class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't this stereo? - public: - GLU(Concurrency::AsyncTaskQueue<false> &audio_queue); +public: + GLU(Concurrency::AsyncTaskQueue<false> &audio_queue); - void set_control(uint8_t); - uint8_t get_control(); - void set_data(uint8_t); - uint8_t get_data(); - void set_address_low(uint8_t); - uint8_t get_address_low(); - void set_address_high(uint8_t); - uint8_t get_address_high(); + void set_control(uint8_t); + uint8_t get_control(); + void set_data(uint8_t); + uint8_t get_data(); + void set_address_low(uint8_t); + uint8_t get_address_low(); + void set_address_high(uint8_t); + uint8_t get_address_high(); - void run_for(Cycles); - Cycles next_sequence_point() const; - bool get_interrupt_line(); + void run_for(Cycles); + Cycles next_sequence_point() const; + bool get_interrupt_line(); - // SampleSource. - template <Outputs::Speaker::Action action> - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); - void set_sample_volume_range(std::int16_t range); - bool is_zero_level() const { return false; } // TODO. + // SampleSource. + template <Outputs::Speaker::Action action> + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); + void set_sample_volume_range(std::int16_t range); + bool is_zero_level() const { return false; } // TODO. - private: - Concurrency::AsyncTaskQueue<false> &audio_queue_; +private: + Concurrency::AsyncTaskQueue<false> &audio_queue_; - uint16_t address_ = 0; + uint16_t address_ = 0; - // Use a circular buffer for piping memory alterations onto the audio - // thread; it would be prohibitive to defer every write individually. - // - // Assumed: on most modern architectures, an atomic 64-bit read or - // write can be achieved locklessly. - struct MemoryWrite { - uint32_t time; - uint16_t address; - uint8_t value; - bool enabled; - }; - static_assert(sizeof(MemoryWrite) == 8); - constexpr static int StoreBufferSize = 16384; + // Use a circular buffer for piping memory alterations onto the audio + // thread; it would be prohibitive to defer every write individually. + // + // Assumed: on most modern architectures, an atomic 64-bit read or + // write can be achieved locklessly. + struct MemoryWrite { + uint32_t time; + uint16_t address; + uint8_t value; + bool enabled; + }; + static_assert(sizeof(MemoryWrite) == 8); + constexpr static int StoreBufferSize = 16384; - std::atomic<MemoryWrite> pending_stores_[StoreBufferSize]; - uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0; - uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0; + std::atomic<MemoryWrite> pending_stores_[StoreBufferSize]; + uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0; + uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0; - // Maintain state both 'locally' (i.e. on the emulation thread) and - // 'remotely' (i.e. on the audio thread). - struct EnsoniqState { - uint8_t ram_[65536]; - struct Oscillator { - uint32_t position; + // Maintain state both 'locally' (i.e. on the emulation thread) and + // 'remotely' (i.e. on the audio thread). + struct EnsoniqState { + uint8_t ram_[65536]; + struct Oscillator { + uint32_t position; - // Programmer-set values. - uint16_t velocity; - uint8_t volume; - uint8_t address; - uint8_t control; - uint8_t table_size; + // Programmer-set values. + uint16_t velocity; + uint8_t volume; + uint8_t address; + uint8_t control; + uint8_t table_size; - // Derived state. - uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel - // has wrapped around. It's a function of table_size. - bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were - // it currently enabled to do so. + // Derived state. + uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel + // has wrapped around. It's a function of table_size. + bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were + // it currently enabled to do so. - uint8_t sample(uint8_t *ram); - int16_t output(uint8_t *ram); - } oscillators[32]; + uint8_t sample(uint8_t *ram); + int16_t output(uint8_t *ram); + } oscillators[32]; - // Some of these aren't actually needed on both threads. - uint8_t control = 0; - int oscillator_count = 1; + // Some of these aren't actually needed on both threads. + uint8_t control = 0; + int oscillator_count = 1; - void set_register(uint16_t address, uint8_t value); - } local_, remote_; + void set_register(uint16_t address, uint8_t value); + } local_, remote_; - // Functions to update an EnsoniqState; these don't belong to the state itself - // because they also access the pending stores (inter alia). - template <Outputs::Speaker::Action action> - void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target); - void skip_audio(EnsoniqState &state, size_t number_of_samples); + // Functions to update an EnsoniqState; these don't belong to the state itself + // because they also access the pending stores (inter alia). + template <Outputs::Speaker::Action action> + void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target); + void skip_audio(EnsoniqState &state, size_t number_of_samples); - // Audio-thread state. - int16_t output_range_ = 0; + // Audio-thread state. + int16_t output_range_ = 0; }; } diff --git a/Machines/Apple/AppleIIgs/Video.hpp b/Machines/Apple/AppleIIgs/Video.hpp index c7d17e9bd..8845cd20c 100644 --- a/Machines/Apple/AppleIIgs/Video.hpp +++ b/Machines/Apple/AppleIIgs/Video.hpp @@ -20,236 +20,236 @@ namespace Apple::IIgs::Video { stretched cycle. */ class Video: public Apple::II::VideoSwitches<Cycles> { +public: + Video(); + void set_internal_ram(const uint8_t *); + + bool get_is_vertical_blank(Cycles offset); + uint8_t get_horizontal_counter(Cycles offset); + uint8_t get_vertical_counter(Cycles offset); + + void set_new_video(uint8_t); + uint8_t get_new_video(); + + void clear_interrupts(uint8_t); + uint8_t get_interrupt_register(); + void set_interrupt_register(uint8_t); + bool get_interrupt_line(); + + void notify_clock_tick(); + + void set_border_colour(uint8_t); + void set_text_colour(uint8_t); + uint8_t get_text_colour(); + uint8_t get_border_colour(); + + void set_composite_is_colour(bool); + bool get_composite_is_colour(); + + /// Sets the scan target. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const; + + /// Sets the type of output. + void set_display_type(Outputs::Display::DisplayType); + + /// Gets the type of output. + Outputs::Display::DisplayType get_display_type() const; + + /// Determines the period until video might autonomously update its interrupt lines. + Cycles next_sequence_point() const; + + /// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are + /// generated here. + void set_megaii_interrupts_enabled(uint8_t); + + uint8_t get_megaii_interrupt_status(); + + void clear_megaii_interrupts(); + +private: + Outputs::CRT::CRT crt_; + + // This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs. + enum class GraphicsMode { + Text = 0, + DoubleText, + HighRes, + DoubleHighRes, + LowRes, + DoubleLowRes, + FatLowRes, + + // Additions: + DoubleHighResMono, + SuperHighRes + }; + constexpr bool is_colour_ntsc(const GraphicsMode m) { + return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes; + } + + GraphicsMode graphics_mode(int row) const { + if(new_video_ & 0x80) { + return GraphicsMode::SuperHighRes; + } + + const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row); + switch(ii_mode) { + // Coupling very much assumed here. + case Apple::II::GraphicsMode::DoubleHighRes: + if(new_video_ & 0x20) { + return GraphicsMode::DoubleHighResMono; + } + [[fallthrough]]; + + default: return GraphicsMode(int(ii_mode)); break; + } + } + + enum class PixelBufferFormat { + Text, DoubleText, NTSC, NTSCMono, SuperHighRes + }; + constexpr PixelBufferFormat format_for_mode(GraphicsMode m) { + switch(m) { + case GraphicsMode::Text: return PixelBufferFormat::Text; + case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText; + default: return PixelBufferFormat::NTSC; + case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono; + case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes; + } + } + + void advance(Cycles); + + uint8_t new_video_ = 0x01; + + class Interrupts { public: - Video(); - void set_internal_ram(const uint8_t *); + void add(uint8_t value) { + // Taken literally, status accumulates regardless of being enabled, + // potentially to be polled, it simply doesn't trigger an interrupt. + value_ |= value; + test(); + } - bool get_is_vertical_blank(Cycles offset); - uint8_t get_horizontal_counter(Cycles offset); - uint8_t get_vertical_counter(Cycles offset); + void clear(uint8_t value) { + // Zeroes in bits 5 or 6 clear the respective interrupts. + value_ &= value | ~0x60; + test(); + } - void set_new_video(uint8_t); - uint8_t get_new_video(); + void set_control(uint8_t value) { + // Ones in bits 1 or 2 enable the respective interrupts. + value_ = (value_ & ~0x6) | (value & 0x6); + test(); + } - void clear_interrupts(uint8_t); - uint8_t get_interrupt_register(); - void set_interrupt_register(uint8_t); - bool get_interrupt_line(); + uint8_t status() const { + return value_; + } - void notify_clock_tick(); - - void set_border_colour(uint8_t); - void set_text_colour(uint8_t); - uint8_t get_text_colour(); - uint8_t get_border_colour(); - - void set_composite_is_colour(bool); - bool get_composite_is_colour(); - - /// Sets the scan target. - void set_scan_target(Outputs::Display::ScanTarget *scan_target); - - /// Gets the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const; - - /// Sets the type of output. - void set_display_type(Outputs::Display::DisplayType); - - /// Gets the type of output. - Outputs::Display::DisplayType get_display_type() const; - - /// Determines the period until video might autonomously update its interrupt lines. - Cycles next_sequence_point() const; - - /// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are - /// generated here. - void set_megaii_interrupts_enabled(uint8_t); - - uint8_t get_megaii_interrupt_status(); - - void clear_megaii_interrupts(); + bool active() const { + return value_ & 0x80; + } private: - Outputs::CRT::CRT crt_; - - // This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs. - enum class GraphicsMode { - Text = 0, - DoubleText, - HighRes, - DoubleHighRes, - LowRes, - DoubleLowRes, - FatLowRes, - - // Additions: - DoubleHighResMono, - SuperHighRes - }; - constexpr bool is_colour_ntsc(const GraphicsMode m) { - return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes; - } - - GraphicsMode graphics_mode(int row) const { - if(new_video_ & 0x80) { - return GraphicsMode::SuperHighRes; - } - - const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row); - switch(ii_mode) { - // Coupling very much assumed here. - case Apple::II::GraphicsMode::DoubleHighRes: - if(new_video_ & 0x20) { - return GraphicsMode::DoubleHighResMono; - } - [[fallthrough]]; - - default: return GraphicsMode(int(ii_mode)); break; + void test() { + value_ &= 0x7f; + if((value_ >> 4) & value_ & 0x6) { + value_ |= 0x80; } } - enum class PixelBufferFormat { - Text, DoubleText, NTSC, NTSCMono, SuperHighRes - }; - constexpr PixelBufferFormat format_for_mode(GraphicsMode m) { - switch(m) { - case GraphicsMode::Text: return PixelBufferFormat::Text; - case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText; - default: return PixelBufferFormat::NTSC; - case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono; - case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes; - } - } - - void advance(Cycles); - - uint8_t new_video_ = 0x01; - - class Interrupts { - public: - void add(uint8_t value) { - // Taken literally, status accumulates regardless of being enabled, - // potentially to be polled, it simply doesn't trigger an interrupt. - value_ |= value; - test(); - } - - void clear(uint8_t value) { - // Zeroes in bits 5 or 6 clear the respective interrupts. - value_ &= value | ~0x60; - test(); - } - - void set_control(uint8_t value) { - // Ones in bits 1 or 2 enable the respective interrupts. - value_ = (value_ & ~0x6) | (value & 0x6); - test(); - } - - uint8_t status() const { - return value_; - } - - bool active() const { - return value_ & 0x80; - } - - private: - void test() { - value_ &= 0x7f; - if((value_ >> 4) & value_ & 0x6) { - value_ |= 0x80; - } - } - - // Overall meaning of value is as per the VGC interrupt register, i.e. - // - // b7: interrupt status; - // b6: 1-second interrupt status; - // b5: scan-line interrupt status; - // b4: reserved; - // b3: reserved; - // b2: 1-second interrupt enable; - // b1: scan-line interrupt enable; - // b0: reserved. - uint8_t value_ = 0x00; - } interrupts_; - - int cycles_into_frame_ = 0; - const uint8_t *ram_ = nullptr; - - // The modal colours. - uint16_t border_colour_ = 0; - uint8_t border_colour_entry_ = 0; - uint8_t text_colour_entry_ = 0xf0; - uint16_t text_colour_ = 0xffff; - uint16_t background_colour_ = 0; - - // Current pixel output buffer and conceptual format. - PixelBufferFormat pixels_format_; - uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr; - int pixels_start_column_; - - void output_row(int row, int start, int end); - - uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const; - - uint16_t *output_text(uint16_t *target, int start, int end, int row) const; - uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const; - uint16_t *output_char(uint16_t *target, uint8_t source, int row) const; - - uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row); - - uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row); - - // Super high-res per-line state. - uint8_t line_control_; - uint16_t palette_[16]; - - // Storage used for fill mode. - uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_; - - // Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB. + // Overall meaning of value is as per the VGC interrupt register, i.e. // - // My understanding of the real-life algorithm is: maintain a four-bit buffer. - // Fill it in a circular fashion. Ordinarily, output the result of looking - // up the RGB mapping of those four bits of Apple II output (which outputs four - // bits per NTSC colour cycle), commuted as per current phase. But if the bit - // being inserted differs from that currently in its position in the shift - // register, hold the existing output for three shifts. - // - // From there I am using the following: + // b7: interrupt status; + // b6: 1-second interrupt status; + // b5: scan-line interrupt status; + // b4: reserved; + // b3: reserved; + // b2: 1-second interrupt enable; + // b1: scan-line interrupt enable; + // b0: reserved. + uint8_t value_ = 0x00; + } interrupts_; - // Maps from: - // - // b0 = b0 of the shift register - // b1 = b4 of the shift register - // b2– = current delay count - // - // to a new delay count. - uint8_t ntsc_delay_lookup_[20]; - uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic. - int ntsc_delay_ = 0; + int cycles_into_frame_ = 0; + const uint8_t *ram_ = nullptr; - /// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB. - /// Phase is derived from @c column. - uint16_t *output_shift(uint16_t *target, int column); + // The modal colours. + uint16_t border_colour_ = 0; + uint8_t border_colour_entry_ = 0; + uint8_t text_colour_entry_ = 0xf0; + uint16_t text_colour_ = 0xffff; + uint16_t background_colour_ = 0; - // Common getter for the two counters. - struct Counters { - Counters(int v, int h) : vertical(v), horizontal(h) {} - const int vertical, horizontal; - }; - Counters get_counters(Cycles offset); + // Current pixel output buffer and conceptual format. + PixelBufferFormat pixels_format_; + uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr; + int pixels_start_column_; - // Marshalls the Mega II-style interrupt state. - uint8_t megaii_interrupt_mask_ = 0; - uint8_t megaii_interrupt_state_ = 0; - int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts. + void output_row(int row, int start, int end); + + uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const; + + uint16_t *output_text(uint16_t *target, int start, int end, int row) const; + uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const; + uint16_t *output_char(uint16_t *target, uint8_t source, int row) const; + + uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row); + + uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row); + + // Super high-res per-line state. + uint8_t line_control_; + uint16_t palette_[16]; + + // Storage used for fill mode. + uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_; + + // Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB. + // + // My understanding of the real-life algorithm is: maintain a four-bit buffer. + // Fill it in a circular fashion. Ordinarily, output the result of looking + // up the RGB mapping of those four bits of Apple II output (which outputs four + // bits per NTSC colour cycle), commuted as per current phase. But if the bit + // being inserted differs from that currently in its position in the shift + // register, hold the existing output for three shifts. + // + // From there I am using the following: + + // Maps from: + // + // b0 = b0 of the shift register + // b1 = b4 of the shift register + // b2– = current delay count + // + // to a new delay count. + uint8_t ntsc_delay_lookup_[20]; + uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic. + int ntsc_delay_ = 0; + + /// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB. + /// Phase is derived from @c column. + uint16_t *output_shift(uint16_t *target, int column); + + // Common getter for the two counters. + struct Counters { + Counters(int v, int h) : vertical(v), horizontal(h) {} + const int vertical, horizontal; + }; + Counters get_counters(Cycles offset); + + // Marshalls the Mega II-style interrupt state. + uint8_t megaii_interrupt_mask_ = 0; + uint8_t megaii_interrupt_state_ = 0; + int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts. }; } diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index 1adb4b06a..762593aec 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -24,61 +24,61 @@ namespace Apple::Macintosh { a shade less than 4Mhz. */ class Audio: public ::Outputs::Speaker::BufferSource<Audio, false> { - public: - Audio(Concurrency::AsyncTaskQueue<false> &task_queue); +public: + Audio(Concurrency::AsyncTaskQueue<false> &task_queue); - /*! - Macintosh audio is (partly) sourced by the same scanning - hardware as the video; each line it collects an additional - word of memory, half of which is used for audio output. + /*! + Macintosh audio is (partly) sourced by the same scanning + hardware as the video; each line it collects an additional + word of memory, half of which is used for audio output. - Use this method to add a newly-collected sample to the queue. - */ - void post_sample(uint8_t sample); + Use this method to add a newly-collected sample to the queue. + */ + void post_sample(uint8_t sample); - /*! - Macintosh audio also separately receives an output volume - level, in the range 0 to 7. + /*! + Macintosh audio also separately receives an output volume + level, in the range 0 to 7. - Use this method to set the current output volume. - */ - void set_volume(int volume); + Use this method to set the current output volume. + */ + void set_volume(int volume); - /*! - A further factor in audio output is the on-off toggle. - */ - void set_enabled(bool on); + /*! + A further factor in audio output is the on-off toggle. + */ + void set_enabled(bool on); - // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. - template <Outputs::Speaker::Action action> - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); - bool is_zero_level() const; - void set_sample_volume_range(std::int16_t range); + // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. + template <Outputs::Speaker::Action action> + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); + bool is_zero_level() const; + void set_sample_volume_range(std::int16_t range); - private: - Concurrency::AsyncTaskQueue<false> &task_queue_; +private: + Concurrency::AsyncTaskQueue<false> &task_queue_; - // A queue of fetched samples; read from by one thread, - // written to by another. - struct { - std::array<std::atomic<uint8_t>, 740> buffer; - size_t read_pointer = 0, write_pointer = 0; - } sample_queue_; + // A queue of fetched samples; read from by one thread, + // written to by another. + struct { + std::array<std::atomic<uint8_t>, 740> buffer; + size_t read_pointer = 0, write_pointer = 0; + } sample_queue_; - // Emulator-thread stateful variables, to avoid work posting - // deferral updates if possible. - int posted_volume_ = 0; - int posted_enable_mask_ = 0; + // Emulator-thread stateful variables, to avoid work posting + // deferral updates if possible. + int posted_volume_ = 0; + int posted_enable_mask_ = 0; - // Stateful variables, modified from the audio generation - // thread only. - int volume_ = 0; - int enabled_mask_ = 0; - std::int16_t output_volume_ = 0; + // Stateful variables, modified from the audio generation + // thread only. + int volume_ = 0; + int enabled_mask_ = 0; + std::int16_t output_volume_ = 0; - std::int16_t volume_multiplier_ = 0; - std::size_t subcycle_offset_ = 0; - void set_volume_multiplier(); + std::int16_t volume_multiplier_ = 0; + std::size_t subcycle_offset_ = 0; + void set_volume_multiplier(); }; } diff --git a/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp b/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp index 21a28066b..8bad1a572 100644 --- a/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp +++ b/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp @@ -15,27 +15,27 @@ namespace Apple::Macintosh { class DriveSpeedAccumulator { - public: - /*! - Accepts fetched motor control values. - */ - void post_sample(uint8_t sample); +public: + /*! + Accepts fetched motor control values. + */ + void post_sample(uint8_t sample); - struct Delegate { - virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0; - }; - /*! - Sets the delegate to receive drive speed changes. - */ - void set_delegate(Delegate *delegate) { - delegate_ = delegate; - } + struct Delegate { + virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0; + }; + /*! + Sets the delegate to receive drive speed changes. + */ + void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } - private: - static constexpr int samples_per_bucket = 20; - int sample_count_ = 0; - int sample_total_ = 0; - Delegate *delegate_ = nullptr; +private: + static constexpr int samples_per_bucket = 20; + int sample_count_ = 0; + int sample_total_ = 0; + Delegate *delegate_ = nullptr; }; } diff --git a/Machines/Apple/Macintosh/Keyboard.hpp b/Machines/Apple/Macintosh/Keyboard.hpp index 2efd603c4..a5a74ea10 100644 --- a/Machines/Apple/Macintosh/Keyboard.hpp +++ b/Machines/Apple/Macintosh/Keyboard.hpp @@ -83,206 +83,206 @@ enum class Key: uint16_t { }; class Keyboard { - public: - void set_input(bool data) { - switch(mode_) { - case Mode::Waiting: - /* - "Only the computer can initiate communication over the keyboard lines. When the computer and keyboard - are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The - computer signals that it is ready to begin communication by pulling the Keyboard Data line low." - */ - if(!data) { - mode_ = Mode::AcceptingCommand; - phase_ = 0; - command_ = 0; - } - break; +public: + void set_input(bool data) { + switch(mode_) { + case Mode::Waiting: + /* + "Only the computer can initiate communication over the keyboard lines. When the computer and keyboard + are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The + computer signals that it is ready to begin communication by pulling the Keyboard Data line low." + */ + if(!data) { + mode_ = Mode::AcceptingCommand; + phase_ = 0; + command_ = 0; + } + break; - case Mode::AcceptingCommand: - /* Note value, so that it can be latched upon a clock transition. */ - data_input_ = data; - break; + case Mode::AcceptingCommand: + /* Note value, so that it can be latched upon a clock transition. */ + data_input_ = data; + break; - case Mode::AwaitingEndOfCommand: - /* - The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready - to receive the keyboard's response by setting the Keyboard Data line high. - */ - if(data) { - mode_ = Mode::PerformingCommand; - phase_ = 0; - } - break; + case Mode::AwaitingEndOfCommand: + /* + The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready + to receive the keyboard's response by setting the Keyboard Data line high. + */ + if(data) { + mode_ = Mode::PerformingCommand; + phase_ = 0; + } + break; - default: - case Mode::SendingResponse: - /* This line isn't currently an input; do nothing. */ - break; - } + default: + case Mode::SendingResponse: + /* This line isn't currently an input; do nothing. */ + break; } + } - bool get_clock() { - return clock_output_; + bool get_clock() { + return clock_output_; + } + + bool get_data() { + return !!(response_ & 0x80); + } + + /*! + The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz. + */ + void run_for(HalfCycles) { // TODO: honour the HalfCycles argument. + switch(mode_) { + default: + case Mode::Waiting: return; + + case Mode::AcceptingCommand: { + /* + "When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low, + 220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places + a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge + of the Keyboard Clock signal." + */ + const auto offset = phase_ % 40; + clock_output_ = offset >= 18; + + if(offset == 26) { + command_ = (command_ << 1) | (data_input_ ? 1 : 0); + } + + ++phase_; + if(phase_ == 8*40) { + mode_ = Mode::AwaitingEndOfCommand; + phase_ = 0; + clock_output_ = false; + } + } break; + + case Mode::AwaitingEndOfCommand: + // Time out if the end-of-command seems not to be forthcoming. + // This is an elaboration on my part; a guess. + ++phase_; + if(phase_ == 1000) { + clock_output_ = false; + mode_ = Mode::Waiting; + phase_ = 0; + } + return; + + case Mode::PerformingCommand: { + response_ = perform_command(command_); + + // Inquiry has a 0.25-second timeout; everything else is instant. + ++phase_; + if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) { + mode_ = Mode::SendingResponse; + phase_ = 0; + } + } break; + + case Mode::SendingResponse: { + /* + "When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high) + on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each + clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the + rising edge of the Keyboard Clock signal." + */ + const auto offset = phase_ % 33; + clock_output_ = offset >= 16; + + if(offset == 29) { + response_ <<= 1; + } + + ++phase_; + if(phase_ == 8*33) { + clock_output_ = false; + mode_ = Mode::Waiting; + phase_ = 0; + } + } break; } + } - bool get_data() { - return !!(response_ & 0x80); + void enqueue_key_state(uint16_t key, bool is_pressed) { + // Front insert; messages will be pop_back'd. + std::lock_guard lock(key_queue_mutex_); + + // Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme + // they are indicated by having bit 8 set. So add the $79 prefix if required. + if(key & KeypadMask) { + key_queue_.insert(key_queue_.begin(), 0x79); } + key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); + } - /*! - The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz. - */ - void run_for(HalfCycles) { // TODO: honour the HalfCycles argument. - switch(mode_) { - default: - case Mode::Waiting: return; +private: + /// Performs the pre-ADB Apple keyboard protocol command @c command, returning + /// the proper result if the command were to terminate now. So, it treats inquiry + /// and instant as the same command. + int perform_command(int command) { + switch(command) { + case 0x10: // Inquiry. + case 0x14: { // Instant. + std::lock_guard lock(key_queue_mutex_); + if(!key_queue_.empty()) { + const auto new_message = key_queue_.back(); + key_queue_.pop_back(); + return new_message; + } + } break; - case Mode::AcceptingCommand: { - /* - "When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low, - 220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places - a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge - of the Keyboard Clock signal." - */ - const auto offset = phase_ % 40; - clock_output_ = offset >= 18; + case 0x16: // Model number. + return + 0x01 | // b0: always 1 + (1 << 1) | // keyboard model number + (1 << 4); // next device number + // (b7 not set => no next device) - if(offset == 26) { - command_ = (command_ << 1) | (data_input_ ? 1 : 0); - } - - ++phase_; - if(phase_ == 8*40) { - mode_ = Mode::AwaitingEndOfCommand; - phase_ = 0; - clock_output_ = false; - } - } break; - - case Mode::AwaitingEndOfCommand: - // Time out if the end-of-command seems not to be forthcoming. - // This is an elaboration on my part; a guess. - ++phase_; - if(phase_ == 1000) { - clock_output_ = false; - mode_ = Mode::Waiting; - phase_ = 0; - } - return; - - case Mode::PerformingCommand: { - response_ = perform_command(command_); - - // Inquiry has a 0.25-second timeout; everything else is instant. - ++phase_; - if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) { - mode_ = Mode::SendingResponse; - phase_ = 0; - } - } break; - - case Mode::SendingResponse: { - /* - "When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high) - on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each - clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the - rising edge of the Keyboard Clock signal." - */ - const auto offset = phase_ % 33; - clock_output_ = offset >= 16; - - if(offset == 29) { - response_ <<= 1; - } - - ++phase_; - if(phase_ == 8*33) { - clock_output_ = false; - mode_ = Mode::Waiting; - phase_ = 0; - } - } break; - } + case 0x36: // Test + return 0x7d; // 0x7d = ACK, 0x77 = not ACK. } + return 0x7b; // No key transition. + } - void enqueue_key_state(uint16_t key, bool is_pressed) { - // Front insert; messages will be pop_back'd. - std::lock_guard lock(key_queue_mutex_); + /// Maintains the current operating mode — a record of what the + /// keyboard is doing now. + enum class Mode { + /// The keyboard is waiting to begin a transaction. + Waiting, + /// The keyboard is currently clocking in a new command. + AcceptingCommand, + /// The keyboard is waiting for the computer to indicate that it is ready for a response. + AwaitingEndOfCommand, + /// The keyboard is in the process of performing the command it most-recently received. + /// If the command was an 'inquiry', this state may persist for a non-neglibible period of time. + PerformingCommand, + /// The keyboard is currently shifting a response back to the computer. + SendingResponse, + } mode_ = Mode::Waiting; - // Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme - // they are indicated by having bit 8 set. So add the $79 prefix if required. - if(key & KeypadMask) { - key_queue_.insert(key_queue_.begin(), 0x79); - } - key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); - } + /// Holds a count of progress through the current @c Mode. Exact meaning depends on mode. + int phase_ = 0; + /// Holds the most-recently-received command; the command is shifted into here as it is received + /// so this may not be valid prior to Mode::PerformingCommand. + int command_ = 0; + /// Populated during PerformingCommand as the response to the most-recently-received command, this + /// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, + /// but not afterwards. + int response_ = 0; - private: - /// Performs the pre-ADB Apple keyboard protocol command @c command, returning - /// the proper result if the command were to terminate now. So, it treats inquiry - /// and instant as the same command. - int perform_command(int command) { - switch(command) { - case 0x10: // Inquiry. - case 0x14: { // Instant. - std::lock_guard lock(key_queue_mutex_); - if(!key_queue_.empty()) { - const auto new_message = key_queue_.back(); - key_queue_.pop_back(); - return new_message; - } - } break; + /// The current state of the serial connection's data input. + bool data_input_ = false; + /// The current clock output from this keyboard. + bool clock_output_ = false; - case 0x16: // Model number. - return - 0x01 | // b0: always 1 - (1 << 1) | // keyboard model number - (1 << 4); // next device number - // (b7 not set => no next device) - - case 0x36: // Test - return 0x7d; // 0x7d = ACK, 0x77 = not ACK. - } - return 0x7b; // No key transition. - } - - /// Maintains the current operating mode — a record of what the - /// keyboard is doing now. - enum class Mode { - /// The keyboard is waiting to begin a transaction. - Waiting, - /// The keyboard is currently clocking in a new command. - AcceptingCommand, - /// The keyboard is waiting for the computer to indicate that it is ready for a response. - AwaitingEndOfCommand, - /// The keyboard is in the process of performing the command it most-recently received. - /// If the command was an 'inquiry', this state may persist for a non-neglibible period of time. - PerformingCommand, - /// The keyboard is currently shifting a response back to the computer. - SendingResponse, - } mode_ = Mode::Waiting; - - /// Holds a count of progress through the current @c Mode. Exact meaning depends on mode. - int phase_ = 0; - /// Holds the most-recently-received command; the command is shifted into here as it is received - /// so this may not be valid prior to Mode::PerformingCommand. - int command_ = 0; - /// Populated during PerformingCommand as the response to the most-recently-received command, this - /// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, - /// but not afterwards. - int response_ = 0; - - /// The current state of the serial connection's data input. - bool data_input_ = false; - /// The current clock output from this keyboard. - bool clock_output_ = false; - - /// Guards multithread access to key_queue_. - std::mutex key_queue_mutex_; - /// A FIFO queue for key events, in the form they'd be communicated to the Macintosh, - /// with the newest events towards the front. - std::vector<uint8_t> key_queue_; + /// Guards multithread access to key_queue_. + std::mutex key_queue_mutex_; + /// A FIFO queue for key events, in the form they'd be communicated to the Macintosh, + /// with the newest events towards the front. + std::vector<uint8_t> key_queue_; }; /*! diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index e7c271408..a33dffed2 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -79,760 +79,760 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin public Configurable::Device, public DriveSpeedAccumulator::Delegate, public ClockingHint::Observer { - public: - using Target = Analyser::Static::Macintosh::Target; +public: + using Target = Analyser::Static::Macintosh::Target; - ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - MachineTypes::MappedKeyboardMachine({ - Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift, - Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption, - Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta, - }), - mc68000_(*this), - iwm_(CLOCK_RATE), - video_(audio_, drive_speed_accumulator_), - via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_), - via_(via_port_handler_), - scsi_bus_(CLOCK_RATE * 2), - scsi_(scsi_bus_, CLOCK_RATE * 2), - hard_drive_(scsi_bus_, 6 /* SCSI ID */), - drives_{ - {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, - {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} - }, - mouse_(1) { + ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + MachineTypes::MappedKeyboardMachine({ + Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift, + Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption, + Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta, + }), + mc68000_(*this), + iwm_(CLOCK_RATE), + video_(audio_, drive_speed_accumulator_), + via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_), + via_(via_port_handler_), + scsi_bus_(CLOCK_RATE * 2), + scsi_(scsi_bus_, CLOCK_RATE * 2), + hard_drive_(scsi_bus_, 6 /* SCSI ID */), + drives_{ + {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, + {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} + }, + mouse_(1) { - // Select a ROM name and determine the proper ROM and RAM sizes - // based on the machine model. - using Model = Analyser::Static::Macintosh::Target::Model; - const std::string machine_name = "Macintosh"; - uint32_t ram_size, rom_size; - ROM::Name rom_name; - switch(model) { - default: - case Model::Mac128k: - ram_size = 128*1024; - rom_size = 64*1024; - rom_name = ROM::Name::Macintosh128k; - break; - case Model::Mac512k: - ram_size = 512*1024; - rom_size = 64*1024; - rom_name = ROM::Name::Macintosh512k; - break; - case Model::Mac512ke: - case Model::MacPlus: { - ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; - rom_size = 128*1024; - rom_name = ROM::Name::MacintoshPlus; - } break; - } - ram_mask_ = ram_size - 1; - rom_mask_ = rom_size - 1; - ram_.resize(ram_size); - video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1); + // Select a ROM name and determine the proper ROM and RAM sizes + // based on the machine model. + using Model = Analyser::Static::Macintosh::Target::Model; + const std::string machine_name = "Macintosh"; + uint32_t ram_size, rom_size; + ROM::Name rom_name; + switch(model) { + default: + case Model::Mac128k: + ram_size = 128*1024; + rom_size = 64*1024; + rom_name = ROM::Name::Macintosh128k; + break; + case Model::Mac512k: + ram_size = 512*1024; + rom_size = 64*1024; + rom_name = ROM::Name::Macintosh512k; + break; + case Model::Mac512ke: + case Model::MacPlus: { + ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; + rom_size = 128*1024; + rom_name = ROM::Name::MacintoshPlus; + } break; + } + ram_mask_ = ram_size - 1; + rom_mask_ = rom_size - 1; + ram_.resize(ram_size); + video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1); - // Grab a copy of the ROM and convert it into big-endian data. - ROM::Request request(rom_name); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); + // Grab a copy of the ROM and convert it into big-endian data. + ROM::Request request(rom_name); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); - // Randomise memory contents. - Memory::Fuzz(ram_); + // Randomise memory contents. + Memory::Fuzz(ram_); - // Attach the drives to the IWM. - iwm_->set_drive(0, &drives_[0]); - iwm_->set_drive(1, &drives_[1]); + // Attach the drives to the IWM. + iwm_->set_drive(0, &drives_[0]); + iwm_->set_drive(1, &drives_[1]); - // If they are 400kb drives, also attach them to the drive-speed accumulator. - if(!drives_[0].is_800k() || !drives_[1].is_800k()) { - drive_speed_accumulator_.set_delegate(this); - } - - // Make sure interrupt changes from the SCC are observed. - scc_.set_delegate(this); - - // Also watch for changes in clocking requirement from the SCSI chip. - if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { - scsi_bus_.set_clocking_hint_observer(this); - } - - // The Mac runs at 7.8336mHz. - set_clock_rate(double(CLOCK_RATE)); - audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); - - // Insert any supplied media. - insert_media(target.media); - - // Set the immutables of the memory map. - setup_memory_map(); + // If they are 400kb drives, also attach them to the drive-speed accumulator. + if(!drives_[0].is_800k() || !drives_[1].is_800k()) { + drive_speed_accumulator_.set_delegate(this); } - ~ConcreteMachine() { - audio_.queue.flush(); + // Make sure interrupt changes from the SCC are observed. + scc_.set_delegate(this); + + // Also watch for changes in clocking requirement from the SCSI chip. + if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + scsi_bus_.set_clocking_hint_observer(this); } - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - video_.set_scan_target(scan_target); - } + // The Mac runs at 7.8336mHz. + set_clock_rate(double(CLOCK_RATE)); + audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return video_.get_scaled_scan_status(); - } + // Insert any supplied media. + insert_media(target.media); - Outputs::Speaker::Speaker *get_speaker() final { - return &audio_.speaker; - } + // Set the immutables of the memory map. + setup_memory_map(); + } - void run_for(const Cycles cycles) final { - mc68000_.run_for(cycles); - } + ~ConcreteMachine() { + audio_.queue.flush(); + } - template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - // Advance time. - advance_time(cycle.length); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + video_.set_scan_target(scan_target); + } - // A null cycle leaves nothing else to do. - if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return video_.get_scaled_scan_status(); + } - // Grab the address. - auto address = cycle.host_endian_byte_address(); + Outputs::Speaker::Speaker *get_speaker() final { + return &audio_.speaker; + } - // Everything above E0 0000 is signalled as being on the peripheral bus. - // - // This will also act to autovector interrupts, since an interrupt acknowledge - // cycle posts an address with all the higher-order bits set, and VPA doubles - // as the input to request an autovector. - mc68000_.set_is_peripheral_address(address >= 0xe0'0000); + void run_for(const Cycles cycles) final { + mc68000_.run_for(cycles); + } - // All code below deals only with reads and writes — cycles in which a - // data select is active. So quit now if this is not the active part of - // a read or write. - // - // The 68000 uses 6800-style autovectored interrupts, so the mere act of - // having set VPA above deals with those given that the generated address - // for interrupt acknowledge cycles always has all bits set except the - // lowest explicit address lines. - if( - !cycle.data_select_active() || - (cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) - ) return HalfCycles(0); + template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + // Advance time. + advance_time(cycle.length); - // Grab the word-precision address being accessed. - uint8_t *memory_base = nullptr; - HalfCycles delay; - switch(memory_map_[address >> 17]) { - default: assert(false); + // A null cycle leaves nothing else to do. + if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); - case BusDevice::Unassigned: - fill_unmapped(cycle); - return delay; + // Grab the address. + auto address = cycle.host_endian_byte_address(); - case BusDevice::VIA: { - if(*cycle.address & 1) { - fill_unmapped(cycle); - } else { - const int register_address = address >> 9; + // Everything above E0 0000 is signalled as being on the peripheral bus. + // + // This will also act to autovector interrupts, since an interrupt acknowledge + // cycle posts an address with all the higher-order bits set, and VPA doubles + // as the input to request an autovector. + mc68000_.set_is_peripheral_address(address >= 0xe0'0000); - // VIA accesses are via address 0xefe1fe + register*512, - // which at word precision is 0x77f0ff + register*256. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(via_.read(register_address)); - } else { - via_.write(register_address, cycle.value8_high()); - } - } - } return delay; + // All code below deals only with reads and writes — cycles in which a + // data select is active. So quit now if this is not the active part of + // a read or write. + // + // The 68000 uses 6800-style autovectored interrupts, so the mere act of + // having set VPA above deals with those given that the generated address + // for interrupt acknowledge cycles always has all bits set except the + // lowest explicit address lines. + if( + !cycle.data_select_active() || + (cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) + ) return HalfCycles(0); - case BusDevice::PhaseRead: { - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_low(phase_ & 7); - } - } return delay; - - case BusDevice::IWM: { - if(*cycle.address & 1) { - const int register_address = address >> 9; - - // The IWM; this is a purely polled device, so can be run on demand. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_low(iwm_->read(register_address)); - } else { - iwm_->write(register_address, cycle.value8_low()); - } - } else { - fill_unmapped(cycle); - } - } return delay; - - case BusDevice::SCSI: { - const int register_address = address >> 4; - const bool dma_acknowledge = address & 0x200; - - // Even accesses = read; odd = write. - if(*cycle.address & 1) { - // Odd access => this is a write. Data will be in the upper byte. - if(cycle.operation & CPU::MC68000::Operation::Read) { - scsi_.write(register_address, 0xff, dma_acknowledge); - } else { - scsi_.write(register_address, cycle.value8_high()); - } - } else { - // Even access => this is a read. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(scsi_.read(register_address, dma_acknowledge)); - } - } - } return delay; - - case BusDevice::SCCReadResetPhase: { - // Any word access here adjusts phase. - if(cycle.operation & CPU::MC68000::Operation::SelectWord) { - adjust_phase(); - } else { - // A0 = 1 => reset; A0 = 0 => read. - if(*cycle.address & 1) { - scc_.reset(); - - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value16(0xffff); - } - } else { - const auto read = scc_.read(int(address >> 1)); - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(read); - } - } - } - } return delay; - - case BusDevice::SCCWrite: { - // Any word access here adjusts phase. - if(cycle.operation & CPU::MC68000::Operation::SelectWord) { - adjust_phase(); - } else { - // This is definitely a byte access; either it's to an odd address, in which - // case it will reach the SCC, or it isn't, in which case it won't. - if(*cycle.address & 1) { - if(cycle.operation & CPU::MC68000::Operation::Read) { - scc_.write(int(address >> 1), 0xff); - cycle.value->b = 0xff; - } else { - scc_.write(int(address >> 1), cycle.value->b); - } - } else { - fill_unmapped(cycle); - } - } - } return delay; - - case BusDevice::RAM: { - // This is coupled with the Macintosh implementation of video; the magic - // constant should probably be factored into the Video class. - // It embodies knowledge of the fact that video (and audio) will always - // be fetched from the final $d900 bytes of memory. - // (And that ram_mask_ = ram size - 1). - if(address > ram_mask_ - 0xd900) - update_video(); - - memory_base = ram_.data(); - address &= ram_mask_; - - // Apply a delay due to video contention if applicable; scheme applied: - // only every other access slot is available during the period of video - // output. I believe this to be correct for the 128k, 512k and Plus. - // More research to do on other models. - if(video_is_outputting() && ram_subcycle_ < 8) { - delay = HalfCycles(8 - ram_subcycle_); - advance_time(delay); - } - } break; - - case BusDevice::ROM: { - if(!(cycle.operation & CPU::MC68000::Operation::Read)) return delay; - memory_base = rom_; - address &= rom_mask_; - } break; - } - - // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. - // Potential writes to ROM and all hardware accesses have already been weeded out. - cycle.apply(&memory_base[address]); + // Grab the word-precision address being accessed. + uint8_t *memory_base = nullptr; + HalfCycles delay; + switch(memory_map_[address >> 17]) { + default: assert(false); + case BusDevice::Unassigned: + fill_unmapped(cycle); return delay; - } - void flush_output(int) { - // Flush the video before the audio queue; in a Mac the - // video is responsible for providing part of the - // audio signal, so the two aren't as distinct as in - // most machines. - update_video(); + case BusDevice::VIA: { + if(*cycle.address & 1) { + fill_unmapped(cycle); + } else { + const int register_address = address >> 9; - // As above: flush audio after video. - via_.flush(); - audio_.queue.perform(); - - // This avoids deferring IWM costs indefinitely, until - // they become artbitrarily large. - iwm_.flush(); - } - - void set_rom_is_overlay(bool rom_is_overlay) { - ROM_is_overlay_ = rom_is_overlay; - - using Model = Analyser::Static::Macintosh::Target::Model; - switch(model) { - case Model::Mac128k: - case Model::Mac512k: - case Model::Mac512ke: - populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { - // Addresses up to $80 0000 aren't affected by this bit. - if(rom_is_overlay) { - // Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. - for(int c = 0; c < 0x600000; c += 0x100000) { - map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); - } - map_to(0x800000, BusDevice::RAM); - } else { - map_to(0x400000, BusDevice::RAM); - map_to(0x500000, BusDevice::ROM); - map_to(0x800000, BusDevice::Unassigned); - } - }); - break; - - case Model::MacPlus: - populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { - // Addresses up to $80 0000 aren't affected by this bit. - if(rom_is_overlay) { - for(int c = 0; c < 0x580000; c += 0x20000) { - map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); - } - map_to(0x600000, BusDevice::SCSI); - map_to(0x800000, BusDevice::RAM); - } else { - map_to(0x400000, BusDevice::RAM); - for(int c = 0x400000; c < 0x580000; c += 0x20000) { - map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); - } - map_to(0x600000, BusDevice::SCSI); - map_to(0x800000, BusDevice::Unassigned); - } - }); - break; - } - } - - bool video_is_outputting() { - return video_.is_outputting(time_since_video_update_); - } - - void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { - update_video(); - video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer); - } - - bool insert_media(const Analyser::Static::Media &media) final { - if(media.disks.empty() && media.mass_storage_devices.empty()) - return false; - - // TODO: shouldn't allow disks to be replaced like this, as the Mac - // uses software eject. Will need to expand messaging ability of - // insert_media. - if(!media.disks.empty()) { - if(drives_[0].has_disk()) - drives_[1].set_disk(media.disks[0]); - else - drives_[0].set_disk(media.disks[0]); - } - - // TODO: allow this only at machine startup? - if(!media.mass_storage_devices.empty()) { - const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get()); - if(volume) { - volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); + // VIA accesses are via address 0xefe1fe + register*512, + // which at word precision is 0x77f0ff + register*256. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(via_.read(register_address)); + } else { + via_.write(register_address, cycle.value8_high()); + } } - hard_drive_->set_storage(media.mass_storage_devices.front()); + } return delay; + + case BusDevice::PhaseRead: { + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_low(phase_ & 7); + } + } return delay; + + case BusDevice::IWM: { + if(*cycle.address & 1) { + const int register_address = address >> 9; + + // The IWM; this is a purely polled device, so can be run on demand. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_low(iwm_->read(register_address)); + } else { + iwm_->write(register_address, cycle.value8_low()); + } + } else { + fill_unmapped(cycle); + } + } return delay; + + case BusDevice::SCSI: { + const int register_address = address >> 4; + const bool dma_acknowledge = address & 0x200; + + // Even accesses = read; odd = write. + if(*cycle.address & 1) { + // Odd access => this is a write. Data will be in the upper byte. + if(cycle.operation & CPU::MC68000::Operation::Read) { + scsi_.write(register_address, 0xff, dma_acknowledge); + } else { + scsi_.write(register_address, cycle.value8_high()); + } + } else { + // Even access => this is a read. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(scsi_.read(register_address, dma_acknowledge)); + } + } + } return delay; + + case BusDevice::SCCReadResetPhase: { + // Any word access here adjusts phase. + if(cycle.operation & CPU::MC68000::Operation::SelectWord) { + adjust_phase(); + } else { + // A0 = 1 => reset; A0 = 0 => read. + if(*cycle.address & 1) { + scc_.reset(); + + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value16(0xffff); + } + } else { + const auto read = scc_.read(int(address >> 1)); + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(read); + } + } + } + } return delay; + + case BusDevice::SCCWrite: { + // Any word access here adjusts phase. + if(cycle.operation & CPU::MC68000::Operation::SelectWord) { + adjust_phase(); + } else { + // This is definitely a byte access; either it's to an odd address, in which + // case it will reach the SCC, or it isn't, in which case it won't. + if(*cycle.address & 1) { + if(cycle.operation & CPU::MC68000::Operation::Read) { + scc_.write(int(address >> 1), 0xff); + cycle.value->b = 0xff; + } else { + scc_.write(int(address >> 1), cycle.value->b); + } + } else { + fill_unmapped(cycle); + } + } + } return delay; + + case BusDevice::RAM: { + // This is coupled with the Macintosh implementation of video; the magic + // constant should probably be factored into the Video class. + // It embodies knowledge of the fact that video (and audio) will always + // be fetched from the final $d900 bytes of memory. + // (And that ram_mask_ = ram size - 1). + if(address > ram_mask_ - 0xd900) + update_video(); + + memory_base = ram_.data(); + address &= ram_mask_; + + // Apply a delay due to video contention if applicable; scheme applied: + // only every other access slot is available during the period of video + // output. I believe this to be correct for the 128k, 512k and Plus. + // More research to do on other models. + if(video_is_outputting() && ram_subcycle_ < 8) { + delay = HalfCycles(8 - ram_subcycle_); + advance_time(delay); + } + } break; + + case BusDevice::ROM: { + if(!(cycle.operation & CPU::MC68000::Operation::Read)) return delay; + memory_base = rom_; + address &= rom_mask_; + } break; + } + + // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. + // Potential writes to ROM and all hardware accesses have already been weeded out. + cycle.apply(&memory_base[address]); + + return delay; + } + + void flush_output(int) { + // Flush the video before the audio queue; in a Mac the + // video is responsible for providing part of the + // audio signal, so the two aren't as distinct as in + // most machines. + update_video(); + + // As above: flush audio after video. + via_.flush(); + audio_.queue.perform(); + + // This avoids deferring IWM costs indefinitely, until + // they become artbitrarily large. + iwm_.flush(); + } + + void set_rom_is_overlay(bool rom_is_overlay) { + ROM_is_overlay_ = rom_is_overlay; + + using Model = Analyser::Static::Macintosh::Target::Model; + switch(model) { + case Model::Mac128k: + case Model::Mac512k: + case Model::Mac512ke: + populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { + // Addresses up to $80 0000 aren't affected by this bit. + if(rom_is_overlay) { + // Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. + for(int c = 0; c < 0x600000; c += 0x100000) { + map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); + } + map_to(0x800000, BusDevice::RAM); + } else { + map_to(0x400000, BusDevice::RAM); + map_to(0x500000, BusDevice::ROM); + map_to(0x800000, BusDevice::Unassigned); + } + }); + break; + + case Model::MacPlus: + populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { + // Addresses up to $80 0000 aren't affected by this bit. + if(rom_is_overlay) { + for(int c = 0; c < 0x580000; c += 0x20000) { + map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); + } + map_to(0x600000, BusDevice::SCSI); + map_to(0x800000, BusDevice::RAM); + } else { + map_to(0x400000, BusDevice::RAM); + for(int c = 0x400000; c < 0x580000; c += 0x20000) { + map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); + } + map_to(0x600000, BusDevice::SCSI); + map_to(0x800000, BusDevice::Unassigned); + } + }); + break; + } + } + + bool video_is_outputting() { + return video_.is_outputting(time_since_video_update_); + } + + void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { + update_video(); + video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer); + } + + bool insert_media(const Analyser::Static::Media &media) final { + if(media.disks.empty() && media.mass_storage_devices.empty()) + return false; + + // TODO: shouldn't allow disks to be replaced like this, as the Mac + // uses software eject. Will need to expand messaging ability of + // insert_media. + if(!media.disks.empty()) { + if(drives_[0].has_disk()) + drives_[1].set_disk(media.disks[0]); + else + drives_[0].set_disk(media.disks[0]); + } + + // TODO: allow this only at machine startup? + if(!media.mass_storage_devices.empty()) { + const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get()); + if(volume) { + volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); + } + hard_drive_->set_storage(media.mass_storage_devices.front()); + } + + return true; + } + + // MARK: Keyboard input. + + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + + void set_key_state(uint16_t key, bool is_pressed) final { + keyboard_.enqueue_key_state(key, is_pressed); + } + + // TODO: clear all keys. + + // MARK: Interrupt updates. + + void did_change_interrupt_status(Zilog::SCC::z8530 *, bool) final { + update_interrupt_input(); + } + + void update_interrupt_input() { + // Update interrupt input. + // TODO: does this really cascade like this? + if(scc_.get_interrupt_line()) { + mc68000_.set_interrupt_level(2); + } else if(via_.get_interrupt_line()) { + mc68000_.set_interrupt_level(1); + } else { + mc68000_.set_interrupt_level(0); + } + } + + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) final { + iwm_->set_activity_observer(observer); + + if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + scsi_bus_.set_activity_observer(observer); + } + } + + // MARK: - Configuration options. + std::unique_ptr<Reflection::Struct> get_options() const final { + auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); + options->quickboot = quickboot_; + return options; + } + + void set_options(const std::unique_ptr<Reflection::Struct> &str) final { + // TODO: should this really be a runtime option? + // It should probably be a construction option. + + const auto options = dynamic_cast<Options *>(str.get()); + quickboot_ = options->quickboot; + + using Model = Analyser::Static::Macintosh::Target::Model; + const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus; + if(quickboot_ && is_plus_rom) { + // Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the + // test at $E00. TODO: adapt as(/if?) necessary for other Macs. + ram_[0x02ae] = 0x40; + ram_[0x02af] = 0x00; + ram_[0x02b0] = 0x00; + ram_[0x02b1] = 0x00; + } + } + +private: + bool quickboot_ = false; + + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { + scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; + } + + void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final { + iwm_.flush(); + drives_[0].set_rotation_speed(speed); + drives_[1].set_rotation_speed(speed); + } + + forceinline void adjust_phase() { + ++phase_; + } + + template <typename Microcycle> + forceinline void fill_unmapped(const Microcycle &cycle) { + if(!(cycle.operation & CPU::MC68000::Operation::Read)) return; + cycle.set_value16(0xffff); + } + + /// Advances all non-CPU components by @c duration half cycles. + forceinline void advance_time(HalfCycles duration) { + time_since_video_update_ += duration; + iwm_ += duration; + ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15; + + // The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. + // See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division + // may occur here in order to provide VSYNC at a proper moment. + + // Possibly route vsync. + if(time_since_video_update_ < time_until_video_event_) { + via_clock_ += duration; + via_.run_for(via_clock_.divide(HalfCycles(10))); + } else { + auto via_time_base = time_since_video_update_ - duration; + auto via_cycles_outstanding = duration; + while(time_until_video_event_ < time_since_video_update_) { + const auto via_cycles = time_until_video_event_ - via_time_base; + via_time_base = HalfCycles(0); + via_cycles_outstanding -= via_cycles; + + via_clock_ += via_cycles; + via_.run_for(via_clock_.divide(HalfCycles(10))); + + video_.run_for(time_until_video_event_); + time_since_video_update_ -= time_until_video_event_; + time_until_video_event_ = video_.next_sequence_point(); + + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); } - return true; + via_clock_ += via_cycles_outstanding; + via_.run_for(via_clock_.divide(HalfCycles(10))); } - // MARK: Keyboard input. - - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; + // The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. + // Its clock and data lines are connected to the VIA. + keyboard_clock_ += duration; + if(keyboard_clock_ >= KEYBOARD_CLOCK_RATE) { + const auto keyboard_ticks = keyboard_clock_.divide(KEYBOARD_CLOCK_RATE); + keyboard_.run_for(keyboard_ticks); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); } - void set_key_state(uint16_t key, bool is_pressed) final { - keyboard_.enqueue_key_state(key, is_pressed); - } - - // TODO: clear all keys. - - // MARK: Interrupt updates. - - void did_change_interrupt_status(Zilog::SCC::z8530 *, bool) final { - update_interrupt_input(); - } - - void update_interrupt_input() { - // Update interrupt input. - // TODO: does this really cascade like this? - if(scc_.get_interrupt_line()) { - mc68000_.set_interrupt_level(2); - } else if(via_.get_interrupt_line()) { - mc68000_.set_interrupt_level(1); - } else { - mc68000_.set_interrupt_level(0); + // Feed mouse inputs within at most 1250 cycles of each other. + if(mouse_.has_steps()) { + time_since_mouse_update_ += duration; + const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); + if(mouse_ticks > HalfCycles(0)) { + mouse_.prepare_step(); + scc_.set_dcd(0, mouse_.get_channel(1) & 1); + scc_.set_dcd(1, mouse_.get_channel(0) & 1); } } - // MARK: - Activity Source - void set_activity_observer(Activity::Observer *observer) final { - iwm_->set_activity_observer(observer); + // TODO: SCC should be clocked at a divide-by-two, if and when it actually has + // anything connected. - if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { - scsi_bus_.set_activity_observer(observer); + // Consider updating the real-time clock. + real_time_clock_ += duration; + auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral(); + while(ticks--) { + clock_.update(); + // TODO: leave a delay between toggling the input rather than using this coupled hack. + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); + } + + // Update the SCSI if currently active. + if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); + } + } + + forceinline void update_video() { + video_.run_for(time_since_video_update_.flush<HalfCycles>()); + time_until_video_event_ = video_.next_sequence_point(); + } + + Inputs::Mouse &get_mouse() final { + return mouse_; + } + + using IWMActor = JustInTimeActor<IWM>; + + class VIAPortHandler: public MOS::MOS6522::PortHandler { + public: + VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : + machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} + + using Port = MOS::MOS6522::Port; + using Line = MOS::MOS6522::Line; + + void set_port_output(Port port, uint8_t value, uint8_t) { + /* + Peripheral lines: keyboard data, interrupt configuration. + (See p176 [/215]) + */ + switch(port) { + case Port::A: + /* + Port A: + b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR) + b6: 0 = alternate screen buffer, 1 = main screen buffer + b5: floppy disk SEL state control (upper/lower head "among other things") + b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map + b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer + b2–b0: audio output volume + */ + iwm_->set_select(!!(value & 0x20)); + + machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); + machine_.set_rom_is_overlay(!!(value & 0x10)); + + audio_.flush(); + audio_.audio.set_volume(value & 7); + break; + + case Port::B: + /* + Port B: + b7: 0 = sound enabled, 1 = sound disabled + b6: [input] 0 = video beam in visible portion of line, 1 = outside + b5: [input] mouse y2 + b4: [input] mouse x2 + b3: [input] 0 = mouse button down, 1 = up + b2: 0 = real-time clock enabled, 1 = disabled + b1: clock's data-clock line + b0: clock's serial data line + */ + if(value & 0x4) clock_.abort(); + else clock_.set_input(!!(value & 0x2), !!(value & 0x1)); + + audio_.flush(); + audio_.audio.set_enabled(!(value & 0x80)); + break; } } - // MARK: - Configuration options. - std::unique_ptr<Reflection::Struct> get_options() const final { - auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); - options->quickboot = quickboot_; - return options; + uint8_t get_port_input(Port port) { + switch(port) { + case Port::A: +// printf("6522 r A\n"); + return 0x00; // TODO: b7 = SCC wait/request + + case Port::B: + return uint8_t( + ((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) | + ((mouse_.get_channel(0) & 2) << 3) | + ((mouse_.get_channel(1) & 2) << 4) | + (clock_.get_data() ? 0x02 : 0x00) | + (machine_.video_is_outputting() ? 0x00 : 0x40) + ); + } + + // Should be unreachable. + return 0xff; } - void set_options(const std::unique_ptr<Reflection::Struct> &str) final { - // TODO: should this really be a runtime option? - // It should probably be a construction option. + void set_control_line_output(Port port, Line line, bool value) { + /* + Keyboard wiring (I believe): + CB2 = data (input/output) + CB1 = clock (input) - const auto options = dynamic_cast<Options *>(str.get()); - quickboot_ = options->quickboot; - - using Model = Analyser::Static::Macintosh::Target::Model; - const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus; - if(quickboot_ && is_plus_rom) { - // Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the - // test at $E00. TODO: adapt as(/if?) necessary for other Macs. - ram_[0x02ae] = 0x40; - ram_[0x02af] = 0x00; - ram_[0x02b0] = 0x00; - ram_[0x02b1] = 0x00; + CA2 is used for receiving RTC interrupts. + CA1 is used for receiving vsync. + */ + if(port == Port::B && line == Line::Two) { + keyboard_.set_input(value); } + else logger.error().append("Unhandled 6522 control line output: %c%d", port ? 'B' : 'A', int(line)); + } + + void run_for(HalfCycles duration) { + // The 6522 enjoys a divide-by-ten, so multiply back up here to make the + // divided-by-two clock the audio works on. + audio_.time_since_update += HalfCycles(duration.as_integral() * 5); + } + + void flush() { + audio_.flush(); + } + + void set_interrupt_status(bool) { + machine_.update_interrupt_input(); } private: - bool quickboot_ = false; + ConcreteMachine &machine_; + Apple::Clock::SerialClock &clock_; + Keyboard &keyboard_; + DeferredAudio &audio_; + IWMActor &iwm_; + Inputs::QuadratureMouse &mouse_; + }; - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { - scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; - } + CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_; - void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final { - iwm_.flush(); - drives_[0].set_rotation_speed(speed); - drives_[1].set_rotation_speed(speed); - } + DriveSpeedAccumulator drive_speed_accumulator_; + IWMActor iwm_; - forceinline void adjust_phase() { - ++phase_; - } + DeferredAudio audio_; + Video video_; - template <typename Microcycle> - forceinline void fill_unmapped(const Microcycle &cycle) { - if(!(cycle.operation & CPU::MC68000::Operation::Read)) return; - cycle.set_value16(0xffff); - } + Apple::Clock::SerialClock clock_; + Keyboard keyboard_; - /// Advances all non-CPU components by @c duration half cycles. - forceinline void advance_time(HalfCycles duration) { - time_since_video_update_ += duration; - iwm_ += duration; - ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15; + VIAPortHandler via_port_handler_; + MOS::MOS6522::MOS6522<VIAPortHandler> via_; - // The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. - // See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division - // may occur here in order to provide VSYNC at a proper moment. + Zilog::SCC::z8530 scc_; + SCSI::Bus scsi_bus_; + NCR::NCR5380::NCR5380 scsi_; + SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_; + bool scsi_bus_is_clocked_ = false; - // Possibly route vsync. - if(time_since_video_update_ < time_until_video_event_) { - via_clock_ += duration; - via_.run_for(via_clock_.divide(HalfCycles(10))); - } else { - auto via_time_base = time_since_video_update_ - duration; - auto via_cycles_outstanding = duration; - while(time_until_video_event_ < time_since_video_update_) { - const auto via_cycles = time_until_video_event_ - via_time_base; - via_time_base = HalfCycles(0); - via_cycles_outstanding -= via_cycles; + HalfCycles via_clock_; + HalfCycles real_time_clock_; + HalfCycles keyboard_clock_; + HalfCycles time_since_video_update_; + HalfCycles time_until_video_event_; + HalfCycles time_since_mouse_update_; - via_clock_ += via_cycles; - via_.run_for(via_clock_.divide(HalfCycles(10))); + bool ROM_is_overlay_ = true; + int phase_ = 1; + int ram_subcycle_ = 0; - video_.run_for(time_until_video_event_); - time_since_video_update_ -= time_until_video_event_; - time_until_video_event_ = video_.next_sequence_point(); + DoubleDensityDrive drives_[2]; + Inputs::QuadratureMouse mouse_; - via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); - } + Apple::Macintosh::KeyboardMapper keyboard_mapper_; - via_clock_ += via_cycles_outstanding; - via_.run_for(via_clock_.divide(HalfCycles(10))); + enum class BusDevice { + RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned + }; + + /// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording + /// which device is current mapped in each area. Keeping it in a table is a bit faster + /// than the multi-level address inspection that is otherwise required, as well as + /// simplifying slightly the handling of different models. + /// + /// So: index with the top 7 bits of the 24-bit address. + BusDevice memory_map_[128]; + + void setup_memory_map() { + // Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; + // start by calling into set_rom_is_overlay to seed everything up to $800000. + set_rom_is_overlay(true); + + populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) { + map_to(0x900000, BusDevice::Unassigned); + map_to(0xa00000, BusDevice::SCCReadResetPhase); + map_to(0xb00000, BusDevice::Unassigned); + map_to(0xc00000, BusDevice::SCCWrite); + map_to(0xd00000, BusDevice::Unassigned); + map_to(0xe00000, BusDevice::IWM); + map_to(0xe80000, BusDevice::Unassigned); + map_to(0xf00000, BusDevice::VIA); + map_to(0xf80000, BusDevice::PhaseRead); + map_to(0x1000000, BusDevice::Unassigned); + }); + } + + void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) { + // Define semantics for below; map_to will write from the current cursor position + // to the supplied 24-bit address, setting a particular mapped device. + int segment = start_address >> 17; + auto map_to = [&segment, this](int address, BusDevice device) { + for(; segment < address >> 17; ++segment) { + this->memory_map_[segment] = device; } - - // The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. - // Its clock and data lines are connected to the VIA. - keyboard_clock_ += duration; - if(keyboard_clock_ >= KEYBOARD_CLOCK_RATE) { - const auto keyboard_ticks = keyboard_clock_.divide(KEYBOARD_CLOCK_RATE); - keyboard_.run_for(keyboard_ticks); - via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); - via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); - } - - // Feed mouse inputs within at most 1250 cycles of each other. - if(mouse_.has_steps()) { - time_since_mouse_update_ += duration; - const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); - if(mouse_ticks > HalfCycles(0)) { - mouse_.prepare_step(); - scc_.set_dcd(0, mouse_.get_channel(1) & 1); - scc_.set_dcd(1, mouse_.get_channel(0) & 1); - } - } - - // TODO: SCC should be clocked at a divide-by-two, if and when it actually has - // anything connected. - - // Consider updating the real-time clock. - real_time_clock_ += duration; - auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral(); - while(ticks--) { - clock_.update(); - // TODO: leave a delay between toggling the input rather than using this coupled hack. - via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); - via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); - } - - // Update the SCSI if currently active. - if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { - if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); - } - } - - forceinline void update_video() { - video_.run_for(time_since_video_update_.flush<HalfCycles>()); - time_until_video_event_ = video_.next_sequence_point(); - } - - Inputs::Mouse &get_mouse() final { - return mouse_; - } - - using IWMActor = JustInTimeActor<IWM>; - - class VIAPortHandler: public MOS::MOS6522::PortHandler { - public: - VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : - machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} - - using Port = MOS::MOS6522::Port; - using Line = MOS::MOS6522::Line; - - void set_port_output(Port port, uint8_t value, uint8_t) { - /* - Peripheral lines: keyboard data, interrupt configuration. - (See p176 [/215]) - */ - switch(port) { - case Port::A: - /* - Port A: - b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR) - b6: 0 = alternate screen buffer, 1 = main screen buffer - b5: floppy disk SEL state control (upper/lower head "among other things") - b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map - b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer - b2–b0: audio output volume - */ - iwm_->set_select(!!(value & 0x20)); - - machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); - machine_.set_rom_is_overlay(!!(value & 0x10)); - - audio_.flush(); - audio_.audio.set_volume(value & 7); - break; - - case Port::B: - /* - Port B: - b7: 0 = sound enabled, 1 = sound disabled - b6: [input] 0 = video beam in visible portion of line, 1 = outside - b5: [input] mouse y2 - b4: [input] mouse x2 - b3: [input] 0 = mouse button down, 1 = up - b2: 0 = real-time clock enabled, 1 = disabled - b1: clock's data-clock line - b0: clock's serial data line - */ - if(value & 0x4) clock_.abort(); - else clock_.set_input(!!(value & 0x2), !!(value & 0x1)); - - audio_.flush(); - audio_.audio.set_enabled(!(value & 0x80)); - break; - } - } - - uint8_t get_port_input(Port port) { - switch(port) { - case Port::A: -// printf("6522 r A\n"); - return 0x00; // TODO: b7 = SCC wait/request - - case Port::B: - return uint8_t( - ((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) | - ((mouse_.get_channel(0) & 2) << 3) | - ((mouse_.get_channel(1) & 2) << 4) | - (clock_.get_data() ? 0x02 : 0x00) | - (machine_.video_is_outputting() ? 0x00 : 0x40) - ); - } - - // Should be unreachable. - return 0xff; - } - - void set_control_line_output(Port port, Line line, bool value) { - /* - Keyboard wiring (I believe): - CB2 = data (input/output) - CB1 = clock (input) - - CA2 is used for receiving RTC interrupts. - CA1 is used for receiving vsync. - */ - if(port == Port::B && line == Line::Two) { - keyboard_.set_input(value); - } - else logger.error().append("Unhandled 6522 control line output: %c%d", port ? 'B' : 'A', int(line)); - } - - void run_for(HalfCycles duration) { - // The 6522 enjoys a divide-by-ten, so multiply back up here to make the - // divided-by-two clock the audio works on. - audio_.time_since_update += HalfCycles(duration.as_integral() * 5); - } - - void flush() { - audio_.flush(); - } - - void set_interrupt_status(bool) { - machine_.update_interrupt_input(); - } - - private: - ConcreteMachine &machine_; - Apple::Clock::SerialClock &clock_; - Keyboard &keyboard_; - DeferredAudio &audio_; - IWMActor &iwm_; - Inputs::QuadratureMouse &mouse_; }; - CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_; + populator(map_to); + } - DriveSpeedAccumulator drive_speed_accumulator_; - IWMActor iwm_; - - DeferredAudio audio_; - Video video_; - - Apple::Clock::SerialClock clock_; - Keyboard keyboard_; - - VIAPortHandler via_port_handler_; - MOS::MOS6522::MOS6522<VIAPortHandler> via_; - - Zilog::SCC::z8530 scc_; - SCSI::Bus scsi_bus_; - NCR::NCR5380::NCR5380 scsi_; - SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_; - bool scsi_bus_is_clocked_ = false; - - HalfCycles via_clock_; - HalfCycles real_time_clock_; - HalfCycles keyboard_clock_; - HalfCycles time_since_video_update_; - HalfCycles time_until_video_event_; - HalfCycles time_since_mouse_update_; - - bool ROM_is_overlay_ = true; - int phase_ = 1; - int ram_subcycle_ = 0; - - DoubleDensityDrive drives_[2]; - Inputs::QuadratureMouse mouse_; - - Apple::Macintosh::KeyboardMapper keyboard_mapper_; - - enum class BusDevice { - RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned - }; - - /// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording - /// which device is current mapped in each area. Keeping it in a table is a bit faster - /// than the multi-level address inspection that is otherwise required, as well as - /// simplifying slightly the handling of different models. - /// - /// So: index with the top 7 bits of the 24-bit address. - BusDevice memory_map_[128]; - - void setup_memory_map() { - // Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; - // start by calling into set_rom_is_overlay to seed everything up to $800000. - set_rom_is_overlay(true); - - populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) { - map_to(0x900000, BusDevice::Unassigned); - map_to(0xa00000, BusDevice::SCCReadResetPhase); - map_to(0xb00000, BusDevice::Unassigned); - map_to(0xc00000, BusDevice::SCCWrite); - map_to(0xd00000, BusDevice::Unassigned); - map_to(0xe00000, BusDevice::IWM); - map_to(0xe80000, BusDevice::Unassigned); - map_to(0xf00000, BusDevice::VIA); - map_to(0xf80000, BusDevice::PhaseRead); - map_to(0x1000000, BusDevice::Unassigned); - }); - } - - void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) { - // Define semantics for below; map_to will write from the current cursor position - // to the supplied 24-bit address, setting a particular mapped device. - int segment = start_address >> 17; - auto map_to = [&segment, this](int address, BusDevice device) { - for(; segment < address >> 17; ++segment) { - this->memory_map_[segment] = device; - } - }; - - populator(map_to); - } - - uint32_t ram_mask_ = 0; - uint32_t rom_mask_ = 0; - uint8_t rom_[128*1024]; - std::vector<uint8_t> ram_; + uint32_t ram_mask_ = 0; + uint32_t rom_mask_ = 0; + uint8_t rom_[128*1024]; + std::vector<uint8_t> ram_; }; } diff --git a/Machines/Apple/Macintosh/Video.hpp b/Machines/Apple/Macintosh/Video.hpp index 6f0ed7658..9149232d4 100644 --- a/Machines/Apple/Macintosh/Video.hpp +++ b/Machines/Apple/Macintosh/Video.hpp @@ -28,76 +28,76 @@ constexpr int sync_end = 38; This class also collects audio and 400kb drive-speed data, forwarding those values. */ class Video { - public: - /*! - Constructs an instance of @c Video sourcing its pixel data from @c ram and - providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. - */ - Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); +public: + /*! + Constructs an instance of @c Video sourcing its pixel data from @c ram and + providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. + */ + Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); - /*! - Sets the target device for video data. - */ - void set_scan_target(Outputs::Display::ScanTarget *scan_target); + /*! + Sets the target device for video data. + */ + void set_scan_target(Outputs::Display::ScanTarget *scan_target); - /// Gets the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const; + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const; - /*! - Produces the next @c duration period of pixels. - */ - void run_for(HalfCycles duration); + /*! + Produces the next @c duration period of pixels. + */ + void run_for(HalfCycles duration); - /*! - Sets whether the alternate screen and/or audio buffers should be used to source data. - */ - void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); + /*! + Sets whether the alternate screen and/or audio buffers should be used to source data. + */ + void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); - /*! - Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are - actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. - */ - void set_ram(uint16_t *ram, uint32_t mask); + /*! + Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are + actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. + */ + void set_ram(uint16_t *ram, uint32_t mask); - /*! - @returns @c true if the video is currently outputting a vertical sync, @c false otherwise. - */ - bool vsync(); + /*! + @returns @c true if the video is currently outputting a vertical sync, @c false otherwise. + */ + bool vsync(); - /* - @returns @c true if in @c offset half cycles from now, the video will be outputting pixels; - @c false otherwise. - */ - bool is_outputting(HalfCycles offset = HalfCycles(0)) { - const auto offset_position = frame_position_ + offset % frame_length; - const int column = int((offset_position % line_length).as_integral()) >> 4; - const int line = int((offset_position / line_length).as_integral()); - return line < 342 && column < 32; - } + /* + @returns @c true if in @c offset half cycles from now, the video will be outputting pixels; + @c false otherwise. + */ + bool is_outputting(HalfCycles offset = HalfCycles(0)) { + const auto offset_position = frame_position_ + offset % frame_length; + const int column = int((offset_position % line_length).as_integral()) >> 4; + const int line = int((offset_position / line_length).as_integral()); + return line < 342 && column < 32; + } - /*! - @returns the amount of time until there is next a transition on the - vsync signal. - */ - HalfCycles next_sequence_point(); + /*! + @returns the amount of time until there is next a transition on the + vsync signal. + */ + HalfCycles next_sequence_point(); - private: - DeferredAudio &audio_; - DriveSpeedAccumulator &drive_speed_accumulator_; +private: + DeferredAudio &audio_; + DriveSpeedAccumulator &drive_speed_accumulator_; - Outputs::CRT::CRT crt_; - uint16_t *ram_ = nullptr; - uint32_t ram_mask_ = 0; + Outputs::CRT::CRT crt_; + uint16_t *ram_ = nullptr; + uint32_t ram_mask_ = 0; - HalfCycles frame_position_; + HalfCycles frame_position_; - size_t video_address_ = 0; - size_t audio_address_ = 0; + size_t video_address_ = 0; + size_t audio_address_ = 0; - uint64_t *pixel_buffer_ = nullptr; + uint64_t *pixel_buffer_ = nullptr; - bool use_alternate_screen_buffer_ = false; - bool use_alternate_audio_buffer_ = false; + bool use_alternate_screen_buffer_ = false; + bool use_alternate_audio_buffer_ = false; }; } diff --git a/Machines/Atari/2600/Atari2600.cpp b/Machines/Atari/2600/Atari2600.cpp index 36db0226a..1e6eb9b8a 100644 --- a/Machines/Atari/2600/Atari2600.cpp +++ b/Machines/Atari/2600/Atari2600.cpp @@ -36,39 +36,39 @@ namespace { namespace Atari2600 { class Joystick: public Inputs::ConcreteJoystick { - public: - Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : - ConcreteJoystick({ - Input(Input::Up), - Input(Input::Down), - Input(Input::Left), - Input(Input::Right), - Input(Input::Fire) - }), - bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} +public: + Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : + ConcreteJoystick({ + Input(Input::Up), + Input(Input::Down), + Input(Input::Left), + Input(Input::Right), + Input(Input::Fire) + }), + bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} - void did_set_input(const Input &digital_input, bool is_active) final { - switch(digital_input.type) { - case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break; - case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break; - case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break; - case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break; + void did_set_input(const Input &digital_input, bool is_active) final { + switch(digital_input.type) { + case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break; + case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break; + case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break; + case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break; - // TODO: latching - case Input::Fire: - if(is_active) - bus_->tia_input_value_[fire_tia_input_] &= ~0x80; - else - bus_->tia_input_value_[fire_tia_input_] |= 0x80; - break; + // TODO: latching + case Input::Fire: + if(is_active) + bus_->tia_input_value_[fire_tia_input_] &= ~0x80; + else + bus_->tia_input_value_[fire_tia_input_] |= 0x80; + break; - default: break; - } + default: break; } + } - private: - Bus *bus_; - std::size_t shift_, fire_tia_input_; +private: + Bus *bus_; + std::size_t shift_, fire_tia_input_; }; using Target = Analyser::Static::Atari2600::Target; @@ -79,133 +79,133 @@ class ConcreteMachine: public MachineTypes::AudioProducer, public MachineTypes::ScanProducer, public MachineTypes::JoystickMachine { - public: - ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) { - const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; +public: + ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) { + const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; - using PagingModel = Target::PagingModel; - switch(target.paging_model) { - case PagingModel::ActivisionStack: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ActivisionStack>>(rom); break; - case PagingModel::CBSRamPlus: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CBSRAMPlus>>(rom); break; - case PagingModel::CommaVid: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CommaVid>>(rom); break; - case PagingModel::MegaBoy: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MegaBoy>>(rom); break; - case PagingModel::MNetwork: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MNetwork>>(rom); break; - case PagingModel::None: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Unpaged>>(rom); break; - case PagingModel::ParkerBros: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ParkerBros>>(rom); break; - case PagingModel::Pitfall2: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Pitfall2>>(rom); break; - case PagingModel::Tigervision: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Tigervision>>(rom); break; + using PagingModel = Target::PagingModel; + switch(target.paging_model) { + case PagingModel::ActivisionStack: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ActivisionStack>>(rom); break; + case PagingModel::CBSRamPlus: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CBSRAMPlus>>(rom); break; + case PagingModel::CommaVid: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::CommaVid>>(rom); break; + case PagingModel::MegaBoy: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MegaBoy>>(rom); break; + case PagingModel::MNetwork: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::MNetwork>>(rom); break; + case PagingModel::None: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Unpaged>>(rom); break; + case PagingModel::ParkerBros: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::ParkerBros>>(rom); break; + case PagingModel::Pitfall2: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Pitfall2>>(rom); break; + case PagingModel::Tigervision: bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Tigervision>>(rom); break; - case PagingModel::Atari8k: - if(target.uses_superchip) { - bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8kSuperChip>>(rom); - } else { - bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8k>>(rom); - } - break; - case PagingModel::Atari16k: - if(target.uses_superchip) { - bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16kSuperChip>>(rom); - } else { - bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16k>>(rom); - } - break; - case PagingModel::Atari32k: - if(target.uses_superchip) { - bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32kSuperChip>>(rom); - } else { - bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32k>>(rom); - } - break; - } - - joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0)); - joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); - - set_is_ntsc(is_ntsc_); + case PagingModel::Atari8k: + if(target.uses_superchip) { + bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8kSuperChip>>(rom); + } else { + bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari8k>>(rom); + } + break; + case PagingModel::Atari16k: + if(target.uses_superchip) { + bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16kSuperChip>>(rom); + } else { + bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari16k>>(rom); + } + break; + case PagingModel::Atari32k: + if(target.uses_superchip) { + bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32kSuperChip>>(rom); + } else { + bus_ = std::make_unique<Cartridge::Cartridge<Cartridge::Atari32k>>(rom); + } + break; } - const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final { - return joysticks_; + joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0)); + joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); + + set_is_ntsc(is_ntsc_); + } + + const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final { + return joysticks_; + } + + void set_switch_is_enabled(Atari2600Switch input, bool state) final { + switch(input) { + case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break; + case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break; + case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break; + case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break; + case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break; } + } - void set_switch_is_enabled(Atari2600Switch input, bool state) final { - switch(input) { - case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break; - case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break; - case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break; - case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break; - case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break; - } + bool get_switch_is_enabled(Atari2600Switch input) const final { + uint8_t port_input = bus_->mos6532_.get_port_input(1); + switch(input) { + case Atari2600SwitchReset: return !!(port_input & 0x01); + case Atari2600SwitchSelect: return !!(port_input & 0x02); + case Atari2600SwitchColour: return !!(port_input & 0x08); + case Atari2600SwitchLeftPlayerDifficulty: return !!(port_input & 0x40); + case Atari2600SwitchRightPlayerDifficulty: return !!(port_input & 0x80); + default: return false; } + } - bool get_switch_is_enabled(Atari2600Switch input) const final { - uint8_t port_input = bus_->mos6532_.get_port_input(1); - switch(input) { - case Atari2600SwitchReset: return !!(port_input & 0x01); - case Atari2600SwitchSelect: return !!(port_input & 0x02); - case Atari2600SwitchColour: return !!(port_input & 0x08); - case Atari2600SwitchLeftPlayerDifficulty: return !!(port_input & 0x40); - case Atari2600SwitchRightPlayerDifficulty: return !!(port_input & 0x80); - default: return false; - } - } + void set_reset_switch(bool state) final { + bus_->set_reset_line(state); + } - void set_reset_switch(bool state) final { - bus_->set_reset_line(state); - } + // to satisfy CRTMachine::Machine + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick))); + bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_); + bus_->tia_.set_scan_target(scan_target); + } - // to satisfy CRTMachine::Machine - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick))); - bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_); - bus_->tia_.set_scan_target(scan_target); - } + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return bus_->tia_.get_scaled_scan_status() / 3.0f; + } - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return bus_->tia_.get_scaled_scan_status() / 3.0f; - } + Outputs::Speaker::Speaker *get_speaker() final { + return &bus_->speaker_; + } - Outputs::Speaker::Speaker *get_speaker() final { - return &bus_->speaker_; - } + void run_for(const Cycles cycles) final { + bus_->run_for(cycles); + bus_->apply_confidence(confidence_counter_); + } - void run_for(const Cycles cycles) final { - bus_->run_for(cycles); - bus_->apply_confidence(confidence_counter_); - } + void flush_output(int) final { + bus_->flush(); + } - void flush_output(int) final { - bus_->flush(); - } + void register_crt_frequency_mismatch() { + is_ntsc_ ^= true; + set_is_ntsc(is_ntsc_); + } - void register_crt_frequency_mismatch() { - is_ntsc_ ^= true; - set_is_ntsc(is_ntsc_); - } + float get_confidence() final { + return confidence_counter_.get_confidence(); + } - float get_confidence() final { - return confidence_counter_.get_confidence(); - } +private: + // The bus. + std::unique_ptr<Bus> bus_; - private: - // The bus. - std::unique_ptr<Bus> bus_; + // Output frame rate tracker. + Outputs::CRT::CRTFrequencyMismatchWarner<ConcreteMachine> frequency_mismatch_warner_; + bool is_ntsc_ = true; + std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; - // Output frame rate tracker. - Outputs::CRT::CRTFrequencyMismatchWarner<ConcreteMachine> frequency_mismatch_warner_; - bool is_ntsc_ = true; - std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; + // a confidence counter + Analyser::Dynamic::ConfidenceCounter confidence_counter_; - // a confidence counter - Analyser::Dynamic::ConfidenceCounter confidence_counter_; - - void set_is_ntsc(bool is_ntsc) { - bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL); - const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate; - bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick)); - bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2)); - set_clock_rate(clock_rate); - } + void set_is_ntsc(bool is_ntsc) { + bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL); + const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate; + bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick)); + bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2)); + set_clock_rate(clock_rate); + } }; } diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index c853acec6..2743933d5 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -64,644 +64,644 @@ class ConcreteMachine: public GI::AY38910::PortHandler, public Configurable::Device, public Video::RangeObserver { - public: - ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - mc68000_(*this), - keyboard_acia_(Cycles(500000)), - midi_acia_(Cycles(500000)), - ay_(GI::AY38910::Personality::YM2149F, audio_queue_), - speaker_(ay_), - ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) { - set_clock_rate(CLOCK_RATE); - speaker_.set_input_rate(float(CLOCK_RATE) / 4.0f); +public: + ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + mc68000_(*this), + keyboard_acia_(Cycles(500000)), + midi_acia_(Cycles(500000)), + ay_(GI::AY38910::Personality::YM2149F, audio_queue_), + speaker_(ay_), + ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) { + set_clock_rate(CLOCK_RATE); + speaker_.set_input_rate(float(CLOCK_RATE) / 4.0f); - switch(target.memory_size) { - default: - case Target::MemorySize::FiveHundredAndTwelveKilobytes: - ram_.resize(512 * 1024); - break; - case Target::MemorySize::OneMegabyte: - ram_.resize(1024 * 1024); - break; - case Target::MemorySize::FourMegabytes: - ram_.resize(4 * 1024 * 1024); - break; - } - Memory::Fuzz(ram_); + switch(target.memory_size) { + default: + case Target::MemorySize::FiveHundredAndTwelveKilobytes: + ram_.resize(512 * 1024); + break; + case Target::MemorySize::OneMegabyte: + ram_.resize(1024 * 1024); + break; + case Target::MemorySize::FourMegabytes: + ram_.resize(4 * 1024 * 1024); + break; + } + Memory::Fuzz(ram_); - video_->set_ram( - reinterpret_cast<uint16_t *>(ram_.data()), - ram_.size() >> 1 - ); + video_->set_ram( + reinterpret_cast<uint16_t *>(ram_.data()), + ram_.size() >> 1 + ); - constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100; - ROM::Request request(rom_name); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); + constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100; + ROM::Request request(rom_name); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); - // Set up basic memory map. - int c = 0; - for(; c < int(ram_.size() >> 16); ++c) memory_map_[c] = BusDevice::RAM; - for(; c < 0x40; ++c) memory_map_[c] = BusDevice::Floating; - for(; c < 0xff; ++c) memory_map_[c] = BusDevice::Unassigned; + // Set up basic memory map. + int c = 0; + for(; c < int(ram_.size() >> 16); ++c) memory_map_[c] = BusDevice::RAM; + for(; c < 0x40; ++c) memory_map_[c] = BusDevice::Floating; + for(; c < 0xff; ++c) memory_map_[c] = BusDevice::Unassigned; - const bool is_early_tos = true; - if(is_early_tos) { - rom_start_ = 0xfc0000; - for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; + const bool is_early_tos = true; + if(is_early_tos) { + rom_start_ = 0xfc0000; + for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; + } else { + rom_start_ = 0xe00000; + for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM; + } + + memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge; + memory_map_[0xff] = BusDevice::IO; + + // Copy the first 8 bytes of ROM into RAM. + reinstall_rom_vector(); + + midi_acia_->set_interrupt_delegate(this); + keyboard_acia_->set_interrupt_delegate(this); + + midi_acia_->set_clocking_hint_observer(this); + keyboard_acia_->set_clocking_hint_observer(this); + ikbd_.set_clocking_hint_observer(this); + mfp_->set_clocking_hint_observer(this); + dma_->set_clocking_hint_observer(this); + + mfp_->set_interrupt_delegate(this); + dma_->set_delegate(this); + ay_.set_port_handler(this); + + set_gpip_input(); + + video_->set_range_observer(this); + + // Insert any supplied media. + insert_media(target.media); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + // MARK: CRTMachine::Machine + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + video_->set_scan_target(scan_target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return video_->get_scaled_scan_status(); + } + + void set_display_type(Outputs::Display::DisplayType display_type) final { + video_->set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return video_->get_display_type(); + } + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + void run_for(const Cycles cycles) final { + // Give the keyboard an opportunity to consume any events. + if(!keyboard_needs_clock_) { + ikbd_.run_for(HalfCycles(0)); + } + + mc68000_.run_for(cycles); + } + + // MARK: MC68000::BusHandler + template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { + // Just in case the last cycle was an interrupt acknowledge or bus error. TODO: find a better solution? + mc68000_.set_is_peripheral_address(false); + mc68000_.set_bus_error(false); + + // Advance time. + advance_time(cycle.length); + + // Check for assertion of reset. + if(cycle.operation & CPU::MC68000::Operation::Reset) { + logger.error().append("Unhandled Reset"); + } + + // A null cycle leaves nothing else to do. + if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); + + // An interrupt acknowledge, perhaps? + if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) { + // Current implementation: everything other than 6 (i.e. the MFP) is autovectored. + const int interrupt_level = cycle.word_address()&7; + if(interrupt_level != 6) { + video_interrupts_pending_ &= ~interrupt_level; + update_interrupt_input(); + mc68000_.set_is_peripheral_address(true); + return HalfCycles(0); } else { - rom_start_ = 0xe00000; - for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM; - } - - memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge; - memory_map_[0xff] = BusDevice::IO; - - // Copy the first 8 bytes of ROM into RAM. - reinstall_rom_vector(); - - midi_acia_->set_interrupt_delegate(this); - keyboard_acia_->set_interrupt_delegate(this); - - midi_acia_->set_clocking_hint_observer(this); - keyboard_acia_->set_clocking_hint_observer(this); - ikbd_.set_clocking_hint_observer(this); - mfp_->set_clocking_hint_observer(this); - dma_->set_clocking_hint_observer(this); - - mfp_->set_interrupt_delegate(this); - dma_->set_delegate(this); - ay_.set_port_handler(this); - - set_gpip_input(); - - video_->set_range_observer(this); - - // Insert any supplied media. - insert_media(target.media); - } - - ~ConcreteMachine() { - audio_queue_.flush(); - } - - // MARK: CRTMachine::Machine - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - video_->set_scan_target(scan_target); - } - - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return video_->get_scaled_scan_status(); - } - - void set_display_type(Outputs::Display::DisplayType display_type) final { - video_->set_display_type(display_type); - } - - Outputs::Display::DisplayType get_display_type() const final { - return video_->get_display_type(); - } - - Outputs::Speaker::Speaker *get_speaker() final { - return &speaker_; - } - - void run_for(const Cycles cycles) final { - // Give the keyboard an opportunity to consume any events. - if(!keyboard_needs_clock_) { - ikbd_.run_for(HalfCycles(0)); - } - - mc68000_.run_for(cycles); - } - - // MARK: MC68000::BusHandler - template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { - // Just in case the last cycle was an interrupt acknowledge or bus error. TODO: find a better solution? - mc68000_.set_is_peripheral_address(false); - mc68000_.set_bus_error(false); - - // Advance time. - advance_time(cycle.length); - - // Check for assertion of reset. - if(cycle.operation & CPU::MC68000::Operation::Reset) { - logger.error().append("Unhandled Reset"); - } - - // A null cycle leaves nothing else to do. - if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); - - // An interrupt acknowledge, perhaps? - if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) { - // Current implementation: everything other than 6 (i.e. the MFP) is autovectored. - const int interrupt_level = cycle.word_address()&7; - if(interrupt_level != 6) { - video_interrupts_pending_ &= ~interrupt_level; - update_interrupt_input(); - mc68000_.set_is_peripheral_address(true); - return HalfCycles(0); - } else { - if(cycle.operation & CPU::MC68000::Operation::SelectByte) { - const int interrupt = mfp_->acknowledge_interrupt(); - if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) { - cycle.value->b = uint8_t(interrupt); - } else { - // TODO: this should take a while. Find out how long. - mc68000_.set_bus_error(true); - } - } - return HalfCycles(0); - } - } - - auto address = cycle.host_endian_byte_address(); - - // If this is a new strobing of the address signal, test for bus error and pre-DTack delay. - HalfCycles delay(0); - if(cycle.operation & CPU::MC68000::Operation::NewAddress) { - // Bus error test. - if( - // Anything unassigned should generate a bus error. - (memory_map_[address >> 16] == BusDevice::Unassigned) || - - // Bus errors also apply to unprivileged access to the first 0x800 bytes, or the IO area. - (!is_supervisor && (address < 0x800 || memory_map_[address >> 16] == BusDevice::IO)) - ) { - mc68000_.set_bus_error(true); - return delay; // TODO: there should be an extra delay here. - } - - // DTack delay rule: if accessing RAM or the shifter, align with the two cycles next available - // for the CPU to access that side of the bus. - if(address < ram_.size() || (address == 0xff8260)) { - // DTack will be implicit; work out how long until that should be, - // and apply bus error constraints. - const int i_phase = bus_phase_.as<int>() & 7; - if(i_phase < 4) { - delay = HalfCycles(4 - i_phase); - advance_time(delay); + if(cycle.operation & CPU::MC68000::Operation::SelectByte) { + const int interrupt = mfp_->acknowledge_interrupt(); + if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) { + cycle.value->b = uint8_t(interrupt); + } else { + // TODO: this should take a while. Find out how long. + mc68000_.set_bus_error(true); } } - } - - uint8_t *memory = nullptr; - switch(memory_map_[address >> 16]) { - default: - case BusDevice::RAM: - memory = ram_.data(); - break; - - case BusDevice::ROM: - memory = rom_.data(); - if(!(cycle.operation & CPU::MC68000::Operation::Read)) { - return delay; - } - address -= rom_start_; - break; - - case BusDevice::Floating: - // TODO: provide vapour reads here. But: will these always be of the last video fetch? - case BusDevice::Unassigned: - case BusDevice::Cartridge: - /* - TOS 1.0 appears to attempt to read from the catridge before it has setup - the bus error vector. Therefore I assume no bus error flows. - */ - switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { - default: break; - case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: - cycle.value->w = 0xffff; - break; - case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: - cycle.value->b = 0xff; - break; - } - return delay; - - case BusDevice::IO: - switch(address & 0xfffe) { // TODO: surely it's going to be even less precise than this? - default: -// assert(false); - - case 0x8000: - /* Memory controller configuration: - b0, b1: bank 1 - b2, b3: bank 0 - - 00 = 128k - 01 = 512k - 10 = 2mb - 11 = reserved - */ - break; - - // Video controls. - case 0x8200: case 0x8202: case 0x8204: case 0x8206: - case 0x8208: case 0x820a: case 0x820c: case 0x820e: - case 0x8210: case 0x8212: case 0x8214: case 0x8216: - case 0x8218: case 0x821a: case 0x821c: case 0x821e: - case 0x8220: case 0x8222: case 0x8224: case 0x8226: - case 0x8228: case 0x822a: case 0x822c: case 0x822e: - case 0x8230: case 0x8232: case 0x8234: case 0x8236: - case 0x8238: case 0x823a: case 0x823c: case 0x823e: - case 0x8240: case 0x8242: case 0x8244: case 0x8246: - case 0x8248: case 0x824a: case 0x824c: case 0x824e: - case 0x8250: case 0x8252: case 0x8254: case 0x8256: - case 0x8258: case 0x825a: case 0x825c: case 0x825e: - case 0x8260: case 0x8262: - if(!cycle.data_select_active()) return delay; - - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value16(video_->read(int(address >> 1))); - } else { - video_->write(int(address >> 1), cycle.value16()); - } - break; - - // DMA. - case 0x8604: case 0x8606: case 0x8608: case 0x860a: case 0x860c: - if(!cycle.data_select_active()) return delay; - - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value16(dma_->read(int(address >> 1))); - } else { - dma_->write(int(address >> 1), cycle.value16()); - } - break; - - // Audio. - // - // Re: mirrors, Dan Hollis' hardware register list asserts: - // - // "Note: PSG Registers are now fixed at these addresses. All other addresses are masked out on the Falcon. Any - // writes to the shadow registers $8804-$88FF will cause bus errors.", which I am taking to imply that those shadow - // registers exist on the Atari ST. - case 0x8800: case 0x8802: case 0x8804: case 0x8806: case 0x8808: case 0x880a: case 0x880c: case 0x880e: - case 0x8810: case 0x8812: case 0x8814: case 0x8816: case 0x8818: case 0x881a: case 0x881c: case 0x881e: - case 0x8820: case 0x8822: case 0x8824: case 0x8826: case 0x8828: case 0x882a: case 0x882c: case 0x882e: - case 0x8830: case 0x8832: case 0x8834: case 0x8836: case 0x8838: case 0x883a: case 0x883c: case 0x883e: - case 0x8840: case 0x8842: case 0x8844: case 0x8846: case 0x8848: case 0x884a: case 0x884c: case 0x884e: - case 0x8850: case 0x8852: case 0x8854: case 0x8856: case 0x8858: case 0x885a: case 0x885c: case 0x885e: - case 0x8860: case 0x8862: case 0x8864: case 0x8866: case 0x8868: case 0x886a: case 0x886c: case 0x886e: - case 0x8870: case 0x8872: case 0x8874: case 0x8876: case 0x8878: case 0x887a: case 0x887c: case 0x887e: - case 0x8880: case 0x8882: case 0x8884: case 0x8886: case 0x8888: case 0x888a: case 0x888c: case 0x888e: - case 0x8890: case 0x8892: case 0x8894: case 0x8896: case 0x8898: case 0x889a: case 0x889c: case 0x889e: - case 0x88a0: case 0x88a2: case 0x88a4: case 0x88a6: case 0x88a8: case 0x88aa: case 0x88ac: case 0x88ae: - case 0x88b0: case 0x88b2: case 0x88b4: case 0x88b6: case 0x88b8: case 0x88ba: case 0x88bc: case 0x88be: - case 0x88c0: case 0x88c2: case 0x88c4: case 0x88c6: case 0x88c8: case 0x88ca: case 0x88cc: case 0x88ce: - case 0x88d0: case 0x88d2: case 0x88d4: case 0x88d6: case 0x88d8: case 0x88da: case 0x88dc: case 0x88de: - case 0x88e0: case 0x88e2: case 0x88e4: case 0x88e6: case 0x88e8: case 0x88ea: case 0x88ec: case 0x88ee: - case 0x88f0: case 0x88f2: case 0x88f4: case 0x88f6: case 0x88f8: case 0x88fa: case 0x88fc: case 0x88fe: - if(!cycle.data_select_active()) return delay; - - advance_time(HalfCycles(2)); - update_audio(); - - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(GI::AY38910::Utility::read(ay_)); - } else { - // Net effect here: addresses with bit 1 set write to a register, - // addresses with bit 1 clear select a register. - GI::AY38910::Utility::write(ay_, address&2, cycle.value8_high()); - } - return delay + HalfCycles(2); - - // The MFP block: - case 0xfa00: case 0xfa02: case 0xfa04: case 0xfa06: - case 0xfa08: case 0xfa0a: case 0xfa0c: case 0xfa0e: - case 0xfa10: case 0xfa12: case 0xfa14: case 0xfa16: - case 0xfa18: case 0xfa1a: case 0xfa1c: case 0xfa1e: - case 0xfa20: case 0xfa22: case 0xfa24: case 0xfa26: - case 0xfa28: case 0xfa2a: case 0xfa2c: case 0xfa2e: - case 0xfa30: case 0xfa32: case 0xfa34: case 0xfa36: - case 0xfa38: case 0xfa3a: case 0xfa3c: case 0xfa3e: - if(!cycle.data_select_active()) return delay; - - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_low(mfp_->read(int(address >> 1))); - } else { - mfp_->write(int(address >> 1), cycle.value8_low()); - } - break; - - // ACIAs. - case 0xfc00: case 0xfc02: case 0xfc04: case 0xfc06: { - // Set VPA. - mc68000_.set_is_peripheral_address(!cycle.data_select_active()); - if(!cycle.data_select_active()) return delay; - - const auto acia_ = (address & 4) ? &midi_acia_ : &keyboard_acia_; - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high((*acia_)->read(int(address >> 1))); - } else { - (*acia_)->write(int(address >> 1), cycle.value8_high()); - } - } break; - } return HalfCycles(0); } + } - // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. - // - // In both write cases, immediately reinstall the first eight bytes of RAM from ROM, so that any write to - // that area is in effect a no-op. This is cheaper than the conditionality of actually checking. - switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { - default: - break; + auto address = cycle.host_endian_byte_address(); - case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: - cycle.value->w = *reinterpret_cast<uint16_t *>(&memory[address]); - break; - case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: - cycle.value->b = memory[address]; - break; - case CPU::MC68000::Operation::SelectWord: - if(address >= video_range_.low_address && address < video_range_.high_address) - video_.flush(); - *reinterpret_cast<uint16_t *>(&memory[address]) = cycle.value->w; - reinstall_rom_vector(); - break; - case CPU::MC68000::Operation::SelectByte: - if(address >= video_range_.low_address && address < video_range_.high_address) - video_.flush(); - memory[address] = cycle.value->b; - reinstall_rom_vector(); - break; + // If this is a new strobing of the address signal, test for bus error and pre-DTack delay. + HalfCycles delay(0); + if(cycle.operation & CPU::MC68000::Operation::NewAddress) { + // Bus error test. + if( + // Anything unassigned should generate a bus error. + (memory_map_[address >> 16] == BusDevice::Unassigned) || + + // Bus errors also apply to unprivileged access to the first 0x800 bytes, or the IO area. + (!is_supervisor && (address < 0x800 || memory_map_[address >> 16] == BusDevice::IO)) + ) { + mc68000_.set_bus_error(true); + return delay; // TODO: there should be an extra delay here. } + // DTack delay rule: if accessing RAM or the shifter, align with the two cycles next available + // for the CPU to access that side of the bus. + if(address < ram_.size() || (address == 0xff8260)) { + // DTack will be implicit; work out how long until that should be, + // and apply bus error constraints. + const int i_phase = bus_phase_.as<int>() & 7; + if(i_phase < 4) { + delay = HalfCycles(4 - i_phase); + advance_time(delay); + } + } + } + + uint8_t *memory = nullptr; + switch(memory_map_[address >> 16]) { + default: + case BusDevice::RAM: + memory = ram_.data(); + break; + + case BusDevice::ROM: + memory = rom_.data(); + if(!(cycle.operation & CPU::MC68000::Operation::Read)) { + return delay; + } + address -= rom_start_; + break; + + case BusDevice::Floating: + // TODO: provide vapour reads here. But: will these always be of the last video fetch? + case BusDevice::Unassigned: + case BusDevice::Cartridge: + /* + TOS 1.0 appears to attempt to read from the catridge before it has setup + the bus error vector. Therefore I assume no bus error flows. + */ + switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { + default: break; + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: + cycle.value->w = 0xffff; + break; + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: + cycle.value->b = 0xff; + break; + } + return delay; + + case BusDevice::IO: + switch(address & 0xfffe) { // TODO: surely it's going to be even less precise than this? + default: +// assert(false); + + case 0x8000: + /* Memory controller configuration: + b0, b1: bank 1 + b2, b3: bank 0 + + 00 = 128k + 01 = 512k + 10 = 2mb + 11 = reserved + */ + break; + + // Video controls. + case 0x8200: case 0x8202: case 0x8204: case 0x8206: + case 0x8208: case 0x820a: case 0x820c: case 0x820e: + case 0x8210: case 0x8212: case 0x8214: case 0x8216: + case 0x8218: case 0x821a: case 0x821c: case 0x821e: + case 0x8220: case 0x8222: case 0x8224: case 0x8226: + case 0x8228: case 0x822a: case 0x822c: case 0x822e: + case 0x8230: case 0x8232: case 0x8234: case 0x8236: + case 0x8238: case 0x823a: case 0x823c: case 0x823e: + case 0x8240: case 0x8242: case 0x8244: case 0x8246: + case 0x8248: case 0x824a: case 0x824c: case 0x824e: + case 0x8250: case 0x8252: case 0x8254: case 0x8256: + case 0x8258: case 0x825a: case 0x825c: case 0x825e: + case 0x8260: case 0x8262: + if(!cycle.data_select_active()) return delay; + + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value16(video_->read(int(address >> 1))); + } else { + video_->write(int(address >> 1), cycle.value16()); + } + break; + + // DMA. + case 0x8604: case 0x8606: case 0x8608: case 0x860a: case 0x860c: + if(!cycle.data_select_active()) return delay; + + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value16(dma_->read(int(address >> 1))); + } else { + dma_->write(int(address >> 1), cycle.value16()); + } + break; + + // Audio. + // + // Re: mirrors, Dan Hollis' hardware register list asserts: + // + // "Note: PSG Registers are now fixed at these addresses. All other addresses are masked out on the Falcon. Any + // writes to the shadow registers $8804-$88FF will cause bus errors.", which I am taking to imply that those shadow + // registers exist on the Atari ST. + case 0x8800: case 0x8802: case 0x8804: case 0x8806: case 0x8808: case 0x880a: case 0x880c: case 0x880e: + case 0x8810: case 0x8812: case 0x8814: case 0x8816: case 0x8818: case 0x881a: case 0x881c: case 0x881e: + case 0x8820: case 0x8822: case 0x8824: case 0x8826: case 0x8828: case 0x882a: case 0x882c: case 0x882e: + case 0x8830: case 0x8832: case 0x8834: case 0x8836: case 0x8838: case 0x883a: case 0x883c: case 0x883e: + case 0x8840: case 0x8842: case 0x8844: case 0x8846: case 0x8848: case 0x884a: case 0x884c: case 0x884e: + case 0x8850: case 0x8852: case 0x8854: case 0x8856: case 0x8858: case 0x885a: case 0x885c: case 0x885e: + case 0x8860: case 0x8862: case 0x8864: case 0x8866: case 0x8868: case 0x886a: case 0x886c: case 0x886e: + case 0x8870: case 0x8872: case 0x8874: case 0x8876: case 0x8878: case 0x887a: case 0x887c: case 0x887e: + case 0x8880: case 0x8882: case 0x8884: case 0x8886: case 0x8888: case 0x888a: case 0x888c: case 0x888e: + case 0x8890: case 0x8892: case 0x8894: case 0x8896: case 0x8898: case 0x889a: case 0x889c: case 0x889e: + case 0x88a0: case 0x88a2: case 0x88a4: case 0x88a6: case 0x88a8: case 0x88aa: case 0x88ac: case 0x88ae: + case 0x88b0: case 0x88b2: case 0x88b4: case 0x88b6: case 0x88b8: case 0x88ba: case 0x88bc: case 0x88be: + case 0x88c0: case 0x88c2: case 0x88c4: case 0x88c6: case 0x88c8: case 0x88ca: case 0x88cc: case 0x88ce: + case 0x88d0: case 0x88d2: case 0x88d4: case 0x88d6: case 0x88d8: case 0x88da: case 0x88dc: case 0x88de: + case 0x88e0: case 0x88e2: case 0x88e4: case 0x88e6: case 0x88e8: case 0x88ea: case 0x88ec: case 0x88ee: + case 0x88f0: case 0x88f2: case 0x88f4: case 0x88f6: case 0x88f8: case 0x88fa: case 0x88fc: case 0x88fe: + if(!cycle.data_select_active()) return delay; + + advance_time(HalfCycles(2)); + update_audio(); + + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(GI::AY38910::Utility::read(ay_)); + } else { + // Net effect here: addresses with bit 1 set write to a register, + // addresses with bit 1 clear select a register. + GI::AY38910::Utility::write(ay_, address&2, cycle.value8_high()); + } + return delay + HalfCycles(2); + + // The MFP block: + case 0xfa00: case 0xfa02: case 0xfa04: case 0xfa06: + case 0xfa08: case 0xfa0a: case 0xfa0c: case 0xfa0e: + case 0xfa10: case 0xfa12: case 0xfa14: case 0xfa16: + case 0xfa18: case 0xfa1a: case 0xfa1c: case 0xfa1e: + case 0xfa20: case 0xfa22: case 0xfa24: case 0xfa26: + case 0xfa28: case 0xfa2a: case 0xfa2c: case 0xfa2e: + case 0xfa30: case 0xfa32: case 0xfa34: case 0xfa36: + case 0xfa38: case 0xfa3a: case 0xfa3c: case 0xfa3e: + if(!cycle.data_select_active()) return delay; + + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_low(mfp_->read(int(address >> 1))); + } else { + mfp_->write(int(address >> 1), cycle.value8_low()); + } + break; + + // ACIAs. + case 0xfc00: case 0xfc02: case 0xfc04: case 0xfc06: { + // Set VPA. + mc68000_.set_is_peripheral_address(!cycle.data_select_active()); + if(!cycle.data_select_active()) return delay; + + const auto acia_ = (address & 4) ? &midi_acia_ : &keyboard_acia_; + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high((*acia_)->read(int(address >> 1))); + } else { + (*acia_)->write(int(address >> 1), cycle.value8_high()); + } + } break; + } return HalfCycles(0); } - void reinstall_rom_vector() { - std::copy(rom_.begin(), rom_.begin() + 8, ram_.begin()); + // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. + // + // In both write cases, immediately reinstall the first eight bytes of RAM from ROM, so that any write to + // that area is in effect a no-op. This is cheaper than the conditionality of actually checking. + switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { + default: + break; + + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: + cycle.value->w = *reinterpret_cast<uint16_t *>(&memory[address]); + break; + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: + cycle.value->b = memory[address]; + break; + case CPU::MC68000::Operation::SelectWord: + if(address >= video_range_.low_address && address < video_range_.high_address) + video_.flush(); + *reinterpret_cast<uint16_t *>(&memory[address]) = cycle.value->w; + reinstall_rom_vector(); + break; + case CPU::MC68000::Operation::SelectByte: + if(address >= video_range_.low_address && address < video_range_.high_address) + video_.flush(); + memory[address] = cycle.value->b; + reinstall_rom_vector(); + break; } - void flush_output(int outputs) final { - dma_.flush(); - mfp_.flush(); + return HalfCycles(0); + } + + void reinstall_rom_vector() { + std::copy(rom_.begin(), rom_.begin() + 8, ram_.begin()); + } + + void flush_output(int outputs) final { + dma_.flush(); + mfp_.flush(); + keyboard_acia_.flush(); + midi_acia_.flush(); + + if(outputs & Output::Video) { + video_.flush(); + } + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); + } + } + +private: + forceinline void advance_time(HalfCycles length) { + // Advance the relevant counters. + cycles_since_audio_update_ += length; + mfp_ += length; + if(dma_clocking_preference_ != ClockingHint::Preference::None) + dma_ += length; + keyboard_acia_ += length; + midi_acia_ += length; + bus_phase_ += length; + + // Don't even count time for the keyboard unless it has requested it. + if(keyboard_needs_clock_) { + cycles_since_ikbd_update_ += length; + ikbd_.run_for(cycles_since_ikbd_update_.divide(HalfCycles(512))); + } + + // Flush anything that needs real-time updating. + if(!may_defer_acias_) { keyboard_acia_.flush(); midi_acia_.flush(); - - if(outputs & Output::Video) { - video_.flush(); - } - if(outputs & Output::Audio) { - update_audio(); - audio_queue_.perform(); - } } - private: - forceinline void advance_time(HalfCycles length) { - // Advance the relevant counters. - cycles_since_audio_update_ += length; - mfp_ += length; - if(dma_clocking_preference_ != ClockingHint::Preference::None) - dma_ += length; - keyboard_acia_ += length; - midi_acia_ += length; - bus_phase_ += length; - - // Don't even count time for the keyboard unless it has requested it. - if(keyboard_needs_clock_) { - cycles_since_ikbd_update_ += length; - ikbd_.run_for(cycles_since_ikbd_update_.divide(HalfCycles(512))); - } - - // Flush anything that needs real-time updating. - if(!may_defer_acias_) { - keyboard_acia_.flush(); - midi_acia_.flush(); - } - - if(mfp_is_realtime_) { - mfp_.flush(); - } - - if(dma_clocking_preference_ == ClockingHint::Preference::RealTime) { - dma_.flush(); - } - - // Update the video output, checking whether a sequence point has been hit. - if(video_.will_flush(length)) { - length -= video_.cycles_until_implicit_flush(); - video_ += video_.cycles_until_implicit_flush(); - - mfp_->set_timer_event_input<1>(video_->display_enabled()); - update_interrupt_input(); - } - - video_ += length; + if(mfp_is_realtime_) { + mfp_.flush(); } - void update_audio() { - speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4))); + if(dma_clocking_preference_ == ClockingHint::Preference::RealTime) { + dma_.flush(); } - CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_; - HalfCycles bus_phase_; + // Update the video output, checking whether a sequence point has been hit. + if(video_.will_flush(length)) { + length -= video_.cycles_until_implicit_flush(); + video_ += video_.cycles_until_implicit_flush(); - JustInTimeActor<Video> video_; - - // The MFP runs at 819200/2673749ths of the CPU clock rate. - JustInTimeActor<Motorola::MFP68901::MFP68901, HalfCycles, 819200, 2673749> mfp_; - JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> keyboard_acia_; - JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> midi_acia_; - - Concurrency::AsyncTaskQueue<false> audio_queue_; - GI::AY38910::AY38910<false> ay_; - Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<false>> speaker_; - HalfCycles cycles_since_audio_update_; - - JustInTimeActor<DMAController> dma_; - - HalfCycles cycles_since_ikbd_update_; - IntelligentKeyboard ikbd_; - - std::vector<uint8_t> ram_; - std::vector<uint8_t> rom_; - uint32_t rom_start_ = 0; - - enum class BusDevice { - /// Allows reads and writes to ram_. - RAM, - /// Nothing is mapped to this area, and it also doesn't trigger an exception upon access. - Floating, - /// Allows reading from rom_; writes do nothing. - ROM, - /// Allows interaction with a cartrige_. - Cartridge, - /// Marks the IO page, in which finer decoding will occur. - IO, - /// An unassigned page has nothing below it, in a way that triggers exceptions. - Unassigned - }; - BusDevice memory_map_[256]; - - // MARK: - Clocking Management. - bool may_defer_acias_ = true; - bool keyboard_needs_clock_ = false; - bool mfp_is_realtime_ = false; - ClockingHint::Preference dma_clocking_preference_ = ClockingHint::Preference::None; - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { - // This is being called by one of the components; avoid any time flushing here as that's - // already dealt with (and, just to be absolutely sure, to avoid recursive mania). - may_defer_acias_ = - (keyboard_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime) && - (midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime); - keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None; - mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime; - dma_clocking_preference_ = dma_.last_valid()->preferred_clocking(); - } - - // MARK: - GPIP input. - void acia6850_did_change_interrupt_status(Motorola::ACIA::ACIA *) final { - set_gpip_input(); - } - void dma_controller_did_change_output(DMAController *) final { - set_gpip_input(); - - // Filty hack, here! Should: set the 68000's bus request line. But until - // that's implemented, just offers magical zero-cost DMA insertion and - // extrication. - if(dma_->get_bus_request_line()) { - dma_->bus_grant(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size() >> 1); - } - } - void set_gpip_input() { - /* - Atari ST GPIP bits: - - GPIP 7: monochrome monitor detect - GPIP 6: RS-232 ring indicator - GPIP 5: FD/HD interrupt - GPIP 4: keyboard/MIDI interrupt - GPIP 3: unused - GPIP 2: RS-232 clear to send - GPIP 1: RS-232 carrier detect - GPIP 0: centronics busy - */ - mfp_->set_port_input( - 0x80 | // b7: Monochrome monitor detect (0 = is monochrome). - 0x40 | // b6: RS-232 ring indicator. - (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). - ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). - 0x08 | // b3: Unused - 0x04 | // b2: RS-232 clear to send. - 0x02 | // b1 : RS-232 carrier detect. - 0x00 // b0: Centronics busy (1 = busy). - ); - } - - // MARK - MFP input. - void mfp68901_did_change_interrupt_status(Motorola::MFP68901::MFP68901 *) final { + mfp_->set_timer_event_input<1>(video_->display_enabled()); update_interrupt_input(); } - int video_interrupts_pending_ = 0; - bool previous_hsync_ = false, previous_vsync_ = false; - void update_interrupt_input() { - // Complete guess: set video interrupts pending if/when hsync of vsync - // go inactive. Reset upon IACK. - const bool hsync = video_.last_valid()->hsync(); - const bool vsync = video_.last_valid()->vsync(); - if(previous_hsync_ != hsync && previous_hsync_) { - video_interrupts_pending_ |= 2; - } - if(previous_vsync_ != vsync && previous_vsync_) { - video_interrupts_pending_ |= 4; - } - previous_vsync_ = vsync; - previous_hsync_ = hsync; + video_ += length; + } - if(mfp_->get_interrupt_line()) { - mc68000_.set_interrupt_level(6); - } else if(video_interrupts_pending_ & 4) { - mc68000_.set_interrupt_level(4); - } else if(video_interrupts_pending_ & 2) { - mc68000_.set_interrupt_level(2); - } else { - mc68000_.set_interrupt_level(0); - } - } + void update_audio() { + speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4))); + } - // MARK: - MouseMachine - Inputs::Mouse &get_mouse() final { - return ikbd_; - } + CPU::MC68000::Processor<ConcreteMachine, true, true> mc68000_; + HalfCycles bus_phase_; - // MARK: - KeyboardMachine - void set_key_state(uint16_t key, bool is_pressed) final { - ikbd_.set_key_state(Key(key), is_pressed); - } + JustInTimeActor<Video> video_; - IntelligentKeyboard::KeyboardMapper keyboard_mapper_; - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; - } + // The MFP runs at 819200/2673749ths of the CPU clock rate. + JustInTimeActor<Motorola::MFP68901::MFP68901, HalfCycles, 819200, 2673749> mfp_; + JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> keyboard_acia_; + JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> midi_acia_; - // MARK: - JoystickMachine - const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final { - return ikbd_.get_joysticks(); - } + Concurrency::AsyncTaskQueue<false> audio_queue_; + GI::AY38910::AY38910<false> ay_; + Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<false>> speaker_; + HalfCycles cycles_since_audio_update_; - // MARK: - AYPortHandler - void set_port_output(bool port_b, uint8_t value) final { - if(port_b) { - // TODO: ? - } else { - /* - Port A: - b7: reserved - b6: "freely usable output (monitor jack)" - b5: centronics strobe - b4: RS-232 DTR output - b3: RS-232 RTS output - b2: select floppy drive 1 - b1: select floppy drive 0 - b0: "page choice signal for double-sided floppy drive" - */ - dma_->set_floppy_drive_selection(!(value & 2), !(value & 4), !(value & 1)); - } - } + JustInTimeActor<DMAController> dma_; - // MARK: - MediaTarget - bool insert_media(const Analyser::Static::Media &media) final { - size_t c = 0; - for(const auto &disk: media.disks) { - dma_->set_floppy_disk(disk, c); - ++c; - if(c == 2) break; - } - return true; - } + HalfCycles cycles_since_ikbd_update_; + IntelligentKeyboard ikbd_; - // MARK: - Activity Source - void set_activity_observer(Activity::Observer *observer) final { - dma_->set_activity_observer(observer); - } + std::vector<uint8_t> ram_; + std::vector<uint8_t> rom_; + uint32_t rom_start_ = 0; - // MARK: - Video Range - Video::Range video_range_; - void video_did_change_access_range(Video *video) final { - video_range_ = video->get_memory_access_range(); - } + enum class BusDevice { + /// Allows reads and writes to ram_. + RAM, + /// Nothing is mapped to this area, and it also doesn't trigger an exception upon access. + Floating, + /// Allows reading from rom_; writes do nothing. + ROM, + /// Allows interaction with a cartrige_. + Cartridge, + /// Marks the IO page, in which finer decoding will occur. + IO, + /// An unassigned page has nothing below it, in a way that triggers exceptions. + Unassigned + }; + BusDevice memory_map_[256]; - // MARK: - Configuration options. - std::unique_ptr<Reflection::Struct> get_options() const final { - auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); - options->output = get_video_signal_configurable(); - return options; - } + // MARK: - Clocking Management. + bool may_defer_acias_ = true; + bool keyboard_needs_clock_ = false; + bool mfp_is_realtime_ = false; + ClockingHint::Preference dma_clocking_preference_ = ClockingHint::Preference::None; + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { + // This is being called by one of the components; avoid any time flushing here as that's + // already dealt with (and, just to be absolutely sure, to avoid recursive mania). + may_defer_acias_ = + (keyboard_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime) && + (midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime); + keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None; + mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime; + dma_clocking_preference_ = dma_.last_valid()->preferred_clocking(); + } - void set_options(const std::unique_ptr<Reflection::Struct> &str) final { - const auto options = dynamic_cast<Options *>(str.get()); - set_video_signal_configurable(options->output); + // MARK: - GPIP input. + void acia6850_did_change_interrupt_status(Motorola::ACIA::ACIA *) final { + set_gpip_input(); + } + void dma_controller_did_change_output(DMAController *) final { + set_gpip_input(); + + // Filty hack, here! Should: set the 68000's bus request line. But until + // that's implemented, just offers magical zero-cost DMA insertion and + // extrication. + if(dma_->get_bus_request_line()) { + dma_->bus_grant(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size() >> 1); } + } + void set_gpip_input() { + /* + Atari ST GPIP bits: + + GPIP 7: monochrome monitor detect + GPIP 6: RS-232 ring indicator + GPIP 5: FD/HD interrupt + GPIP 4: keyboard/MIDI interrupt + GPIP 3: unused + GPIP 2: RS-232 clear to send + GPIP 1: RS-232 carrier detect + GPIP 0: centronics busy + */ + mfp_->set_port_input( + 0x80 | // b7: Monochrome monitor detect (0 = is monochrome). + 0x40 | // b6: RS-232 ring indicator. + (dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested). + ((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested). + 0x08 | // b3: Unused + 0x04 | // b2: RS-232 clear to send. + 0x02 | // b1 : RS-232 carrier detect. + 0x00 // b0: Centronics busy (1 = busy). + ); + } + + // MARK - MFP input. + void mfp68901_did_change_interrupt_status(Motorola::MFP68901::MFP68901 *) final { + update_interrupt_input(); + } + + int video_interrupts_pending_ = 0; + bool previous_hsync_ = false, previous_vsync_ = false; + void update_interrupt_input() { + // Complete guess: set video interrupts pending if/when hsync of vsync + // go inactive. Reset upon IACK. + const bool hsync = video_.last_valid()->hsync(); + const bool vsync = video_.last_valid()->vsync(); + if(previous_hsync_ != hsync && previous_hsync_) { + video_interrupts_pending_ |= 2; + } + if(previous_vsync_ != vsync && previous_vsync_) { + video_interrupts_pending_ |= 4; + } + previous_vsync_ = vsync; + previous_hsync_ = hsync; + + if(mfp_->get_interrupt_line()) { + mc68000_.set_interrupt_level(6); + } else if(video_interrupts_pending_ & 4) { + mc68000_.set_interrupt_level(4); + } else if(video_interrupts_pending_ & 2) { + mc68000_.set_interrupt_level(2); + } else { + mc68000_.set_interrupt_level(0); + } + } + + // MARK: - MouseMachine + Inputs::Mouse &get_mouse() final { + return ikbd_; + } + + // MARK: - KeyboardMachine + void set_key_state(uint16_t key, bool is_pressed) final { + ikbd_.set_key_state(Key(key), is_pressed); + } + + IntelligentKeyboard::KeyboardMapper keyboard_mapper_; + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + + // MARK: - JoystickMachine + const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final { + return ikbd_.get_joysticks(); + } + + // MARK: - AYPortHandler + void set_port_output(bool port_b, uint8_t value) final { + if(port_b) { + // TODO: ? + } else { + /* + Port A: + b7: reserved + b6: "freely usable output (monitor jack)" + b5: centronics strobe + b4: RS-232 DTR output + b3: RS-232 RTS output + b2: select floppy drive 1 + b1: select floppy drive 0 + b0: "page choice signal for double-sided floppy drive" + */ + dma_->set_floppy_drive_selection(!(value & 2), !(value & 4), !(value & 1)); + } + } + + // MARK: - MediaTarget + bool insert_media(const Analyser::Static::Media &media) final { + size_t c = 0; + for(const auto &disk: media.disks) { + dma_->set_floppy_disk(disk, c); + ++c; + if(c == 2) break; + } + return true; + } + + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) final { + dma_->set_activity_observer(observer); + } + + // MARK: - Video Range + Video::Range video_range_; + void video_did_change_access_range(Video *video) final { + video_range_ = video->get_memory_access_range(); + } + + // MARK: - Configuration options. + std::unique_ptr<Reflection::Struct> get_options() const final { + auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; + } + + void set_options(const std::unique_ptr<Reflection::Struct> &str) final { + const auto options = dynamic_cast<Options *>(str.get()); + set_video_signal_configurable(options->output); + } }; } diff --git a/Machines/Atari/ST/DMAController.hpp b/Machines/Atari/ST/DMAController.hpp index ab4280595..5501d89ea 100644 --- a/Machines/Atari/ST/DMAController.hpp +++ b/Machines/Atari/ST/DMAController.hpp @@ -19,93 +19,93 @@ namespace Atari::ST { class DMAController: public WD::WD1770::Delegate, public ClockingHint::Source, public ClockingHint::Observer { - public: - DMAController(); +public: + DMAController(); - uint16_t read(int address); - void write(int address, uint16_t value); - void run_for(HalfCycles duration); + uint16_t read(int address); + void write(int address, uint16_t value); + void run_for(HalfCycles duration); - bool get_interrupt_line(); - bool get_bus_request_line(); + bool get_interrupt_line(); + bool get_bus_request_line(); - /*! - Indicates that the DMA controller has been granted bus access to the block of memory at @c ram, which - is of size @c size. + /*! + Indicates that the DMA controller has been granted bus access to the block of memory at @c ram, which + is of size @c size. - @returns The number of words read or written. - */ - int bus_grant(uint16_t *ram, size_t size); + @returns The number of words read or written. + */ + int bus_grant(uint16_t *ram, size_t size); - void set_floppy_drive_selection(bool drive1, bool drive2, bool side2); - void set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive); + void set_floppy_drive_selection(bool drive1, bool drive2, bool side2); + void set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive); - struct Delegate { - virtual void dma_controller_did_change_output(DMAController *) = 0; - }; - void set_delegate(Delegate *delegate); + struct Delegate { + virtual void dma_controller_did_change_output(DMAController *) = 0; + }; + void set_delegate(Delegate *delegate); - void set_activity_observer(Activity::Observer *observer); + void set_activity_observer(Activity::Observer *observer); - // ClockingHint::Source. - ClockingHint::Preference preferred_clocking() const final; + // ClockingHint::Source. + ClockingHint::Preference preferred_clocking() const final; - private: - HalfCycles running_time_; - struct WD1772: public WD::WD1770 { - WD1772(): WD::WD1770(WD::WD1770::P1772) { - emplace_drives(2, 8000000, 300, 2); - set_is_double_density(true); // TODO: is this selectable on the ST? - } +private: + HalfCycles running_time_; + struct WD1772: public WD::WD1770 { + WD1772(): WD::WD1770(WD::WD1770::P1772) { + emplace_drives(2, 8000000, 300, 2); + set_is_double_density(true); // TODO: is this selectable on the ST? + } - void set_motor_on(bool motor_on) final { - for_all_drives([motor_on] (Storage::Disk::Drive &drive, size_t) { - drive.set_motor_on(motor_on); - }); - } + void set_motor_on(bool motor_on) final { + for_all_drives([motor_on] (Storage::Disk::Drive &drive, size_t) { + drive.set_motor_on(motor_on); + }); + } - void set_floppy_drive_selection(bool drive1, bool drive2, bool side2) { - set_drive( - (drive1 ? 1 : 0) | - (drive2 ? 2 : 0) - ); + void set_floppy_drive_selection(bool drive1, bool drive2, bool side2) { + set_drive( + (drive1 ? 1 : 0) | + (drive2 ? 2 : 0) + ); - for_all_drives([side2] (Storage::Disk::Drive &drive, size_t) { - drive.set_head(side2); - }); - } + for_all_drives([side2] (Storage::Disk::Drive &drive, size_t) { + drive.set_head(side2); + }); + } - void set_activity_observer(Activity::Observer *observer) { - get_drive(0).set_activity_observer(observer, "Internal", true); - get_drive(1).set_activity_observer(observer, "External", true); - } + void set_activity_observer(Activity::Observer *observer) { + get_drive(0).set_activity_observer(observer, "Internal", true); + get_drive(1).set_activity_observer(observer, "External", true); + } - void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) { - get_drive(drive).set_disk(disk); - } + void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) { + get_drive(drive).set_disk(disk); + } - } fdc_; + } fdc_; - void wd1770_did_change_output(WD::WD1770 *) final; + void wd1770_did_change_output(WD::WD1770 *) final; - uint16_t control_ = 0; + uint16_t control_ = 0; - Delegate *delegate_ = nullptr; - bool interrupt_line_ = false; - bool bus_request_line_ = false; + Delegate *delegate_ = nullptr; + bool interrupt_line_ = false; + bool bus_request_line_ = false; - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final; + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final; - // MARK: - DMA State. - struct Buffer { - uint8_t contents[16]; - bool is_full = false; - } buffer_[2]; - int active_buffer_ = 0; - int bytes_received_ = 0; - bool error_ = false; - int address_ = 0; - int byte_count_ = 0; + // MARK: - DMA State. + struct Buffer { + uint8_t contents[16]; + bool is_full = false; + } buffer_[2]; + int active_buffer_ = 0; + int bytes_received_ = 0; + bool error_ = false; + int address_ = 0; + int byte_count_ = 0; }; } diff --git a/Machines/Atari/ST/IntelligentKeyboard.hpp b/Machines/Atari/ST/IntelligentKeyboard.hpp index 54f9306ee..64ca0b18e 100644 --- a/Machines/Atari/ST/IntelligentKeyboard.hpp +++ b/Machines/Atari/ST/IntelligentKeyboard.hpp @@ -54,144 +54,144 @@ class IntelligentKeyboard: public Serial::Line<false>::ReadDelegate, public ClockingHint::Source, public Inputs::Mouse { +public: + IntelligentKeyboard(Serial::Line<false> &input, Serial::Line<false> &output); + ClockingHint::Preference preferred_clocking() const final; + void run_for(HalfCycles duration); + + void set_key_state(Key key, bool is_pressed); + class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final; + }; + + const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { + return joysticks_; + } + +private: + // MARK: - Key queue. + std::mutex key_queue_mutex_; + std::vector<uint8_t> key_queue_; + + // MARK: - Serial line state. + int bit_count_ = 0; + int command_ = 0; + Serial::Line<false> &output_line_; + + void output_bytes(std::initializer_list<uint8_t> value); + bool serial_line_did_produce_bit(Serial::Line<false> *, int bit) final; + + // MARK: - Command dispatch. + std::vector<uint8_t> command_sequence_; + void dispatch_command(uint8_t command); + + // MARK: - Flow control. + void reset(); + void resume(); + void pause(); + + // MARK: - Mouse. + void disable_mouse(); + void set_relative_mouse_position_reporting(); + void set_absolute_mouse_position_reporting(uint16_t max_x, uint16_t max_y); + void set_mouse_position(uint16_t x, uint16_t y); + void set_mouse_keycode_reporting(uint8_t delta_x, uint8_t delta_y); + void set_mouse_threshold(uint8_t x, uint8_t y); + void set_mouse_scale(uint8_t x, uint8_t y); + void set_mouse_y_downward(); + void set_mouse_y_upward(); + void set_mouse_button_actions(uint8_t actions); + void interrogate_mouse_position(); + + // Inputs::Mouse. + void move(int x, int y) final; + int get_number_of_buttons() const final; + void set_button_pressed(int index, bool is_pressed) final; + void reset_all_buttons() final; + + enum class MouseMode { + Relative, Absolute, Disabled + } mouse_mode_ = MouseMode::Relative; + + // Absolute positioning state. + int mouse_range_[2] = {320, 200}; + int mouse_scale_[2] = {1, 1}; + int mouse_position_[2] = {0, 0}; + int mouse_y_multiplier_ = 1; + + // Relative positioning state. + int posted_button_state_ = 0; + int mouse_threshold_[2] = {1, 1}; + void post_relative_mouse_event(int x, int y); + + // Received mouse state. + std::atomic<int> mouse_movement_[2]{0, 0}; + std::atomic<int> mouse_button_state_{0}; + std::atomic<int> mouse_button_events_{0}; + + // MARK: - Joystick. + void disable_joysticks(); + void set_joystick_event_mode(); + void set_joystick_interrogation_mode(); + void set_joystick_monitoring_mode(uint8_t rate); + void set_joystick_fire_button_monitoring_mode(); + struct VelocityThreshold { + uint8_t threshold; + uint8_t prior_rate; + uint8_t post_rate; + }; + void set_joystick_keycode_mode(VelocityThreshold horizontal, VelocityThreshold vertical); + void interrogate_joysticks(); + + void clear_joystick_events(); + + enum class JoystickMode { + Disabled, Event, Interrogation, KeyCode + } joystick_mode_ = JoystickMode::Event; + + class Joystick: public Inputs::ConcreteJoystick { public: - IntelligentKeyboard(Serial::Line<false> &input, Serial::Line<false> &output); - ClockingHint::Preference preferred_clocking() const final; - void run_for(HalfCycles duration); + Joystick() : + ConcreteJoystick({ + Input(Input::Up), + Input(Input::Down), + Input(Input::Left), + Input(Input::Right), + Input(Input::Fire, 0), + }) {} - void set_key_state(Key key, bool is_pressed); - class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { - uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final; - }; + void did_set_input(const Input &input, bool is_active) final { + uint8_t mask = 0; + switch(input.type) { + default: return; + case Input::Up: mask = 0x01; break; + case Input::Down: mask = 0x02; break; + case Input::Left: mask = 0x04; break; + case Input::Right: mask = 0x08; break; + case Input::Fire: mask = 0x80; break; + } - const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { - return joysticks_; + if(is_active) state_ |= mask; else state_ &= ~mask; + } + + uint8_t get_state() { + returned_state_ = state_; + return state_; + } + + bool has_event() { + return returned_state_ != state_; + } + + uint8_t event_mask() { + return returned_state_ ^ state_; } private: - // MARK: - Key queue. - std::mutex key_queue_mutex_; - std::vector<uint8_t> key_queue_; - - // MARK: - Serial line state. - int bit_count_ = 0; - int command_ = 0; - Serial::Line<false> &output_line_; - - void output_bytes(std::initializer_list<uint8_t> value); - bool serial_line_did_produce_bit(Serial::Line<false> *, int bit) final; - - // MARK: - Command dispatch. - std::vector<uint8_t> command_sequence_; - void dispatch_command(uint8_t command); - - // MARK: - Flow control. - void reset(); - void resume(); - void pause(); - - // MARK: - Mouse. - void disable_mouse(); - void set_relative_mouse_position_reporting(); - void set_absolute_mouse_position_reporting(uint16_t max_x, uint16_t max_y); - void set_mouse_position(uint16_t x, uint16_t y); - void set_mouse_keycode_reporting(uint8_t delta_x, uint8_t delta_y); - void set_mouse_threshold(uint8_t x, uint8_t y); - void set_mouse_scale(uint8_t x, uint8_t y); - void set_mouse_y_downward(); - void set_mouse_y_upward(); - void set_mouse_button_actions(uint8_t actions); - void interrogate_mouse_position(); - - // Inputs::Mouse. - void move(int x, int y) final; - int get_number_of_buttons() const final; - void set_button_pressed(int index, bool is_pressed) final; - void reset_all_buttons() final; - - enum class MouseMode { - Relative, Absolute, Disabled - } mouse_mode_ = MouseMode::Relative; - - // Absolute positioning state. - int mouse_range_[2] = {320, 200}; - int mouse_scale_[2] = {1, 1}; - int mouse_position_[2] = {0, 0}; - int mouse_y_multiplier_ = 1; - - // Relative positioning state. - int posted_button_state_ = 0; - int mouse_threshold_[2] = {1, 1}; - void post_relative_mouse_event(int x, int y); - - // Received mouse state. - std::atomic<int> mouse_movement_[2]{0, 0}; - std::atomic<int> mouse_button_state_{0}; - std::atomic<int> mouse_button_events_{0}; - - // MARK: - Joystick. - void disable_joysticks(); - void set_joystick_event_mode(); - void set_joystick_interrogation_mode(); - void set_joystick_monitoring_mode(uint8_t rate); - void set_joystick_fire_button_monitoring_mode(); - struct VelocityThreshold { - uint8_t threshold; - uint8_t prior_rate; - uint8_t post_rate; - }; - void set_joystick_keycode_mode(VelocityThreshold horizontal, VelocityThreshold vertical); - void interrogate_joysticks(); - - void clear_joystick_events(); - - enum class JoystickMode { - Disabled, Event, Interrogation, KeyCode - } joystick_mode_ = JoystickMode::Event; - - class Joystick: public Inputs::ConcreteJoystick { - public: - Joystick() : - ConcreteJoystick({ - Input(Input::Up), - Input(Input::Down), - Input(Input::Left), - Input(Input::Right), - Input(Input::Fire, 0), - }) {} - - void did_set_input(const Input &input, bool is_active) final { - uint8_t mask = 0; - switch(input.type) { - default: return; - case Input::Up: mask = 0x01; break; - case Input::Down: mask = 0x02; break; - case Input::Left: mask = 0x04; break; - case Input::Right: mask = 0x08; break; - case Input::Fire: mask = 0x80; break; - } - - if(is_active) state_ |= mask; else state_ &= ~mask; - } - - uint8_t get_state() { - returned_state_ = state_; - return state_; - } - - bool has_event() { - return returned_state_ != state_; - } - - uint8_t event_mask() { - return returned_state_ ^ state_; - } - - private: - uint8_t state_ = 0x00; - uint8_t returned_state_ = 0x00; - }; - std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; + uint8_t state_ = 0x00; + uint8_t returned_state_ = 0x00; + }; + std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; }; } diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index dc3184073..ed1a6894e 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -31,220 +31,220 @@ struct LineLength { (hopefully) to a subsystem. */ class Video { +public: + Video(); + + /*! + Sets the memory pool that provides video, and its size in words. + */ + void set_ram(uint16_t *, size_t size); + + /*! + Sets the target device for video data. + */ + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const; + + /*! + Sets the type of output. + */ + void set_display_type(Outputs::Display::DisplayType); + + /*! + Gets the type of output. + */ + Outputs::Display::DisplayType get_display_type() const; + + /*! + Produces the next @c duration period of pixels. + */ + void run_for(HalfCycles duration); + + + /*! + @returns the number of cycles until there is next a change in the hsync, + vsync or display_enable outputs. + */ + HalfCycles next_sequence_point(); + + /*! + @returns @c true if the horizontal sync output is currently active; @c false otherwise. + + @discussion On an Atari ST, this generates a VPA-style interrupt, which is often erroneously + documented as being triggered by horizontal blank. + */ + bool hsync(); + + /*! + @returns @c true if the vertical sync output is currently active; @c false otherwise. + + @discussion On an Atari ST, this generates a VPA-style interrupt, which is often erroneously + documented as being triggered by vertical blank. + */ + bool vsync(); + + /*! + @returns @c true if the display enabled output is currently active; @c false otherwise. + + @discussion On an Atari ST this is fed to the MFP. The documentation that I've been able to + find implies a total 28-cycle delay between the real delay enabled signal changing and its effect + on the 68000 interrupt input via the MFP. As I have yet to determine how much delay is caused + by the MFP a full 28-cycle delay is applied by this class. This should be dialled down when the + MFP's responsibility is clarified. + */ + bool display_enabled(); + + /// @returns the effect of reading from @c address; only the low 6 bits are decoded. + uint16_t read(int address); + + /// Writes @c value to @c address, of which only the low 6 bits are decoded. + void write(int address, uint16_t value); + + /// Used internally to track state. + enum class FieldFrequency { + Fifty = 0, Sixty = 1, SeventyTwo = 2 + }; + + struct RangeObserver { + /// Indicates to the observer that the memory access range has changed. + virtual void video_did_change_access_range(Video *) = 0; + }; + + /// Sets a range observer, which is an actor that will be notified if the memory access range changes. + void set_range_observer(RangeObserver *); + + struct Range { + uint32_t low_address, high_address; + }; + /*! + @returns the range of addresses that the video might read from. + */ + Range get_memory_access_range(); + +private: + DeferredQueue<HalfCycles> deferrer_; + + Outputs::CRT::CRT crt_; + RangeObserver *range_observer_ = nullptr; + + uint16_t raw_palette_[16]; + uint16_t palette_[16]; + int base_address_ = 0; + int previous_base_address_ = 0; + int current_address_ = 0; + + uint16_t *ram_ = nullptr; + int ram_mask_ = 0; + + int x_ = 0, y_ = 0, next_y_ = 0; + bool load_ = false; + int load_base_ = 0; + + uint16_t video_mode_ = 0; + uint16_t sync_mode_ = 0; + + FieldFrequency field_frequency_ = FieldFrequency::Fifty; + enum class OutputBpp { + One, Two, Four + } output_bpp_ = OutputBpp::Four; + void update_output_mode(); + + struct HorizontalState { + bool enable = false; + bool blank = false; + bool sync = false; + } horizontal_; + struct VerticalState { + bool enable = false; + bool blank = false; + + enum class SyncSchedule { + /// No sync events this line. + None, + /// Sync should begin during this horizontal line. + Begin, + /// Sync should end during this horizontal line. + End, + } sync_schedule = SyncSchedule::None; + bool sync = false; + } vertical_, next_vertical_; + LineLength line_length_; + + int data_latch_position_ = 0; + int data_latch_read_position_ = 0; + uint16_t data_latch_[128]; + void push_latched_data(); + + void reset_fifo(); + + /*! + Provides a target for control over the output video stream, which is considered to be + a permanently shifting shifter, that you need to reload when appropriate, which can be + overridden by the blank and sync levels. + + This stream will automatically insert a colour burst. + */ + class VideoStream { public: - Video(); + VideoStream(Outputs::CRT::CRT &crt, uint16_t *palette) : crt_(crt), palette_(palette) {} - /*! - Sets the memory pool that provides video, and its size in words. - */ - void set_ram(uint16_t *, size_t size); - - /*! - Sets the target device for video data. - */ - void set_scan_target(Outputs::Display::ScanTarget *scan_target); - - /// Gets the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const; - - /*! - Sets the type of output. - */ - void set_display_type(Outputs::Display::DisplayType); - - /*! - Gets the type of output. - */ - Outputs::Display::DisplayType get_display_type() const; - - /*! - Produces the next @c duration period of pixels. - */ - void run_for(HalfCycles duration); - - - /*! - @returns the number of cycles until there is next a change in the hsync, - vsync or display_enable outputs. - */ - HalfCycles next_sequence_point(); - - /*! - @returns @c true if the horizontal sync output is currently active; @c false otherwise. - - @discussion On an Atari ST, this generates a VPA-style interrupt, which is often erroneously - documented as being triggered by horizontal blank. - */ - bool hsync(); - - /*! - @returns @c true if the vertical sync output is currently active; @c false otherwise. - - @discussion On an Atari ST, this generates a VPA-style interrupt, which is often erroneously - documented as being triggered by vertical blank. - */ - bool vsync(); - - /*! - @returns @c true if the display enabled output is currently active; @c false otherwise. - - @discussion On an Atari ST this is fed to the MFP. The documentation that I've been able to - find implies a total 28-cycle delay between the real delay enabled signal changing and its effect - on the 68000 interrupt input via the MFP. As I have yet to determine how much delay is caused - by the MFP a full 28-cycle delay is applied by this class. This should be dialled down when the - MFP's responsibility is clarified. - */ - bool display_enabled(); - - /// @returns the effect of reading from @c address; only the low 6 bits are decoded. - uint16_t read(int address); - - /// Writes @c value to @c address, of which only the low 6 bits are decoded. - void write(int address, uint16_t value); - - /// Used internally to track state. - enum class FieldFrequency { - Fifty = 0, Sixty = 1, SeventyTwo = 2 + enum class OutputMode { + Sync, Blank, ColourBurst, Pixels, }; - struct RangeObserver { - /// Indicates to the observer that the memory access range has changed. - virtual void video_did_change_access_range(Video *) = 0; - }; + /// Sets the current data format for the shifter. Changes in output BPP flush the shifter. + void set_bpp(OutputBpp bpp); - /// Sets a range observer, which is an actor that will be notified if the memory access range changes. - void set_range_observer(RangeObserver *); + /// Outputs signal of type @c mode for @c duration. + void output(int duration, OutputMode mode); - struct Range { - uint32_t low_address, high_address; - }; - /*! - @returns the range of addresses that the video might read from. - */ - Range get_memory_access_range(); + /// Warns the video stream that the border colour, included in the palette that it holds a pointer to, + /// will change momentarily. This should be called after the relevant @c output() updates, and + /// is used to help elide border-regio output. + void will_change_border_colour(); + + /// Loads 64 bits into the Shifter. The shifter shifts continuously. If you also declare + /// a pixels region then whatever is being shifted will reach the display, in a form that + /// depends on the current output BPP. + void load(uint64_t value); private: - DeferredQueue<HalfCycles> deferrer_; + // The target CRT and the palette to use. + Outputs::CRT::CRT &crt_; + uint16_t *palette_ = nullptr; - Outputs::CRT::CRT crt_; - RangeObserver *range_observer_ = nullptr; + // Internal stateful processes. + void generate(int duration, OutputMode mode, bool is_terminal); - uint16_t raw_palette_[16]; - uint16_t palette_[16]; - int base_address_ = 0; - int previous_base_address_ = 0; - int current_address_ = 0; + void flush_border(); + void flush_pixels(); + void shift(int duration); + void output_pixels(int duration); - uint16_t *ram_ = nullptr; - int ram_mask_ = 0; + // Internal state that is a function of output intent. + int duration_ = 0; + OutputMode output_mode_ = OutputMode::Sync; + OutputBpp bpp_ = OutputBpp::Four; + union { + uint64_t output_shifter_; + uint32_t shifter_halves_[2]; + }; - int x_ = 0, y_ = 0, next_y_ = 0; - bool load_ = false; - int load_base_ = 0; + // Internal state for handling output serialisation. + uint16_t *pixel_buffer_ = nullptr; + int pixel_pointer_ = 0; + } video_stream_; - uint16_t video_mode_ = 0; - uint16_t sync_mode_ = 0; + /// Contains copies of the various observeable fields, after the relevant propagation delay. + struct PublicState { + bool display_enable = false; + bool hsync = false; + bool vsync = false; + } public_state_; - FieldFrequency field_frequency_ = FieldFrequency::Fifty; - enum class OutputBpp { - One, Two, Four - } output_bpp_ = OutputBpp::Four; - void update_output_mode(); - - struct HorizontalState { - bool enable = false; - bool blank = false; - bool sync = false; - } horizontal_; - struct VerticalState { - bool enable = false; - bool blank = false; - - enum class SyncSchedule { - /// No sync events this line. - None, - /// Sync should begin during this horizontal line. - Begin, - /// Sync should end during this horizontal line. - End, - } sync_schedule = SyncSchedule::None; - bool sync = false; - } vertical_, next_vertical_; - LineLength line_length_; - - int data_latch_position_ = 0; - int data_latch_read_position_ = 0; - uint16_t data_latch_[128]; - void push_latched_data(); - - void reset_fifo(); - - /*! - Provides a target for control over the output video stream, which is considered to be - a permanently shifting shifter, that you need to reload when appropriate, which can be - overridden by the blank and sync levels. - - This stream will automatically insert a colour burst. - */ - class VideoStream { - public: - VideoStream(Outputs::CRT::CRT &crt, uint16_t *palette) : crt_(crt), palette_(palette) {} - - enum class OutputMode { - Sync, Blank, ColourBurst, Pixels, - }; - - /// Sets the current data format for the shifter. Changes in output BPP flush the shifter. - void set_bpp(OutputBpp bpp); - - /// Outputs signal of type @c mode for @c duration. - void output(int duration, OutputMode mode); - - /// Warns the video stream that the border colour, included in the palette that it holds a pointer to, - /// will change momentarily. This should be called after the relevant @c output() updates, and - /// is used to help elide border-regio output. - void will_change_border_colour(); - - /// Loads 64 bits into the Shifter. The shifter shifts continuously. If you also declare - /// a pixels region then whatever is being shifted will reach the display, in a form that - /// depends on the current output BPP. - void load(uint64_t value); - - private: - // The target CRT and the palette to use. - Outputs::CRT::CRT &crt_; - uint16_t *palette_ = nullptr; - - // Internal stateful processes. - void generate(int duration, OutputMode mode, bool is_terminal); - - void flush_border(); - void flush_pixels(); - void shift(int duration); - void output_pixels(int duration); - - // Internal state that is a function of output intent. - int duration_ = 0; - OutputMode output_mode_ = OutputMode::Sync; - OutputBpp bpp_ = OutputBpp::Four; - union { - uint64_t output_shifter_; - uint32_t shifter_halves_[2]; - }; - - // Internal state for handling output serialisation. - uint16_t *pixel_buffer_ = nullptr; - int pixel_pointer_ = 0; - } video_stream_; - - /// Contains copies of the various observeable fields, after the relevant propagation delay. - struct PublicState { - bool display_enable = false; - bool hsync = false; - bool vsync = false; - } public_state_; - - friend class ::VideoTester; + friend class ::VideoTester; }; } diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index ecb9c879a..774afb0a5 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -38,25 +38,25 @@ namespace Commodore::C1540 { The attention input is also connected to CA1, similarly invertedl; the CA1 wire will be high when the bus is low and vice versa. */ class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { - public: - SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via); +public: + SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via); - uint8_t get_port_input(MOS::MOS6522::Port); + uint8_t get_port_input(MOS::MOS6522::Port); - void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask); - void set_serial_line_state(::Commodore::Serial::Line, bool); + void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask); + void set_serial_line_state(::Commodore::Serial::Line, bool); - void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &); + void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &); - private: - MOS::MOS6522::MOS6522<SerialPortVIA> &via_; - uint8_t port_b_ = 0x0; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; - bool attention_acknowledge_level_ = false; - bool attention_level_input_ = true; - bool data_level_output_ = false; +private: + MOS::MOS6522::MOS6522<SerialPortVIA> &via_; + uint8_t port_b_ = 0x0; + std::weak_ptr<::Commodore::Serial::Port> serial_port_; + bool attention_acknowledge_level_ = false; + bool attention_level_input_ = true; + bool data_level_output_ = false; - void update_data_line(); + void update_data_line(); }; /*! @@ -76,46 +76,46 @@ class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO. */ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { - public: - class Delegate { - public: - virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0; - virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0; - }; - void set_delegate(Delegate *); +public: + class Delegate { + public: + virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0; + virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0; + }; + void set_delegate(Delegate *); - uint8_t get_port_input(MOS::MOS6522::Port port); + uint8_t get_port_input(MOS::MOS6522::Port port); - void set_sync_detected(bool); - void set_data_input(uint8_t); - bool get_should_set_overflow(); - bool get_motor_enabled(); + void set_sync_detected(bool); + void set_data_input(uint8_t); + bool get_should_set_overflow(); + bool get_motor_enabled(); - void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value); + void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value); - void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask); + void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask); - void set_activity_observer(Activity::Observer *observer); + void set_activity_observer(Activity::Observer *observer); - private: - uint8_t port_b_ = 0xff, port_a_ = 0xff; - bool should_set_overflow_ = false; - bool drive_motor_ = false; - uint8_t previous_port_b_output_ = 0; - Delegate *delegate_ = nullptr; - Activity::Observer *observer_ = nullptr; +private: + uint8_t port_b_ = 0xff, port_a_ = 0xff; + bool should_set_overflow_ = false; + bool drive_motor_ = false; + uint8_t previous_port_b_output_ = 0; + Delegate *delegate_ = nullptr; + Activity::Observer *observer_ = nullptr; }; /*! An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA. */ class SerialPort : public ::Commodore::Serial::Port { - public: - void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel); - void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &); +public: + void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel); + void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &); - private: - std::weak_ptr<SerialPortVIA> serial_port_VIA_; +private: + std::weak_ptr<SerialPortVIA> serial_port_VIA_; }; class MachineBase: @@ -124,38 +124,38 @@ class MachineBase: public DriveVIA::Delegate, public Storage::Disk::Controller { - public: - MachineBase(Personality personality, const ROM::Map &roms); +public: + MachineBase(Personality personality, const ROM::Map &roms); - // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + // to satisfy CPU::MOS6502::Processor + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); - // to satisfy MOS::MOS6522::Delegate - virtual void mos6522_did_change_interrupt_status(void *mos6522); + // to satisfy MOS::MOS6522::Delegate + virtual void mos6522_did_change_interrupt_status(void *mos6522); - // to satisfy DriveVIA::Delegate - void drive_via_did_step_head(void *driveVIA, int direction); - void drive_via_did_set_data_density(void *driveVIA, int density); + // to satisfy DriveVIA::Delegate + void drive_via_did_step_head(void *driveVIA, int direction); + void drive_via_did_set_data_density(void *driveVIA, int density); - /// Attaches the activity observer to this C1540. - void set_activity_observer(Activity::Observer *observer); + /// Attaches the activity observer to this C1540. + void set_activity_observer(Activity::Observer *observer); - protected: - CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_; +protected: + CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_; - uint8_t ram_[0x800]; - uint8_t rom_[0x4000]; + uint8_t ram_[0x800]; + uint8_t rom_[0x4000]; - std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_; - std::shared_ptr<SerialPort> serial_port_; - DriveVIA drive_VIA_port_handler_; + std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_; + std::shared_ptr<SerialPort> serial_port_; + DriveVIA drive_VIA_port_handler_; - MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_; - MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_; + MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_; + MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_; - int shift_register_ = 0, bit_window_offset_; - virtual void process_input_bit(int value); - virtual void process_index_hole(); + int shift_register_ = 0, bit_window_offset_; + virtual void process_input_bit(int value); + virtual void process_index_hole(); }; } diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index 44d9022bc..a176ffc0e 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -209,17 +209,17 @@ struct Description { /// plus all the fields provided as @c flags . std::string description(int flags) const; - private: - template <typename FileNameT, typename CRC32T> Description( - Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0) - ) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} { - // Slightly lazy: deal with the case where the constructor wasn't provided with any - // CRCs by spotting that the set has exactly one member, which has value 0. The alternative - // would be to provide a partial specialisation that never put anything into the set. - if(this->crc32s.size() == 1 && !*this->crc32s.begin()) { - this->crc32s.clear(); - } +private: + template <typename FileNameT, typename CRC32T> Description( + Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0) + ) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} { + // Slightly lazy: deal with the case where the constructor wasn't provided with any + // CRCs by spotting that the set has exactly one member, which has value 0. The alternative + // would be to provide a partial specialisation that never put anything into the set. + if(this->crc32s.size() == 1 && !*this->crc32s.begin()) { + this->crc32s.clear(); } + } }; /// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM @@ -273,48 +273,48 @@ struct Request { /// portion of a sentence, e.g. "Please supply" + request.description(0, L'*'). std::wstring description(int description_flags, wchar_t bullet_point); - private: - struct Node { - enum class Type { - Any, All, One - }; - Type type = Type::One; - Name name = Name::None; - /// @c true if this ROM is optional for machine startup. Generally indicates something - /// that would make emulation more accurate, but not sufficiently so to make it - /// a necessity. - bool is_optional = false; - std::vector<Node> children; - - bool empty() const { - return type == Type::One && name == Name::None; - } - - void add_descriptions(std::vector<Description> &) const; - bool validate(Map &) const; - void visit( - const std::function<void(ListType, size_t)> &enter_list, - const std::function<void(void)> &exit_list, - const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item - ) const; - bool subtract(const ROM::Map &map); - void sort() { - // Don't do a full sort, but move anything optional to the back. - // This makes them print more nicely; it's a human-facing tweak only. - ssize_t index = ssize_t(children.size() - 1); - bool has_seen_non_optional = false; - while(index >= 0) { - has_seen_non_optional |= !children[size_t(index)].is_optional; - if(children[size_t(index)].is_optional && has_seen_non_optional) { - std::rotate(children.begin() + index, children.begin() + index + 1, children.end()); - } - --index; - } - } +private: + struct Node { + enum class Type { + Any, All, One }; - Node node; + Type type = Type::One; + Name name = Name::None; + /// @c true if this ROM is optional for machine startup. Generally indicates something + /// that would make emulation more accurate, but not sufficiently so to make it + /// a necessity. + bool is_optional = false; + std::vector<Node> children; - Request append(Node::Type type, const Request &rhs); + bool empty() const { + return type == Type::One && name == Name::None; + } + + void add_descriptions(std::vector<Description> &) const; + bool validate(Map &) const; + void visit( + const std::function<void(ListType, size_t)> &enter_list, + const std::function<void(void)> &exit_list, + const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item + ) const; + bool subtract(const ROM::Map &map); + void sort() { + // Don't do a full sort, but move anything optional to the back. + // This makes them print more nicely; it's a human-facing tweak only. + ssize_t index = ssize_t(children.size() - 1); + bool has_seen_non_optional = false; + while(index >= 0) { + has_seen_non_optional |= !children[size_t(index)].is_optional; + if(children[size_t(index)].is_optional && has_seen_non_optional) { + std::rotate(children.begin() + index, children.begin() + index + 1, children.end()); + } + --index; + } + } + }; + Node node; + + Request append(Node::Type type, const Request &rhs); }; } diff --git a/Machines/Utility/StringSerialiser.hpp b/Machines/Utility/StringSerialiser.hpp index 135b6020b..d0ee5522c 100644 --- a/Machines/Utility/StringSerialiser.hpp +++ b/Machines/Utility/StringSerialiser.hpp @@ -14,15 +14,15 @@ namespace Utility { class StringSerialiser { - public: - StringSerialiser(const std::string &source, bool use_linefeed_only = false); +public: + StringSerialiser(const std::string &source, bool use_linefeed_only = false); - uint8_t head(); - bool advance(); + uint8_t head(); + bool advance(); - private: - std::string input_string_; - std::size_t input_string_pointer_ = 0; +private: + std::string input_string_; + std::size_t input_string_pointer_ = 0; }; } diff --git a/Machines/Utility/TypedDynamicMachine.hpp b/Machines/Utility/TypedDynamicMachine.hpp index 933e03192..857e90bcf 100644 --- a/Machines/Utility/TypedDynamicMachine.hpp +++ b/Machines/Utility/TypedDynamicMachine.hpp @@ -15,55 +15,55 @@ namespace Machine { template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine { - public: - TypedDynamicMachine(std::unique_ptr<T> &&machine) : machine_(std::move(machine)) {} - T *get() { return machine_.get(); } +public: + TypedDynamicMachine(std::unique_ptr<T> &&machine) : machine_(std::move(machine)) {} + T *get() { return machine_.get(); } - TypedDynamicMachine() : TypedDynamicMachine(nullptr) {} - TypedDynamicMachine(TypedDynamicMachine &&rhs) : machine_(std::move(rhs.machine_)) {} - TypedDynamicMachine &operator=(TypedDynamicMachine &&rhs) { - machine_ = std::move(rhs.machine_); - return *this; - } + TypedDynamicMachine() : TypedDynamicMachine(nullptr) {} + TypedDynamicMachine(TypedDynamicMachine &&rhs) : machine_(std::move(rhs.machine_)) {} + TypedDynamicMachine &operator=(TypedDynamicMachine &&rhs) { + machine_ = std::move(rhs.machine_); + return *this; + } #define Provide(type, name) \ - type *name() final { \ - return get<type>(); \ - } + type *name() final { \ + return get<type>(); \ + } - Provide(Activity::Source, activity_source) - Provide(Configurable::Device, configurable_device) - Provide(MachineTypes::TimedMachine, timed_machine) - Provide(MachineTypes::ScanProducer, scan_producer) - Provide(MachineTypes::AudioProducer, audio_producer) - Provide(MachineTypes::JoystickMachine, joystick_machine) - Provide(MachineTypes::KeyboardMachine, keyboard_machine) - Provide(MachineTypes::MouseMachine, mouse_machine) - Provide(MachineTypes::MediaTarget, media_target) + Provide(Activity::Source, activity_source) + Provide(Configurable::Device, configurable_device) + Provide(MachineTypes::TimedMachine, timed_machine) + Provide(MachineTypes::ScanProducer, scan_producer) + Provide(MachineTypes::AudioProducer, audio_producer) + Provide(MachineTypes::JoystickMachine, joystick_machine) + Provide(MachineTypes::KeyboardMachine, keyboard_machine) + Provide(MachineTypes::MouseMachine, mouse_machine) + Provide(MachineTypes::MediaTarget, media_target) #undef Provide - void *raw_pointer() final { - return get(); - } + void *raw_pointer() final { + return get(); + } - private: - template <typename Class> Class *get() { - return dynamic_cast<Class *>(machine_.get()); +private: + template <typename Class> Class *get() { + return dynamic_cast<Class *>(machine_.get()); - // Note to self: the below is not [currently] used - // because in practice TypedDynamicMachine is instantiated - // with an abstract parent of the actual class. - // - // TODO: rethink type hiding here. I think I've boxed myself - // into an uncomfortable corner. + // Note to self: the below is not [currently] used + // because in practice TypedDynamicMachine is instantiated + // with an abstract parent of the actual class. + // + // TODO: rethink type hiding here. I think I've boxed myself + // into an uncomfortable corner. // if constexpr (std::is_base_of_v<Class, T>) { // return static_cast<Class *>(machine_.get()); // } else { // return nullptr; // } - } - std::unique_ptr<T> machine_; + } + std::unique_ptr<T> machine_; }; } diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index e0c6e0fdf..9aba38e1a 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -63,41 +63,41 @@ class CharacterMapper { fresh key transition is ready to be consumed. */ class Typer { - public: - class Delegate: public MachineTypes::KeyActions { - public: - /// Informs the delegate that this typer has reached the end of its content. - virtual void typer_reset(Typer *typer) = 0; - }; +public: + class Delegate: public MachineTypes::KeyActions { + public: + /// Informs the delegate that this typer has reached the end of its content. + virtual void typer_reset(Typer *typer) = 0; + }; - Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, CharacterMapper &character_mapper, Delegate *delegate); + Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, CharacterMapper &character_mapper, Delegate *delegate); - /// Advances for @c duration. - void run_for(const HalfCycles duration); + /// Advances for @c duration. + void run_for(const HalfCycles duration); - /// Types the next character now, if there is one. - /// @returns @c true if there was anything left to type; @c false otherwise. - bool type_next_character(); + /// Types the next character now, if there is one. + /// @returns @c true if there was anything left to type; @c false otherwise. + bool type_next_character(); - /// Adds the contents of @c str to the end of the current string. - void append(const std::string &str); + /// Adds the contents of @c str to the end of the current string. + void append(const std::string &str); - const char BeginString = 0x02; // i.e. ASCII start of text - const char EndString = 0x03; // i.e. ASCII end of text + const char BeginString = 0x02; // i.e. ASCII start of text + const char EndString = 0x03; // i.e. ASCII end of text - private: - std::string string_; - std::size_t string_pointer_ = 0; +private: + std::string string_; + std::size_t string_pointer_ = 0; - const HalfCycles frequency_; - HalfCycles counter_; - int phase_ = 0; + const HalfCycles frequency_; + HalfCycles counter_; + int phase_ = 0; - Delegate *const delegate_; - CharacterMapper &character_mapper_; + Delegate *const delegate_; + CharacterMapper &character_mapper_; - uint16_t try_type_next_character(); - const uint16_t *sequence_for_character(char) const; + uint16_t try_type_next_character(); + const uint16_t *sequence_for_character(char) const; }; /*! @@ -106,47 +106,47 @@ class Typer { */ template <typename CMapper> class TypeRecipient: public Typer::Delegate { - protected: - template <typename... Args> TypeRecipient(Args&&... args) : character_mapper(std::forward<Args>(args)...) {} +protected: + template <typename... Args> TypeRecipient(Args&&... args) : character_mapper(std::forward<Args>(args)...) {} - /// Attaches a typer to this class that will type @c string using @c character_mapper as a source. - void add_typer(const std::string &string) { - if(!typer_) { - typer_ = std::make_unique<Typer>(string, get_typer_delay(string), get_typer_frequency(), character_mapper, this); - } else { - typer_->append(string); - } + /// Attaches a typer to this class that will type @c string using @c character_mapper as a source. + void add_typer(const std::string &string) { + if(!typer_) { + typer_ = std::make_unique<Typer>(string, get_typer_delay(string), get_typer_frequency(), character_mapper, this); + } else { + typer_->append(string); } + } - /*! - @returns @c true if the character mapper provides a mapping for @c c; @c false otherwise. - */ - bool can_type(char c) const { - const auto sequence = character_mapper.sequence_for_character(c); - return sequence && sequence[0] != MachineTypes::MappedKeyboardMachine::KeyNotMapped; - } + /*! + @returns @c true if the character mapper provides a mapping for @c c; @c false otherwise. + */ + bool can_type(char c) const { + const auto sequence = character_mapper.sequence_for_character(c); + return sequence && sequence[0] != MachineTypes::MappedKeyboardMachine::KeyNotMapped; + } - /*! - Provided in order to conform to that part of the Typer::Delegate interface that goes above and - beyond KeyboardMachine::Machine; responds to the end of typing by clearing all keys. - */ - void typer_reset(Typer *) override { - clear_all_keys(); + /*! + Provided in order to conform to that part of the Typer::Delegate interface that goes above and + beyond KeyboardMachine::Machine; responds to the end of typing by clearing all keys. + */ + void typer_reset(Typer *) override { + clear_all_keys(); - // It's unsafe to deallocate typer right now, since it is the caller, but also it has a small - // memory footprint and it's desireable not to imply that the subclass need call it any more. - // So shuffle it off into a siding. - previous_typer_ = std::move(typer_); - typer_ = nullptr; - } + // It's unsafe to deallocate typer right now, since it is the caller, but also it has a small + // memory footprint and it's desireable not to imply that the subclass need call it any more. + // So shuffle it off into a siding. + previous_typer_ = std::move(typer_); + typer_ = nullptr; + } - virtual HalfCycles get_typer_delay(const std::string &) const { return HalfCycles(0); } - virtual HalfCycles get_typer_frequency() const { return HalfCycles(0); } - std::unique_ptr<Typer> typer_; + virtual HalfCycles get_typer_delay(const std::string &) const { return HalfCycles(0); } + virtual HalfCycles get_typer_frequency() const { return HalfCycles(0); } + std::unique_ptr<Typer> typer_; - private: - std::unique_ptr<Typer> previous_typer_; - CMapper character_mapper; +private: + std::unique_ptr<Typer> previous_typer_; + CMapper character_mapper; }; } diff --git a/Numeric/CRC.hpp b/Numeric/CRC.hpp index 9b99b5726..76fa186a9 100644 --- a/Numeric/CRC.hpp +++ b/Numeric/CRC.hpp @@ -26,81 +26,82 @@ constexpr uint8_t reverse_byte(uint8_t byte) { } /*! Provides a class capable of generating a CRC from source data. */ -template <typename IntType, IntType reset_value, IntType output_xor, bool reflect_input, bool reflect_output> class Generator { - public: - /*! - Instantiates a CRC16 that will compute the CRC16 specified by the supplied - @c polynomial and @c reset_value. - */ - constexpr Generator(IntType polynomial) noexcept: value_(reset_value) { - const IntType top_bit = IntType(~(IntType(~0) >> 1)); - for(int c = 0; c < 256; c++) { - IntType shift_value = IntType(c << multibyte_shift); - for(int b = 0; b < 8; b++) { - IntType exclusive_or = (shift_value&top_bit) ? polynomial : 0; - shift_value = IntType(shift_value << 1) ^ exclusive_or; - } - xor_table[c] = shift_value; +template <typename IntType, IntType reset_value, IntType output_xor, bool reflect_input, bool reflect_output> +class Generator { +public: + /*! + Instantiates a CRC16 that will compute the CRC16 specified by the supplied + @c polynomial and @c reset_value. + */ + constexpr Generator(IntType polynomial) noexcept: value_(reset_value) { + const IntType top_bit = IntType(~(IntType(~0) >> 1)); + for(int c = 0; c < 256; c++) { + IntType shift_value = IntType(c << multibyte_shift); + for(int b = 0; b < 8; b++) { + IntType exclusive_or = (shift_value&top_bit) ? polynomial : 0; + shift_value = IntType(shift_value << 1) ^ exclusive_or; } + xor_table[c] = shift_value; } + } - /// Resets the CRC to the reset value. - void reset() { value_ = reset_value; } + /// Resets the CRC to the reset value. + void reset() { value_ = reset_value; } - /// Updates the CRC to include @c byte. - void add(uint8_t byte) { - if constexpr (reflect_input) byte = reverse_byte(byte); - value_ = IntType((value_ << 8) ^ xor_table[(value_ >> multibyte_shift) ^ byte]); - } + /// Updates the CRC to include @c byte. + void add(uint8_t byte) { + if constexpr (reflect_input) byte = reverse_byte(byte); + value_ = IntType((value_ << 8) ^ xor_table[(value_ >> multibyte_shift) ^ byte]); + } - /// @returns The current value of the CRC. - inline IntType get_value() const { - IntType result = value_ ^ output_xor; - if constexpr (reflect_output) { - IntType reflected_output = 0; - for(std::size_t c = 0; c < sizeof(IntType); ++c) { - reflected_output = IntType(reflected_output << 8) | IntType(reverse_byte(result & 0xff)); - result >>= 8; - } - return reflected_output; + /// @returns The current value of the CRC. + inline IntType get_value() const { + IntType result = value_ ^ output_xor; + if constexpr (reflect_output) { + IntType reflected_output = 0; + for(std::size_t c = 0; c < sizeof(IntType); ++c) { + reflected_output = IntType(reflected_output << 8) | IntType(reverse_byte(result & 0xff)); + result >>= 8; } - return result; + return reflected_output; } + return result; + } - /// Sets the current value of the CRC. - inline void set_value(IntType value) { value_ = value; } + /// Sets the current value of the CRC. + inline void set_value(IntType value) { value_ = value; } - /*! - A compound for: + /*! + A compound for: - reset() - [add all data from @c data] - get_value() - */ - template <typename Collection> IntType compute_crc(const Collection &data) { - return compute_crc(data.begin(), data.end()); + reset() + [add all data from @c data] + get_value() + */ + template <typename Collection> IntType compute_crc(const Collection &data) { + return compute_crc(data.begin(), data.end()); + } + + /*! + A compound for: + + reset() + [add all data from @c begin to @c end] + get_value() + */ + template <typename Iterator> IntType compute_crc(Iterator begin, Iterator end) { + reset(); + while(begin != end) { + add(*begin); + ++begin; } + return get_value(); + } - /*! - A compound for: - - reset() - [add all data from @c begin to @c end] - get_value() - */ - template <typename Iterator> IntType compute_crc(Iterator begin, Iterator end) { - reset(); - while(begin != end) { - add(*begin); - ++begin; - } - return get_value(); - } - - private: - static constexpr int multibyte_shift = (sizeof(IntType) * 8) - 8; - IntType xor_table[256]; - IntType value_; +private: + static constexpr int multibyte_shift = (sizeof(IntType) * 8) - 8; + IntType xor_table[256]; + IntType value_; }; /*! diff --git a/Numeric/LFSR.hpp b/Numeric/LFSR.hpp index ae9019efe..117ae7dad 100644 --- a/Numeric/LFSR.hpp +++ b/Numeric/LFSR.hpp @@ -40,41 +40,41 @@ template <> struct LSFRPolynomial<uint8_t> { in the specified int type. */ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntType>::value> class LFSR { - public: - /*! - Constructs an LFSR with a random initial value. - */ - constexpr LFSR() noexcept { - // Randomise the value, ensuring it doesn't end up being 0; - // don't set any top bits, in case this is a signed type. - while(!value_) { - uint8_t *value_byte = reinterpret_cast<uint8_t *>(&value_); - for(size_t c = 0; c < sizeof(IntType); ++c) { - *value_byte = uint8_t(uint64_t(rand()) * 127 / RAND_MAX); - ++value_byte; - } +public: + /*! + Constructs an LFSR with a random initial value. + */ + constexpr LFSR() noexcept { + // Randomise the value, ensuring it doesn't end up being 0; + // don't set any top bits, in case this is a signed type. + while(!value_) { + uint8_t *value_byte = reinterpret_cast<uint8_t *>(&value_); + for(size_t c = 0; c < sizeof(IntType); ++c) { + *value_byte = uint8_t(uint64_t(rand()) * 127 / RAND_MAX); + ++value_byte; } } + } - /*! - Constructs an LFSR with the specified initial value. + /*! + Constructs an LFSR with the specified initial value. - An initial value of 0 is invalid. - */ - LFSR(IntType initial_value) : value_(initial_value) {} + An initial value of 0 is invalid. + */ + LFSR(IntType initial_value) : value_(initial_value) {} - /*! - Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0, - determining the bit that was just shifted out. - */ - IntType next() { - const auto result = IntType(value_ & 1); - value_ = IntType((value_ >> 1) ^ (result * polynomial)); - return result; - } + /*! + Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0, + determining the bit that was just shifted out. + */ + IntType next() { + const auto result = IntType(value_ & 1); + value_ = IntType((value_ >> 1) ^ (result * polynomial)); + return result; + } - private: - IntType value_ = 0; +private: + IntType value_ = 0; }; template <uint64_t polynomial> class LFSRv: public LFSR<typename MinIntTypeValue<polynomial>::type, polynomial> {}; diff --git a/Numeric/NumericCoder.hpp b/Numeric/NumericCoder.hpp index d33280310..6644a5ead 100644 --- a/Numeric/NumericCoder.hpp +++ b/Numeric/NumericCoder.hpp @@ -28,47 +28,47 @@ namespace Numeric { /// = 65 /// template <int... Sizes> class NumericCoder { - public: - /// Modifies @c target to hold @c value at @c index. - template <int index> static void encode(int &target, int value) { - static_assert(index < sizeof...(Sizes), "Index must be within range"); - NumericEncoder<Sizes...>::template encode<index>(target, value); - } +public: + /// Modifies @c target to hold @c value at @c index. + template <int index> static void encode(int &target, int value) { + static_assert(index < sizeof...(Sizes), "Index must be within range"); + NumericEncoder<Sizes...>::template encode<index>(target, value); + } - /// @returns The value from @c source at @c index. - template <int index> static int decode(int source) { - static_assert(index < sizeof...(Sizes), "Index must be within range"); - return NumericDecoder<Sizes...>::template decode<index>(source); - } + /// @returns The value from @c source at @c index. + template <int index> static int decode(int source) { + static_assert(index < sizeof...(Sizes), "Index must be within range"); + return NumericDecoder<Sizes...>::template decode<index>(source); + } - private: +private: - template <int size, int... Tail> - struct NumericEncoder { - template <int index, int i = 0, int divider = 1> static void encode(int &target, int value) { - if constexpr (i == index) { - const int suffix = target % divider; - target /= divider; - target -= target % size; - target += value % size; - target *= divider; - target += suffix; - } else { - NumericEncoder<Tail...>::template encode<index, i+1, divider*size>(target, value); - } + template <int size, int... Tail> + struct NumericEncoder { + template <int index, int i = 0, int divider = 1> static void encode(int &target, int value) { + if constexpr (i == index) { + const int suffix = target % divider; + target /= divider; + target -= target % size; + target += value % size; + target *= divider; + target += suffix; + } else { + NumericEncoder<Tail...>::template encode<index, i+1, divider*size>(target, value); } - }; + } + }; - template <int size, int... Tail> - struct NumericDecoder { - template <int index, int i = 0, int divider = 1> static int decode(int source) { - if constexpr (i == index) { - return (source / divider) % size; - } else { - return NumericDecoder<Tail...>::template decode<index, i+1, divider*size>(source); - } + template <int size, int... Tail> + struct NumericDecoder { + template <int index, int i = 0, int divider = 1> static int decode(int source) { + if constexpr (i == index) { + return (source / divider) % size; + } else { + return NumericDecoder<Tail...>::template decode<index, i+1, divider*size>(source); } - }; + } + }; }; } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index eb69fa3ff..a8204bab1 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -55,297 +55,297 @@ class Delegate { colour phase for colour composite video. */ class CRT { - private: - // The incoming clock lengths will be multiplied by @c time_multiplier_; this increases - // precision across the line. - int time_multiplier_ = 1; +private: + // The incoming clock lengths will be multiplied by @c time_multiplier_; this increases + // precision across the line. + int time_multiplier_ = 1; - // Two flywheels regulate scanning; the vertical will have a range much greater than the horizontal; - // the output divider is what that'll need to be divided by to reduce it into a 16-bit range as - // posted on to the scan target. - std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_; - int vertical_flywheel_output_divider_ = 1; - int cycles_since_horizontal_sync_ = 0; - Display::ScanTarget::Scan::EndPoint end_point(uint16_t data_offset); + // Two flywheels regulate scanning; the vertical will have a range much greater than the horizontal; + // the output divider is what that'll need to be divided by to reduce it into a 16-bit range as + // posted on to the scan target. + std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_; + int vertical_flywheel_output_divider_ = 1; + int cycles_since_horizontal_sync_ = 0; + Display::ScanTarget::Scan::EndPoint end_point(uint16_t data_offset); - struct Scan { - enum Type { - Sync, Level, Data, Blank, ColourBurst - } type = Scan::Blank; - int number_of_cycles = 0, number_of_samples = 0; - uint8_t phase = 0, amplitude = 0; - }; - void output_scan(const Scan *scan); + struct Scan { + enum Type { + Sync, Level, Data, Blank, ColourBurst + } type = Scan::Blank; + int number_of_cycles = 0, number_of_samples = 0; + uint8_t phase = 0, amplitude = 0; + }; + void output_scan(const Scan *scan); - uint8_t colour_burst_amplitude_ = 30; - int colour_burst_phase_adjustment_ = 0xff; + uint8_t colour_burst_amplitude_ = 30; + int colour_burst_phase_adjustment_ = 0xff; - int64_t phase_denominator_ = 1; - int64_t phase_numerator_ = 0; - int64_t colour_cycle_numerator_ = 1; - bool is_alternate_line_ = false, phase_alternates_ = false, should_be_alternate_line_ = false; + int64_t phase_denominator_ = 1; + int64_t phase_numerator_ = 0; + int64_t colour_cycle_numerator_ = 1; + bool is_alternate_line_ = false, phase_alternates_ = false, should_be_alternate_line_ = false; - void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples); - Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced); - Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced); + void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples); + Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced); + Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced); - Delegate *delegate_ = nullptr; - int frames_since_last_delegate_call_ = 0; + Delegate *delegate_ = nullptr; + int frames_since_last_delegate_call_ = 0; - bool is_receiving_sync_ = false; // @c true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync); @c false otherwise. - bool is_accumulating_sync_ = false; // @c true if a sync level has triggered the suspicion that a vertical sync might be in progress; @c false otherwise. - bool is_refusing_sync_ = false; // @c true once a vertical sync has been detected, until a prolonged period of non-sync has ended suspicion of an ongoing vertical sync. - int sync_capacitor_charge_threshold_ = 0; // Charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync. - int cycles_of_sync_ = 0; // The number of cycles since the potential vertical sync began. - int cycles_since_sync_ = 0; // The number of cycles since last in sync, for defeating the possibility of this being a vertical sync. + bool is_receiving_sync_ = false; // @c true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync); @c false otherwise. + bool is_accumulating_sync_ = false; // @c true if a sync level has triggered the suspicion that a vertical sync might be in progress; @c false otherwise. + bool is_refusing_sync_ = false; // @c true once a vertical sync has been detected, until a prolonged period of non-sync has ended suspicion of an ongoing vertical sync. + int sync_capacitor_charge_threshold_ = 0; // Charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync. + int cycles_of_sync_ = 0; // The number of cycles since the potential vertical sync began. + int cycles_since_sync_ = 0; // The number of cycles since last in sync, for defeating the possibility of this being a vertical sync. - int cycles_per_line_ = 1; + int cycles_per_line_ = 1; - Outputs::Display::ScanTarget *scan_target_ = &Outputs::Display::NullScanTarget::singleton; - Outputs::Display::ScanTarget::Modals scan_target_modals_; - static constexpr uint8_t DefaultAmplitude = 41; // Based upon a black level to maximum excursion and positive burst peak of: NTSC: 882 & 143; PAL: 933 & 150. + Outputs::Display::ScanTarget *scan_target_ = &Outputs::Display::NullScanTarget::singleton; + Outputs::Display::ScanTarget::Modals scan_target_modals_; + static constexpr uint8_t DefaultAmplitude = 41; // Based upon a black level to maximum excursion and positive burst peak of: NTSC: 882 & 143; PAL: 933 & 150. #ifndef NDEBUG - size_t allocated_data_length_ = std::numeric_limits<size_t>::min(); + size_t allocated_data_length_ = std::numeric_limits<size_t>::min(); #endif - public: - /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. - The requested number of buffers, each with the requested number of bytes per pixel, - is created for the machine to write raw pixel data to. +public: + /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. + The requested number of buffers, each with the requested number of bytes per pixel, + is created for the machine to write raw pixel data to. - @param cycles_per_line The clock rate at which this CRT will be driven, specified as the number - of cycles expected to take up one whole scanline of the display. + @param cycles_per_line The clock rate at which this CRT will be driven, specified as the number + of cycles expected to take up one whole scanline of the display. - @param clocks_per_pixel_greatest_common_divisor The GCD of all potential lengths of a pixel - in terms of the clock rate given as @c cycles_per_line. + @param clocks_per_pixel_greatest_common_divisor The GCD of all potential lengths of a pixel + in terms of the clock rate given as @c cycles_per_line. - @param height_of_display The number of lines that nominally form one field of the display, rounded - up to the next whole integer. + @param height_of_display The number of lines that nominally form one field of the display, rounded + up to the next whole integer. - @param colour_cycle_numerator Specifies the numerator for the per-line frequency of the colour subcarrier. + @param colour_cycle_numerator Specifies the numerator for the per-line frequency of the colour subcarrier. - @param colour_cycle_denominator Specifies the denominator for the per-line frequency of the colour subcarrier. - The colour subcarrier is taken to have colour_cycle_numerator/colour_cycle_denominator cycles per line. + @param colour_cycle_denominator Specifies the denominator for the per-line frequency of the colour subcarrier. + The colour subcarrier is taken to have colour_cycle_numerator/colour_cycle_denominator cycles per line. - @param vertical_sync_half_lines The expected length of vertical synchronisation (equalisation pulses aside), - in multiples of half a line. + @param vertical_sync_half_lines The expected length of vertical synchronisation (equalisation pulses aside), + in multiples of half a line. - @param data_type The format that the caller will use for input data. - */ - CRT(int cycles_per_line, - int clocks_per_pixel_greatest_common_divisor, - int height_of_display, - Outputs::Display::ColourSpace colour_space, - int colour_cycle_numerator, - int colour_cycle_denominator, - int vertical_sync_half_lines, - bool should_alternate, - Outputs::Display::InputDataType data_type); + @param data_type The format that the caller will use for input data. + */ + CRT(int cycles_per_line, + int clocks_per_pixel_greatest_common_divisor, + int height_of_display, + Outputs::Display::ColourSpace colour_space, + int colour_cycle_numerator, + int colour_cycle_denominator, + int vertical_sync_half_lines, + bool should_alternate, + Outputs::Display::InputDataType data_type); - /*! Constructs a monitor-style CRT — one that will take only an RGB or monochrome signal, and therefore has - no colour space or colour subcarrier frequency. This monitor will automatically map colour bursts to the black level. - */ - CRT(int cycles_per_line, - int clocks_per_pixel_greatest_common_divisor, - int height_of_display, - int vertical_sync_half_lines, - Outputs::Display::InputDataType data_type); + /*! Constructs a monitor-style CRT — one that will take only an RGB or monochrome signal, and therefore has + no colour space or colour subcarrier frequency. This monitor will automatically map colour bursts to the black level. + */ + CRT(int cycles_per_line, + int clocks_per_pixel_greatest_common_divisor, + int height_of_display, + int vertical_sync_half_lines, + Outputs::Display::InputDataType data_type); - /*! Exactly identical to calling the designated constructor with colour subcarrier information - looked up by display type. - */ - CRT(int cycles_per_line, - int minimum_cycles_per_pixel, - Outputs::Display::Type display_type, - Outputs::Display::InputDataType data_type); + /*! Exactly identical to calling the designated constructor with colour subcarrier information + looked up by display type. + */ + CRT(int cycles_per_line, + int minimum_cycles_per_pixel, + Outputs::Display::Type display_type, + Outputs::Display::InputDataType data_type); - /*! Constructs a CRT with no guaranteed expectations as to input signal other than data type; - this allows for callers that intend to rely on @c set_new_timing. - */ - CRT(Outputs::Display::InputDataType data_type); + /*! Constructs a CRT with no guaranteed expectations as to input signal other than data type; + this allows for callers that intend to rely on @c set_new_timing. + */ + CRT(Outputs::Display::InputDataType data_type); - /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had - been provided at construction. */ - void set_new_timing( - int cycles_per_line, - int height_of_display, - Outputs::Display::ColourSpace colour_space, - int colour_cycle_numerator, - int colour_cycle_denominator, - int vertical_sync_half_lines, - bool should_alternate); + /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had + been provided at construction. */ + void set_new_timing( + int cycles_per_line, + int height_of_display, + Outputs::Display::ColourSpace colour_space, + int colour_cycle_numerator, + int colour_cycle_denominator, + int vertical_sync_half_lines, + bool should_alternate); - /*! Resets the CRT with new timing information derived from a new display type. The CRT then continues - as though the new timing had been provided at construction. */ - void set_new_display_type( - int cycles_per_line, - Outputs::Display::Type display_type); + /*! Resets the CRT with new timing information derived from a new display type. The CRT then continues + as though the new timing had been provided at construction. */ + void set_new_display_type( + int cycles_per_line, + Outputs::Display::Type display_type); - /*! Changes the type of data being supplied as input. - */ - void set_new_data_type(Outputs::Display::InputDataType data_type); + /*! Changes the type of data being supplied as input. + */ + void set_new_data_type(Outputs::Display::InputDataType data_type); - /*! Sets the CRT's intended aspect ratio — 4.0/3.0 by default. - */ - void set_aspect_ratio(float aspect_ratio); + /*! Sets the CRT's intended aspect ratio — 4.0/3.0 by default. + */ + void set_aspect_ratio(float aspect_ratio); - /*! Output at the sync level. + /*! Output at the sync level. - @param number_of_cycles The amount of time to putput sync for. - */ - void output_sync(int number_of_cycles); + @param number_of_cycles The amount of time to putput sync for. + */ + void output_sync(int number_of_cycles); - /*! Output at the blanking level. + /*! Output at the blanking level. - @param number_of_cycles The amount of time to putput the blanking level for. - */ - void output_blank(int number_of_cycles); + @param number_of_cycles The amount of time to putput the blanking level for. + */ + void output_blank(int number_of_cycles); - /*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period. + /*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period. - @param number_of_cycles The number of cycles to repeat the output for. - */ - void output_level(int number_of_cycles); + @param number_of_cycles The number of cycles to repeat the output for. + */ + void output_level(int number_of_cycles); - /*! Outputs @c value for @c number_of_cycles . + /*! Outputs @c value for @c number_of_cycles . - @param value The value to output. - @param number_of_cycles The number of cycles to repeat the output for. - */ - template <typename IntT> - void output_level(int number_of_cycles, IntT value) { - auto colour_pointer = reinterpret_cast<IntT *>(begin_data(1)); - if(colour_pointer) *colour_pointer = value; - output_level(number_of_cycles); - } + @param value The value to output. + @param number_of_cycles The number of cycles to repeat the output for. + */ + template <typename IntT> + void output_level(int number_of_cycles, IntT value) { + auto colour_pointer = reinterpret_cast<IntT *>(begin_data(1)); + if(colour_pointer) *colour_pointer = value; + output_level(number_of_cycles); + } - /*! Declares that the caller has created a run of data via @c begin_data that is at least @c number_of_samples - long, and that the first @c number_of_samples should be spread over @c number_of_cycles. + /*! Declares that the caller has created a run of data via @c begin_data that is at least @c number_of_samples + long, and that the first @c number_of_samples should be spread over @c number_of_cycles. - @param number_of_cycles The amount of data to output. + @param number_of_cycles The amount of data to output. - @param number_of_samples The number of samples of input data to output. + @param number_of_samples The number of samples of input data to output. - @see @c begin_data - */ - void output_data(int number_of_cycles, size_t number_of_samples); + @see @c begin_data + */ + void output_data(int number_of_cycles, size_t number_of_samples); - /*! A shorthand form for output_data that assumes the number of cycles to output for is the same as the number of samples. */ - void output_data(int number_of_cycles) { - output_data(number_of_cycles, size_t(number_of_cycles)); - } + /*! A shorthand form for output_data that assumes the number of cycles to output for is the same as the number of samples. */ + void output_data(int number_of_cycles) { + output_data(number_of_cycles, size_t(number_of_cycles)); + } - /*! Outputs a colour burst. + /*! Outputs a colour burst. - @param number_of_cycles The length of the colour burst. + @param number_of_cycles The length of the colour burst. - @param phase The initial phase of the colour burst in a measuring system with 256 units - per circle, e.g. 0 = 0 degrees, 128 = 180 degrees, 256 = 360 degree. + @param phase The initial phase of the colour burst in a measuring system with 256 units + per circle, e.g. 0 = 0 degrees, 128 = 180 degrees, 256 = 360 degree. - @param amplitude The amplitude of the colour burst in 1/255ths of the amplitude of the - positive portion of the wave. - */ - void output_colour_burst(int number_of_cycles, uint8_t phase, bool is_alternate_line = false, uint8_t amplitude = DefaultAmplitude); + @param amplitude The amplitude of the colour burst in 1/255ths of the amplitude of the + positive portion of the wave. + */ + void output_colour_burst(int number_of_cycles, uint8_t phase, bool is_alternate_line = false, uint8_t amplitude = DefaultAmplitude); - /*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude. + /*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude. - @param number_of_cycles The length of the colour burst; - */ - void output_default_colour_burst(int number_of_cycles, uint8_t amplitude = DefaultAmplitude); + @param number_of_cycles The length of the colour burst; + */ + void output_default_colour_burst(int number_of_cycles, uint8_t amplitude = DefaultAmplitude); - /*! Sets the current phase of the colour subcarrier used by output_default_colour_burst. + /*! Sets the current phase of the colour subcarrier used by output_default_colour_burst. - @param phase The normalised instantaneous phase. 0.0f is the start of a colour cycle, 1.0f is the - end of a colour cycle, 0.25f is a quarter of the way through a colour cycle, etc. - */ - void set_immediate_default_phase(float phase); + @param phase The normalised instantaneous phase. 0.0f is the start of a colour cycle, 1.0f is the + end of a colour cycle, 0.25f is a quarter of the way through a colour cycle, etc. + */ + void set_immediate_default_phase(float phase); - /*! Attempts to allocate the given number of output samples for writing. + /*! Attempts to allocate the given number of output samples for writing. - The beginning of the most recently allocated area is used as the start - of data written by a call to @c output_data; it is acceptable to write and to - output less data than the amount requested but that may be less efficient. + The beginning of the most recently allocated area is used as the start + of data written by a call to @c output_data; it is acceptable to write and to + output less data than the amount requested but that may be less efficient. - Allocation should fail only if emulation is running significantly below real speed. + Allocation should fail only if emulation is running significantly below real speed. - @param required_length The number of samples to allocate. - @returns A pointer to the allocated area if room is available; @c nullptr otherwise. - */ - inline uint8_t *begin_data(std::size_t required_length, std::size_t required_alignment = 1) { - const auto result = scan_target_->begin_data(required_length, required_alignment); + @param required_length The number of samples to allocate. + @returns A pointer to the allocated area if room is available; @c nullptr otherwise. + */ + inline uint8_t *begin_data(std::size_t required_length, std::size_t required_alignment = 1) { + const auto result = scan_target_->begin_data(required_length, required_alignment); #ifndef NDEBUG - // If data was allocated, make a record of how much so as to be able to hold the caller to that - // contract later. If allocation failed, don't constrain the caller. This allows callers that - // allocate on demand but may allow one failure to hold for a longer period — e.g. until the - // next line. - allocated_data_length_ = result ? required_length : std::numeric_limits<size_t>::max(); + // If data was allocated, make a record of how much so as to be able to hold the caller to that + // contract later. If allocation failed, don't constrain the caller. This allows callers that + // allocate on demand but may allow one failure to hold for a longer period — e.g. until the + // next line. + allocated_data_length_ = result ? required_length : std::numeric_limits<size_t>::max(); #endif - return result; - } + return result; + } - /*! Sets the gamma exponent for the simulated screen. */ - void set_input_gamma(float gamma); + /*! Sets the gamma exponent for the simulated screen. */ + void set_input_gamma(float gamma); - enum CompositeSourceType { - /// The composite function provides continuous output. - Continuous, - /// The composite function provides discrete output with four unique values per colour cycle. - DiscreteFourSamplesPerCycle - }; + enum CompositeSourceType { + /// The composite function provides continuous output. + Continuous, + /// The composite function provides discrete output with four unique values per colour cycle. + DiscreteFourSamplesPerCycle + }; - /*! Provides information about the type of output the composite sampling function provides, discrete or continuous. + /*! Provides information about the type of output the composite sampling function provides, discrete or continuous. - This is necessary because the CRT implementation samples discretely and therefore can use fewer intermediate - samples if it can exactly duplicate the sampling rate and placement of the composite sampling function. + This is necessary because the CRT implementation samples discretely and therefore can use fewer intermediate + samples if it can exactly duplicate the sampling rate and placement of the composite sampling function. - A continuous function is assumed by default. + A continuous function is assumed by default. - @param type The type of output provided by the function supplied to `set_composite_sampling_function`. - @param offset_of_first_sample The relative position within a full cycle of the colour subcarrier at which the - first sample falls. E.g. 0.125 means "at 1/8th of the way through the complete cycle". - */ - void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f); + @param type The type of output provided by the function supplied to `set_composite_sampling_function`. + @param offset_of_first_sample The relative position within a full cycle of the colour subcarrier at which the + first sample falls. E.g. 0.125 means "at 1/8th of the way through the complete cycle". + */ + void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f); - /*! Nominates a section of the display to crop to for output. */ - void set_visible_area(Outputs::Display::Rect visible_area); + /*! Nominates a section of the display to crop to for output. */ + void set_visible_area(Outputs::Display::Rect visible_area); - /*! @returns The rectangle describing a subset of the display, allowing for sync periods. */ - Outputs::Display::Rect get_rect_for_area( - int first_line_after_sync, - int number_of_lines, - int first_cycle_after_sync, - int number_of_cycles, - float aspect_ratio) const; + /*! @returns The rectangle describing a subset of the display, allowing for sync periods. */ + Outputs::Display::Rect get_rect_for_area( + int first_line_after_sync, + int number_of_lines, + int first_cycle_after_sync, + int number_of_cycles, + float aspect_ratio) const; - /*! Sets the CRT delegate; set to @c nullptr if no delegate is desired. */ - inline void set_delegate(Delegate *delegate) { - delegate_ = delegate; - } + /*! Sets the CRT delegate; set to @c nullptr if no delegate is desired. */ + inline void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } - /*! Sets the scan target for CRT output. */ - void set_scan_target(Outputs::Display::ScanTarget *); + /*! Sets the scan target for CRT output. */ + void set_scan_target(Outputs::Display::ScanTarget *); - /*! - Gets current scan status, with time based fields being in the input scale — e.g. if you're supplying - 86 cycles/line and 98 lines/field then it'll return a field duration of 86*98. - */ - Outputs::Display::ScanStatus get_scaled_scan_status() const; + /*! + Gets current scan status, with time based fields being in the input scale — e.g. if you're supplying + 86 cycles/line and 98 lines/field then it'll return a field duration of 86*98. + */ + Outputs::Display::ScanStatus get_scaled_scan_status() const; - /*! Sets the display type that will be nominated to the scan target. */ - void set_display_type(Outputs::Display::DisplayType); + /*! Sets the display type that will be nominated to the scan target. */ + void set_display_type(Outputs::Display::DisplayType); - /*! Gets the last display type provided to set_display_type. */ - Outputs::Display::DisplayType get_display_type() const; + /*! Gets the last display type provided to set_display_type. */ + Outputs::Display::DisplayType get_display_type() const; - /*! Sets the offset to apply to phase when using the PhaseLinkedLuminance8 input data type. */ - void set_phase_linked_luminance_offset(float); + /*! Sets the offset to apply to phase when using the PhaseLinkedLuminance8 input data type. */ + void set_phase_linked_luminance_offset(float); - /*! Sets the input data type. */ - void set_input_data_type(Outputs::Display::InputDataType); + /*! Sets the input data type. */ + void set_input_data_type(Outputs::Display::InputDataType); - /*! Sets the output brightness. */ - void set_brightness(float); + /*! Sets the output brightness. */ + void set_brightness(float); }; /*! @@ -353,44 +353,44 @@ class CRT { ask its receiver to try a different display frequency. */ template <typename Receiver> class CRTFrequencyMismatchWarner: public Outputs::CRT::Delegate { - public: - CRTFrequencyMismatchWarner(Receiver &receiver) : receiver_(receiver) {} +public: + CRTFrequencyMismatchWarner(Receiver &receiver) : receiver_(receiver) {} - void crt_did_end_batch_of_frames(Outputs::CRT::CRT *, int number_of_frames, int number_of_unexpected_vertical_syncs) final { - frame_records_[frame_record_pointer_ % frame_records_.size()].number_of_frames = number_of_frames; - frame_records_[frame_record_pointer_ % frame_records_.size()].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; - ++frame_record_pointer_; + void crt_did_end_batch_of_frames(Outputs::CRT::CRT *, int number_of_frames, int number_of_unexpected_vertical_syncs) final { + frame_records_[frame_record_pointer_ % frame_records_.size()].number_of_frames = number_of_frames; + frame_records_[frame_record_pointer_ % frame_records_.size()].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; + ++frame_record_pointer_; - if(frame_record_pointer_*2 >= frame_records_.size()*3) { - int total_number_of_frames = 0; - int total_number_of_unexpected_vertical_syncs = 0; - for(const auto &record: frame_records_) { - total_number_of_frames += record.number_of_frames; - total_number_of_unexpected_vertical_syncs += record.number_of_unexpected_vertical_syncs; - } + if(frame_record_pointer_*2 >= frame_records_.size()*3) { + int total_number_of_frames = 0; + int total_number_of_unexpected_vertical_syncs = 0; + for(const auto &record: frame_records_) { + total_number_of_frames += record.number_of_frames; + total_number_of_unexpected_vertical_syncs += record.number_of_unexpected_vertical_syncs; + } - if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { - reset(); - receiver_.register_crt_frequency_mismatch(); - } + if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { + reset(); + receiver_.register_crt_frequency_mismatch(); } } + } - void reset() { - for(auto &record: frame_records_) { - record.number_of_frames = 0; - record.number_of_unexpected_vertical_syncs = 0; - } + void reset() { + for(auto &record: frame_records_) { + record.number_of_frames = 0; + record.number_of_unexpected_vertical_syncs = 0; } + } - private: - Receiver &receiver_; - struct FrameRecord { - int number_of_frames = 0; - int number_of_unexpected_vertical_syncs = 0; - }; - std::array<FrameRecord, 4> frame_records_; - size_t frame_record_pointer_ = 0; +private: + Receiver &receiver_; + struct FrameRecord { + int number_of_frames = 0; + int number_of_unexpected_vertical_syncs = 0; + }; + std::array<FrameRecord, 4> frame_records_; + size_t frame_record_pointer_ = 0; }; } diff --git a/Outputs/CRT/Internals/Flywheel.hpp b/Outputs/CRT/Internals/Flywheel.hpp index b6914f197..abc3e52ed 100644 --- a/Outputs/CRT/Internals/Flywheel.hpp +++ b/Outputs/CRT/Internals/Flywheel.hpp @@ -229,32 +229,32 @@ struct Flywheel { (counter_ >= expected_next_sync_ - (standard_period_ / 100)); } - private: - const int standard_period_; // The idealised length of time between syncs. - const int retrace_time_; // A constant indicating the amount of time it takes to perform a retrace. - const int sync_error_window_; // A constant indicating the window either side of the next expected sync in which we'll accept other syncs. +private: + const int standard_period_; // The idealised length of time between syncs. + const int retrace_time_; // A constant indicating the amount of time it takes to perform a retrace. + const int sync_error_window_; // A constant indicating the window either side of the next expected sync in which we'll accept other syncs. - int counter_ = 0; // Time since the _start_ of the last sync. - int counter_before_retrace_; // The value of _counter immediately before retrace began. - int expected_next_sync_; // Our current expection of when the next sync will be encountered (which implies velocity). + int counter_ = 0; // Time since the _start_ of the last sync. + int counter_before_retrace_; // The value of _counter immediately before retrace began. + int expected_next_sync_; // Our current expection of when the next sync will be encountered (which implies velocity). - int number_of_surprises_ = 0; // A count of the surprising syncs. - int number_of_retraces_ = 0; // A count of the number of retraces to date. + int number_of_surprises_ = 0; // A count of the surprising syncs. + int number_of_retraces_ = 0; // A count of the number of retraces to date. - int last_adjustment_ = 0; // The amount by which expected_next_sync_ was adjusted at the last sync. + int last_adjustment_ = 0; // The amount by which expected_next_sync_ was adjusted at the last sync. - /* - Implementation notes: + /* + Implementation notes: - Retrace takes a fixed amount of time and runs during [0, _retrace_time). + Retrace takes a fixed amount of time and runs during [0, _retrace_time). - For the current line, scan then occurs from [_retrace_time, _expected_next_sync), at which point - retrace begins and the internal counter is reset. + For the current line, scan then occurs from [_retrace_time, _expected_next_sync), at which point + retrace begins and the internal counter is reset. - All synchronisation events that occur within (-_sync_error_window, _sync_error_window) of the - expected synchronisation time will cause a proportional adjustment in the expected time for the next - synchronisation. Other synchronisation events are clamped as though they occurred in that range. - */ + All synchronisation events that occur within (-_sync_error_window, _sync_error_window) of the + expected synchronisation time will cause a proportional adjustment in the expected time for the next + synchronisation. Other synchronisation events are clamped as though they occurred in that range. + */ }; } diff --git a/Outputs/DisplayMetrics.hpp b/Outputs/DisplayMetrics.hpp index a5eb0c059..6583278d2 100644 --- a/Outputs/DisplayMetrics.hpp +++ b/Outputs/DisplayMetrics.hpp @@ -22,36 +22,36 @@ namespace Outputs::Display { to allow for host-client frame synchronisation. */ class Metrics { - public: - /// Notifies Metrics of a beam event. - void announce_event(ScanTarget::Event event); +public: + /// Notifies Metrics of a beam event. + void announce_event(ScanTarget::Event); - /// Notifies Metrics that the size of the output buffer has changed. - void announce_did_resize(); + /// Notifies Metrics that the size of the output buffer has changed. + void announce_did_resize(); - /// Provides Metrics with a new data point for output speed estimation. - void announce_draw_status(size_t lines, std::chrono::high_resolution_clock::duration duration, bool complete); + /// Provides Metrics with a new data point for output speed estimation. + void announce_draw_status(size_t lines, std::chrono::high_resolution_clock::duration, bool complete); - /// Provides Metrics with a new data point for output speed estimation, albeit without line-specific information. - void announce_draw_status(bool complete); + /// Provides Metrics with a new data point for output speed estimation, albeit without line-specific information. + void announce_draw_status(bool complete); - /// @returns @c true if Metrics thinks a lower output buffer resolution is desirable in the abstract; @c false otherwise. - bool should_lower_resolution() const; + /// @returns @c true if Metrics thinks a lower output buffer resolution is desirable in the abstract; @c false otherwise. + bool should_lower_resolution() const; - /// @returns An estimate of the number of lines being produced per frame, excluding vertical sync. - float visible_lines_per_frame_estimate() const; + /// @returns An estimate of the number of lines being produced per frame, excluding vertical sync. + float visible_lines_per_frame_estimate() const; - /// @returns The number of lines since vertical retrace ended. - int current_line() const; + /// @returns The number of lines since vertical retrace ended. + int current_line() const; - private: - int lines_this_frame_ = 0; - std::array<int, 20> line_total_history_; - size_t line_total_history_pointer_ = 0; - void add_line_total(int); +private: + int lines_this_frame_ = 0; + std::array<int, 20> line_total_history_; + size_t line_total_history_pointer_ = 0; + void add_line_total(int); - std::atomic<int> frames_hit_ = 0; - std::atomic<int> frames_missed_ = 0; + std::atomic<int> frames_hit_ = 0; + std::atomic<int> frames_missed_ = 0; }; } diff --git a/Outputs/Log.hpp b/Outputs/Log.hpp index 7e1e234d9..c935687fa 100644 --- a/Outputs/Log.hpp +++ b/Outputs/Log.hpp @@ -137,31 +137,31 @@ public: static constexpr bool enabled = is_enabled(source); struct LogLine { - public: - LogLine(FILE *const stream) : stream_(stream) { - if constexpr (!enabled) return; + public: + LogLine(FILE *const stream) : stream_(stream) { + if constexpr (!enabled) return; - const auto source_prefix = prefix(source); - if(source_prefix) { - fprintf(stream_, "[%s] ", source_prefix); - } + const auto source_prefix = prefix(source); + if(source_prefix) { + fprintf(stream_, "[%s] ", source_prefix); } + } - ~LogLine() { - if constexpr (!enabled) return; - fprintf(stream_, "\n"); - } + ~LogLine() { + if constexpr (!enabled) return; + fprintf(stream_, "\n"); + } - void append(const char *const format, ...) { - if constexpr (!enabled) return; - va_list args; - va_start(args, format); - vfprintf(stream_, format, args); - va_end(args); - } + void append(const char *const format, ...) { + if constexpr (!enabled) return; + va_list args; + va_start(args, format); + vfprintf(stream_, format, args); + va_end(args); + } - private: - FILE *stream_; + private: + FILE *stream_; }; LogLine info() { return LogLine(stdout); } diff --git a/Outputs/OpenGL/Primitives/Rectangle.hpp b/Outputs/OpenGL/Primitives/Rectangle.hpp index 4895bb391..bbe6d1fb2 100644 --- a/Outputs/OpenGL/Primitives/Rectangle.hpp +++ b/Outputs/OpenGL/Primitives/Rectangle.hpp @@ -18,21 +18,21 @@ namespace Outputs::Display::OpenGL { Provides a wrapper for drawing a solid, single-colour rectangle. */ class Rectangle { - public: - /*! - Instantiates an instance of Rectange with the coordinates given. - */ - Rectangle(float x, float y, float width, float height); +public: + /*! + Instantiates an instance of Rectange with the coordinates given. + */ + Rectangle(float x, float y, float width, float height); - /*! - Draws this rectangle in the colour supplied. - */ - void draw(float red, float green, float blue); + /*! + Draws this rectangle in the colour supplied. + */ + void draw(float red, float green, float blue); - private: - Shader pixel_shader_; - GLuint drawing_vertex_array_ = 0, drawing_array_buffer_ = 0; - GLint colour_uniform_; +private: + Shader pixel_shader_; + GLuint drawing_vertex_array_ = 0, drawing_array_buffer_ = 0; + GLint colour_uniform_; }; } diff --git a/Outputs/OpenGL/Primitives/TextureTarget.hpp b/Outputs/OpenGL/Primitives/TextureTarget.hpp index 60292c621..a26fc13b3 100644 --- a/Outputs/OpenGL/Primitives/TextureTarget.hpp +++ b/Outputs/OpenGL/Primitives/TextureTarget.hpp @@ -19,70 +19,70 @@ namespace Outputs::Display::OpenGL { handles render-to-texture framebuffer objects. */ class TextureTarget { - public: - /*! - Creates a new texture target. Contents are initially undefined. +public: + /*! + Creates a new texture target. Contents are initially undefined. - Leaves both the generated texture and framebuffer bound. + Leaves both the generated texture and framebuffer bound. - @throws std::runtime_error if creation fails. + @throws std::runtime_error if creation fails. - @param width The width of target to create. - @param height The height of target to create. - @param texture_unit A texture unit on which to bind the texture. - @param has_stencil_buffer An 8-bit stencil buffer is attached if this is @c true; no stencil buffer is attached otherwise. - */ - TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter, bool has_stencil_buffer); - ~TextureTarget(); + @param width The width of target to create. + @param height The height of target to create. + @param texture_unit A texture unit on which to bind the texture. + @param has_stencil_buffer An 8-bit stencil buffer is attached if this is @c true; no stencil buffer is attached otherwise. + */ + TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter, bool has_stencil_buffer); + ~TextureTarget(); - /*! - Binds this target as a framebuffer and sets the @c glViewport accordingly. - */ - void bind_framebuffer(); + /*! + Binds this target as a framebuffer and sets the @c glViewport accordingly. + */ + void bind_framebuffer(); - /*! - Binds this target as a texture. - */ - void bind_texture() const; + /*! + Binds this target as a texture. + */ + void bind_texture() const; - /*! - @returns the width of the texture target. - */ - GLsizei get_width() const { - return width_; - } + /*! + @returns the width of the texture target. + */ + GLsizei get_width() const { + return width_; + } - /*! - @returns the height of the texture target. - */ - GLsizei get_height() const { - return height_; - } + /*! + @returns the height of the texture target. + */ + GLsizei get_height() const { + return height_; + } - /*! - Draws this texture to the currently-bound framebuffer, which has the aspect ratio - @c aspect_ratio. This texture will fill the height of the frame buffer, and pick - an appropriate width based on the aspect ratio. + /*! + Draws this texture to the currently-bound framebuffer, which has the aspect ratio + @c aspect_ratio. This texture will fill the height of the frame buffer, and pick + an appropriate width based on the aspect ratio. - @c colour_threshold sets a threshold test that each colour must satisfy to be - output. A threshold of 0.0f means that all colours will pass through. A threshold - of 0.5f means that only colour components above 0.5f will pass through, with - 0.5f being substituted elsewhere. This provides a way to ensure that the sort of - persistent low-value errors that can result from an IIR are hidden. - */ - void draw(float aspect_ratio, float colour_threshold = 0.0f) const; + @c colour_threshold sets a threshold test that each colour must satisfy to be + output. A threshold of 0.0f means that all colours will pass through. A threshold + of 0.5f means that only colour components above 0.5f will pass through, with + 0.5f being substituted elsewhere. This provides a way to ensure that the sort of + persistent low-value errors that can result from an IIR are hidden. + */ + void draw(float aspect_ratio, float colour_threshold = 0.0f) const; - private: - GLuint framebuffer_ = 0, texture_ = 0, renderbuffer_ = 0; - const GLsizei width_ = 0, height_ = 0; - GLsizei expanded_width_ = 0, expanded_height_ = 0; - const GLenum texture_unit_ = 0; +private: + GLuint framebuffer_ = 0, texture_ = 0, renderbuffer_ = 0; + const GLsizei width_ = 0, height_ = 0; + GLsizei expanded_width_ = 0, expanded_height_ = 0; + const GLenum texture_unit_ = 0; - mutable std::unique_ptr<Shader> pixel_shader_; - mutable GLuint drawing_vertex_array_ = 0, drawing_array_buffer_ = 0; - mutable float set_aspect_ratio_ = 0.0f; + mutable std::unique_ptr<Shader> pixel_shader_; + mutable GLuint drawing_vertex_array_ = 0, drawing_array_buffer_ = 0; + mutable float set_aspect_ratio_ = 0.0f; - mutable GLint threshold_uniform_; + mutable GLint threshold_uniform_; }; } diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index aa4572b69..cb66eee60 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -36,124 +36,124 @@ namespace Outputs::Display::OpenGL { drawn to the target framebuffer is a quad. */ class ScanTarget: public Outputs::Display::BufferingScanTarget { // TODO: use private inheritance and expose only display_metrics() and a custom cast? - public: - ScanTarget(GLuint target_framebuffer = 0, float output_gamma = 2.2f); - ~ScanTarget(); +public: + ScanTarget(GLuint target_framebuffer = 0, float output_gamma = 2.2f); + ~ScanTarget(); - void set_target_framebuffer(GLuint); + void set_target_framebuffer(GLuint); - /*! Pushes the current state of output to the target framebuffer. */ - void draw(int output_width, int output_height); - /*! Processes all the latest input, at a resolution suitable for later output to a framebuffer of the specified size. */ - void update(int output_width, int output_height); + /*! Pushes the current state of output to the target framebuffer. */ + void draw(int output_width, int output_height); + /*! Processes all the latest input, at a resolution suitable for later output to a framebuffer of the specified size. */ + void update(int output_width, int output_height); - private: - static constexpr int LineBufferWidth = 2048; - static constexpr int LineBufferHeight = 2048; +private: + static constexpr int LineBufferWidth = 2048; + static constexpr int LineBufferHeight = 2048; #ifndef NDEBUG - struct OpenGLVersionDumper { - OpenGLVersionDumper() { - // Note the OpenGL version, as the first thing this class does prior to construction. - Log::Logger<Log::Source::OpenGL>().info().append( - "Constructing scan target with OpenGL %s; shading language version %s", - glGetString(GL_VERSION), - glGetString(GL_SHADING_LANGUAGE_VERSION)); - } - } dumper_; + struct OpenGLVersionDumper { + OpenGLVersionDumper() { + // Note the OpenGL version, as the first thing this class does prior to construction. + Log::Logger<Log::Source::OpenGL>().info().append( + "Constructing scan target with OpenGL %s; shading language version %s", + glGetString(GL_VERSION), + glGetString(GL_SHADING_LANGUAGE_VERSION)); + } + } dumper_; #endif - GLuint target_framebuffer_; - const float output_gamma_; + GLuint target_framebuffer_; + const float output_gamma_; - int resolution_reduction_level_ = 1; - int output_height_ = 0; + int resolution_reduction_level_ = 1; + int output_height_ = 0; - size_t lines_submitted_ = 0; - std::chrono::high_resolution_clock::time_point line_submission_begin_time_; + size_t lines_submitted_ = 0; + std::chrono::high_resolution_clock::time_point line_submission_begin_time_; - // Contains the first composition of scans into lines; - // they're accumulated prior to output to allow for continuous - // application of any necessary conversions — e.g. composite processing. - TextureTarget unprocessed_line_texture_; + // Contains the first composition of scans into lines; + // they're accumulated prior to output to allow for continuous + // application of any necessary conversions — e.g. composite processing. + TextureTarget unprocessed_line_texture_; - // Contains pre-lowpass-filtered chrominance information that is - // part-QAM-demoduled, if dealing with a QAM data source. - std::unique_ptr<TextureTarget> qam_chroma_texture_; + // Contains pre-lowpass-filtered chrominance information that is + // part-QAM-demoduled, if dealing with a QAM data source. + std::unique_ptr<TextureTarget> qam_chroma_texture_; - // Scans are accumulated to the accumulation texture; the full-display - // rectangle is used to ensure untouched pixels properly decay. - std::unique_ptr<TextureTarget> accumulation_texture_; - Rectangle full_display_rectangle_; - bool stencil_is_valid_ = false; + // Scans are accumulated to the accumulation texture; the full-display + // rectangle is used to ensure untouched pixels properly decay. + std::unique_ptr<TextureTarget> accumulation_texture_; + Rectangle full_display_rectangle_; + bool stencil_is_valid_ = false; - // OpenGL storage handles for buffer data. - GLuint scan_buffer_name_ = 0, scan_vertex_array_ = 0; - GLuint line_buffer_name_ = 0, line_vertex_array_ = 0; + // OpenGL storage handles for buffer data. + GLuint scan_buffer_name_ = 0, scan_vertex_array_ = 0; + GLuint line_buffer_name_ = 0, line_vertex_array_ = 0; - template <typename T> void allocate_buffer(const T &array, GLuint &buffer_name, GLuint &vertex_array_name); - template <typename T> void patch_buffer(const T &array, GLuint target, uint16_t submit_pointer, uint16_t read_pointer); + template <typename T> void allocate_buffer(const T &array, GLuint &buffer_name, GLuint &vertex_array_name); + template <typename T> void patch_buffer(const T &array, GLuint target, uint16_t submit_pointer, uint16_t read_pointer); - GLuint write_area_texture_name_ = 0; - bool texture_exists_ = false; + GLuint write_area_texture_name_ = 0; + bool texture_exists_ = false; - // Receives scan target modals. - void setup_pipeline(); + // Receives scan target modals. + void setup_pipeline(); - enum class ShaderType { - Composition, - Conversion, - QAMSeparation - }; + enum class ShaderType { + Composition, + Conversion, + QAMSeparation + }; - /*! - Calls @c taret.enable_vertex_attribute_with_pointer to attach all - globals for shaders of @c type to @c target. - */ - static void enable_vertex_attributes(ShaderType type, Shader &target); - void set_uniforms(ShaderType type, Shader &target) const; - std::vector<std::string> bindings(ShaderType type) const; + /*! + Calls @c taret.enable_vertex_attribute_with_pointer to attach all + globals for shaders of @c type to @c target. + */ + static void enable_vertex_attributes(ShaderType type, Shader &target); + void set_uniforms(ShaderType type, Shader &target) const; + std::vector<std::string> bindings(ShaderType type) const; - GLsync fence_ = nullptr; - std::atomic_flag is_drawing_to_accumulation_buffer_; + GLsync fence_ = nullptr; + std::atomic_flag is_drawing_to_accumulation_buffer_; - std::unique_ptr<Shader> input_shader_; - std::unique_ptr<Shader> output_shader_; - std::unique_ptr<Shader> qam_separation_shader_; + std::unique_ptr<Shader> input_shader_; + std::unique_ptr<Shader> output_shader_; + std::unique_ptr<Shader> qam_separation_shader_; - /*! - Produces a shader that composes fragment of the input stream to a single buffer, - normalising the data into one of four forms: RGB, 8-bit luminance, - phase-linked luminance or luminance+phase offset. - */ - std::unique_ptr<Shader> composition_shader() const; - /*! - Produces a shader that reads from a composition buffer and converts to host - output RGB, decoding composite or S-Video as necessary. - */ - std::unique_ptr<Shader> conversion_shader() const; - /*! - Produces a shader that writes separated but not-yet filtered QAM components - from the unprocessed line texture to the QAM chroma texture, at a fixed - size of four samples per colour clock, point sampled. - */ - std::unique_ptr<Shader> qam_separation_shader() const; + /*! + Produces a shader that composes fragment of the input stream to a single buffer, + normalising the data into one of four forms: RGB, 8-bit luminance, + phase-linked luminance or luminance+phase offset. + */ + std::unique_ptr<Shader> composition_shader() const; + /*! + Produces a shader that reads from a composition buffer and converts to host + output RGB, decoding composite or S-Video as necessary. + */ + std::unique_ptr<Shader> conversion_shader() const; + /*! + Produces a shader that writes separated but not-yet filtered QAM components + from the unprocessed line texture to the QAM chroma texture, at a fixed + size of four samples per colour clock, point sampled. + */ + std::unique_ptr<Shader> qam_separation_shader() const; - void set_sampling_window(int output_Width, int output_height, Shader &target); + void set_sampling_window(int output_Width, int output_height, Shader &target); - std::string sampling_function() const; + std::string sampling_function() const; - /*! - @returns true if the current display type is a 'soft' one, i.e. one in which - contrast tends to be low, such as a composite colour display. - */ - bool is_soft_display_type(); + /*! + @returns true if the current display type is a 'soft' one, i.e. one in which + contrast tends to be low, such as a composite colour display. + */ + bool is_soft_display_type(); - // Storage for the various buffers. - std::vector<uint8_t> write_area_texture_; - std::array<Scan, LineBufferHeight*5> scan_buffer_; - std::array<Line, LineBufferHeight> line_buffer_; - std::array<LineMetadata, LineBufferHeight> line_metadata_buffer_; + // Storage for the various buffers. + std::vector<uint8_t> write_area_texture_; + std::array<Scan, LineBufferHeight*5> scan_buffer_; + std::array<Line, LineBufferHeight> line_buffer_; + std::array<LineMetadata, LineBufferHeight> line_metadata_buffer_; }; } diff --git a/Outputs/ScanTargets/BufferingScanTarget.hpp b/Outputs/ScanTargets/BufferingScanTarget.hpp index 9d43c22c7..2c95a1d76 100644 --- a/Outputs/ScanTargets/BufferingScanTarget.hpp +++ b/Outputs/ScanTargets/BufferingScanTarget.hpp @@ -33,241 +33,241 @@ namespace Outputs::Display { This buffer rejects new data when full. */ class BufferingScanTarget: public Outputs::Display::ScanTarget { - public: - /*! @returns The DisplayMetrics object that this ScanTarget has been providing with announcements and draw overages. */ - const Metrics &display_metrics(); +public: + /*! @returns The DisplayMetrics object that this ScanTarget has been providing with announcements and draw overages. */ + const Metrics &display_metrics(); - static constexpr int WriteAreaWidth = 2048; - static constexpr int WriteAreaHeight = 2048; + static constexpr int WriteAreaWidth = 2048; + static constexpr int WriteAreaHeight = 2048; - BufferingScanTarget(); + BufferingScanTarget(); - // This is included because it's assumed that scan targets will want to expose one. - // It is the subclass's responsibility to post timings. - Metrics display_metrics_; + // This is included because it's assumed that scan targets will want to expose one. + // It is the subclass's responsibility to post timings. + Metrics display_metrics_; - /// Extends the definition of a Scan to include two extra fields, - /// completing this scan's source data and destination locations. - struct Scan { - Outputs::Display::ScanTarget::Scan scan; + /// Extends the definition of a Scan to include two extra fields, + /// completing this scan's source data and destination locations. + struct Scan { + Outputs::Display::ScanTarget::Scan scan; - /// Stores the y coordinate for this scan's data within the write area texture. - /// Use this plus the scan's endpoints' data_offsets to locate this data in 2d. - /// Note that the data_offsets will have been adjusted to be relative to the line - /// they fall within, not the data allocation. - uint16_t data_y; - /// Stores the y coordinate assigned to this scan within the intermediate buffers. - /// Use this plus this scan's endpoints' x locations to determine where to composite - /// this data for intermediate processing. - uint16_t line; + /// Stores the y coordinate for this scan's data within the write area texture. + /// Use this plus the scan's endpoints' data_offsets to locate this data in 2d. + /// Note that the data_offsets will have been adjusted to be relative to the line + /// they fall within, not the data allocation. + uint16_t data_y; + /// Stores the y coordinate assigned to this scan within the intermediate buffers. + /// Use this plus this scan's endpoints' x locations to determine where to composite + /// this data for intermediate processing. + uint16_t line; + }; + + /// Defines the boundaries of a complete line of video — a 2d start and end location, + /// composite phase and amplitude (if relevant), the source line in the intermediate buffer + /// plus the start and end offsets of the area that is visible from the intermediate buffer. + struct Line { + struct EndPoint { + uint16_t x, y; + int16_t composite_angle; + uint16_t cycles_since_end_of_horizontal_retrace; + } end_points[2]; + + uint8_t composite_amplitude; + uint16_t line; + }; + + /// Provides additional metadata about lines; this is separate because it's unlikely to be of + /// interest to the GPU, unlike the fields in Line. + struct LineMetadata { + /// @c true if this line was the first drawn after vertical sync; @c false otherwise. + bool is_first_in_frame; + /// @c true if this line is the first in the frame and if every single piece of output + /// from the previous frame was recorded; @c false otherwise. Data can be dropped + /// from a frame if performance problems mean that the emulated machine is running + /// more quickly than complete frames can be generated. + bool previous_frame_was_complete; + /// The index of the first scan that will appear on this line. + size_t first_scan; + }; + + /// Sets the area of memory to use as a scan buffer. + void set_scan_buffer(Scan *buffer, size_t size); + + /// Sets the area of memory to use as line and line metadata buffers. + void set_line_buffer(Line *line_buffer, LineMetadata *metadata_buffer, size_t size); + + /// Sets a new base address for the texture. + /// When called this will flush all existing data and load up the + /// new data size. + void set_write_area(uint8_t *base); + + /// @returns The number of bytes per input sample, as per the latest modals. + size_t write_area_data_size() const; + + /// Defines a segment of data now ready for output, consisting of start and endpoints for: + /// + /// (i) the region of the write area that has been modified; if the caller is using shared memory + /// for the write area then it can ignore this information; + /// + /// (ii) the number of scans that have been completed; and + /// + /// (iii) the number of lines that have been completed. + /// + /// New write areas and scans are exposed only upon completion of the corresponding lines. + /// The values indicated by the start point are the first that should be drawn. Those indicated + /// by the end point are one after the final that should be drawn. + /// + /// So e.g. start.scan = 23, end.scan = 24 means draw a single scan, index 23. + struct OutputArea { + struct Endpoint { + int write_area_x, write_area_y; + size_t scan; + size_t line; }; - /// Defines the boundaries of a complete line of video — a 2d start and end location, - /// composite phase and amplitude (if relevant), the source line in the intermediate buffer - /// plus the start and end offsets of the area that is visible from the intermediate buffer. - struct Line { - struct EndPoint { - uint16_t x, y; - int16_t composite_angle; - uint16_t cycles_since_end_of_horizontal_retrace; - } end_points[2]; - - uint8_t composite_amplitude; - uint16_t line; - }; - - /// Provides additional metadata about lines; this is separate because it's unlikely to be of - /// interest to the GPU, unlike the fields in Line. - struct LineMetadata { - /// @c true if this line was the first drawn after vertical sync; @c false otherwise. - bool is_first_in_frame; - /// @c true if this line is the first in the frame and if every single piece of output - /// from the previous frame was recorded; @c false otherwise. Data can be dropped - /// from a frame if performance problems mean that the emulated machine is running - /// more quickly than complete frames can be generated. - bool previous_frame_was_complete; - /// The index of the first scan that will appear on this line. - size_t first_scan; - }; - - /// Sets the area of memory to use as a scan buffer. - void set_scan_buffer(Scan *buffer, size_t size); - - /// Sets the area of memory to use as line and line metadata buffers. - void set_line_buffer(Line *line_buffer, LineMetadata *metadata_buffer, size_t size); - - /// Sets a new base address for the texture. - /// When called this will flush all existing data and load up the - /// new data size. - void set_write_area(uint8_t *base); - - /// @returns The number of bytes per input sample, as per the latest modals. - size_t write_area_data_size() const; - - /// Defines a segment of data now ready for output, consisting of start and endpoints for: - /// - /// (i) the region of the write area that has been modified; if the caller is using shared memory - /// for the write area then it can ignore this information; - /// - /// (ii) the number of scans that have been completed; and - /// - /// (iii) the number of lines that have been completed. - /// - /// New write areas and scans are exposed only upon completion of the corresponding lines. - /// The values indicated by the start point are the first that should be drawn. Those indicated - /// by the end point are one after the final that should be drawn. - /// - /// So e.g. start.scan = 23, end.scan = 24 means draw a single scan, index 23. - struct OutputArea { - struct Endpoint { - int write_area_x, write_area_y; - size_t scan; - size_t line; - }; - - Endpoint start, end; + Endpoint start, end; #ifndef NDEBUG - size_t counter; + size_t counter; #endif - }; + }; - /// Gets the current range of content that has been posted but not yet returned by - /// a previous call to get_output_area(). - /// - /// Does not require the caller to be within a @c perform block. - OutputArea get_output_area(); + /// Gets the current range of content that has been posted but not yet returned by + /// a previous call to get_output_area(). + /// + /// Does not require the caller to be within a @c perform block. + OutputArea get_output_area(); - /// Announces that the output area has now completed output, freeing up its memory for - /// further modification. - /// - /// It is the caller's responsibility to ensure that the areas passed to complete_output_area - /// are those from get_output_area and are marked as completed in the same order that - /// they were originally provided. - /// - /// Does not require the caller to be within a @c perform block. - void complete_output_area(const OutputArea &); + /// Announces that the output area has now completed output, freeing up its memory for + /// further modification. + /// + /// It is the caller's responsibility to ensure that the areas passed to complete_output_area + /// are those from get_output_area and are marked as completed in the same order that + /// they were originally provided. + /// + /// Does not require the caller to be within a @c perform block. + void complete_output_area(const OutputArea &); - /// Performs @c action ensuring that no other @c perform actions, or any - /// change to modals, occurs simultaneously. - void perform(const std::function<void(void)> &action); + /// Performs @c action ensuring that no other @c perform actions, or any + /// change to modals, occurs simultaneously. + void perform(const std::function<void(void)> &action); - /// @returns new Modals if any have been set since the last call to get_new_modals(). - /// The caller must be within a @c perform block. - const Modals *new_modals(); + /// @returns new Modals if any have been set since the last call to get_new_modals(). + /// The caller must be within a @c perform block. + const Modals *new_modals(); - /// @returns the current @c Modals. - const Modals &modals() const; + /// @returns the current @c Modals. + const Modals &modals() const; - /// @returns @c true if new modals are available; @c false otherwise. - /// - /// Safe to call from any thread. - bool has_new_modals() const; + /// @returns @c true if new modals are available; @c false otherwise. + /// + /// Safe to call from any thread. + bool has_new_modals() const; - private: - // ScanTarget overrides. - void set_modals(Modals) final; - Outputs::Display::ScanTarget::Scan *begin_scan() final; - void end_scan() final; - uint8_t *begin_data(size_t required_length, size_t required_alignment) final; - void end_data(size_t actual_length) final; - void announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location, uint8_t colour_burst_amplitude) final; - void will_change_owner() final; +private: + // ScanTarget overrides. + void set_modals(Modals) final; + Outputs::Display::ScanTarget::Scan *begin_scan() final; + void end_scan() final; + uint8_t *begin_data(size_t required_length, size_t required_alignment) final; + void end_data(size_t actual_length) final; + void announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location, uint8_t colour_burst_amplitude) final; + void will_change_owner() final; - // Uses a texture to vend write areas. - uint8_t *write_area_ = nullptr; - size_t data_type_size_ = 0; + // Uses a texture to vend write areas. + uint8_t *write_area_ = nullptr; + size_t data_type_size_ = 0; - // Tracks changes in raster visibility in order to populate - // Lines and LineMetadatas. - bool output_is_visible_ = false; + // Tracks changes in raster visibility in order to populate + // Lines and LineMetadatas. + bool output_is_visible_ = false; - // Track allocation failures. - bool data_is_allocated_ = false; - bool allocation_has_failed_ = false; + // Track allocation failures. + bool data_is_allocated_ = false; + bool allocation_has_failed_ = false; - // Ephemeral information for the begin/end functions. - Scan *vended_scan_ = nullptr; - int vended_write_area_pointer_ = 0; + // Ephemeral information for the begin/end functions. + Scan *vended_scan_ = nullptr; + int vended_write_area_pointer_ = 0; - // Ephemeral state that helps in line composition. - int provided_scans_ = 0; - bool is_first_in_frame_ = true; - bool frame_is_complete_ = true; - bool previous_frame_was_complete_ = true; + // Ephemeral state that helps in line composition. + int provided_scans_ = 0; + bool is_first_in_frame_ = true; + bool frame_is_complete_ = true; + bool previous_frame_was_complete_ = true; - // By convention everything in the PointerSet points to the next instance - // of whatever it is that will be used. So a client should start with whatever - // is pointed to by the read pointers and carry until it gets to a value that - // is equal to whatever is in the submit pointers. - struct PointerSet { - // This constructor is here to appease GCC's interpretation of - // an ambiguity in the C++ standard; cf. https://stackoverflow.com/questions/17430377 - PointerSet() noexcept = default; + // By convention everything in the PointerSet points to the next instance + // of whatever it is that will be used. So a client should start with whatever + // is pointed to by the read pointers and carry until it gets to a value that + // is equal to whatever is in the submit pointers. + struct PointerSet { + // This constructor is here to appease GCC's interpretation of + // an ambiguity in the C++ standard; cf. https://stackoverflow.com/questions/17430377 + PointerSet() noexcept = default; - // Squeezing this struct into 64 bits makes the std::atomics more likely - // to be lock free; they are under LLVM x86-64. + // Squeezing this struct into 64 bits makes the std::atomics more likely + // to be lock free; they are under LLVM x86-64. - // Points to the vended area in the write area texture. - // The vended area is always preceded by a guard pixel, so a - // sensible default construction is write_area = 1. - int32_t write_area = 1; + // Points to the vended area in the write area texture. + // The vended area is always preceded by a guard pixel, so a + // sensible default construction is write_area = 1. + int32_t write_area = 1; - // Points into the scan buffer. - uint16_t scan = 0; + // Points into the scan buffer. + uint16_t scan = 0; - // Points into the line buffer. - uint16_t line = 0; - }; + // Points into the line buffer. + uint16_t line = 0; + }; - /// A pointer to the final thing currently cleared for submission. - std::atomic<PointerSet> submit_pointers_; + /// A pointer to the final thing currently cleared for submission. + std::atomic<PointerSet> submit_pointers_; - /// A pointer to the first thing not yet submitted for display; this is - /// atomic since it also acts as the buffer into which the write_pointers_ - /// may run and is therefore used by both producer and consumer. - std::atomic<PointerSet> read_pointers_; + /// A pointer to the first thing not yet submitted for display; this is + /// atomic since it also acts as the buffer into which the write_pointers_ + /// may run and is therefore used by both producer and consumer. + std::atomic<PointerSet> read_pointers_; - std::atomic<PointerSet> read_ahead_pointers_; + std::atomic<PointerSet> read_ahead_pointers_; - /// This is used as a spinlock to guard `perform` calls. - std::atomic_flag is_updating_; + /// This is used as a spinlock to guard `perform` calls. + std::atomic_flag is_updating_; - /// A mutex for gettng access to anything the producer modifies — i.e. the write_pointers_, - /// data_type_size_ and write_area_texture_, and all other state to do with capturing - /// data, scans and lines. - /// - /// This is almost never contended. The main collision is a user-prompted change of modals while the - /// emulation thread is running. - std::mutex producer_mutex_; + /// A mutex for gettng access to anything the producer modifies — i.e. the write_pointers_, + /// data_type_size_ and write_area_texture_, and all other state to do with capturing + /// data, scans and lines. + /// + /// This is almost never contended. The main collision is a user-prompted change of modals while the + /// emulation thread is running. + std::mutex producer_mutex_; - /// A pointer to the next thing that should be provided to the caller for data. - PointerSet write_pointers_; + /// A pointer to the next thing that should be provided to the caller for data. + PointerSet write_pointers_; - // The owner-supplied scan buffer and size. - Scan *scan_buffer_ = nullptr; - size_t scan_buffer_size_ = 0; + // The owner-supplied scan buffer and size. + Scan *scan_buffer_ = nullptr; + size_t scan_buffer_size_ = 0; - // The owner-supplied line buffer and size. - Line *line_buffer_ = nullptr; - LineMetadata *line_metadata_buffer_ = nullptr; - size_t line_buffer_size_ = 0; + // The owner-supplied line buffer and size. + Line *line_buffer_ = nullptr; + LineMetadata *line_metadata_buffer_ = nullptr; + size_t line_buffer_size_ = 0; - // Current modals and whether they've yet been returned - // from a call to @c get_new_modals. - Modals modals_; - std::atomic<bool> modals_are_dirty_ = false; + // Current modals and whether they've yet been returned + // from a call to @c get_new_modals. + Modals modals_; + std::atomic<bool> modals_are_dirty_ = false; - // Provides a per-data size implementation of end_data; a previous - // implementation used blind memcpy and that turned into something - // of a profiling hot spot. - template <typename DataUnit> void end_data(size_t actual_length); + // Provides a per-data size implementation of end_data; a previous + // implementation used blind memcpy and that turned into something + // of a profiling hot spot. + template <typename DataUnit> void end_data(size_t actual_length); #ifndef NDEBUG - // Debug features; these amount to API validation. - bool scan_is_ongoing_ = false; - size_t output_area_counter_ = 0; - size_t output_area_next_returned_ = 0; + // Debug features; these amount to API validation. + bool scan_is_ongoing_ = false; + size_t output_area_counter_ = 0; + size_t output_area_next_returned_ = 0; #endif }; diff --git a/Outputs/Speaker/Implementation/BufferSource.hpp b/Outputs/Speaker/Implementation/BufferSource.hpp index 3ac7eb323..e806a7951 100644 --- a/Outputs/Speaker/Implementation/BufferSource.hpp +++ b/Outputs/Speaker/Implementation/BufferSource.hpp @@ -100,52 +100,52 @@ class BufferSource { /// template <typename SourceT, bool stereo, int divider = 1> struct SampleSource: public BufferSource<SourceT, stereo> { - public: - template <Action action> - void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target) { - auto &source = *static_cast<SourceT *>(this); +public: + template <Action action> + void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target) { + auto &source = *static_cast<SourceT *>(this); - if constexpr (divider == 1) { - while(number_of_samples--) { - apply<action>(*target, source.level()); - ++target; - source.advance(); - } - } else { - std::size_t c = 0; - - // Fill in the tail of any partially-captured level. - auto level = source.level(); - while(c < number_of_samples && master_divider_ != divider) { - apply<action>(target[c], level); - ++c; - ++master_divider_; - } + if constexpr (divider == 1) { + while(number_of_samples--) { + apply<action>(*target, source.level()); + ++target; source.advance(); - - // Provide all full levels. - auto whole_steps = static_cast<int>((number_of_samples - c) / divider); - while(whole_steps--) { - fill<action>(&target[c], &target[c + divider], source.level()); - c += divider; - source.advance(); - } - - // Provide the head of a further partial capture. - level = source.level(); - master_divider_ = static_cast<int>(number_of_samples - c); - fill<action>(&target[c], &target[number_of_samples], source.level()); } + } else { + std::size_t c = 0; + + // Fill in the tail of any partially-captured level. + auto level = source.level(); + while(c < number_of_samples && master_divider_ != divider) { + apply<action>(target[c], level); + ++c; + ++master_divider_; + } + source.advance(); + + // Provide all full levels. + auto whole_steps = static_cast<int>((number_of_samples - c) / divider); + while(whole_steps--) { + fill<action>(&target[c], &target[c + divider], source.level()); + c += divider; + source.advance(); + } + + // Provide the head of a further partial capture. + level = source.level(); + master_divider_ = static_cast<int>(number_of_samples - c); + fill<action>(&target[c], &target[number_of_samples], source.level()); } + } - // TODO: use a concept here, when C++20 filters through. - // - // Until then: sample sources should implement this. -// typename SampleT<stereo>::type level() const; -// void advance(); + // TODO: use a concept here, when C++20 filters through. + // + // Until then: sample sources should implement this. +// typename SampleT<stereo>::type level() const; +// void advance(); - private: - int master_divider_{}; +private: + int master_divider_{}; }; } diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index 2cafa10c7..8ff10d9fa 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -45,139 +45,140 @@ template <typename... S> constexpr bool are_properly_ordered() { An owner may optionally assign relative volumes. */ template <typename... T> class CompoundSource: - public Outputs::Speaker::BufferSource<CompoundSource<T...>, ::Outputs::Speaker::is_stereo<T...>()> { - private: - template <typename... S> class CompoundSourceHolder { - public: - static constexpr bool is_stereo = false; - void set_scaled_volume_range(int16_t, double *, double) {} - static constexpr std::size_t size() { return 0; } - double total_scale(double *) const { return 0.0; } - }; - - template <typename S, typename... R> class CompoundSourceHolder<S, R...> { - public: - CompoundSourceHolder(S &source, R &...next) : source_(source), next_source_(next...) {} - - static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder<R...>::is_stereo; - - template <Outputs::Speaker::Action action, bool output_stereo> - void apply_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT<output_stereo>::type *target) { - - // If this is the step at which a mono-to-stereo adaptation happens, apply it. - if constexpr (output_stereo && !S::is_stereo) { - // There'll be only one place in the chain that this conversion happens, but it'll - // happen there often. So avoid continuously reallocating. - if(conversion_source_.size() < number_of_samples) { - conversion_source_.resize(number_of_samples); - } - - // Populate the conversion buffer with this source and all below. - apply_samples<Action::Store, false>(number_of_samples, conversion_source_.data()); - - // Map up and return. - for(std::size_t c = 0; c < number_of_samples; c++) { - Outputs::Speaker::apply<action>(target[c], StereoSample(conversion_source_[c])); - } - } else { - constexpr bool is_final_source = sizeof...(R) == 0; - - // Get the rest of the output, if any. - if constexpr (!is_final_source) { - next_source_.template apply_samples<action, output_stereo>(number_of_samples, target); - } - - if(source_.is_zero_level()) { - // This component is currently outputting silence; therefore don't add anything to the output - // audio. Zero fill only if this is the final source (as everything above will be additive). - if constexpr (is_final_source) { - Outputs::Speaker::fill<action>(target, target + number_of_samples, typename SampleT<output_stereo>::type()); - } - return; - } - - // Add in this component's output. - source_.template apply_samples<is_final_source ? Action::Store : Action::Mix>(number_of_samples, target); - } - } - - void set_scaled_volume_range(int16_t range, double *volumes, double scale) { - const auto scaled_range = volumes[0] / double(source_.average_output_peak()) * double(range) / scale; - source_.set_sample_volume_range(int16_t(scaled_range)); - next_source_.set_scaled_volume_range(range, &volumes[1], scale); - } - - static constexpr std::size_t size() { - return 1 + CompoundSourceHolder<R...>::size(); - } - - double total_scale(double *volumes) const { - return (volumes[0] / source_.average_output_peak()) + next_source_.total_scale(&volumes[1]); - } - - private: - S &source_; - CompoundSourceHolder<R...> next_source_; - std::vector<MonoSample> conversion_source_; - }; + public Outputs::Speaker::BufferSource<CompoundSource<T...>, ::Outputs::Speaker::is_stereo<T...>()> +{ +private: + template <typename... S> class CompoundSourceHolder { + public: + static constexpr bool is_stereo = false; + void set_scaled_volume_range(int16_t, double *, double) {} + static constexpr std::size_t size() { return 0; } + double total_scale(double *) const { return 0.0; } + }; + template <typename S, typename... R> class CompoundSourceHolder<S, R...> { public: - using Sample = typename SampleT<::Outputs::Speaker::is_stereo<T...>()>::type; + CompoundSourceHolder(S &source, R &...next) : source_(source), next_source_(next...) {} - // To ensure at most one mono to stereo conversion, require appropriate source ordering. - static_assert(are_properly_ordered<T...>(), "Sources should be listed with all stereo sources before all mono sources"); + static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder<R...>::is_stereo; - CompoundSource(T &... sources) : source_holder_(sources...) { - // Default: give all sources equal volume. - const auto volume = 1.0 / double(source_holder_.size()); - for(std::size_t c = 0; c < source_holder_.size(); ++c) { - volumes_.push_back(volume); + template <Outputs::Speaker::Action action, bool output_stereo> + void apply_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT<output_stereo>::type *target) { + + // If this is the step at which a mono-to-stereo adaptation happens, apply it. + if constexpr (output_stereo && !S::is_stereo) { + // There'll be only one place in the chain that this conversion happens, but it'll + // happen there often. So avoid continuously reallocating. + if(conversion_source_.size() < number_of_samples) { + conversion_source_.resize(number_of_samples); + } + + // Populate the conversion buffer with this source and all below. + apply_samples<Action::Store, false>(number_of_samples, conversion_source_.data()); + + // Map up and return. + for(std::size_t c = 0; c < number_of_samples; c++) { + Outputs::Speaker::apply<action>(target[c], StereoSample(conversion_source_[c])); + } + } else { + constexpr bool is_final_source = sizeof...(R) == 0; + + // Get the rest of the output, if any. + if constexpr (!is_final_source) { + next_source_.template apply_samples<action, output_stereo>(number_of_samples, target); + } + + if(source_.is_zero_level()) { + // This component is currently outputting silence; therefore don't add anything to the output + // audio. Zero fill only if this is the final source (as everything above will be additive). + if constexpr (is_final_source) { + Outputs::Speaker::fill<action>(target, target + number_of_samples, typename SampleT<output_stereo>::type()); + } + return; + } + + // Add in this component's output. + source_.template apply_samples<is_final_source ? Action::Store : Action::Mix>(number_of_samples, target); } } - template <Outputs::Speaker::Action action> - void apply_samples(std::size_t number_of_samples, Sample *target) { - source_holder_.template apply_samples<action, ::Outputs::Speaker::is_stereo<T...>()>(number_of_samples, target); + void set_scaled_volume_range(int16_t range, double *volumes, double scale) { + const auto scaled_range = volumes[0] / double(source_.average_output_peak()) * double(range) / scale; + source_.set_sample_volume_range(int16_t(scaled_range)); + next_source_.set_scaled_volume_range(range, &volumes[1], scale); } - /*! - Sets the total output volume of this CompoundSource. - */ - void set_sample_volume_range(int16_t range) { - volume_range_ = range; - push_volumes(); + static constexpr std::size_t size() { + return 1 + CompoundSourceHolder<R...>::size(); } - /*! - Sets the relative volumes of the various sources underlying this - compound. The caller should ensure that the number of items supplied - matches the number of sources and that the values in it sum to 1.0. - */ - void set_relative_volumes(const std::vector<double> &volumes) { - assert(volumes.size() == source_holder_.size()); - volumes_ = volumes; - push_volumes(); - average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data()); - } - - /*! - @returns the average output peak given the sources owned by this CompoundSource and the - current relative volumes. - */ - double average_output_peak() const { - return average_output_peak_; + double total_scale(double *volumes) const { + return (volumes[0] / source_.average_output_peak()) + next_source_.total_scale(&volumes[1]); } private: - void push_volumes() { - const double scale = source_holder_.total_scale(volumes_.data()); - source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale); - } + S &source_; + CompoundSourceHolder<R...> next_source_; + std::vector<MonoSample> conversion_source_; + }; - CompoundSourceHolder<T...> source_holder_; - std::vector<double> volumes_; - int16_t volume_range_ = 0; - std::atomic<double> average_output_peak_{1.0}; +public: + using Sample = typename SampleT<::Outputs::Speaker::is_stereo<T...>()>::type; + + // To ensure at most one mono to stereo conversion, require appropriate source ordering. + static_assert(are_properly_ordered<T...>(), "Sources should be listed with all stereo sources before all mono sources"); + + CompoundSource(T &... sources) : source_holder_(sources...) { + // Default: give all sources equal volume. + const auto volume = 1.0 / double(source_holder_.size()); + for(std::size_t c = 0; c < source_holder_.size(); ++c) { + volumes_.push_back(volume); + } + } + + template <Outputs::Speaker::Action action> + void apply_samples(std::size_t number_of_samples, Sample *target) { + source_holder_.template apply_samples<action, ::Outputs::Speaker::is_stereo<T...>()>(number_of_samples, target); + } + + /*! + Sets the total output volume of this CompoundSource. + */ + void set_sample_volume_range(int16_t range) { + volume_range_ = range; + push_volumes(); + } + + /*! + Sets the relative volumes of the various sources underlying this + compound. The caller should ensure that the number of items supplied + matches the number of sources and that the values in it sum to 1.0. + */ + void set_relative_volumes(const std::vector<double> &volumes) { + assert(volumes.size() == source_holder_.size()); + volumes_ = volumes; + push_volumes(); + average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data()); + } + + /*! + @returns the average output peak given the sources owned by this CompoundSource and the + current relative volumes. + */ + double average_output_peak() const { + return average_output_peak_; + } + +private: + void push_volumes() { + const double scale = source_holder_.total_scale(volumes_.data()); + source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale); + } + + CompoundSourceHolder<T...> source_holder_; + std::vector<double> volumes_; + int16_t volume_range_ = 0; + std::atomic<double> average_output_peak_{1.0}; }; } diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index 1730022e8..b552ea91e 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -23,324 +23,324 @@ namespace Outputs::Speaker { template <typename ConcreteT, bool is_stereo> class LowpassBase: public Speaker { - public: - /*! - Sets the clock rate of the input audio. - */ - void set_input_rate(float cycles_per_second) { - std::lock_guard lock_guard(filter_parameters_mutex_); - if(filter_parameters_.input_cycles_per_second == cycles_per_second) { - return; - } - filter_parameters_.input_cycles_per_second = cycles_per_second; - filter_parameters_.parameters_are_dirty = true; - filter_parameters_.input_rate_changed = true; +public: + /*! + Sets the clock rate of the input audio. + */ + void set_input_rate(float cycles_per_second) { + std::lock_guard lock_guard(filter_parameters_mutex_); + if(filter_parameters_.input_cycles_per_second == cycles_per_second) { + return; + } + filter_parameters_.input_cycles_per_second = cycles_per_second; + filter_parameters_.parameters_are_dirty = true; + filter_parameters_.input_rate_changed = true; + } + + /*! + Allows a cut-off frequency to be specified for audio. Ordinarily this low-pass speaker + will determine a cut-off based on the output audio rate. A caller can manually select + an alternative cut-off. This allows machines with a low-pass filter on their audio output + path to be explicit about its effect, and get that simulation for free. + */ + void set_high_frequency_cutoff(float high_frequency) { + std::lock_guard lock_guard(filter_parameters_mutex_); + if(filter_parameters_.high_frequency_cutoff == high_frequency) { + return; + } + filter_parameters_.high_frequency_cutoff = high_frequency; + filter_parameters_.parameters_are_dirty = true; + } + +private: + float get_ideal_clock_rate_in_range(float minimum, float maximum) final { + std::lock_guard lock_guard(filter_parameters_mutex_); + + // Return twice the cut off, if applicable. + if( filter_parameters_.high_frequency_cutoff > 0.0f && + filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.0f && + filter_parameters_.input_cycles_per_second <= filter_parameters_.high_frequency_cutoff * 3.0f) + return filter_parameters_.high_frequency_cutoff * 3.0f; + + // Return exactly the input rate if possible. + if( filter_parameters_.input_cycles_per_second >= minimum && + filter_parameters_.input_cycles_per_second <= maximum) + return filter_parameters_.input_cycles_per_second; + + // If the input rate is lower, return the minimum... + if(filter_parameters_.input_cycles_per_second < minimum) + return minimum; + + // ... otherwise, return the maximum. + return maximum; + } + + // Implemented as per Speaker. + void set_computed_output_rate(float cycles_per_second, int buffer_size, bool) final { + std::lock_guard lock_guard(filter_parameters_mutex_); + if(filter_parameters_.output_cycles_per_second == cycles_per_second && size_t(buffer_size) == output_buffer_.size()) { + return; } - /*! - Allows a cut-off frequency to be specified for audio. Ordinarily this low-pass speaker - will determine a cut-off based on the output audio rate. A caller can manually select - an alternative cut-off. This allows machines with a low-pass filter on their audio output - path to be explicit about its effect, and get that simulation for free. - */ - void set_high_frequency_cutoff(float high_frequency) { - std::lock_guard lock_guard(filter_parameters_mutex_); - if(filter_parameters_.high_frequency_cutoff == high_frequency) { - return; - } - filter_parameters_.high_frequency_cutoff = high_frequency; - filter_parameters_.parameters_are_dirty = true; + filter_parameters_.output_cycles_per_second = cycles_per_second; + filter_parameters_.parameters_are_dirty = true; + output_buffer_.resize(std::size_t(buffer_size) * (is_stereo + 1)); + } + + // MARK: - Filtering. + + std::size_t output_buffer_pointer_ = 0; + std::size_t input_buffer_depth_ = 0; + std::vector<int16_t> input_buffer_; + std::vector<int16_t> output_buffer_; + + float step_rate_ = 0.0f; + float position_error_ = 0.0f; + std::unique_ptr<SignalProcessing::FIRFilter> filter_; + + std::mutex filter_parameters_mutex_; + struct FilterParameters { + float input_cycles_per_second = 0.0f; + float output_cycles_per_second = 0.0f; + float high_frequency_cutoff = -1.0; + + bool parameters_are_dirty = true; + bool input_rate_changed = false; + } filter_parameters_; + + void update_filter_coefficients(const FilterParameters &filter_parameters) { + float high_pass_frequency = filter_parameters.output_cycles_per_second / 2.0f; + if(filter_parameters.high_frequency_cutoff > 0.0) { + high_pass_frequency = std::min(filter_parameters.high_frequency_cutoff, high_pass_frequency); } - private: - float get_ideal_clock_rate_in_range(float minimum, float maximum) final { - std::lock_guard lock_guard(filter_parameters_mutex_); + // Make a guess at a good number of taps. + std::size_t number_of_taps = std::size_t( + ceilf((filter_parameters.input_cycles_per_second + high_pass_frequency) / high_pass_frequency) + ); + number_of_taps = (number_of_taps * 2) | 1; - // Return twice the cut off, if applicable. - if( filter_parameters_.high_frequency_cutoff > 0.0f && - filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.0f && - filter_parameters_.input_cycles_per_second <= filter_parameters_.high_frequency_cutoff * 3.0f) - return filter_parameters_.high_frequency_cutoff * 3.0f; + step_rate_ = filter_parameters.input_cycles_per_second / filter_parameters.output_cycles_per_second; + position_error_ = 0.0f; - // Return exactly the input rate if possible. - if( filter_parameters_.input_cycles_per_second >= minimum && - filter_parameters_.input_cycles_per_second <= maximum) - return filter_parameters_.input_cycles_per_second; + filter_ = std::make_unique<SignalProcessing::FIRFilter>( + unsigned(number_of_taps), + filter_parameters.input_cycles_per_second, + 0.0, + high_pass_frequency, + SignalProcessing::FIRFilter::DefaultAttenuation); - // If the input rate is lower, return the minimum... - if(filter_parameters_.input_cycles_per_second < minimum) - return minimum; - - // ... otherwise, return the maximum. - return maximum; + // Pick the new conversion function. + if( filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second && + filter_parameters.high_frequency_cutoff < 0.0) { + // If input and output rates exactly match, and no additional cut-off has been specified, + // just accumulate results and pass on. + conversion_ = Conversion::Copy; + } else if( filter_parameters.input_cycles_per_second > filter_parameters.output_cycles_per_second || + (filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second && filter_parameters.high_frequency_cutoff >= 0.0)) { + // If the output rate is less than the input rate, or an additional cut-off has been specified, use the filter. + conversion_ = Conversion::ResampleSmaller; + } else { + conversion_ = Conversion::ResampleLarger; } - // Implemented as per Speaker. - void set_computed_output_rate(float cycles_per_second, int buffer_size, bool) final { - std::lock_guard lock_guard(filter_parameters_mutex_); - if(filter_parameters_.output_cycles_per_second == cycles_per_second && size_t(buffer_size) == output_buffer_.size()) { - return; - } + // Do something sensible with any dangling input, if necessary. + const int scale = static_cast<ConcreteT *>(this)->get_scale(); + switch(conversion_) { + // Neither direct copying nor resampling larger currently use any temporary input. + // Although in the latter case that's just because it's unimplemented. But, regardless, + // that means nothing to do. + default: break; - filter_parameters_.output_cycles_per_second = cycles_per_second; - filter_parameters_.parameters_are_dirty = true; - output_buffer_.resize(std::size_t(buffer_size) * (is_stereo + 1)); - } - - // MARK: - Filtering. - - std::size_t output_buffer_pointer_ = 0; - std::size_t input_buffer_depth_ = 0; - std::vector<int16_t> input_buffer_; - std::vector<int16_t> output_buffer_; - - float step_rate_ = 0.0f; - float position_error_ = 0.0f; - std::unique_ptr<SignalProcessing::FIRFilter> filter_; - - std::mutex filter_parameters_mutex_; - struct FilterParameters { - float input_cycles_per_second = 0.0f; - float output_cycles_per_second = 0.0f; - float high_frequency_cutoff = -1.0; - - bool parameters_are_dirty = true; - bool input_rate_changed = false; - } filter_parameters_; - - void update_filter_coefficients(const FilterParameters &filter_parameters) { - float high_pass_frequency = filter_parameters.output_cycles_per_second / 2.0f; - if(filter_parameters.high_frequency_cutoff > 0.0) { - high_pass_frequency = std::min(filter_parameters.high_frequency_cutoff, high_pass_frequency); - } - - // Make a guess at a good number of taps. - std::size_t number_of_taps = std::size_t( - ceilf((filter_parameters.input_cycles_per_second + high_pass_frequency) / high_pass_frequency) - ); - number_of_taps = (number_of_taps * 2) | 1; - - step_rate_ = filter_parameters.input_cycles_per_second / filter_parameters.output_cycles_per_second; - position_error_ = 0.0f; - - filter_ = std::make_unique<SignalProcessing::FIRFilter>( - unsigned(number_of_taps), - filter_parameters.input_cycles_per_second, - 0.0, - high_pass_frequency, - SignalProcessing::FIRFilter::DefaultAttenuation); - - // Pick the new conversion function. - if( filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second && - filter_parameters.high_frequency_cutoff < 0.0) { - // If input and output rates exactly match, and no additional cut-off has been specified, - // just accumulate results and pass on. - conversion_ = Conversion::Copy; - } else if( filter_parameters.input_cycles_per_second > filter_parameters.output_cycles_per_second || - (filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second && filter_parameters.high_frequency_cutoff >= 0.0)) { - // If the output rate is less than the input rate, or an additional cut-off has been specified, use the filter. - conversion_ = Conversion::ResampleSmaller; - } else { - conversion_ = Conversion::ResampleLarger; - } - - // Do something sensible with any dangling input, if necessary. - const int scale = static_cast<ConcreteT *>(this)->get_scale(); - switch(conversion_) { - // Neither direct copying nor resampling larger currently use any temporary input. - // Although in the latter case that's just because it's unimplemented. But, regardless, - // that means nothing to do. - default: break; - - case Conversion::ResampleSmaller: { - // Reize the input buffer only if absolutely necessary; if sizing downward - // such that a sample would otherwise be lost then output it now. Keep anything - // currently in the input buffer that hasn't yet been processed. - const size_t required_buffer_size = size_t(number_of_taps) * (is_stereo + 1); - if(input_buffer_.size() != required_buffer_size) { - if(input_buffer_depth_ >= required_buffer_size) { - resample_input_buffer(scale); - input_buffer_depth_ %= required_buffer_size; - } - input_buffer_.resize(required_buffer_size); + case Conversion::ResampleSmaller: { + // Reize the input buffer only if absolutely necessary; if sizing downward + // such that a sample would otherwise be lost then output it now. Keep anything + // currently in the input buffer that hasn't yet been processed. + const size_t required_buffer_size = size_t(number_of_taps) * (is_stereo + 1); + if(input_buffer_.size() != required_buffer_size) { + if(input_buffer_depth_ >= required_buffer_size) { + resample_input_buffer(scale); + input_buffer_depth_ %= required_buffer_size; } - } break; - } + input_buffer_.resize(required_buffer_size); + } + } break; + } + } + + inline void resample_input_buffer(int scale) { + if(output_buffer_.empty()) { + return; } - inline void resample_input_buffer(int scale) { - if(output_buffer_.empty()) { - return; - } + if constexpr (is_stereo) { + output_buffer_[output_buffer_pointer_ + 0] = filter_->apply(input_buffer_.data(), 2); + output_buffer_[output_buffer_pointer_ + 1] = filter_->apply(input_buffer_.data() + 1, 2); + output_buffer_pointer_+= 2; + } else { + output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data()); + output_buffer_pointer_++; + } + // Apply scale, if supplied, clamping appropriately. + if(scale != 65536) { + #define SCALE(x) x = int16_t(std::clamp((int(x) * scale) >> 16, -32768, 32767)) if constexpr (is_stereo) { - output_buffer_[output_buffer_pointer_ + 0] = filter_->apply(input_buffer_.data(), 2); - output_buffer_[output_buffer_pointer_ + 1] = filter_->apply(input_buffer_.data() + 1, 2); - output_buffer_pointer_+= 2; + SCALE(output_buffer_[output_buffer_pointer_ - 2]); + SCALE(output_buffer_[output_buffer_pointer_ - 1]); } else { - output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data()); - output_buffer_pointer_++; - } - - // Apply scale, if supplied, clamping appropriately. - if(scale != 65536) { - #define SCALE(x) x = int16_t(std::clamp((int(x) * scale) >> 16, -32768, 32767)) - if constexpr (is_stereo) { - SCALE(output_buffer_[output_buffer_pointer_ - 2]); - SCALE(output_buffer_[output_buffer_pointer_ - 1]); - } else { - SCALE(output_buffer_[output_buffer_pointer_ - 1]); - } - #undef SCALE - } - - // Announce to delegate if full. - if(output_buffer_pointer_ == output_buffer_.size()) { - output_buffer_pointer_ = 0; - did_complete_samples(this, output_buffer_, is_stereo); - } - - // If the next loop around is going to reuse some of the samples just collected, use a memmove to - // preserve them in the correct locations (TODO: use a longer buffer to fix that?) and don't skip - // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. - const size_t steps = size_t(step_rate_ + position_error_) * (is_stereo + 1); - position_error_ = fmodf(step_rate_ + position_error_, 1.0f); - if(steps < input_buffer_.size()) { - auto *const input_buffer = input_buffer_.data(); - std::memmove( input_buffer, - &input_buffer[steps], - sizeof(int16_t) * (input_buffer_.size() - steps)); - input_buffer_depth_ -= steps; - } else { - if(steps > input_buffer_.size()) { - static_cast<ConcreteT *>(this)->skip_samples((steps - input_buffer_.size()) / (1 + is_stereo)); - } - input_buffer_depth_ = 0; + SCALE(output_buffer_[output_buffer_pointer_ - 1]); } + #undef SCALE } - enum class Conversion { - ResampleSmaller, - Copy, - ResampleLarger - } conversion_ = Conversion::Copy; - - bool recalculate_filter_if_dirty() { - FilterParameters filter_parameters; - { - std::lock_guard lock_guard(filter_parameters_mutex_); - filter_parameters = filter_parameters_; - filter_parameters_.parameters_are_dirty = false; - filter_parameters_.input_rate_changed = false; - } - if(filter_parameters.parameters_are_dirty) update_filter_coefficients(filter_parameters); - return filter_parameters.input_rate_changed; + // Announce to delegate if full. + if(output_buffer_pointer_ == output_buffer_.size()) { + output_buffer_pointer_ = 0; + did_complete_samples(this, output_buffer_, is_stereo); } - protected: - bool process(size_t length) { - const auto delegate = delegate_.load(std::memory_order_relaxed); - if(!delegate) return false; - - const int scale = static_cast<ConcreteT *>(this)->get_scale(); - - if(recalculate_filter_if_dirty()) { - delegate->speaker_did_change_input_clock(this); + // If the next loop around is going to reuse some of the samples just collected, use a memmove to + // preserve them in the correct locations (TODO: use a longer buffer to fix that?) and don't skip + // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. + const size_t steps = size_t(step_rate_ + position_error_) * (is_stereo + 1); + position_error_ = fmodf(step_rate_ + position_error_, 1.0f); + if(steps < input_buffer_.size()) { + auto *const input_buffer = input_buffer_.data(); + std::memmove( input_buffer, + &input_buffer[steps], + sizeof(int16_t) * (input_buffer_.size() - steps)); + input_buffer_depth_ -= steps; + } else { + if(steps > input_buffer_.size()) { + static_cast<ConcreteT *>(this)->skip_samples((steps - input_buffer_.size()) / (1 + is_stereo)); } + input_buffer_depth_ = 0; + } + } - switch(conversion_) { - case Conversion::Copy: - while(length) { - const auto samples_to_read = std::min((output_buffer_.size() - output_buffer_pointer_) / (1 + is_stereo), length); - static_cast<ConcreteT *>(this)->get_samples(samples_to_read, &output_buffer_[output_buffer_pointer_ ]); - output_buffer_pointer_ += samples_to_read * (1 + is_stereo); + enum class Conversion { + ResampleSmaller, + Copy, + ResampleLarger + } conversion_ = Conversion::Copy; - // TODO: apply scale. + bool recalculate_filter_if_dirty() { + FilterParameters filter_parameters; + { + std::lock_guard lock_guard(filter_parameters_mutex_); + filter_parameters = filter_parameters_; + filter_parameters_.parameters_are_dirty = false; + filter_parameters_.input_rate_changed = false; + } + if(filter_parameters.parameters_are_dirty) update_filter_coefficients(filter_parameters); + return filter_parameters.input_rate_changed; + } - // Announce to delegate if full. - if(output_buffer_pointer_ == output_buffer_.size()) { - output_buffer_pointer_ = 0; - did_complete_samples(this, output_buffer_, is_stereo); - } +protected: + bool process(size_t length) { + const auto delegate = delegate_.load(std::memory_order_relaxed); + if(!delegate) return false; - length -= samples_to_read; + const int scale = static_cast<ConcreteT *>(this)->get_scale(); + + if(recalculate_filter_if_dirty()) { + delegate->speaker_did_change_input_clock(this); + } + + switch(conversion_) { + case Conversion::Copy: + while(length) { + const auto samples_to_read = std::min((output_buffer_.size() - output_buffer_pointer_) / (1 + is_stereo), length); + static_cast<ConcreteT *>(this)->get_samples(samples_to_read, &output_buffer_[output_buffer_pointer_ ]); + output_buffer_pointer_ += samples_to_read * (1 + is_stereo); + + // TODO: apply scale. + + // Announce to delegate if full. + if(output_buffer_pointer_ == output_buffer_.size()) { + output_buffer_pointer_ = 0; + did_complete_samples(this, output_buffer_, is_stereo); } - break; - case Conversion::ResampleSmaller: - while(length) { - const auto cycles_to_read = std::min((input_buffer_.size() - input_buffer_depth_) / (1 + is_stereo), length); - static_cast<ConcreteT *>(this)->get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]); - input_buffer_depth_ += cycles_to_read * (1 + is_stereo); + length -= samples_to_read; + } + break; - if(input_buffer_depth_ == input_buffer_.size()) { - resample_input_buffer(scale); - } + case Conversion::ResampleSmaller: + while(length) { + const auto cycles_to_read = std::min((input_buffer_.size() - input_buffer_depth_) / (1 + is_stereo), length); + static_cast<ConcreteT *>(this)->get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]); + input_buffer_depth_ += cycles_to_read * (1 + is_stereo); - length -= cycles_to_read; + if(input_buffer_depth_ == input_buffer_.size()) { + resample_input_buffer(scale); } - break; - case Conversion::ResampleLarger: - // TODO: input rate is less than output rate. - break; - } + length -= cycles_to_read; + } + break; - return true; + case Conversion::ResampleLarger: + // TODO: input rate is less than output rate. + break; } + + return true; + } }; /*! Provides a low-pass speaker to which blocks of samples are pushed. */ template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_stereo>, is_stereo> { - private: - using BaseT = LowpassBase<PushLowpass<is_stereo>, is_stereo>; - friend BaseT; - using BaseT::process; +private: + using BaseT = LowpassBase<PushLowpass<is_stereo>, is_stereo>; + friend BaseT; + using BaseT::process; - std::atomic<int> scale_ = 65536; - int get_scale() const { - return scale_.load(std::memory_order_relaxed); - } + std::atomic<int> scale_ = 65536; + int get_scale() const { + return scale_.load(std::memory_order_relaxed); + } - const int16_t *buffer_ = nullptr; + const int16_t *buffer_ = nullptr; - void skip_samples(size_t count) { - buffer_ += count; - } + void skip_samples(size_t count) { + buffer_ += count; + } - void get_samples(size_t length, int16_t *target) { - const auto word_length = length * (1 + is_stereo); - memcpy(target, buffer_, word_length * sizeof(int16_t)); - buffer_ += word_length; - } + void get_samples(size_t length, int16_t *target) { + const auto word_length = length * (1 + is_stereo); + memcpy(target, buffer_, word_length * sizeof(int16_t)); + buffer_ += word_length; + } - public: - void set_output_volume(float volume) final { - scale_.store(int(std::clamp(volume * 65536.0f, 0.0f, 65536.0f))); - } +public: + void set_output_volume(float volume) final { + scale_.store(int(std::clamp(volume * 65536.0f, 0.0f, 65536.0f))); + } - bool get_is_stereo() final { - return is_stereo; - } + bool get_is_stereo() final { + return is_stereo; + } - /*! - Filters and posts onward the provided buffer, on the calling thread. + /*! + Filters and posts onward the provided buffer, on the calling thread. - @param buffer The source for samples. - @param length The number of samples provided; in mono this will be the number of int16_ts - it is safe to read from @c buffer, and in stereo it will be half the number — it is a count - of the number of time points at which audio was sampled. - */ - void push(const int16_t *buffer, size_t length) { - buffer_ = buffer; + @param buffer The source for samples. + @param length The number of samples provided; in mono this will be the number of int16_ts + it is safe to read from @c buffer, and in stereo it will be half the number — it is a count + of the number of time points at which audio was sampled. + */ + void push(const int16_t *buffer, size_t length) { + buffer_ = buffer; #ifndef NDEBUG - const bool did_process = + const bool did_process = #endif - process(length); - assert(!did_process || buffer_ == buffer + (length * (1 + is_stereo))); - } + process(length); + assert(!did_process || buffer_ == buffer + (length * (1 + is_stereo))); + } }; /*! @@ -350,68 +350,68 @@ template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_s lower-frequency output. */ template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpass<SampleSource>, SampleSource::is_stereo> { - public: - PullLowpass(SampleSource &sample_source) : sample_source_(sample_source) { - // Propagate an initial volume level. - sample_source.set_sample_volume_range(32767); +public: + PullLowpass(SampleSource &sample_source) : sample_source_(sample_source) { + // Propagate an initial volume level. + sample_source.set_sample_volume_range(32767); + } + + void set_output_volume(float volume) final { + // Clamp to the acceptable range, and set. + volume = std::clamp(volume, 0.0f, 1.0f); + sample_source_.set_sample_volume_range(int16_t(32767.0f * volume)); + } + + bool get_is_stereo() final { + return SampleSource::is_stereo; + } + + /*! + Schedules an advancement by the number of cycles specified on the provided queue. + The speaker will advance by obtaining data from the sample source supplied + at construction, filtering it and passing it on to the speaker's delegate if there is one. + */ + void run_for(Concurrency::AsyncTaskQueue<false> &queue, const Cycles cycles) { + if(cycles == Cycles(0)) { + return; } - void set_output_volume(float volume) final { - // Clamp to the acceptable range, and set. - volume = std::clamp(volume, 0.0f, 1.0f); - sample_source_.set_sample_volume_range(int16_t(32767.0f * volume)); - } - - bool get_is_stereo() final { - return SampleSource::is_stereo; - } - - /*! - Schedules an advancement by the number of cycles specified on the provided queue. - The speaker will advance by obtaining data from the sample source supplied - at construction, filtering it and passing it on to the speaker's delegate if there is one. - */ - void run_for(Concurrency::AsyncTaskQueue<false> &queue, const Cycles cycles) { - if(cycles == Cycles(0)) { - return; - } - - queue.enqueue([this, cycles] { - run_for(cycles); - }); - } - - private: - using BaseT = LowpassBase<PullLowpass<SampleSource>, SampleSource::is_stereo>; - friend BaseT; - using BaseT::process; - - /*! - Advances by the number of cycles specified, obtaining data from the sample source supplied - at construction, filtering it and passing it on to the speaker's delegate if there is one. - */ - void run_for(const Cycles cycles) { - process(size_t(cycles.as_integral())); - } - - SampleSource &sample_source_; - - void skip_samples(size_t count) { - sample_source_.template apply_samples<Action::Ignore>(count, nullptr); - } - - int get_scale() { - return int(65536.0 / sample_source_.average_output_peak()); - } - - void get_samples(size_t length, int16_t *target) { - if constexpr (SampleSource::is_stereo) { - StereoSample *const stereo_target = reinterpret_cast<StereoSample *>(target); - sample_source_.template apply_samples<Action::Store>(length, stereo_target); - } else { - sample_source_.template apply_samples<Action::Store>(length, target); - } + queue.enqueue([this, cycles] { + run_for(cycles); + }); + } + +private: + using BaseT = LowpassBase<PullLowpass<SampleSource>, SampleSource::is_stereo>; + friend BaseT; + using BaseT::process; + + /*! + Advances by the number of cycles specified, obtaining data from the sample source supplied + at construction, filtering it and passing it on to the speaker's delegate if there is one. + */ + void run_for(const Cycles cycles) { + process(size_t(cycles.as_integral())); + } + + SampleSource &sample_source_; + + void skip_samples(size_t count) { + sample_source_.template apply_samples<Action::Ignore>(count, nullptr); + } + + int get_scale() { + return int(65536.0 / sample_source_.average_output_peak()); + } + + void get_samples(size_t length, int16_t *target) { + if constexpr (SampleSource::is_stereo) { + StereoSample *const stereo_target = reinterpret_cast<StereoSample *>(target); + sample_source_.template apply_samples<Action::Store>(length, stereo_target); + } else { + sample_source_.template apply_samples<Action::Store>(length, target); } + } }; } diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp index c6a934156..aaed2b77f 100644 --- a/Outputs/Speaker/Speaker.hpp +++ b/Outputs/Speaker/Speaker.hpp @@ -61,128 +61,128 @@ template <bool stereo> struct SampleT { audio output. */ class Speaker { - public: - virtual ~Speaker() = default; +public: + virtual ~Speaker() = default; + + /*! + @returns The best output clock rate for the audio being supplied to this speaker, from the range given. + */ + virtual float get_ideal_clock_rate_in_range(float minimum, float maximum) = 0; + + /*! + @returns @c true if the device would most ideally output stereo sound; @c false otherwise. + */ + virtual bool get_is_stereo() = 0; + + /*! + Sets the actual output rate; packets provided to the delegate will conform to these + specifications regardless of the input. + */ + void set_output_rate(float cycles_per_second, int buffer_size, bool stereo) { + output_cycles_per_second_ = cycles_per_second; + output_buffer_size_ = buffer_size; + stereo_output_ = stereo; + compute_output_rate(); + } + + /*! + Takes a copy of the most recent output rate provided to @c rhs. + */ + void copy_output_rate(const Speaker &rhs) { + output_cycles_per_second_ = rhs.output_cycles_per_second_; + output_buffer_size_ = rhs.output_buffer_size_; + stereo_output_.store(rhs.stereo_output_.load(std::memory_order_relaxed), std::memory_order_relaxed); + compute_output_rate(); + } + + /// Sets the output volume, in the range [0, 1]. + virtual void set_output_volume(float) = 0; + + /*! + Speeds a speed multiplier for this machine, e.g. that it is currently being run at 2.0x its normal rate. + This will affect the number of input samples that are combined to produce one output sample. + */ + void set_input_rate_multiplier(float multiplier) { + input_rate_multiplier_ = multiplier; + compute_output_rate(); + } + + /*! + @returns The number of sample sets so far delivered to the delegate. + */ + int completed_sample_sets() const { return completed_sample_sets_; } + + /*! + Defines a receiver for audio packets. + */ + struct Delegate { + /*! + Indicates that a new audio packet is ready. If the output is stereo, samples will be interleaved with the first + being left, the second being right, etc. + */ + virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0; /*! - @returns The best output clock rate for the audio being supplied to this speaker, from the range given. + Provides the delegate with a hint that the input clock rate has changed, which provides an opportunity to + renegotiate the ideal clock rate, if desired. */ - virtual float get_ideal_clock_rate_in_range(float minimum, float maximum) = 0; + virtual void speaker_did_change_input_clock([[maybe_unused]] Speaker *speaker) {} + }; + virtual void set_delegate(Delegate *delegate) { + delegate_.store(delegate, std::memory_order_relaxed); + } - /*! - @returns @c true if the device would most ideally output stereo sound; @c false otherwise. - */ - virtual bool get_is_stereo() = 0; - /*! - Sets the actual output rate; packets provided to the delegate will conform to these - specifications regardless of the input. - */ - void set_output_rate(float cycles_per_second, int buffer_size, bool stereo) { - output_cycles_per_second_ = cycles_per_second; - output_buffer_size_ = buffer_size; - stereo_output_ = stereo; - compute_output_rate(); + // This is primarily exposed for MultiSpeaker et al; it's not for general callers. + virtual void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) = 0; + +protected: + void did_complete_samples(Speaker *, const std::vector<int16_t> &buffer, bool is_stereo) { + // Test the delegate for existence again, as it may have changed. + const auto delegate = delegate_.load(std::memory_order_relaxed); + if(!delegate) return; + + ++completed_sample_sets_; + + // Hope for the fast path first: producer and consumer agree about + // number of channels. + if(is_stereo == stereo_output_) { + delegate->speaker_did_complete_samples(this, buffer); + return; } - /*! - Takes a copy of the most recent output rate provided to @c rhs. - */ - void copy_output_rate(const Speaker &rhs) { - output_cycles_per_second_ = rhs.output_cycles_per_second_; - output_buffer_size_ = rhs.output_buffer_size_; - stereo_output_.store(rhs.stereo_output_.load(std::memory_order_relaxed), std::memory_order_relaxed); - compute_output_rate(); - } - - /// Sets the output volume, in the range [0, 1]. - virtual void set_output_volume(float) = 0; - - /*! - Speeds a speed multiplier for this machine, e.g. that it is currently being run at 2.0x its normal rate. - This will affect the number of input samples that are combined to produce one output sample. - */ - void set_input_rate_multiplier(float multiplier) { - input_rate_multiplier_ = multiplier; - compute_output_rate(); - } - - /*! - @returns The number of sample sets so far delivered to the delegate. - */ - int completed_sample_sets() const { return completed_sample_sets_; } - - /*! - Defines a receiver for audio packets. - */ - struct Delegate { - /*! - Indicates that a new audio packet is ready. If the output is stereo, samples will be interleaved with the first - being left, the second being right, etc. - */ - virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0; - - /*! - Provides the delegate with a hint that the input clock rate has changed, which provides an opportunity to - renegotiate the ideal clock rate, if desired. - */ - virtual void speaker_did_change_input_clock([[maybe_unused]] Speaker *speaker) {} - }; - virtual void set_delegate(Delegate *delegate) { - delegate_.store(delegate, std::memory_order_relaxed); - } - - - // This is primarily exposed for MultiSpeaker et al; it's not for general callers. - virtual void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) = 0; - - protected: - void did_complete_samples(Speaker *, const std::vector<int16_t> &buffer, bool is_stereo) { - // Test the delegate for existence again, as it may have changed. - const auto delegate = delegate_.load(std::memory_order_relaxed); - if(!delegate) return; - - ++completed_sample_sets_; - - // Hope for the fast path first: producer and consumer agree about - // number of channels. - if(is_stereo == stereo_output_) { - delegate->speaker_did_complete_samples(this, buffer); - return; + // Producer and consumer don't agree, so mix two channels to one, or double out one to two. + if(is_stereo) { + // Mix down. + mix_buffer_.resize(buffer.size() / 2); + for(size_t c = 0; c < mix_buffer_.size(); ++c) { + mix_buffer_[c] = (buffer[(c << 1) + 0] + buffer[(c << 1) + 1]) >> 1; + // TODO: is there an Accelerate framework solution to this? } - - // Producer and consumer don't agree, so mix two channels to one, or double out one to two. - if(is_stereo) { - // Mix down. - mix_buffer_.resize(buffer.size() / 2); - for(size_t c = 0; c < mix_buffer_.size(); ++c) { - mix_buffer_[c] = (buffer[(c << 1) + 0] + buffer[(c << 1) + 1]) >> 1; - // TODO: is there an Accelerate framework solution to this? - } - } else { - // Double up. - mix_buffer_.resize(buffer.size() * 2); - for(size_t c = 0; c < buffer.size(); ++c) { - mix_buffer_[(c << 1) + 0] = mix_buffer_[(c << 1) + 1] = buffer[c]; - } + } else { + // Double up. + mix_buffer_.resize(buffer.size() * 2); + for(size_t c = 0; c < buffer.size(); ++c) { + mix_buffer_[(c << 1) + 0] = mix_buffer_[(c << 1) + 1] = buffer[c]; } - delegate->speaker_did_complete_samples(this, mix_buffer_); } - std::atomic<Delegate *> delegate_{nullptr}; + delegate->speaker_did_complete_samples(this, mix_buffer_); + } + std::atomic<Delegate *> delegate_{nullptr}; - private: - void compute_output_rate() { - // The input rate multiplier is actually used as an output rate divider, - // to confirm to the public interface of a generic speaker being output-centric. - set_computed_output_rate(output_cycles_per_second_ / input_rate_multiplier_, output_buffer_size_, stereo_output_); - } +private: + void compute_output_rate() { + // The input rate multiplier is actually used as an output rate divider, + // to confirm to the public interface of a generic speaker being output-centric. + set_computed_output_rate(output_cycles_per_second_ / input_rate_multiplier_, output_buffer_size_, stereo_output_); + } - int completed_sample_sets_ = 0; - float input_rate_multiplier_ = 1.0f; - float output_cycles_per_second_ = 1.0f; - int output_buffer_size_ = 1; - std::atomic<bool> stereo_output_{false}; - std::vector<int16_t> mix_buffer_; + int completed_sample_sets_ = 0; + float input_rate_multiplier_ = 1.0f; + float output_cycles_per_second_ = 1.0f; + int output_buffer_size_ = 1; + std::atomic<bool> stereo_output_{false}; + std::vector<int16_t> mix_buffer_; }; } diff --git a/Processors/6502/6502.hpp b/Processors/6502/6502.hpp index 74983db96..a1b009d86 100644 --- a/Processors/6502/6502.hpp +++ b/Processors/6502/6502.hpp @@ -142,28 +142,28 @@ class ProcessorBase: public ProcessorStorage { can produce a minor runtime performance improvement. */ template <Personality personality, typename BusHandler, bool uses_ready_line> class Processor: public ProcessorBase { - public: - /*! - Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. - */ - Processor(BusHandler &bus_handler) : ProcessorBase(personality), bus_handler_(bus_handler) {} +public: + /*! + Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. + */ + Processor(BusHandler &bus_handler) : ProcessorBase(personality), bus_handler_(bus_handler) {} - /*! - Runs the 6502 for a supplied number of cycles. + /*! + Runs the 6502 for a supplied number of cycles. - @param cycles The number of cycles to run the 6502 for. - */ - void run_for(const Cycles cycles); + @param cycles The number of cycles to run the 6502 for. + */ + void run_for(const Cycles cycles); - /*! - Sets the current level of the RDY line. + /*! + Sets the current level of the RDY line. - @param active @c true if the line is logically active; @c false otherwise. - */ - void set_ready_line(bool active); + @param active @c true if the line is logically active; @c false otherwise. + */ + void set_ready_line(bool active); - private: - BusHandler &bus_handler_; +private: + BusHandler &bus_handler_; }; #include "Implementation/6502Implementation.hpp" diff --git a/Processors/6502/AllRAM/6502AllRAM.cpp b/Processors/6502/AllRAM/6502AllRAM.cpp index ca7963432..e5497a4d5 100644 --- a/Processors/6502/AllRAM/6502AllRAM.cpp +++ b/Processors/6502/AllRAM/6502AllRAM.cpp @@ -28,131 +28,131 @@ using Type = CPU::MOS6502Esque::Type; template <Type type, bool has_cias> class ConcreteAllRAMProcessor: public AllRAMProcessor, public CPU::MOS6502Esque::BusHandlerT<type> { - public: - using typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType; +public: + using typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType; - ConcreteAllRAMProcessor(size_t memory_size) : - AllRAMProcessor(memory_size), - mos6502_(*this), - cia1_(cia1_handler_), - cia2_(cia2_handler_) { - mos6502_.set_power_on(false); + ConcreteAllRAMProcessor(size_t memory_size) : + AllRAMProcessor(memory_size), + mos6502_(*this), + cia1_(cia1_handler_), + cia2_(cia2_handler_) { + mos6502_.set_power_on(false); + } + + Cycles perform_bus_operation(BusOperation operation, AddressType address, uint8_t *value) { + timestamp_ += Cycles(1); + + if constexpr (has_cias) { + cia1_.run_for(HalfCycles(2)); + cia2_.run_for(HalfCycles(2)); } - Cycles perform_bus_operation(BusOperation operation, AddressType address, uint8_t *value) { - timestamp_ += Cycles(1); - - if constexpr (has_cias) { - cia1_.run_for(HalfCycles(2)); - cia2_.run_for(HalfCycles(2)); + if(isAccessOperation(operation)) { + if(operation == BusOperation::ReadOpcode) { + if constexpr (LogProgramCounter) { + printf("[%04x] %02x a:%04x x:%04x y:%04x p:%02x s:%02x\n", address, memory_[address], + mos6502_.value_of(Register::A), + mos6502_.value_of(Register::X), + mos6502_.value_of(Register::Y), + mos6502_.value_of(Register::Flags) & 0xff, + mos6502_.value_of(Register::StackPointer) & 0xff); + } + check_address_for_trap(address); + --instructions_; } - if(isAccessOperation(operation)) { - if(operation == BusOperation::ReadOpcode) { - if constexpr (LogProgramCounter) { - printf("[%04x] %02x a:%04x x:%04x y:%04x p:%02x s:%02x\n", address, memory_[address], - mos6502_.value_of(Register::A), - mos6502_.value_of(Register::X), - mos6502_.value_of(Register::Y), - mos6502_.value_of(Register::Flags) & 0xff, - mos6502_.value_of(Register::StackPointer) & 0xff); - } - check_address_for_trap(address); - --instructions_; - } + if(isReadOperation(operation)) { + *value = memory_[address]; - if(isReadOperation(operation)) { - *value = memory_[address]; - - if constexpr (has_cias) { - if((address & 0xff00) == 0xdc00) { - *value = cia1_.read(address); - if constexpr (LogCIAAccesses) { - printf("[%d] CIA1: %04x -> %02x\n", timestamp_.as<int>(), address, *value); - } - } else if((address & 0xff00) == 0xdd00) { - *value = cia2_.read(address); - if constexpr (LogCIAAccesses) { - printf("[%d] CIA2: %04x -> %02x\n", timestamp_.as<int>(), address, *value); - } + if constexpr (has_cias) { + if((address & 0xff00) == 0xdc00) { + *value = cia1_.read(address); + if constexpr (LogCIAAccesses) { + printf("[%d] CIA1: %04x -> %02x\n", timestamp_.as<int>(), address, *value); + } + } else if((address & 0xff00) == 0xdd00) { + *value = cia2_.read(address); + if constexpr (LogCIAAccesses) { + printf("[%d] CIA2: %04x -> %02x\n", timestamp_.as<int>(), address, *value); } } + } - if constexpr (LogAllReads) { -// if((address&0xff00) == 0x100) { - printf("%04x -> %02x\n", address, *value); -// } - } - } else { - memory_[address] = *value; + if constexpr (LogAllReads) { +// if((address&0xff00) == 0x100) { + printf("%04x -> %02x\n", address, *value); +// } + } + } else { + memory_[address] = *value; - if constexpr (has_cias) { - if((address & 0xff00) == 0xdc00) { - cia1_.write(address, *value); - if constexpr (LogCIAAccesses) { - printf("[%d] CIA1: %04x <- %02x\n", timestamp_.as<int>(), address, *value); - } - } else if((address & 0xff00) == 0xdd00) { - cia2_.write(address, *value); - if constexpr (LogCIAAccesses) { - printf("[%d] CIA2: %04x <- %02x\n", timestamp_.as<int>(), address, *value); - } + if constexpr (has_cias) { + if((address & 0xff00) == 0xdc00) { + cia1_.write(address, *value); + if constexpr (LogCIAAccesses) { + printf("[%d] CIA1: %04x <- %02x\n", timestamp_.as<int>(), address, *value); + } + } else if((address & 0xff00) == 0xdd00) { + cia2_.write(address, *value); + if constexpr (LogCIAAccesses) { + printf("[%d] CIA2: %04x <- %02x\n", timestamp_.as<int>(), address, *value); } } + } - if constexpr (LogAllWrites) { -// if((address&0xff00) == 0x100) { - printf("%04x <- %02x\n", address, *value); -// } - } + if constexpr (LogAllWrites) { +// if((address&0xff00) == 0x100) { + printf("%04x <- %02x\n", address, *value); +// } } } - - mos6502_.set_irq_line(cia1_.get_interrupt_line()); - mos6502_.set_nmi_line(cia2_.get_interrupt_line()); - - return Cycles(1); } - void run_for(const Cycles cycles) { - mos6502_.run_for(cycles); + mos6502_.set_irq_line(cia1_.get_interrupt_line()); + mos6502_.set_nmi_line(cia2_.get_interrupt_line()); + + return Cycles(1); + } + + void run_for(const Cycles cycles) { + mos6502_.run_for(cycles); + } + + void run_for_instructions(int count) { + instructions_ = count; + while(instructions_) { + mos6502_.run_for(Cycles(1)); } + } - void run_for_instructions(int count) { - instructions_ = count; - while(instructions_) { - mos6502_.run_for(Cycles(1)); - } - } + bool is_jammed() { + return mos6502_.is_jammed(); + } - bool is_jammed() { - return mos6502_.is_jammed(); - } + void set_irq_line(bool value) { + mos6502_.set_irq_line(value); + } - void set_irq_line(bool value) { - mos6502_.set_irq_line(value); - } + void set_nmi_line(bool value) { + mos6502_.set_nmi_line(value); + } - void set_nmi_line(bool value) { - mos6502_.set_nmi_line(value); - } + uint16_t value_of(Register r) { + return mos6502_.value_of(r); + } - uint16_t value_of(Register r) { - return mos6502_.value_of(r); - } + void set_value_of(Register r, uint16_t value) { + mos6502_.set_value_of(r, value); + } - void set_value_of(Register r, uint16_t value) { - mos6502_.set_value_of(r, value); - } +private: + CPU::MOS6502Esque::Processor<type, ConcreteAllRAMProcessor, false> mos6502_; + int instructions_ = 0; - private: - CPU::MOS6502Esque::Processor<type, ConcreteAllRAMProcessor, false> mos6502_; - int instructions_ = 0; + class PortHandler: public MOS::MOS6526::PortHandler {}; + PortHandler cia1_handler_, cia2_handler_; - class PortHandler: public MOS::MOS6526::PortHandler {}; - PortHandler cia1_handler_, cia2_handler_; - - MOS::MOS6526::MOS6526<PortHandler, MOS::MOS6526::Personality::P6526> cia1_, cia2_; + MOS::MOS6526::MOS6526<PortHandler, MOS::MOS6526::Personality::P6526> cia1_, cia2_; }; } diff --git a/Processors/65816/65816.hpp b/Processors/65816/65816.hpp index 0e03845ba..a27012734 100644 --- a/Processors/65816/65816.hpp +++ b/Processors/65816/65816.hpp @@ -34,58 +34,58 @@ enum ExtendedBusOutput { #include "Implementation/65816Storage.hpp" class ProcessorBase: protected ProcessorStorage { - public: - inline void set_power_on(bool); - inline void set_irq_line(bool); - inline void set_nmi_line(bool); - inline void set_reset_line(bool); - inline void set_abort_line(bool); - inline bool get_is_resetting() const; +public: + inline void set_power_on(bool); + inline void set_irq_line(bool); + inline void set_nmi_line(bool); + inline void set_reset_line(bool); + inline void set_abort_line(bool); + inline bool get_is_resetting() const; - /*! - Returns the current state of all lines not ordinarily pushed to the BusHandler, - as listed in the ExtendedBusOutput enum. - */ - inline int get_extended_bus_output(); + /*! + Returns the current state of all lines not ordinarily pushed to the BusHandler, + as listed in the ExtendedBusOutput enum. + */ + inline int get_extended_bus_output(); - /*! - Provided for symmetry with the 6502; a 65816 is never jammed. - */ - inline bool is_jammed() const; + /*! + Provided for symmetry with the 6502; a 65816 is never jammed. + */ + inline bool is_jammed() const; - /*! - FOR TESTING PURPOSES ONLY: forces the processor into a state where - the next thing it intends to do is fetch a new opcode. - */ - inline void restart_operation_fetch(); + /*! + FOR TESTING PURPOSES ONLY: forces the processor into a state where + the next thing it intends to do is fetch a new opcode. + */ + inline void restart_operation_fetch(); - void set_value_of(Register r, uint16_t value); - uint16_t value_of(Register r) const; + void set_value_of(Register r, uint16_t value); + uint16_t value_of(Register r) const; }; template <typename BusHandler, bool uses_ready_line> class Processor: public ProcessorBase { - public: - /*! - Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. - */ - Processor(BusHandler &bus_handler) : bus_handler_(bus_handler) {} +public: + /*! + Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. + */ + Processor(BusHandler &bus_handler) : bus_handler_(bus_handler) {} - /*! - Runs the 6502 for a supplied number of cycles. + /*! + Runs the 6502 for a supplied number of cycles. - @param cycles The number of cycles to run the 6502 for. - */ - void run_for(const Cycles cycles); + @param cycles The number of cycles to run the 6502 for. + */ + void run_for(const Cycles); - /*! - Sets the current level of the RDY line. + /*! + Sets the current level of the RDY line. - @param active @c true if the line is logically active; @c false otherwise. - */ - void set_ready_line(bool active); + @param active @c true if the line is logically active; @c false otherwise. + */ + void set_ready_line(bool active); - private: - BusHandler &bus_handler_; +private: + BusHandler &bus_handler_; }; #include "Implementation/65816Implementation.hpp" diff --git a/Processors/65816/Implementation/65816Storage.cpp b/Processors/65816/Implementation/65816Storage.cpp index 5d7f43f31..0f9eae345 100644 --- a/Processors/65816/Implementation/65816Storage.cpp +++ b/Processors/65816/Implementation/65816Storage.cpp @@ -108,7 +108,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { storage_.micro_ops_.push_back(OperationDecode); } - private: +private: PatternTable::iterator install(Generator generator, AccessType access_type = AccessType::Read) { // Check whether this access type + addressing mode generator has already been generated. diff --git a/Processors/65816/Implementation/65816Storage.hpp b/Processors/65816/Implementation/65816Storage.hpp index d227dd899..aa5cc265c 100644 --- a/Processors/65816/Implementation/65816Storage.hpp +++ b/Processors/65816/Implementation/65816Storage.hpp @@ -395,15 +395,15 @@ struct ProcessorStorage { return reinterpret_cast<uint8_t *>(&value); } - private: - uint8_t *byte(int pointer) { - assert(pointer >= 0 && pointer < 4); - #if TARGET_RT_BIG_ENDIAN - return reinterpret_cast<uint8_t *>(&value) + (3 ^ pointer); - #else - return reinterpret_cast<uint8_t *>(&value) + pointer; - #endif - } + private: + uint8_t *byte(int pointer) { + assert(pointer >= 0 && pointer < 4); + #if TARGET_RT_BIG_ENDIAN + return reinterpret_cast<uint8_t *>(&value) + (3 ^ pointer); + #else + return reinterpret_cast<uint8_t *>(&value) + pointer; + #endif + } }; Buffer instruction_buffer_, data_buffer_; uint32_t data_address_; diff --git a/Processors/68000/68000.hpp b/Processors/68000/68000.hpp index 013d36145..537173122 100644 --- a/Processors/68000/68000.hpp +++ b/Processors/68000/68000.hpp @@ -438,61 +438,61 @@ namespace CPU::MC68000 { */ template <class BusHandler, bool dtack_is_implicit = true, bool permit_overrun = true, bool signal_will_perform = false> class Processor: private ProcessorBase { - public: - Processor(BusHandler &bus_handler) : ProcessorBase(), bus_handler_(bus_handler) {} - Processor(const Processor& rhs) = delete; - Processor& operator=(const Processor& rhs) = delete; +public: + Processor(BusHandler &bus_handler) : ProcessorBase(), bus_handler_(bus_handler) {} + Processor(const Processor& rhs) = delete; + Processor& operator=(const Processor& rhs) = delete; - void run_for(HalfCycles duration); + void run_for(HalfCycles duration); - /// @returns The current processor state. - CPU::MC68000::State get_state(); + /// @returns The current processor state. + CPU::MC68000::State get_state(); - /// Sets the current processor state. - void set_state(const CPU::MC68000::State &); + /// Sets the current processor state. + void set_state(const CPU::MC68000::State &); - /// Sets all registers to the values provided, fills the prefetch queue and ensures the - /// next action the processor will take is to decode whatever is in the queue. - /// - /// The queue is filled synchronously, during this call, causing calls to the bus handler. - void decode_from_state(const InstructionSet::M68k::RegisterSet &); + /// Sets all registers to the values provided, fills the prefetch queue and ensures the + /// next action the processor will take is to decode whatever is in the queue. + /// + /// The queue is filled synchronously, during this call, causing calls to the bus handler. + void decode_from_state(const InstructionSet::M68k::RegisterSet &); - // TODO: bus ack/grant, halt, + // TODO: bus ack/grant, halt, - /// Sets the DTack line — @c true for active, @c false for inactive. - inline void set_dtack(bool dtack) { - dtack_ = dtack; - } + /// Sets the DTack line — @c true for active, @c false for inactive. + inline void set_dtack(bool dtack) { + dtack_ = dtack; + } - /// Sets the VPA (valid peripheral address) line — @c true for active, @c false for inactive. - inline void set_is_peripheral_address(bool is_peripheral_address) { - vpa_ = is_peripheral_address; - } + /// Sets the VPA (valid peripheral address) line — @c true for active, @c false for inactive. + inline void set_is_peripheral_address(bool is_peripheral_address) { + vpa_ = is_peripheral_address; + } - /// Sets the bus error line — @c true for active, @c false for inactive. - inline void set_bus_error(bool bus_error) { - berr_ = bus_error; - } + /// Sets the bus error line — @c true for active, @c false for inactive. + inline void set_bus_error(bool bus_error) { + berr_ = bus_error; + } - /// Sets the interrupt lines, IPL0, IPL1 and IPL2. - inline void set_interrupt_level(int interrupt_level) { - bus_interrupt_level_ = interrupt_level; - } + /// Sets the interrupt lines, IPL0, IPL1 and IPL2. + inline void set_interrupt_level(int interrupt_level) { + bus_interrupt_level_ = interrupt_level; + } - /// @returns The current phase of the E clock; this will be a number of - /// half-cycles between 0 and 19 inclusive, indicating how far the 68000 - /// is into the current E cycle. - /// - /// This is guaranteed to be 0 at initial 68000 construction. It is not guaranteed - /// to return the correct result if called during a bus transaction. - HalfCycles get_e_clock_phase() { - return e_clock_phase_; - } + /// @returns The current phase of the E clock; this will be a number of + /// half-cycles between 0 and 19 inclusive, indicating how far the 68000 + /// is into the current E cycle. + /// + /// This is guaranteed to be 0 at initial 68000 construction. It is not guaranteed + /// to return the correct result if called during a bus transaction. + HalfCycles get_e_clock_phase() { + return e_clock_phase_; + } - void reset(); + void reset(); - private: - BusHandler &bus_handler_; +private: + BusHandler &bus_handler_; }; } diff --git a/Processors/AllRAMProcessor.hpp b/Processors/AllRAMProcessor.hpp index e155baf4e..c59342f15 100644 --- a/Processors/AllRAMProcessor.hpp +++ b/Processors/AllRAMProcessor.hpp @@ -17,32 +17,32 @@ namespace CPU { class AllRAMProcessor { - public: - AllRAMProcessor(std::size_t memory_size); - HalfCycles get_timestamp(); - void set_data_at_address(size_t startAddress, size_t length, const uint8_t *data); - void get_data_at_address(size_t startAddress, size_t length, uint8_t *data); +public: + AllRAMProcessor(std::size_t memory_size); + HalfCycles get_timestamp(); + void set_data_at_address(size_t startAddress, size_t length, const uint8_t *data); + void get_data_at_address(size_t startAddress, size_t length, uint8_t *data); - class TrapHandler { - public: - virtual void processor_did_trap(AllRAMProcessor &, uint16_t address) = 0; - }; - void set_trap_handler(TrapHandler *trap_handler); - void add_trap_address(uint16_t address); + class TrapHandler { + public: + virtual void processor_did_trap(AllRAMProcessor &, uint16_t address) = 0; + }; + void set_trap_handler(TrapHandler *trap_handler); + void add_trap_address(uint16_t address); - protected: - std::vector<uint8_t> memory_; - HalfCycles timestamp_; +protected: + std::vector<uint8_t> memory_; + HalfCycles timestamp_; - inline void check_address_for_trap(uint16_t address) { - if(traps_[address]) { - trap_handler_->processor_did_trap(*this, address); - } + inline void check_address_for_trap(uint16_t address) { + if(traps_[address]) { + trap_handler_->processor_did_trap(*this, address); } + } - private: - TrapHandler *trap_handler_; - std::vector<bool> traps_; +private: + TrapHandler *trap_handler_; + std::vector<bool> traps_; }; } diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 6ff3543db..4eff3ff27 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -376,7 +376,7 @@ struct PartialMachineCycle { } PartialMachineCycle(const PartialMachineCycle &rhs) noexcept; - PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept; + PartialMachineCycle(Operation, HalfCycles, uint16_t *address, uint8_t *value, bool was_requested) noexcept; PartialMachineCycle() noexcept; }; @@ -386,18 +386,18 @@ struct PartialMachineCycle { handler. */ class BusHandler { - public: - /*! - Announces that the Z80 has performed the partial machine cycle defined by @c cycle. +public: + /*! + Announces that the Z80 has performed the partial machine cycle defined by @c cycle. - @returns The number of additional HalfCycles that passed in objective time while this Z80 operation was ongoing. - On an archetypal machine this will be HalfCycles(0) but some architectures may choose not to clock the Z80 - during some periods or may impose wait states so predictably that it's more efficient just to add them - via this mechanism. - */ - HalfCycles perform_machine_cycle([[maybe_unused]] const PartialMachineCycle &cycle) { - return HalfCycles(0); - } + @returns The number of additional HalfCycles that passed in objective time while this Z80 operation was ongoing. + On an archetypal machine this will be HalfCycles(0) but some architectures may choose not to clock the Z80 + during some periods or may impose wait states so predictably that it's more efficient just to add them + via this mechanism. + */ + HalfCycles perform_machine_cycle([[maybe_unused]] const PartialMachineCycle &) { + return HalfCycles(0); + } }; #include "Implementation/Z80Storage.hpp" @@ -406,82 +406,82 @@ class BusHandler { A base class from which the Z80 descends; separated for implementation reasons only. */ class ProcessorBase: public ProcessorStorage { - public: - /*! - Gets the value of a register. +public: + /*! + Gets the value of a register. - @see set_value_of + @see set_value_of - @param r The register to set. - @returns The value of the register. 8-bit registers will be returned as unsigned. - */ - uint16_t value_of(Register r) const; + @param r The register to set. + @returns The value of the register. 8-bit registers will be returned as unsigned. + */ + uint16_t value_of(Register r) const; - /*! - Sets the value of a register. + /*! + Sets the value of a register. - @see value_of + @see value_of - @param r The register to set. - @param value The value to set. If the register is only 8 bit, the value will be truncated. - */ - void set_value_of(Register r, uint16_t value); + @param r The register to set. + @param value The value to set. If the register is only 8 bit, the value will be truncated. + */ + void set_value_of(Register r, uint16_t value); - /*! - Gets the value of the HALT output line. - */ - inline bool get_halt_line() const; + /*! + Gets the value of the HALT output line. + */ + inline bool get_halt_line() const; - /*! - Sets the logical value of the interrupt line. + /*! + Sets the logical value of the interrupt line. - @param offset If called while within perform_machine_cycle this may be a value indicating - how many cycles before now the line changed state. The value may not be longer than the - current machine cycle. If called at any other time, this must be zero. - */ - inline void set_interrupt_line(bool value, HalfCycles offset = 0); + @param offset If called while within perform_machine_cycle this may be a value indicating + how many cycles before now the line changed state. The value may not be longer than the + current machine cycle. If called at any other time, this must be zero. + */ + inline void set_interrupt_line(bool, HalfCycles offset = 0); - /*! - Gets the value of the interrupt line. - */ - inline bool get_interrupt_line() const; + /*! + Gets the value of the interrupt line. + */ + inline bool get_interrupt_line() const; - /*! - Sets the logical value of the non-maskable interrupt line. + /*! + Sets the logical value of the non-maskable interrupt line. - @param offset See discussion in set_interrupt_line. - */ - inline void set_non_maskable_interrupt_line(bool value, HalfCycles offset = 0); + @param offset See discussion in set_interrupt_line. + */ + inline void set_non_maskable_interrupt_line(bool, HalfCycles offset = 0); - /*! - Gets the value of the non-maskable interrupt line. - */ - inline bool get_non_maskable_interrupt_line() const; + /*! + Gets the value of the non-maskable interrupt line. + */ + inline bool get_non_maskable_interrupt_line() const; - /*! - Sets the logical value of the reset line. - */ - inline void set_reset_line(bool value); + /*! + Sets the logical value of the reset line. + */ + inline void set_reset_line(bool); - /*! - Gets whether the Z80 would reset at the next opportunity. + /*! + Gets whether the Z80 would reset at the next opportunity. - @returns @c true if the line is logically active; @c false otherwise. - */ - bool get_is_resetting() const; + @returns @c true if the line is logically active; @c false otherwise. + */ + bool get_is_resetting() const; - /*! - This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a - reset at the first opportunity. Use @c reset_power_on to disable that behaviour. - */ - void reset_power_on(); + /*! + This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a + reset at the first opportunity. Use @c reset_power_on to disable that behaviour. + */ + void reset_power_on(); - /*! - @returns @c true if the Z80 is currently beginning to fetch a new instruction; @c false otherwise. + /*! + @returns @c true if the Z80 is currently beginning to fetch a new instruction; @c false otherwise. - This is not a speedy operation. - */ - bool is_starting_new_instruction() const; + This is not a speedy operation. + */ + bool is_starting_new_instruction() const; }; /*! @@ -493,45 +493,45 @@ class ProcessorBase: public ProcessorStorage { support either can produce a minor runtime performance improvement. */ template <class T, bool uses_bus_request, bool uses_wait_line> class Processor: public ProcessorBase { - public: - Processor(T &bus_handler); +public: + Processor(T &bus_handler); - /*! - Runs the Z80 for a supplied number of cycles. + /*! + Runs the Z80 for a supplied number of cycles. - @discussion Subclasses must implement @c perform_machine_cycle(const PartialMachineCycle &cycle) . + @discussion Subclasses must implement @c perform_machine_cycle(const PartialMachineCycle &cycle) . - If it is a read operation then @c value will be seeded with the value 0xff. + If it is a read operation then @c value will be seeded with the value 0xff. - @param cycles The number of cycles to run for. - */ - void run_for(const HalfCycles cycles); + @param cycles The number of cycles to run for. + */ + void run_for(const HalfCycles); - /*! - Sets the logical value of the bus request line, having asserted that this Z80 supports the bus request line. - */ - void set_bus_request_line(bool value); + /*! + Sets the logical value of the bus request line, having asserted that this Z80 supports the bus request line. + */ + void set_bus_request_line(bool); - /*! - Gets the logical value of the bus request line. - */ - bool get_bus_request_line() const; + /*! + Gets the logical value of the bus request line. + */ + bool get_bus_request_line() const; - /*! - Sets the logical value of the wait line, having asserted that this Z80 supports the wait line. - */ - void set_wait_line(bool value); + /*! + Sets the logical value of the wait line, having asserted that this Z80 supports the wait line. + */ + void set_wait_line(bool); - /*! - Gets the logical value of the bus request line. - */ - bool get_wait_line() const; + /*! + Gets the logical value of the bus request line. + */ + bool get_wait_line() const; - private: - T &bus_handler_; +private: + T &bus_handler_; - void assemble_page(InstructionPage &target, InstructionTable &table, bool add_offsets); - void copy_program(const MicroOp *source, std::vector<MicroOp> &destination); + void assemble_page(InstructionPage &, InstructionTable &, bool add_offsets); + void copy_program(const MicroOp *source, std::vector<MicroOp> &destination); }; #include "Implementation/Z80Implementation.hpp" From 9cb28d23a3ecd13cac1e3d9c7800ffe35cc4e541 Mon Sep 17 00:00:00 2001 From: Thomas Harte <thomas.harte@gmail.com> Date: Wed, 4 Dec 2024 22:39:29 -0500 Subject: [PATCH 2/2] Remove deprecated macos-12; add macos-15. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ce5ed315..e504c9dc5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ jobs: name: Mac UI / xcodebuild / ${{ matrix.os }} strategy: matrix: - os: [macos-12, macos-13, macos-14] + os: [macos-13, macos-14, macos-15] runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -53,7 +53,7 @@ jobs: name: SDL UI / scons / ${{ matrix.os }} strategy: matrix: - os: [macos-14, ubuntu-latest] + os: [macos-latest, ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout