mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #198 from TomHarte/LiveKeyboard
Introduces active input handling for the AY and uses it for CPC keyboard input
This commit is contained in:
commit
f18206767f
@ -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_;
|
||||
}
|
||||
|
||||
|
@ -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<AY38910> {
|
||||
/// 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<AY38910> {
|
||||
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<AY38910> {
|
||||
inline void evaluate_output_volume();
|
||||
|
||||
inline void update_bus();
|
||||
PortHandler *port_handler_;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AY_3_8910_hpp */
|
||||
|
@ -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<GI::AY38910> ay_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> 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:
|
||||
|
@ -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_);
|
||||
|
@ -145,7 +145,7 @@ class Machine:
|
||||
uint8_t get_port_input(Port port);
|
||||
inline void run_for(const Cycles cycles);
|
||||
|
||||
std::shared_ptr<GI::AY38910> ay8910;
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay8910;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user