diff --git a/ActivityObserver/ActivityObserver.hpp b/ActivityObserver/ActivityObserver.hpp new file mode 100644 index 000000000..48115e374 --- /dev/null +++ b/ActivityObserver/ActivityObserver.hpp @@ -0,0 +1,47 @@ +// +// Header.h +// Clock Signal +// +// Created by Thomas Harte on 07/05/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef Header_h +#define Header_h + +#include + +/*! + 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 ActivityObserver { + 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 { + Step, + 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 /* Header_h */ diff --git a/Analyser/Dynamic/MultiMachine/MultiMachine.cpp b/Analyser/Dynamic/MultiMachine/MultiMachine.cpp index 9e33047dd..6343bcc1c 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); } +ActivitySource::Machine *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..9b4124cca 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); + ActivitySource::Machine *activity_source() override; ConfigurationTarget::Machine *configuration_target() override; CRTMachine::Machine *crt_machine() override; JoystickMachine::Machine *joystick_machine() override; diff --git a/Machines/ActivitySource.hpp b/Machines/ActivitySource.hpp new file mode 100644 index 000000000..2747ebf00 --- /dev/null +++ b/Machines/ActivitySource.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 "../ActivityObserver/ActivityObserver.hpp" + +namespace ActivitySource { + +class Machine { + public: + virtual void set_activity_observer(ActivityObserver *receiver) = 0; +}; + +} + + +#endif /* ActivitySource_h */ diff --git a/Machines/DynamicMachine.hpp b/Machines/DynamicMachine.hpp index 6d261b6da..2b51c5e60 100644 --- a/Machines/DynamicMachine.hpp +++ b/Machines/DynamicMachine.hpp @@ -10,6 +10,7 @@ #define DynamicMachine_h #include "../Configurable/Configurable.hpp" +#include "ActivitySource.hpp" #include "ConfigurationTarget.hpp" #include "CRTMachine.hpp" #include "JoystickMachine.hpp" @@ -24,6 +25,8 @@ namespace Machine { */ struct DynamicMachine { virtual ~DynamicMachine() {} + + virtual ActivitySource::Machine *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..9b4c18762 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -8,6 +8,7 @@ #include "Electron.hpp" +#include "../ActivitySource.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 ActivitySource::Machine { 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(ActivityObserver *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; + ActivityObserver *activity_observer_ = nullptr; }; } diff --git a/Machines/Utility/TypedDynamicMachine.hpp b/Machines/Utility/TypedDynamicMachine.hpp index d0ecc70c8..07084b035 100644 --- a/Machines/Utility/TypedDynamicMachine.hpp +++ b/Machines/Utility/TypedDynamicMachine.hpp @@ -25,6 +25,10 @@ template class TypedDynamicMachine: public ::Machine::DynamicMachine return *this; } + ActivitySource::Machine *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 d16ca4732..dabdcccbb 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -697,6 +697,8 @@ 4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MachineForTarget.cpp; sourceTree = ""; }; 4B055ABF1FAE98000060FFFF /* MachineForTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineForTarget.hpp; sourceTree = ""; }; 4B055AF01FAE9C080060FFFF /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 4B055C1E20A113B00014E5AB /* ActivityObserver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ActivityObserver.hpp; sourceTree = ""; }; + 4B055C1F20A1166A0014E5AB /* ActivitySource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ActivitySource.hpp; sourceTree = ""; }; 4B0783591FC11D10001D12BB /* Configurable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Configurable.cpp; sourceTree = ""; }; 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80InterruptTests.swift; sourceTree = ""; }; 4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = ""; }; @@ -1496,6 +1498,15 @@ path = ../SDL; sourceTree = ""; }; + 4B055C1D20A113B00014E5AB /* ActivityObserver */ = { + isa = PBXGroup; + children = ( + 4B055C1E20A113B00014E5AB /* ActivityObserver.hpp */, + ); + name = ActivityObserver; + path = ../../ActivityObserver; + sourceTree = ""; + }; 4B0CCC411C62D0B3001CAC5F /* CRT */ = { isa = PBXGroup; children = ( @@ -2697,6 +2708,7 @@ isa = PBXGroup; children = ( 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */, + 4B055C1D20A113B00014E5AB /* ActivityObserver */, 4B8944E2201967B4007DE474 /* Analyser */, 4BB73EA01B587A5100552FC2 /* Clock Signal */, 4BB73EB51B587A5100552FC2 /* Clock SignalTests */, @@ -2800,6 +2812,7 @@ isa = PBXGroup; children = ( 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, + 4B055C1F20A1166A0014E5AB /* ActivitySource.hpp */, 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */, 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, 4BBB709C2020109C002FE009 /* DynamicMachine.hpp */,