diff --git a/Activity/Observer.hpp b/Activity/Observer.hpp new file mode 100644 index 000000000..2690dc0a0 --- /dev/null +++ b/Activity/Observer.hpp @@ -0,0 +1,51 @@ +// +// ActivityObserver.h +// Clock Signal +// +// Created by Thomas Harte on 07/05/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ActivityObserver_h +#define ActivityObserver_h + +#include + +namespace Activity { + +/*! + Provides a purely virtual base class for anybody that wants to receive notifications of + 'activity' — any feedback from an emulated system which a user could perceive other than + through the machine's native audio and video outputs. + + So: status LEDs, drive activity, etc. A receiver may choose to make appropriate noises + and/or to show or unshow status indicators. +*/ +class Observer { + public: + /// Announces to the receiver that there is an LED of name @c name. + virtual void register_led(const std::string &name) = 0; + + /// Announces to the receiver that there is a drive of name @c name. + virtual void register_drive(const std::string &name) = 0; + + /// Informs the receiver of the new state of the LED with name @c name. + virtual void set_led_status(const std::string &name, bool lit) = 0; + + enum class DriveEvent { + StepNormal, + StepBelowZero, + StepBeyondMaximum + }; + + /// Informs the receiver that the named event just occurred for the drive with name @c name. + virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0; + + /// Informs the receiver of the motor-on status of the drive with name @c name. + virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0; + +}; + +} + +#endif /* ActivityObserver_h */ diff --git a/Activity/Source.hpp b/Activity/Source.hpp new file mode 100644 index 000000000..9db6b7a53 --- /dev/null +++ b/Activity/Source.hpp @@ -0,0 +1,24 @@ +// +// ActivitySource.h +// Clock Signal +// +// Created by Thomas Harte on 07/05/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ActivitySource_h +#define ActivitySource_h + +#include "Observer.hpp" + +namespace Activity { + +class Source { + public: + virtual void set_activity_observer(Observer *observer) = 0; +}; + +} + + +#endif /* ActivitySource_h */ diff --git a/Analyser/Dynamic/MultiMachine/MultiMachine.cpp b/Analyser/Dynamic/MultiMachine/MultiMachine.cpp index 9e33047dd..5f7a92f29 100644 --- a/Analyser/Dynamic/MultiMachine/MultiMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/MultiMachine.cpp @@ -22,6 +22,10 @@ MultiMachine::MultiMachine(std::vector> &&machin crt_machine_.set_delegate(this); } +Activity::Source *MultiMachine::activity_source() { + return nullptr; // TODO +} + ConfigurationTarget::Machine *MultiMachine::configuration_target() { if(has_picked_) { return machines_.front()->configuration_target(); diff --git a/Analyser/Dynamic/MultiMachine/MultiMachine.hpp b/Analyser/Dynamic/MultiMachine/MultiMachine.hpp index de343e9c3..f1a8ea845 100644 --- a/Analyser/Dynamic/MultiMachine/MultiMachine.hpp +++ b/Analyser/Dynamic/MultiMachine/MultiMachine.hpp @@ -50,6 +50,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De static bool would_collapse(const std::vector> &machines); MultiMachine(std::vector> &&machines); + Activity::Source *activity_source() override; ConfigurationTarget::Machine *configuration_target() override; CRTMachine::Machine *crt_machine() override; JoystickMachine::Machine *joystick_machine() override; diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 45ef59fc9..d21e2190c 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -113,6 +113,9 @@ template class MOS6522: public MOS6522Base { /*! Gets a register value. */ uint8_t get_register(int address); + /*! @returns the bus handler. */ + T &bus_handler(); + private: T &bus_handler_; diff --git a/Components/6522/Implementation/6522Implementation.hpp b/Components/6522/Implementation/6522Implementation.hpp index eac2a0648..5efe8905e 100644 --- a/Components/6522/Implementation/6522Implementation.hpp +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -145,6 +145,10 @@ template uint8_t MOS6522::get_port_input(Port port, uint8_t outp return (input & ~output_mask) | (output & output_mask); } +template T &MOS6522::bus_handler() { + return bus_handler_; +} + // Delegate and communications template void MOS6522::reevaluate_interrupts() { bool new_interrupt_status = get_interrupt_line(); diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index da897c36c..373c5ccf6 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -236,3 +236,7 @@ uint8_t DiskII::trigger_address(int address, uint8_t value) { return 0xff; } +void DiskII::set_activity_observer(Activity::Observer *observer) { + drives_[0].set_activity_observer(observer, "Drive 1", true); + drives_[1].set_activity_observer(observer, "Drive 2", true); +} diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index fd37e9353..37e083ef8 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -15,6 +15,8 @@ #include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Disk/Drive.hpp" +#include "../../Activity/Observer.hpp" + #include #include #include @@ -40,6 +42,8 @@ class DiskII: void set_disk(const std::shared_ptr &disk, int drive); bool is_sleeping() override; + void set_activity_observer(Activity::Observer *observer); + private: enum class Control { P0, P1, P2, P3, diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index db1720cb5..fa60a0d4f 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -20,6 +20,7 @@ #include "../Utility/MemoryFuzzer.hpp" #include "../Utility/Typer.hpp" +#include "../../Activity/Source.hpp" #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../KeyboardMachine.hpp" @@ -607,6 +608,10 @@ class FDC: public Intel::i8272::i8272 { void set_disk(std::shared_ptr disk, int drive) { drive_->set_disk(disk); } + + void set_activity_observer(Activity::Observer *observer) { + drive_->set_activity_observer(observer, "Drive 1", true); + } }; /*! @@ -690,7 +695,8 @@ class ConcreteMachine: public Utility::TypeRecipient, public CPU::Z80::BusHandler, public Sleeper::SleepObserver, - public Machine { + public Machine, + public Activity::Source { public: ConcreteMachine() : z80_(*this), @@ -995,6 +1001,12 @@ class ConcreteMachine: return &keyboard_mapper_; } + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) override { + if(has_fdc_) fdc_.set_activity_observer(observer); + } + + private: inline void write_to_gate_array(uint8_t value) { switch(value >> 6) { diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index f75655762..e312e05b8 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -8,6 +8,7 @@ #include "AppleII.hpp" +#include "../../Activity/Source.hpp" #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../KeyboardMachine.hpp" @@ -24,6 +25,7 @@ #include "../../Analyser/Static/AppleII/Target.hpp" +#include #include namespace { @@ -34,7 +36,8 @@ class ConcreteMachine: public KeyboardMachine::Machine, public CPU::MOS6502::BusHandler, public Inputs::Keyboard, - public AppleII::Machine { + public AppleII::Machine, + public Activity::Source { private: struct VideoBusHandler : public AppleII::Video::BusHandler { public: @@ -62,8 +65,8 @@ class ConcreteMachine: speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); } void update_cards() { - for(int c = 0; c < 7; ++c) { - if(cards_[c]) cards_[c]->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); + for(const auto &card : cards_) { + if(card) card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); } cycles_since_card_update_ = 0; stretched_cycles_since_card_update_ = 0; @@ -80,7 +83,7 @@ class ConcreteMachine: Cycles cycles_since_audio_update_; ROMMachine::ROMFetcher rom_fetcher_; - std::unique_ptr cards_[7]; + std::array, 7> cards_; Cycles cycles_since_card_update_; int stretched_cycles_since_card_update_ = 0; @@ -254,7 +257,7 @@ class ConcreteMachine: Decode the area conventionally used by cards for ROMs: 0xCn00 — 0xCnff: card n. */ - const int card_number = (address - 0xc100) >> 8; + const size_t card_number = (address - 0xc100) >> 8; if(cards_[card_number]) { update_cards(); cards_[card_number]->perform_bus_operation(operation, address & 0xff, value); @@ -264,7 +267,7 @@ class ConcreteMachine: Decode the area conventionally used by cards for registers: C0n0--C0nF: card n - 8. */ - const int card_number = (address - 0xc090) >> 4; + const size_t card_number = (address - 0xc090) >> 4; if(cards_[card_number]) { update_cards(); cards_[card_number]->perform_bus_operation(operation, 0x100 | (address&0xf), value); @@ -372,6 +375,13 @@ class ConcreteMachine: } return true; } + + // MARK: Activity::Source + void set_activity_observer(Activity::Observer *observer) override { + for(const auto &card: cards_) { + if(card) card->set_activity_observer(observer); + } + } }; } diff --git a/Machines/AppleII/Card.hpp b/Machines/AppleII/Card.hpp index d7c00cdb6..de89bb75e 100644 --- a/Machines/AppleII/Card.hpp +++ b/Machines/AppleII/Card.hpp @@ -11,6 +11,7 @@ #include "../../Processors/6502/6502.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Activity/Observer.hpp" namespace AppleII { @@ -21,6 +22,9 @@ class Card { /*! Performs a bus operation; the card is implicitly selected. */ virtual void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) = 0; + + /*! Supplies a target for observers. */ + virtual void set_activity_observer(Activity::Observer *observer) {} }; } diff --git a/Machines/AppleII/DiskIICard.cpp b/Machines/AppleII/DiskIICard.cpp index b0941b28e..527b23b39 100644 --- a/Machines/AppleII/DiskIICard.cpp +++ b/Machines/AppleII/DiskIICard.cpp @@ -40,3 +40,7 @@ void DiskIICard::run_for(Cycles cycles, int stretches) { void DiskIICard::set_disk(const std::shared_ptr &disk, int drive) { diskii_.set_disk(disk, drive); } + +void DiskIICard::set_activity_observer(Activity::Observer *observer) { + diskii_.set_activity_observer(observer); +} diff --git a/Machines/AppleII/DiskIICard.hpp b/Machines/AppleII/DiskIICard.hpp index 7f03d0ddc..849e37f94 100644 --- a/Machines/AppleII/DiskIICard.hpp +++ b/Machines/AppleII/DiskIICard.hpp @@ -24,8 +24,11 @@ namespace AppleII { class DiskIICard: public Card { public: DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector); + void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) override; void run_for(Cycles cycles, int stretches) override; + void set_activity_observer(Activity::Observer *observer) override; + void set_disk(const std::shared_ptr &disk, int drive); private: diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index f08deb661..ec0140f71 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -108,6 +108,11 @@ void Machine::run_for(const Cycles cycles) { Storage::Disk::Controller::run_for(cycles); } +void MachineBase::set_activity_observer(Activity::Observer *observer) { + drive_VIA_.bus_handler().set_activity_observer(observer); + drive_->set_activity_observer(observer, "Drive", false); +} + // MARK: - 6522 delegate void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) { @@ -209,8 +214,6 @@ void DriveVIA::set_delegate(Delegate *delegate) { } // write protect tab uncovered -DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {} - uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) { return port ? port_b_ : port_a_; } @@ -255,14 +258,22 @@ void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t d delegate_->drive_via_did_set_data_density(this, (value >> 5)&3); } - // TODO: something with the drive LED -// printf("LED: %s\n", value&8 ? "On" : "Off"); + // post the LED status + if(observer_) observer_->set_led_status("Drive", !!(value&8)); previous_port_b_output_ = value; } } } +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)); + } +} + // MARK: - SerialPort void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 3b54788e7..aef0b8686 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -14,6 +14,7 @@ #include "../../SerialBus.hpp" +#include "../../../../Activity/Source.hpp" #include "../../../../Storage/Disk/Disk.hpp" #include "../../../../Storage/Disk/Controller/DiskController.hpp" @@ -83,8 +84,6 @@ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { }; void set_delegate(Delegate *); - DriveVIA(); - uint8_t get_port_input(MOS::MOS6522::Port port); void set_sync_detected(bool); @@ -96,12 +95,15 @@ 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); + private: - uint8_t port_b_, port_a_; - bool should_set_overflow_; - bool drive_motor_; - uint8_t previous_port_b_output_; - Delegate *delegate_; + 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; }; /*! @@ -135,6 +137,9 @@ class MachineBase: 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); + protected: CPU::MOS6502::Processor m6502_; std::shared_ptr drive_; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index e119014bd..d2066ec9e 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -10,6 +10,7 @@ #include "Keyboard.hpp" +#include "../../../Activity/Source.hpp" #include "../../ConfigurationTarget.hpp" #include "../../CRTMachine.hpp" #include "../../KeyboardMachine.hpp" @@ -303,7 +304,8 @@ class ConcreteMachine: public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Machine, - public Sleeper::SleepObserver { + public Sleeper::SleepObserver, + public Activity::Source { public: ConcreteMachine() : m6502_(*this), @@ -752,6 +754,11 @@ class ConcreteMachine: set_use_fast_tape(); } + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) override { + if(c1540_) c1540_->set_activity_observer(observer); + } + private: void update_video() { mos6560_->run_for(cycles_since_mos6560_update_.flush()); diff --git a/Machines/DynamicMachine.hpp b/Machines/DynamicMachine.hpp index 6d261b6da..a8f2595b5 100644 --- a/Machines/DynamicMachine.hpp +++ b/Machines/DynamicMachine.hpp @@ -10,6 +10,7 @@ #define DynamicMachine_h #include "../Configurable/Configurable.hpp" +#include "../Activity/Source.hpp" #include "ConfigurationTarget.hpp" #include "CRTMachine.hpp" #include "JoystickMachine.hpp" @@ -24,6 +25,8 @@ namespace Machine { */ struct DynamicMachine { virtual ~DynamicMachine() {} + + virtual Activity::Source *activity_source() = 0; virtual ConfigurationTarget::Machine *configuration_target() = 0; virtual CRTMachine::Machine *crt_machine() = 0; virtual JoystickMachine::Machine *joystick_machine() = 0; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 36a2ff5fd..103c2f855 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -8,6 +8,7 @@ #include "Electron.hpp" +#include "../../Activity/Source.hpp" #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../KeyboardMachine.hpp" @@ -45,7 +46,8 @@ class ConcreteMachine: public Configurable::Device, public CPU::MOS6502::BusHandler, public Tape::Delegate, - public Utility::TypeRecipient { + public Utility::TypeRecipient, + public Activity::Source { public: ConcreteMachine() : m6502_(*this), @@ -215,12 +217,15 @@ class ConcreteMachine: tape_.set_is_enabled((*value & 6) != 6); tape_.set_is_in_input_mode((*value & 6) == 0); - tape_.set_is_running(((*value)&0x40) ? true : false); + tape_.set_is_running((*value & 0x40) ? true : false); - // TODO: caps lock LED + caps_led_state_ = !!(*value & 0x80); + if(activity_observer_) + activity_observer_->set_led_status(caps_led, caps_led_state_); } - // deliberate fallthrough + // deliberate fallthrough; fe07 contains the display mode. + case 0xfe02: case 0xfe03: case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: @@ -322,8 +327,6 @@ class ConcreteMachine: if(!ram_[0x247] && service_call == 14) { tape_.set_delegate(nullptr); - // TODO: handle tape wrap around. - int cycles_left_while_plausibly_in_data = 50; tape_.clear_interrupts(Interrupt::ReceiveDataFull); while(!tape_.get_tape()->is_at_end()) { @@ -477,6 +480,14 @@ class ConcreteMachine: return selection_set; } + void set_activity_observer(Activity::Observer *observer) override { + activity_observer_ = observer; + if(activity_observer_) { + activity_observer_->register_led(caps_led); + activity_observer_->set_led_status(caps_led, caps_led_state_); + } + } + private: // MARK: - Work deferral updates. inline void update_display() { @@ -563,6 +574,11 @@ class ConcreteMachine: Outputs::Speaker::LowpassSpeaker speaker_; bool speaker_is_enabled_ = false; + + // MARK: - Caps Lock status and the activity observer. + const std::string caps_led = "CAPS"; + bool caps_led_state_ = false; + Activity::Observer *activity_observer_ = nullptr; }; } diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index 95178291a..1338bd11d 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -53,3 +53,8 @@ void Plus3::set_motor_on(bool on) { // writing state, so plenty of work to do in general here. get_drive().set_motor_on(on); } + +void Plus3::set_activity_observer(Activity::Observer *observer) { + drives_[0]->set_activity_observer(observer, "Drive 1", true); + drives_[1]->set_activity_observer(observer, "Drive 2", true); +} diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp index 71b8b1c4b..d5bf1a7c9 100644 --- a/Machines/Electron/Plus3.hpp +++ b/Machines/Electron/Plus3.hpp @@ -10,6 +10,7 @@ #define Plus3_hpp #include "../../Components/1770/1770.hpp" +#include "../../Activity/Observer.hpp" namespace Electron { @@ -19,6 +20,7 @@ class Plus3 : public WD::WD1770 { void set_disk(std::shared_ptr disk, int drive); void set_control_register(uint8_t control); + void set_activity_observer(Activity::Observer *observer); private: void set_control_register(uint8_t control, uint8_t changes); diff --git a/Machines/MSX/DiskROM.cpp b/Machines/MSX/DiskROM.cpp index 2025098b9..af414657a 100644 --- a/Machines/MSX/DiskROM.cpp +++ b/Machines/MSX/DiskROM.cpp @@ -56,11 +56,12 @@ void DiskROM::run_for(HalfCycles half_cycles) { controller_cycles_ %= 715909; } -void DiskROM::set_disk(std::shared_ptr disk, int drive) { +void DiskROM::set_disk(std::shared_ptr disk, size_t drive) { if(!drives_[drive]) { drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); drives_[drive]->set_head(selected_head_); if(drive == selected_drive_) set_drive(drives_[drive]); + drives_[drive]->set_activity_observer(observer_, drive_name(drive), true); } drives_[drive]->set_disk(disk); } @@ -69,3 +70,16 @@ void DiskROM::set_head_load_request(bool head_load) { // Magic! set_head_loaded(head_load); } + +void DiskROM::set_activity_observer(Activity::Observer *observer) { + size_t c = 0; + observer_ = observer; + for(auto &drive: drives_) { + if(drive) drive->set_activity_observer(observer, drive_name(c), true); + ++c; + } +} + +std::string DiskROM::drive_name(size_t index) { + return "Drive " + std::to_string(index); +} diff --git a/Machines/MSX/DiskROM.hpp b/Machines/MSX/DiskROM.hpp index bab03d380..b3f56d92d 100644 --- a/Machines/MSX/DiskROM.hpp +++ b/Machines/MSX/DiskROM.hpp @@ -11,9 +11,12 @@ #include "ROMSlotHandler.hpp" +#include "../../Activity/Source.hpp" #include "../../Components/1770/1770.hpp" +#include #include +#include #include namespace MSX { @@ -26,17 +29,20 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 { uint8_t read(uint16_t address) override; void run_for(HalfCycles half_cycles) override; - void set_disk(std::shared_ptr disk, int drive); + void set_disk(std::shared_ptr disk, size_t drive); + void set_activity_observer(Activity::Observer *observer); private: const std::vector &rom_; long int controller_cycles_ = 0; - int selected_drive_ = 0; + size_t selected_drive_ = 0; int selected_head_ = 0; - std::shared_ptr drives_[4]; + std::array, 2> drives_; void set_head_load_request(bool head_load) override; + std::string drive_name(size_t index); + Activity::Observer *observer_ = nullptr; }; } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 6b57f714c..daed39b44 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -30,6 +30,7 @@ #include "../../Storage/Tape/Parsers/MSX.hpp" #include "../../Storage/Tape/Tape.hpp" +#include "../../Activity/Source.hpp" #include "../CRTMachine.hpp" #include "../ConfigurationTarget.hpp" #include "../KeyboardMachine.hpp" @@ -87,7 +88,8 @@ class ConcreteMachine: public KeyboardMachine::Machine, public Configurable::Device, public MemoryMap, - public Sleeper::SleepObserver { + public Sleeper::SleepObserver, + public Activity::Source { public: ConcreteMachine(): z80_(*this), @@ -200,12 +202,14 @@ class ConcreteMachine: } if(!media.disks.empty()) { - DiskROM *disk_rom = dynamic_cast(memory_slots_[2].handler.get()); - int drive = 0; - for(auto &disk : media.disks) { - disk_rom->set_disk(disk, drive); - drive++; - if(drive == 2) break; + DiskROM *disk_rom = get_disk_rom(); + if(disk_rom) { + size_t drive = 0; + for(auto &disk : media.disks) { + disk_rom->set_disk(disk, drive); + drive++; + if(drive == 2) break; + } } } @@ -556,7 +560,18 @@ class ConcreteMachine: set_use_fast_tape(); } + // MARK: - Activity::Source + void set_activity_observer(Activity::Observer *observer) override { + DiskROM *disk_rom = get_disk_rom(); + if(disk_rom) { + disk_rom->set_activity_observer(observer); + } + } + private: + DiskROM *get_disk_rom() { + return dynamic_cast(memory_slots_[2].handler.get()); + } void update_audio() { speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); } diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index a9701967e..e040ce5fb 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -22,10 +22,11 @@ Microdisc::Microdisc() : WD1770(P1793) { set_control_register(last_control_, 0xff); } -void Microdisc::set_disk(std::shared_ptr disk, int drive) { +void Microdisc::set_disk(std::shared_ptr disk, size_t drive) { if(!drives_[drive]) { drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); if(drive == selected_drive_) set_drive(drives_[drive]); + drives_[drive]->set_activity_observer(observer_, drive_name(drive), false); } drives_[drive]->set_disk(disk); } @@ -48,8 +49,8 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) { // b4: side select if(changes & 0x10) { int head = (control & 0x10) ? 1 : 0; - for(int c = 0; c < 4; c++) { - if(drives_[c]) drives_[c]->set_head(head); + for(auto &drive : drives_) { + if(drive) drive->set_head(head); } } @@ -89,10 +90,12 @@ uint8_t Microdisc::get_data_request_register() { } void Microdisc::set_head_load_request(bool head_load) { + head_load_request_ = head_load; + // The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive // the current head load request state. - for(int c = 0; c < 4; c++) { - if(drives_[c]) drives_[c]->set_motor_on(head_load); + for(auto &drive : drives_) { + if(drive) drive->set_motor_on(head_load); } // A request to load the head results in a delay until the head is confirmed loaded. This delay is handled @@ -103,6 +106,10 @@ void Microdisc::set_head_load_request(bool head_load) { head_load_request_counter_ = head_load_request_counter_target; set_head_loaded(head_load); } + + if(observer_) { + observer_->set_led_status("Microdisc", head_load); + } } void Microdisc::run_for(const Cycles cycles) { @@ -116,3 +123,20 @@ void Microdisc::run_for(const Cycles cycles) { bool Microdisc::get_drive_is_ready() { return true; } + +void Microdisc::set_activity_observer(Activity::Observer *observer) { + observer_ = observer; + if(observer) { + observer->register_led("Microdisc"); + observer_->set_led_status("Microdisc", head_load_request_); + } + size_t c = 0; + for(auto &drive : drives_) { + if(drive) drive->set_activity_observer(observer, drive_name(c), false); + ++c; + } +} + +std::string Microdisc::drive_name(size_t index) { + return "Drive " + std::to_string(index); +} diff --git a/Machines/Oric/Microdisc.hpp b/Machines/Oric/Microdisc.hpp index 1ea325dc7..862e7ec8e 100644 --- a/Machines/Oric/Microdisc.hpp +++ b/Machines/Oric/Microdisc.hpp @@ -10,6 +10,9 @@ #define Microdisc_hpp #include "../../Components/1770/1770.hpp" +#include "../../Activity/Observer.hpp" + +#include namespace Oric { @@ -17,7 +20,7 @@ class Microdisc: public WD::WD1770 { public: Microdisc(); - void set_disk(std::shared_ptr disk, int drive); + void set_disk(std::shared_ptr disk, size_t drive); void set_control_register(uint8_t control); uint8_t get_interrupt_request_register(); uint8_t get_data_request_register(); @@ -39,17 +42,23 @@ class Microdisc: public WD::WD1770 { inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); } inline int get_paging_flags() { return paging_flags_; } + void set_activity_observer(Activity::Observer *observer); + private: void set_control_register(uint8_t control, uint8_t changes); void set_head_load_request(bool head_load); bool get_drive_is_ready(); - std::shared_ptr drives_[4]; - int selected_drive_; + std::array, 4> drives_; + size_t selected_drive_; bool irq_enable_ = false; int paging_flags_ = BASICDisable; int head_load_request_counter_ = -1; + bool head_load_request_ = false; Delegate *delegate_ = nullptr; uint8_t last_control_ = 0; + Activity::Observer *observer_ = nullptr; + + std::string drive_name(size_t index); }; } diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index c5a8492a9..b1d8b0a2e 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -12,6 +12,7 @@ #include "Microdisc.hpp" #include "Video.hpp" +#include "../../Activity/Source.hpp" #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../KeyboardMachine.hpp" @@ -200,6 +201,7 @@ template class Co public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Microdisc::Delegate, + public Activity::Source, public Machine { public: @@ -329,10 +331,10 @@ template class Co switch(disk_interface) { case Analyser::Static::Oric::Target::DiskInterface::Microdisc: { inserted = true; - int drive_index = 0; + size_t drive_index = 0; for(auto &disk : media.disks) { if(drive_index < 4) microdisc_.set_disk(disk, drive_index); - drive_index++; + ++drive_index; } } break; case Analyser::Static::Oric::Target::DiskInterface::Pravetz: { @@ -340,7 +342,7 @@ template class Co int drive_index = 0; for(auto &disk : media.disks) { if(drive_index < 2) diskii_.set_disk(disk, drive_index); - drive_index++; + ++drive_index; } } break; @@ -551,6 +553,18 @@ template class Co return selection_set; } + void set_activity_observer(Activity::Observer *observer) override { + switch(disk_interface) { + default: break; + case Analyser::Static::Oric::Target::DiskInterface::Microdisc: + microdisc_.set_activity_observer(observer); + break; + case Analyser::Static::Oric::Target::DiskInterface::Pravetz: + diskii_.set_activity_observer(observer); + break; + } + } + private: const uint16_t basic_invisible_ram_top_ = 0xffff; const uint16_t basic_visible_ram_top_ = 0xbfff; diff --git a/Machines/Utility/TypedDynamicMachine.hpp b/Machines/Utility/TypedDynamicMachine.hpp index d0ecc70c8..db3632733 100644 --- a/Machines/Utility/TypedDynamicMachine.hpp +++ b/Machines/Utility/TypedDynamicMachine.hpp @@ -25,6 +25,10 @@ template class TypedDynamicMachine: public ::Machine::DynamicMachine return *this; } + Activity::Source *activity_source() override { + return get(); + } + ConfigurationTarget::Machine *configuration_target() override { return get(); } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6cfd56378..cab4b4feb 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -863,6 +863,8 @@ 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = ""; }; 4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = ""; }; 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = ""; }; + 4B51F70920A521D700AFA2C1 /* Source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Source.hpp; sourceTree = ""; }; + 4B51F70A20A521D700AFA2C1 /* Observer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Observer.hpp; sourceTree = ""; }; 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = KeyboardMachine.cpp; sourceTree = ""; }; 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = Oric/Keyboard.cpp; sourceTree = ""; }; 4B54C0BE1F8D8F450050900F /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = Oric/Keyboard.hpp; sourceTree = ""; }; @@ -1961,6 +1963,16 @@ path = 1540; sourceTree = ""; }; + 4B51F70820A521D700AFA2C1 /* Activity */ = { + isa = PBXGroup; + children = ( + 4B51F70920A521D700AFA2C1 /* Source.hpp */, + 4B51F70A20A521D700AFA2C1 /* Observer.hpp */, + ); + name = Activity; + path = ../../Activity; + sourceTree = ""; + }; 4B55CE551C3B7D360093A61B /* Documents */ = { isa = PBXGroup; children = ( @@ -2697,6 +2709,7 @@ isa = PBXGroup; children = ( 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */, + 4B51F70820A521D700AFA2C1 /* Activity */, 4B8944E2201967B4007DE474 /* Analyser */, 4BB73EA01B587A5100552FC2 /* Clock Signal */, 4BB73EB51B587A5100552FC2 /* Clock SignalTests */, diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index d62654171..8418aa1ff 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -50,7 +50,12 @@ bool Drive::get_is_track_zero() { void Drive::step(HeadPosition offset) { HeadPosition old_head_position = head_position_; head_position_ += offset; - if(head_position_ < HeadPosition(0)) head_position_ = HeadPosition(0); + if(head_position_ < HeadPosition(0)) { + head_position_ = HeadPosition(0); + if(observer_) observer_->announce_drive_event(drive_name_, Activity::Observer::DriveEvent::StepBelowZero); + } else { + if(observer_) observer_->announce_drive_event(drive_name_, Activity::Observer::DriveEvent::StepNormal); + } // If the head moved, flush the old track. if(head_position_ != old_head_position) { @@ -88,6 +93,14 @@ bool Drive::get_is_ready() { void Drive::set_motor_on(bool motor_is_on) { motor_is_on_ = motor_is_on; + + if(observer_) { + observer_->set_drive_motor_status(drive_name_, motor_is_on_); + if(announce_motor_led_) { + observer_->set_led_status(drive_name_, motor_is_on_); + } + } + if(!motor_is_on) { ready_index_count_ = 0; if(disk_) disk_->flush_tracks(); @@ -265,3 +278,19 @@ void Drive::end_writing() { invalidate_track(); } } + +void Drive::set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led) { + observer_ = observer; + announce_motor_led_ = add_motor_led; + if(observer) { + drive_name_ = name; + + observer->register_drive(drive_name_); + observer->set_drive_motor_status(drive_name_, motor_is_on_); + + if(add_motor_led) { + observer->register_led(drive_name_); + observer->set_led_status(drive_name_, motor_is_on_); + } + } +} diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index be32071e7..e10ab4099 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -14,6 +14,7 @@ #include "Track/PCMPatchedTrack.hpp" #include "../TimedEventLoop.hpp" +#include "../../Activity/Observer.hpp" #include "../../ClockReceiver/Sleeper.hpp" #include @@ -122,6 +123,10 @@ class Drive: public Sleeper, public TimedEventLoop { // As per Sleeper. bool is_sleeping(); + /// Adds an activity observer; it'll be notified of disk activity. + /// The caller can specify whether to add an LED based on disk motor. + void set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led); + private: // Drives contain an entire disk; from that a certain track // will be currently under the head. @@ -189,6 +194,11 @@ class Drive: public Sleeper, public TimedEventLoop { void setup_track(); void invalidate_track(); + + // Activity observer description. + Activity::Observer *observer_ = nullptr; + std::string drive_name_; + bool announce_motor_led_ = false; };