diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 12a64a2f8..f1fa07628 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -8,7 +8,7 @@ #include "AY38910.hpp" -using namespace GI; +using namespace GI::AY38910; AY38910::AY38910() : selected_register_(0), @@ -16,7 +16,8 @@ AY38910::AY38910() : noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0), envelope_divider_(0), envelope_period_(0), envelope_position_(0), master_divider_(0), - output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} { + output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + port_handler_(nullptr) { output_registers_[8] = output_registers_[9] = output_registers_[10] = 0; // set up envelope lookup tables @@ -188,23 +189,19 @@ void AY38910::set_register_value(uint8_t value) { tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8); else tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value; -// tone_counters_[channel] = tone_periods_[channel]; } break; case 6: noise_period_ = value & 0x1f; -// noise_counter_ = noise_period_; break; case 11: envelope_period_ = (envelope_period_ & ~0xff) | value; -// envelope_divider_ = envelope_period_; break; case 12: envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8); -// envelope_divider_ = envelope_period_; break; case 13: @@ -215,6 +212,8 @@ void AY38910::set_register_value(uint8_t value) { output_registers_[selected_register] = masked_value; evaluate_output_volume(); }); + } else { + if(port_handler_) port_handler_->set_port_output(value == 15, value); } } @@ -241,19 +240,25 @@ uint8_t AY38910::get_port_output(bool port_b) { return registers_[port_b ? 15 : 14]; } -void AY38910::set_port_input(bool port_b, uint8_t value) { - port_inputs_[port_b ? 1 : 0] = value; - update_bus(); -} - #pragma mark - Bus handling +void AY38910::set_port_handler(PortHandler *handler) { + port_handler_ = handler; +} + void AY38910::set_data_input(uint8_t r) { data_input_ = r; update_bus(); } uint8_t AY38910::get_data_output() { + if(control_state_ == Read && selected_register_ >= 14) { + if(port_handler_) { + return port_handler_->get_port_input(selected_register_ == 15); + } else { + return 0xff; + } + } return data_output_; } diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 2c021ed95..9bc7eef5b 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -12,6 +12,21 @@ #include "../../Outputs/Speaker.hpp" namespace GI { +namespace AY38910 { + +class PortHandler { + public: + virtual uint8_t get_port_input(bool port_b) { + return 0xff; + } + virtual void set_port_output(bool port_b, uint8_t value) {} +}; + +enum ControlLines { + BC1 = (1 << 0), + BC2 = (1 << 1), + BDIR = (1 << 2) +}; /*! Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a @@ -26,12 +41,6 @@ class AY38910: public ::Outputs::Filter { /// Sets the clock rate at which this AY38910 will be run. void set_clock_rate(double clock_rate); - enum ControlLines { - BC1 = (1 << 0), - BC2 = (1 << 1), - BDIR = (1 << 2) - }; - /// Sets the value the AY would read from its data lines if it were not outputting. void set_data_input(uint8_t r); @@ -48,10 +57,11 @@ class AY38910: public ::Outputs::Filter { uint8_t get_port_output(bool port_b); /*! - Sets the value that would appear on the requested interface port if it were in output mode. - @parameter port_b @c true to get the value for Port B, @c false to get the value for Port A. + Sets the port handler, which will receive a call every time the AY either wants to sample + input or else declare new output. As a convenience, current port output can be obtained + without installing a port handler via get_port_output. */ - void set_port_input(bool port_b, uint8_t value); + void set_port_handler(PortHandler *); // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption void get_samples(unsigned int number_of_samples, int16_t *target); @@ -97,8 +107,10 @@ class AY38910: public ::Outputs::Filter { inline void evaluate_output_volume(); inline void update_bus(); + PortHandler *port_handler_; }; -}; +} +} #endif /* AY_3_8910_hpp */ diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 0c7b71fc2..c1f1476b0 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -100,7 +100,7 @@ class AYDeferrer { public: /// Constructs a new AY instance and sets its clock rate. inline void setup_output() { - ay_.reset(new GI::AY38910); + ay_.reset(new GI::AY38910::AY38910); ay_->set_input_rate(1000000); } @@ -130,12 +130,12 @@ class AYDeferrer { } /// @returns the AY itself. - GI::AY38910 *ay() { + GI::AY38910::AY38910 *ay() { return ay_.get(); } private: - std::shared_ptr ay_; + std::shared_ptr ay_; HalfCycles cycles_since_update_; }; @@ -425,13 +425,49 @@ class CRTCBusHandler { }; /*! - Passively holds the current keyboard state. Keyboard state is modified in response - to external messages, so is handled by the machine, but is read by the i8255 port - handler, so is factored out. + Holds and vends the current keyboard state, acting as the AY's port handler. */ -struct KeyboardState { - KeyboardState() : rows{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} {} - uint8_t rows[10]; +class KeyboardState: public GI::AY38910::PortHandler { + public: + KeyboardState() : rows_{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} {} + + /*! + Sets the row currently being reported to the AY. + */ + void set_row(int row) { + row_ = row; + } + + /*! + Reports the state of the currently-selected row as Port A to the AY. + */ + uint8_t get_port_input(bool port_b) { + if(!port_b && row_ < 10) { + return rows_[row_]; + } + + return 0xff; + } + + /*! + Sets whether @c key on line @c line is currently pressed. + */ + void set_is_pressed(bool is_pressed, int line, int key) { + int mask = 1 << key; + assert(line < 10); + if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask; + } + + /*! + Sets all keys as currently unpressed. + */ + void clear_all_keys() { + memset(rows_, 0xff, 10); + } + + private: + uint8_t rows_[10]; + int row_; }; /*! @@ -456,7 +492,7 @@ class FDC: public Intel::i8272::i8272 { class i8255PortHandler : public Intel::i8255::PortHandler { public: i8255PortHandler( - const KeyboardState &key_state, + KeyboardState &key_state, const CRTCBusHandler &crtc_bus_handler, AYDeferrer &ay, Storage::Tape::BinaryTapePlayer &tape_player) : @@ -478,14 +514,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler { break; case 2: { // The low four bits of the value sent to Port C select a keyboard line. - // At least for now, do a static push of the keyboard state here. So this - // is a capture. TODO: it should be a live connection. int key_row = value & 15; - if(key_row < 10) { - ay_.ay()->set_port_input(false, key_state_.rows[key_row]); - } else { - ay_.ay()->set_port_input(false, 0xff); - } + key_state_.set_row(key_row); // Bit 4 sets the tape motor on or off. tape_player_.set_motor_control((value & 0x10) ? true : false); @@ -522,7 +552,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { private: AYDeferrer &ay_; - const KeyboardState &key_state_; + KeyboardState &key_state_; const CRTCBusHandler &crtc_bus_handler_; Storage::Tape::BinaryTapePlayer &tape_player_; }; @@ -688,6 +718,7 @@ class ConcreteMachine: void setup_output(float aspect_ratio) { crtc_bus_handler_.setup_output(aspect_ratio); ay_.setup_output(); + ay_.ay()->set_port_handler(&key_state_); } /// A CRTMachine function; indicates that outputs should be destroyed now. @@ -786,14 +817,12 @@ class ConcreteMachine: // See header; sets a key as either pressed or released. void set_key_state(uint16_t key, bool isPressed) { - int line = key >> 4; - uint8_t mask = (uint8_t)(1 << (key & 7)); - if(isPressed) key_state_.rows[line] &= ~mask; else key_state_.rows[line] |= mask; + key_state_.set_is_pressed(isPressed, key >> 4, key & 7); } // See header; sets all keys to released. void clear_all_keys() { - memset(key_state_.rows, 0xff, 10); + key_state_.clear_all_keys(); } private: diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index b60cdcc91..11038a440 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -154,7 +154,7 @@ void Machine::update_video() { } void Machine::setup_output(float aspect_ratio) { - via_.ay8910.reset(new GI::AY38910()); + via_.ay8910.reset(new GI::AY38910::AY38910()); via_.ay8910->set_clock_rate(1000000); video_output_.reset(new VideoOutput(ram_)); if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index dd7ec48d9..4da1a312e 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -145,7 +145,7 @@ class Machine: uint8_t get_port_input(Port port); inline void run_for(const Cycles cycles); - std::shared_ptr ay8910; + std::shared_ptr ay8910; std::unique_ptr tape; std::shared_ptr keyboard; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm index ad42574bd..ae54f4d47 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm @@ -109,31 +109,41 @@ case VK_ANSI_M: _amstradCPC->set_key_state(AmstradCPC::Key::KeyM, isPressed); break; case VK_Space: _amstradCPC->set_key_state(AmstradCPC::Key::KeySpace, isPressed); break; - case VK_ANSI_Grave: _amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed); break; + case VK_ANSI_Grave: _amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed); break; case VK_Return: _amstradCPC->set_key_state(AmstradCPC::Key::KeyReturn, isPressed); break; case VK_ANSI_Minus: _amstradCPC->set_key_state(AmstradCPC::Key::KeyMinus, isPressed); break; case VK_RightArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyRight, isPressed); break; - case VK_LeftArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed); break; - case VK_DownArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed); break; + case VK_LeftArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed); break; + case VK_DownArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed); break; case VK_UpArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyUp, isPressed); break; case VK_Delete: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDelete, isPressed); break; case VK_Escape: _amstradCPC->set_key_state(AmstradCPC::Key::KeyEscape, isPressed); break; case VK_ANSI_Comma: _amstradCPC->set_key_state(AmstradCPC::Key::KeyComma, isPressed); break; - case VK_ANSI_Period: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed); break; + case VK_ANSI_Period: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed); break; case VK_ANSI_Semicolon: _amstradCPC->set_key_state(AmstradCPC::Key::KeySemicolon, isPressed); break; case VK_ANSI_Quote: _amstradCPC->set_key_state(AmstradCPC::Key::KeyColon, isPressed); break; - case VK_ANSI_Slash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed); break; + case VK_ANSI_Slash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed); break; case VK_ANSI_Backslash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyBackSlash, isPressed); break; case VK_Shift: _amstradCPC->set_key_state(AmstradCPC::Key::KeyShift, isPressed); break; case VK_Control: _amstradCPC->set_key_state(AmstradCPC::Key::KeyControl, isPressed); break; + case VK_F1: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF1, isPressed); break; + case VK_F2: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF2, isPressed); break; + case VK_F3: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF3, isPressed); break; + case VK_F4: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF4, isPressed); break; + case VK_F5: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF5, isPressed); break; + case VK_F6: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF6, isPressed); break; + case VK_F7: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF7, isPressed); break; + case VK_F8: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF8, isPressed); break; + case VK_F9: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF9, isPressed); break; + case VK_F10: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF0, isPressed); break; case VK_F12: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed); break; default: