diff --git a/Analyser/Static/Commodore/Disk.cpp b/Analyser/Static/Commodore/Disk.cpp index 94d3a3b520..2d0f0381be 100644 --- a/Analyser/Static/Commodore/Disk.cpp +++ b/Analyser/Static/Commodore/Disk.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace Analyser::Static::Commodore; @@ -37,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { @returns a sector if one was found; @c nullptr otherwise. */ - std::shared_ptr sector(const uint8_t track, const uint8_t sector) { + const Sector *sector(const uint8_t track, const uint8_t sector) { int difference = int(track) - int(track_); track_ = track; @@ -68,7 +69,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { int index_count_; int bit_count_; uint8_t track_; - std::shared_ptr sector_cache_[65536]; + std::unordered_map> sector_cache_; void process_input_bit(const int value) override { shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff; @@ -111,23 +112,24 @@ class CommodoreGCRParser: public Storage::Disk::Controller { index_count_++; } - std::shared_ptr get_sector(const uint8_t sector) { + const Sector *get_sector(const uint8_t sector) { const uint16_t sector_address = uint16_t((track_ << 8) | sector); - if(sector_cache_[sector_address]) return sector_cache_[sector_address]; + auto existing = sector_cache_.find(sector_address); + if(existing != sector_cache_.end()) return existing->second.get(); - const std::shared_ptr first_sector = get_next_sector(); + const auto first_sector = get_next_sector(); if(!first_sector) return first_sector; if(first_sector->sector == sector) return first_sector; - while(1) { - const std::shared_ptr next_sector = get_next_sector(); + while(true) { + const auto next_sector = get_next_sector(); if(next_sector->sector == first_sector->sector) return nullptr; if(next_sector->sector == sector) return next_sector; } } - std::shared_ptr get_next_sector() { - auto sector = std::make_shared(); + const Sector *get_next_sector() { + auto sector = std::make_unique(); const int max_index_count = index_count_ + 2; while(index_count_ < max_index_count) { @@ -160,8 +162,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { if(checksum == get_next_byte()) { uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector); - sector_cache_[sector_address] = sector; - return sector; + auto pair = sector_cache_.emplace(sector_address, std::move(sector)); + return pair.first->second.get(); } } @@ -171,21 +173,29 @@ class CommodoreGCRParser: public Storage::Disk::Controller { std::vector Analyser::Static::Commodore::GetFiles(const std::shared_ptr &disk) { std::vector files; - auto parser = std::make_unique(); - parser->set_disk(disk); + CommodoreGCRParser parser; + parser.set_disk(disk); // Assemble directory. std::vector directory; uint8_t next_track = 18; uint8_t next_sector = 1; directory.reserve(20 * 1024); // Probably more than plenty. + std::set> visited; while(true) { - auto sector = parser->sector(next_track, next_sector); + // Don't be fooled by disks that are encoded with a looping directory. + const auto key = std::make_pair(next_track, next_sector); + if(visited.find(key) != visited.end()) break; + visited.insert(key); + + // Append sector to directory and follow next link. + const auto sector = parser.sector(next_track, next_sector); if(!sector) break; directory.insert(directory.end(), sector->data.begin(), sector->data.end()); next_track = sector->data[0]; next_sector = sector->data[1]; + // Check for end-of-directory. if(!next_track) break; } @@ -222,7 +232,7 @@ std::vector Analyser::Static::Commodore::GetFiles(const std::shared_ptrsector(next_track, next_sector); + const auto sector = parser.sector(next_track, next_sector); if(!sector) break; next_track = sector->data[0]; diff --git a/Analyser/Static/Commodore/StaticAnalyser.cpp b/Analyser/Static/Commodore/StaticAnalyser.cpp index f077348886..094a9d4f56 100644 --- a/Analyser/Static/Commodore/StaticAnalyser.cpp +++ b/Analyser/Static/Commodore/StaticAnalyser.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -108,7 +109,7 @@ std::optional analyse(const File &file) { while(true) { // Analysis has failed if there isn't at least one complete BASIC line from here. // Fall back on guessing the start address as a machine code entrypoint. - if(size_t(line_address - file.starting_address) + 5 >= file.data.size()) { + if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) { analysis.machine_code_addresses.push_back(file.starting_address); break; } @@ -209,10 +210,6 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets( // Inspect discovered files to try to divine machine and memory model. auto vic_memory_model = Target::MemoryModel::Unexpanded; - if(files.size() > 1) { - printf(""); - } - auto it = files.begin(); while(it != files.end()) { const auto &file = *it; diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 6fb41f39c8..90720c9768 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -127,7 +127,10 @@ Video::Video() : // Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's // usual output height of 200 lines. - crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f)); + crt_.set_visible_area(crt_.get_rect_for_area( + 33, 260, + 480, 1280, + 4.0f / 3.0f)); } void Video::set_ram(uint16_t *ram, size_t size) { diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 47f8cf7261..a5a815c628 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -36,20 +36,20 @@ namespace Commodore::C1540 { Provides an emulation of the C1540. */ class Machine final: public MachineBase { - public: - static ROM::Request rom_request(Personality personality); - Machine(Personality personality, const ROM::Map &roms); +public: + static ROM::Request rom_request(Personality personality); + Machine(Personality personality, const ROM::Map &roms); - /*! - Sets the serial bus to which this drive should attach itself. - */ - void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); + /*! + Sets the serial bus to which this drive should attach itself. + */ + void set_serial_bus(Commodore::Serial::Bus &); - /// Advances time. - void run_for(const Cycles cycles); + /// Advances time. + void run_for(Cycles); - /// Inserts @c disk into the drive. - void set_disk(std::shared_ptr disk); + /// Inserts @c disk into the drive. + void set_disk(std::shared_ptr disk); }; } diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 6ec47298d6..6a1b066c16 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -16,56 +16,54 @@ using namespace Commodore::C1540; -ROM::Request Machine::rom_request(Personality personality) { +namespace { +ROM::Name rom_name(Personality personality) { switch(personality) { default: - case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540); - case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541); + case Personality::C1540: return ROM::Name::Commodore1540; + case Personality::C1541: return ROM::Name::Commodore1541; } } +} + +ROM::Request Machine::rom_request(Personality personality) { + return ROM::Request(rom_name(personality)); +} MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : Storage::Disk::Controller(1000000), m6502_(*this), - serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), - serial_port_(new SerialPort), + serial_port_VIA_port_handler_(serial_port_VIA_), drive_VIA_(drive_VIA_port_handler_), - serial_port_VIA_(*serial_port_VIA_port_handler_) { - // attach the serial port to its VIA and vice versa - serial_port_->set_serial_port_via(serial_port_VIA_port_handler_); - serial_port_VIA_port_handler_->set_serial_port(serial_port_); + serial_port_VIA_(serial_port_VIA_port_handler_) { + // Attach the serial port to its VIA and vice versa. + serial_port_.set_serial_port_via(serial_port_VIA_port_handler_); + serial_port_VIA_port_handler_.set_serial_port(serial_port_); - // set this instance as the delegate to receive interrupt requests from both VIAs - serial_port_VIA_port_handler_->set_interrupt_delegate(this); + // Set this instance as the delegate to receive interrupt requests from both VIAs. + serial_port_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_delegate(this); - // set a bit rate + // Set a bit rate. set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); - // attach the only drive there is + // Attach the only drive there is. emplace_drive(1000000, 300, 2); set_drive(1); - ROM::Name rom_name; - switch(personality) { - default: - case Personality::C1540: rom_name = ROM::Name::Commodore1540; break; - case Personality::C1541: rom_name = ROM::Name::Commodore1541; break; - } - - auto rom = roms.find(rom_name); + const auto rom = roms.find(rom_name(personality)); if(rom == roms.end()) { throw ROMMachine::Error::MissingROMs; } - std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size())); + std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size())); } Machine::Machine(Personality personality, const ROM::Map &roms) : MachineBase(personality, roms) {} -void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { - Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); +void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { + Commodore::Serial::attach(serial_port_, serial_bus); } Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { @@ -155,11 +153,11 @@ void MachineBase::process_index_hole() {} // MARK: - Drive VIA delegate -void MachineBase::drive_via_did_step_head(void *, int direction) { +void MachineBase::drive_via_did_step_head(void *, const int direction) { get_drive().step(Storage::Disk::HeadPosition(direction, 2)); } -void MachineBase::drive_via_did_set_data_density(void *, int density) { +void MachineBase::drive_via_did_set_data_density(void *, const int density) { set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density))); } @@ -174,14 +172,11 @@ uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) { void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { if(port) { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) { - attention_acknowledge_level_ = !(value&0x10); - data_level_output_ = (value&0x02); + attention_acknowledge_level_ = !(value&0x10); + data_level_output_ = (value&0x02); - serialPort->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08))); - update_data_line(); - } + serial_port_->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08))); + update_data_line(); } } @@ -199,17 +194,14 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v } } -void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) { - serial_port_ = serialPort; +void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) { + serial_port_ = &port; } void SerialPortVIA::update_data_line() { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) { - // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" - serialPort->set_output(::Commodore::Serial::Line::Data, - ::Commodore::Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); - } + // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" + serial_port_->set_output(::Commodore::Serial::Line::Data, + Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); } // MARK: - DriveVIA @@ -248,23 +240,23 @@ void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Li void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { if(port) { if(previous_port_b_output_ != value) { - // record drive motor state - drive_motor_ = !!(value&4); + // Record drive motor state. + drive_motor_ = value&4; - // check for a head step - int step_difference = ((value&3) - (previous_port_b_output_&3))&3; + // Check for a head step. + const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; if(step_difference) { if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1); } - // check for a change in density - int density_difference = (previous_port_b_output_^value) & (3 << 5); + // Check for a change in density. + const int density_difference = (previous_port_b_output_^value) & (3 << 5); if(density_difference && delegate_) { delegate_->drive_via_did_set_data_density(this, (value >> 5)&3); } - // post the LED status - if(observer_) observer_->set_led_status("Drive", !!(value&8)); + // Post the LED status. + if(observer_) observer_->set_led_status("Drive", value&8); previous_port_b_output_ = value; } @@ -275,17 +267,16 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) { observer_ = observer; if(observer) { observer->register_led("Drive"); - observer->set_led_status("Drive", !!(previous_port_b_output_&8)); + observer->set_led_status("Drive", previous_port_b_output_&8); } } // MARK: - SerialPort -void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { - std::shared_ptr serialPortVIA = serial_port_VIA_.lock(); - if(serialPortVIA) serialPortVIA->set_serial_line_state(line, bool(level)); +void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) { + serial_port_VIA_->set_serial_line_state(line, bool(level)); } -void SerialPort::set_serial_port_via(const std::shared_ptr &serialPortVIA) { - serial_port_VIA_ = serialPortVIA; +void SerialPort::set_serial_port_via(SerialPortVIA &serialPortVIA) { + serial_port_VIA_ = &serialPortVIA; } diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 774afb0a55..91f5da8179 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -46,12 +46,12 @@ class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { 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(Commodore::Serial::Port &); private: MOS::MOS6522::MOS6522 &via_; uint8_t port_b_ = 0x0; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; + Commodore::Serial::Port *serial_port_ = nullptr; bool attention_acknowledge_level_ = false; bool attention_level_input_ = true; bool data_level_output_ = false; @@ -84,7 +84,7 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { }; void set_delegate(Delegate *); - uint8_t get_port_input(MOS::MOS6522::Port port); + uint8_t get_port_input(MOS::MOS6522::Port); void set_sync_detected(bool); void set_data_input(uint8_t); @@ -95,7 +95,7 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { 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 *); private: uint8_t port_b_ = 0xff, port_a_ = 0xff; @@ -111,11 +111,11 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { */ 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 &); + void set_input(Commodore::Serial::Line, Commodore::Serial::LineLevel); + void set_serial_port_via(SerialPortVIA &); private: - std::weak_ptr serial_port_VIA_; + SerialPortVIA *serial_port_VIA_ = nullptr; }; class MachineBase: @@ -125,37 +125,37 @@ class MachineBase: public Storage::Disk::Controller { public: - MachineBase(Personality personality, const ROM::Map &roms); + MachineBase(Personality, const ROM::Map &); + + /// Attaches the activity observer to this C1540. + void set_activity_observer(Activity::Observer *); // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); + Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value); +protected: // to satisfy MOS::MOS6522::Delegate - virtual void mos6522_did_change_interrupt_status(void *mos6522); + void mos6522_did_change_interrupt_status(void *mos6522) override; // 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); + void drive_via_did_step_head(void *driveVIA, int direction) override; + void drive_via_did_set_data_density(void *driveVIA, int density) override; - /// Attaches the activity observer to this C1540. - void set_activity_observer(Activity::Observer *observer); - -protected: CPU::MOS6502::Processor m6502_; uint8_t ram_[0x800]; uint8_t rom_[0x4000]; - std::shared_ptr serial_port_VIA_port_handler_; - std::shared_ptr serial_port_; + SerialPortVIA serial_port_VIA_port_handler_; + SerialPort serial_port_; DriveVIA drive_VIA_port_handler_; MOS::MOS6522::MOS6522 drive_VIA_; MOS::MOS6522::MOS6522 serial_port_VIA_; int shift_register_ = 0, bit_window_offset_; - virtual void process_input_bit(int value); - virtual void process_index_hole(); + void process_input_bit(int value) override; + void process_index_hole() override; }; } diff --git a/Machines/Commodore/Plus4/Audio.hpp b/Machines/Commodore/Plus4/Audio.hpp new file mode 100644 index 0000000000..5d7f038ddd --- /dev/null +++ b/Machines/Commodore/Plus4/Audio.hpp @@ -0,0 +1,113 @@ +// +// Audio.hpp +// Clock Signal +// +// Created by Thomas Harte on 30/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" +#include "../../../Concurrency/AsyncTaskQueue.hpp" + +namespace Commodore::Plus4 { + +// PAL: / 160 i.e. 5*32 +// NTSC: / 128 i.e. 4*32 + +// 111860.78125 = NTSC +// 110840.46875 = PAL + +class Audio: public Outputs::Speaker::BufferSource { +public: + Audio(Concurrency::AsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + + template + void apply_samples(std::size_t size, Outputs::Speaker::MonoSample *const target) { + const auto count_frequency = [&](int index) { + ++counts_[index]; + if(counts_[index] == (frequencies_[index] /* ^ 1023 */) * frequency_multiplier_) { + states_[index] ^= 1; + counts_[index] = 0; + } else if(counts_[index] == 1024 * frequency_multiplier_) { + counts_[index] = 0; + } + }; + +// if(sound_dc_) { +// Outputs::Speaker::fill(target, target + size, Outputs::Speaker::MonoSample(volume_ * 2)); +// } else { + // TODO: noise generation. + + for(size_t c = 0; c < size; c++) { + count_frequency(0); + count_frequency(1); + + Outputs::Speaker::apply( + target[c], + Outputs::Speaker::MonoSample( + ( + ((states_[0] & masks_[0]) * external_volume_) + + ((states_[1] & masks_[1]) * external_volume_) + ) * volume_ + )); + } +// } + r_ += size; + } + + void set_sample_volume_range(const std::int16_t range) { + external_volume_ = range / (2 * 9); // Two channels and nine output levels. + } + + bool is_zero_level() const { + return !(masks_[0] || masks_[1] || sound2_noise_on_) || !volume_; + } + + template void set_frequency_low(uint8_t value) { + audio_queue_.enqueue([this, value] { + frequencies_[channel] = (frequencies_[channel] & 0xff00) | value; + }); + } + + template void set_frequency_high(uint8_t value) { + audio_queue_.enqueue([this, value] { + frequencies_[channel] = (frequencies_[channel] & 0x00ff) | ((value&3) << 8); + }); + } + + void set_constrol(uint8_t value) { + audio_queue_.enqueue([this, value] { + volume_ = std::min(value & 0xf, 8); // Only nine volumes are available. + masks_[0] = (value & 0x10) ? 1 : 0; + + masks_[1] = (value & 0x20) ? 1 : 0; + sound2_noise_on_ = (value & 0x40) && !(value & 0x20); + + sound_dc_ = value & 0x80; + }); + } + +private: + // Calling-thread state. + Concurrency::AsyncTaskQueue &audio_queue_; + + // Audio-thread state. + int16_t external_volume_ = 0; + int frequencies_[2]{}; + int frequency_multiplier_ = 5; + int counts_[2]{}; + + int states_[2]{}; + int masks_[2]{}; + + bool sound2_noise_on_ = false; + bool sound_dc_ = false; + int volume_ = 0; + + int r_ = 0; +}; + +} diff --git a/Machines/Commodore/Plus4/Interrupts.hpp b/Machines/Commodore/Plus4/Interrupts.hpp new file mode 100644 index 0000000000..5eb2b408e7 --- /dev/null +++ b/Machines/Commodore/Plus4/Interrupts.hpp @@ -0,0 +1,72 @@ +// +// Interrupts.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../../Processors/6502/6502.hpp" + +namespace Commodore::Plus4 { + +struct BusController { + virtual void set_irq_line(bool) = 0; + virtual void set_ready_line(bool) = 0; +}; + +struct Interrupts { +public: + Interrupts(BusController &delegate) : delegate_(delegate) {} + BusController &bus() { + return delegate_; + } + + enum Flag { + Timer3 = 0x40, + Timer2 = 0x10, + Timer1 = 0x08, + Raster = 0x02, + }; + + uint8_t status() const { + return status_ | ((status_ & mask_) ? 0x80 : 0x00); + } + + uint8_t mask() const { + return mask_; + } + + void set_status(const uint8_t status) { + status_ &= ~status; + update_output(); + } + + void apply(const uint8_t interrupt) { + status_ |= interrupt; + update_output(); + } + + void set_mask(const uint8_t mask) { + mask_ = mask; + update_output(); + } + +private: + void update_output() { + const bool set = status_ & mask_; + if(set != last_set_) { + delegate_.set_irq_line(set); + last_set_ = set; + } + } + + BusController &delegate_; + uint8_t status_; + uint8_t mask_; + bool last_set_ = false; +}; + +} diff --git a/Machines/Commodore/Plus4/Keyboard.cpp b/Machines/Commodore/Plus4/Keyboard.cpp new file mode 100644 index 0000000000..1158c25b26 --- /dev/null +++ b/Machines/Commodore/Plus4/Keyboard.cpp @@ -0,0 +1,134 @@ +// +// Keyboard.cpp +// Clock Signal +// +// Created by Thomas Harte on 13/12/2024. +// Copyright 2024 Thomas Harte. All rights reserved. +// + +#include "Keyboard.hpp" + +using namespace Commodore::Plus4; + +uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { +#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Plus4::dest + switch(key) { + default: break; + + BIND(k0, k0); BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4); + BIND(k5, k5); BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9); + BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T); + BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P); + BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G); + BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L); + BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V); + BIND(B, B); BIND(N, N); BIND(M, M); + + BIND(Backspace, InsDel); + BIND(Escape, Escape); + BIND(F1, F1_F4); + BIND(F2, F2_F5); + BIND(F3, F3_F6); + BIND(F4, Help_F7); + BIND(Enter, Return); + BIND(Space, Space); + + BIND(Up, Up); BIND(Down, Down); BIND(Left, Left); BIND(Right, Right); + + BIND(LeftShift, Shift); BIND(RightShift, Shift); + BIND(LeftControl, Control); BIND(RightControl, Control); + BIND(LeftOption, Commodore); BIND(RightOption, Commodore); + + BIND(FullStop, FullStop); BIND(Comma, Comma); + BIND(Semicolon, Semicolon); BIND(Quote, Colon); + BIND(Equals, Equals); BIND(ForwardSlash, Slash); + + BIND(OpenSquareBracket, At); + BIND(CloseSquareBracket, Plus); + BIND(Backslash, Clear_Home); + BIND(BackTick, Asterisk); + + BIND(F11, Clear_Home); + BIND(F12, Run_Stop); + + // TODO: + // GBP + } +#undef BIND + return MachineTypes::MappedKeyboardMachine::KeyNotMapped; +} + +//const uint16_t *CharacterMapper::sequence_for_character(char character) const { +//#define KEYS(...) {__VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +//#define SHIFT(...) {KeyLShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence} +//#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped} +// static KeySequence key_sequences[] = { +// /* NUL */ X, /* SOH */ X, +// /* STX */ X, /* ETX */ X, +// /* EOT */ X, /* ENQ */ X, +// /* ACK */ X, /* BEL */ X, +// /* BS */ KEYS(KeyDelete), /* HT */ X, +// /* LF */ KEYS(KeyReturn), /* VT */ X, +// /* FF */ X, /* CR */ X, +// /* SO */ X, /* SI */ X, +// /* DLE */ X, /* DC1 */ X, +// /* DC2 */ X, /* DC3 */ X, +// /* DC4 */ X, /* NAK */ X, +// /* SYN */ X, /* ETB */ X, +// /* CAN */ X, /* EM */ X, +// /* SUB */ X, /* ESC */ X, +// /* FS */ X, /* GS */ X, +// /* RS */ X, /* US */ X, +// /* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1), +// /* " */ SHIFT(Key2), /* # */ SHIFT(Key3), +// /* $ */ SHIFT(Key4), /* % */ SHIFT(Key5), +// /* & */ SHIFT(Key6), /* ' */ SHIFT(Key7), +// /* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9), +// /* * */ KEYS(KeyAsterisk), /* + */ KEYS(KeyPlus), +// /* , */ KEYS(KeyComma), /* - */ KEYS(KeyDash), +// /* . */ KEYS(KeyFullStop), /* / */ KEYS(KeySlash), +// /* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1), +// /* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3), +// /* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5), +// /* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7), +// /* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9), +// /* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon), +// /* < */ SHIFT(KeyComma), /* = */ KEYS(KeyEquals), +// /* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeySlash), +// /* @ */ KEYS(KeyAt), /* A */ KEYS(KeyA), +// /* B */ KEYS(KeyB), /* C */ KEYS(KeyC), +// /* D */ KEYS(KeyD), /* E */ KEYS(KeyE), +// /* F */ KEYS(KeyF), /* G */ KEYS(KeyG), +// /* H */ KEYS(KeyH), /* I */ KEYS(KeyI), +// /* J */ KEYS(KeyJ), /* K */ KEYS(KeyK), +// /* L */ KEYS(KeyL), /* M */ KEYS(KeyM), +// /* N */ KEYS(KeyN), /* O */ KEYS(KeyO), +// /* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ), +// /* R */ KEYS(KeyR), /* S */ KEYS(KeyS), +// /* T */ KEYS(KeyT), /* U */ KEYS(KeyU), +// /* V */ KEYS(KeyV), /* W */ KEYS(KeyW), +// /* X */ KEYS(KeyX), /* Y */ KEYS(KeyY), +// /* Z */ KEYS(KeyZ), /* [ */ SHIFT(KeyColon), +// /* \ */ X, /* ] */ SHIFT(KeySemicolon), +// /* ^ */ X, /* _ */ X, +// /* ` */ X, /* a */ KEYS(KeyA), +// /* b */ KEYS(KeyB), /* c */ KEYS(KeyC), +// /* d */ KEYS(KeyD), /* e */ KEYS(KeyE), +// /* f */ KEYS(KeyF), /* g */ KEYS(KeyG), +// /* h */ KEYS(KeyH), /* i */ KEYS(KeyI), +// /* j */ KEYS(KeyJ), /* k */ KEYS(KeyK), +// /* l */ KEYS(KeyL), /* m */ KEYS(KeyM), +// /* n */ KEYS(KeyN), /* o */ KEYS(KeyO), +// /* p */ KEYS(KeyP), /* q */ KEYS(KeyQ), +// /* r */ KEYS(KeyR), /* s */ KEYS(KeyS), +// /* t */ KEYS(KeyT), /* u */ KEYS(KeyU), +// /* v */ KEYS(KeyV), /* w */ KEYS(KeyW), +// /* x */ KEYS(KeyX), /* y */ KEYS(KeyY), +// /* z */ KEYS(KeyZ) +// }; +//#undef KEYS +//#undef SHIFT +//#undef X +// +// return table_lookup_sequence_for_character(key_sequences, character); +//} diff --git a/Machines/Commodore/Plus4/Keyboard.hpp b/Machines/Commodore/Plus4/Keyboard.hpp new file mode 100644 index 0000000000..850c1bdaa0 --- /dev/null +++ b/Machines/Commodore/Plus4/Keyboard.hpp @@ -0,0 +1,82 @@ +// +// Keyboard.hpp +// Clock Signal +// +// Created by Thomas Harte on 13/12/2024. +// Copyright 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "../../KeyboardMachine.hpp" +#include "../../Utility/Typer.hpp" + +namespace Commodore::Plus4 { + +static constexpr uint16_t key(const int line, const int mask) { + return uint16_t((mask << 3) | line); +} +static constexpr size_t line(const uint16_t key) { + return key & 7; +} +static constexpr uint8_t mask(const uint16_t key) { + return uint8_t(key >> 3); +} + +enum Key: uint16_t { + InsDel = key(0, 0x01), Return = key(0, 0x02), + GBP = key(0, 0x04), Help_F7 = key(0, 0x08), + F1_F4 = key(0, 0x10), F2_F5 = key(0, 0x20), + F3_F6 = key(0, 0x40), At = key(0, 0x80), + + k3 = key(1, 0x01), W = key(1, 0x02), + A = key(1, 0x04), k4 = key(1, 0x08), + Z = key(1, 0x10), S = key(1, 0x20), + E = key(1, 0x40), Shift = key(1, 0x80), + + k5 = key(2, 0x01), R = key(2, 0x02), + D = key(2, 0x04), k6 = key(2, 0x08), + C = key(2, 0x10), F = key(2, 0x20), + T = key(2, 0x40), X = key(2, 0x80), + + k7 = key(3, 0x01), Y = key(3, 0x02), + G = key(3, 0x04), k8 = key(3, 0x08), + B = key(3, 0x10), H = key(3, 0x20), + U = key(3, 0x40), V = key(3, 0x80), + + k9 = key(4, 0x01), I = key(4, 0x02), + J = key(4, 0x04), k0 = key(4, 0x08), + M = key(4, 0x10), K = key(4, 0x20), + O = key(4, 0x40), N = key(4, 0x80), + + Down = key(5, 0x01), P = key(5, 0x02), + L = key(5, 0x04), Up = key(5, 0x08), + FullStop = key(5, 0x10), Colon = key(5, 0x20), + Minus = key(5, 0x40), Comma = key(5, 0x80), + + Left = key(6, 0x01), Asterisk = key(6, 0x02), + Semicolon = key(6, 0x04), Right = key(6, 0x08), + Escape = key(6, 0x10), Equals = key(6, 0x20), + Plus = key(6, 0x40), Slash = key(6, 0x80), + + k1 = key(7, 0x01), Clear_Home = key(7, 0x02), + Control = key(7, 0x04), k2 = key(7, 0x08), + Space = key(7, 0x10), Commodore = key(7, 0x20), + Q = key(7, 0x40), Run_Stop = key(7, 0x80), + + // Bonus virtual keys: + F4 = 0xfe00, + F5 = 0xfe01, + F6 = 0xfe02, + F7 = 0xfe03, +}; + +struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final; +}; + +//struct CharacterMapper: public ::Utility::CharacterMapper { +// const uint16_t *sequence_for_character(char character) const final; +//}; + +} diff --git a/Machines/Commodore/Plus4/Pager.hpp b/Machines/Commodore/Plus4/Pager.hpp new file mode 100644 index 0000000000..1bb17789bf --- /dev/null +++ b/Machines/Commodore/Plus4/Pager.hpp @@ -0,0 +1,62 @@ +// +// Pager.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/12/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +enum PagerSide { + Read = 1, + Write = 2, + ReadWrite = Read | Write, +}; + +template +class Pager { +public: + DataT read(AddressT address) const { + return read_[address >> Shift][address]; + } + DataT &write(AddressT address) { + return write_[address >> Shift][address]; + } + + template + void page(uint8_t *data) { + static_assert(!(start % PageSize), "Start address must be a multiple of the page size"); + static_assert(!(length % PageSize), "Data length must be a multiple of the page size"); + + for(size_t slot = start >> Shift; slot < (start + length) >> Shift; slot++) { + if constexpr (side & PagerSide::Write) write_[slot] = data - (slot << Shift); + if constexpr (side & PagerSide::Read) read_[slot] = data - (slot << Shift); + data += PageSize; + } + } + +private: + std::array write_{}; + std::array read_{}; + + static constexpr auto AddressBits = sizeof(AddressT) * 8; + static constexpr auto PageSize = (1 << AddressBits) / NumPages; + static_assert(!(PageSize & (PageSize - 1)), "Pages must be a power of two in size"); + + static constexpr int ln2(int value) { + int result = 0; + while(value != 1) { + value >>= 1; + ++result; + } + return result; + } + static constexpr auto Shift = ln2(PageSize); +}; + +namespace Commodore::Plus4 { + +using Pager = Pager; + +} diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index 7832c35d7f..f8ec1fd9c8 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -8,37 +8,514 @@ #include "Plus4.hpp" -#include "../../MachineTypes.hpp" +#include "Audio.hpp" +#include "Interrupts.hpp" +#include "Keyboard.hpp" +#include "Pager.hpp" +#include "Video.hpp" +#include "../../MachineTypes.hpp" +#include "../../Utility/MemoryFuzzer.hpp" +#include "../../../Processors/6502/6502.hpp" #include "../../../Analyser/Static/Commodore/Target.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../../Storage/Tape/Tape.hpp" +#include "../SerialBus.hpp" +#include "../1540/C1540.hpp" + +using namespace Commodore; using namespace Commodore::Plus4; namespace { +class Timers { +public: + Timers(Interrupts &interrupts) : interrupts_(interrupts) {} + + template + void write(const uint8_t value) { + const auto load_low = [&](uint16_t &target) { + target = uint16_t((target & 0xff00) | (value << 0)); + }; + const auto load_high = [&](uint16_t &target) { + target = uint16_t((target & 0x00ff) | (value << 8)); + }; + + constexpr auto timer = offset >> 1; + paused_[timer] = !(offset & 1); + if constexpr (offset & 1) { + load_high(timers_[timer]); + if(!timer) { + load_high(timer0_reload_); + } + } else { + load_low(timers_[timer]); + if(!timer) { + load_low(timer0_reload_); + } + } + } + + template + uint8_t read() { + constexpr auto timer = offset >> 1; + if constexpr (offset & 1) { + return uint8_t(timers_[timer] >> 8); + } else { + return uint8_t(timers_[timer] >> 0); + } + } + + void tick(int count) { + // Quick hack here; do better than stepping through one at a time. + while(count--) { + decrement<0>(); + decrement<1>(); + decrement<2>(); + } + } + +private: + template + void decrement() { + if(paused_[timer]) return; + + // Check for reload. + if(!timer && !timers_[timer]) { + timers_[timer] = timer0_reload_; + } + + -- timers_[timer]; + + // Check for interrupt. + if(!timers_[timer]) { + switch(timer) { + case 0: interrupts_.apply(Interrupts::Flag::Timer1); break; + case 1: interrupts_.apply(Interrupts::Flag::Timer2); break; + case 2: interrupts_.apply(Interrupts::Flag::Timer3); break; + } + } + } + + uint16_t timers_[3]{}; + uint16_t timer0_reload_ = 0xffff; + bool paused_[3]{}; + + Interrupts &interrupts_; +}; + +class SerialPort: public Serial::Port { +public: + void set_input(const Serial::Line line, const Serial::LineLevel value) override { + levels_[size_t(line)] = value; + } + + Serial::LineLevel level(const Serial::Line line) const { + return levels_[size_t(line)]; + } + +private: + Serial::LineLevel levels_[5]; +}; + class ConcreteMachine: + public Activity::Source, + public BusController, + public CPU::MOS6502::BusHandler, + public MachineTypes::AudioProducer, + public MachineTypes::MappedKeyboardMachine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, + public MachineTypes::MediaTarget, public Machine { public: - ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) { - (void)target; - (void)rom_fetcher; + ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + m6502_(*this), + interrupts_(*this), + timers_(interrupts_), + video_(video_map_, interrupts_), + audio_(audio_queue_), + speaker_(audio_) + { + const auto clock = clock_rate(false); + media_divider_ = Cycles(clock); + set_clock_rate(clock); + speaker_.set_input_rate(float(clock) / 32.0f); + + // TODO: decide whether to attach a 1541 for real. + const bool has_c1541 = true; + + const auto kernel = ROM::Name::Plus4KernelPALv5; + const auto basic = ROM::Name::Plus4BASIC; + ROM::Request request = ROM::Request(basic) && ROM::Request(kernel); + if(has_c1541) { + request = request && C1540::Machine::rom_request(C1540::Personality::C1541); + } + + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + + kernel_ = roms.find(kernel)->second; + basic_ = roms.find(basic)->second; + + Memory::Fuzz(ram_); + map_.page(ram_.data()); + page_cpu_rom(); + + video_map_.page(ram_.data()); + + if(has_c1541) { + c1541_ = std::make_unique(C1540::Personality::C1541, roms); + c1541_->set_serial_bus(serial_bus_); + Serial::attach(serial_port_, serial_bus_); + } + + tape_player_ = std::make_unique(clock); + + insert_media(target.media); + printf("Loading command is: %s\n", target.loading_command.c_str()); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + Cycles perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + const uint16_t address, + uint8_t *const value + ) { + // Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502, + // relative to the master clock. + const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready); + + // Update other subsystems. + timers_subcycles_ += length; + const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length()); + timers_.tick(timers_cycles.as()); + + video_.run_for(length); + tape_player_->run_for(length); + + if(c1541_) { + c1541_cycles_ += length * Cycles(1'000'000); + c1541_->run_for(c1541_cycles_.divide(media_divider_)); + } + + time_since_audio_update_ += length; + + if(operation == CPU::MOS6502::BusOperation::Ready) { + return length; + } + + // Perform actual access. + if(address < 0x0002) { + // 0x0000: data directions for parallel IO; 1 = output. + // 0x0001: + // b7 = serial data in; + // b6 = serial clock in and cassette write; + // b5 = [unconnected]; + // b4 = cassette read; + // b3 = cassette motor, 1 = off; + // b2 = serial ATN out; + // b1 = serial clock out and cassette write; + // b0 = serial data out. + + if(isReadOperation(operation)) { + if(!address) { + *value = io_direction_; + } else { + const uint8_t all_inputs = + (tape_player_->input() ? 0x10 : 0x00) | + (serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) | + (serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00); + *value = + (io_direction_ & io_output_) | + (~io_direction_ & all_inputs); + } + } else { + if(!address) { + io_direction_ = *value; + } else { + io_output_ = *value; + } + + const auto output = io_output_ | ~io_direction_; +// tape_player_->set_motor_control(~output & 0x08); + serial_port_.set_output(Serial::Line::Data, Serial::LineLevel(~output & 0x01)); + serial_port_.set_output(Serial::Line::Clock, Serial::LineLevel(~output & 0x02)); + serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04)); + } + +// printf("%04x: %02x %c\n", address, *value, isReadOperation(operation) ? 'r' : 'w'); + } else if(address < 0xfd00 || address >= 0xff40) { + if(isReadOperation(operation)) { + *value = map_.read(address); + } else { + map_.write(address) = *value; + } + } else if(address < 0xff00) { + // Miscellaneous hardware. All TODO. + if(isReadOperation(operation)) { +// printf("TODO: read @ %04x\n", address); + if((address & 0xfff0) == 0xfd10) { + tape_player_->set_motor_control(true); + *value = 0xff ^ 0x4; // Seems to detect the play button. + } else { + *value = 0xff; + } + } else { +// printf("TODO: write of %02x @ %04x\n", *value, address); + } + } else { + if(isReadOperation(operation)) { + switch(address) { + case 0xff00: *value = timers_.read<0>(); break; + case 0xff01: *value = timers_.read<1>(); break; + case 0xff02: *value = timers_.read<2>(); break; + case 0xff03: *value = timers_.read<3>(); break; + case 0xff04: *value = timers_.read<4>(); break; + case 0xff05: *value = timers_.read<5>(); break; + + case 0xff08: *value = keyboard_latch_; break; + + case 0xff09: *value = interrupts_.status(); break; + case 0xff0a: *value = interrupts_.mask(); break; + + case 0xff06: *value = video_.read<0xff06>(); break; + case 0xff07: *value = video_.read<0xff07>(); break; + case 0xff0b: *value = video_.read<0xff0b>(); break; + case 0xff1c: *value = video_.read<0xff1c>(); break; + case 0xff1d: *value = video_.read<0xff1d>(); break; + + case 0xff12: *value = ff12_; break; + case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break; + + case 0xff14: *value = video_.read<0xff14>(); break; + case 0xff15: *value = video_.read<0xff15>(); break; + case 0xff16: *value = video_.read<0xff16>(); break; + case 0xff17: *value = video_.read<0xff17>(); break; + case 0xff18: *value = video_.read<0xff18>(); break; + case 0xff19: *value = video_.read<0xff19>(); break; + + default: + printf("TODO: TED read at %04x\n", address); + } + } else { + switch(address) { + case 0xff00: timers_.write<0>(*value); break; + case 0xff01: timers_.write<1>(*value); break; + case 0xff02: timers_.write<2>(*value); break; + case 0xff03: timers_.write<3>(*value); break; + case 0xff04: timers_.write<4>(*value); break; + case 0xff05: timers_.write<5>(*value); break; + + case 0xff08: + keyboard_latch_ = ~( + ((*value & 0x01) ? 0x00 : key_states_[0]) | + ((*value & 0x02) ? 0x00 : key_states_[1]) | + ((*value & 0x04) ? 0x00 : key_states_[2]) | + ((*value & 0x08) ? 0x00 : key_states_[3]) | + ((*value & 0x10) ? 0x00 : key_states_[4]) | + ((*value & 0x20) ? 0x00 : key_states_[5]) | + ((*value & 0x40) ? 0x00 : key_states_[6]) | + ((*value & 0x80) ? 0x00 : key_states_[7]) + ); + break; + + case 0xff09: + interrupts_.set_status(*value); + break; + case 0xff0a: + interrupts_.set_mask(*value); + video_.write<0xff0a>(*value); + break; + case 0xff0b: video_.write<0xff0b>(*value); break; + + case 0xff06: video_.write<0xff06>(*value); break; + case 0xff07: video_.write<0xff07>(*value); break; + case 0xff0c: video_.write<0xff0c>(*value); break; + case 0xff0d: video_.write<0xff0d>(*value); break; + case 0xff0e: + update_audio(); + audio_.set_frequency_low<0>(*value); + break; + case 0xff0f: + update_audio(); + audio_.set_frequency_low<1>(*value); + break; + case 0xff10: + update_audio(); + audio_.set_frequency_high<1>(*value); + break; + case 0xff11: + update_audio(); + audio_.set_constrol(*value); + break; + case 0xff12: + ff12_ = *value & 0x3f; + video_.write<0xff12>(*value); + + if((*value & 4)) { + page_video_rom(); + } else { + page_video_ram(); + } + + update_audio(); + audio_.set_frequency_high<0>(*value); + break; + case 0xff13: + ff13_ = *value & 0xfe; + video_.write<0xff13>(*value); + break; + case 0xff14: video_.write<0xff14>(*value); break; + case 0xff1a: video_.write<0xff1a>(*value); break; + case 0xff1b: video_.write<0xff1b>(*value); break; + + case 0xff15: video_.write<0xff15>(*value); break; + case 0xff16: video_.write<0xff16>(*value); break; + case 0xff17: video_.write<0xff17>(*value); break; + case 0xff18: video_.write<0xff18>(*value); break; + case 0xff19: video_.write<0xff19>(*value); break; + + case 0xff3e: page_cpu_rom(); break; + case 0xff3f: page_cpu_ram(); break; + + // TODO: audio is 0xff10, 0xff11, 0xff0e, 0xff0f and shares 0xff18. + + default: + printf("TODO: TED write at %04x\n", address); + } + } + } + + return length; } private: - void set_scan_target(Outputs::Display::ScanTarget *const) final { + CPU::MOS6502::Processor m6502_; + + Outputs::Speaker::Speaker *get_speaker() override { + return &speaker_; + } + + void set_activity_observer(Activity::Observer *const observer) final { + if(c1541_) c1541_->set_activity_observer(observer); + } + + void set_irq_line(bool active) override { + m6502_.set_irq_line(active); + } + void set_ready_line(bool active) override { + m6502_.set_ready_line(active); + } + + void page_video_rom() { + video_map_.page(basic_.data()); + video_map_.page(kernel_.data()); + } + void page_video_ram() { + video_map_.page(&ram_[0x8000]); + } + + void page_cpu_rom() { + // TODO: allow other ROM selection. And no ROM? + map_.page(basic_.data()); + map_.page(kernel_.data()); + rom_is_paged_ = true; + } + void page_cpu_ram() { + map_.page(&ram_[0x8000]); + rom_is_paged_ = false; + } + bool rom_is_paged_ = false; + + void set_scan_target(Outputs::Display::ScanTarget *const target) final { + video_.set_scan_target(target); } Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return {}; + return video_.get_scaled_scan_status(); } - void run_for(const Cycles) final { + void run_for(const Cycles cycles) final { + m6502_.run_for(cycles); + + // I don't know why. + update_audio(); + audio_queue_.perform(); + } + + void flush_output(int outputs) override { + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); + } + } + + bool insert_media(const Analyser::Static::Media &media) final { + if(!media.tapes.empty()) { + tape_player_->set_tape(media.tapes[0]); + } + + if(!media.disks.empty() && c1541_) { + c1541_->set_disk(media.disks[0]); + } + + return true; } -}; + Plus4::Pager map_; + Plus4::Pager video_map_; + std::array ram_; + std::vector kernel_; + std::vector basic_; + uint8_t ff12_, ff13_; + + Interrupts interrupts_; + Cycles timers_subcycles_; + Timers timers_; + Video video_; + + Concurrency::AsyncTaskQueue audio_queue_; + Audio audio_; + Cycles time_since_audio_update_; + Outputs::Speaker::PullLowpass