1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-22 14:30:29 +00:00

Introduces active input handling for the AY and uses it in the CPC to give proper, active keyboard input, rather than push-on-select, which was only ever a temporary hack. Also maps a few more keys for the Amstrad.

This commit is contained in:
Thomas Harte 2017-08-15 22:47:17 -04:00
parent 8a37a0ff2e
commit 3947347d88
6 changed files with 106 additions and 50 deletions

View File

@ -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_;
}

View File

@ -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 */

View File

@ -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:

View File

@ -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_);

View File

@ -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;

View File

@ -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: