1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 16:31:31 +00:00

Merge pull request #168 from TomHarte/CPC

Adds an initial Amstrad CPC 464 emuation
This commit is contained in:
Thomas Harte 2017-08-02 20:50:02 -04:00 committed by GitHub
commit a54ccd1969
28 changed files with 1579 additions and 96 deletions

View File

@ -95,9 +95,17 @@ template <class T> class WrappedInt {
return *static_cast<T *>(this);
}
inline T &operator &=(const T &rhs) {
length_ &= rhs.length_;
return *static_cast<T *>(this);
}
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
inline T operator -() const { return T(- length_); }
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
@ -167,6 +175,17 @@ class HalfCycles: public WrappedInt<HalfCycles> {
length_ &= 1;
return result;
}
/*!
Severs from @c this the effect of dividing by @c divisor @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/
inline Cycles divide_cycles(const Cycles &divisor) {
HalfCycles half_divisor = HalfCycles(divisor);
Cycles result(length_ / half_divisor.length_);
length_ %= half_divisor.length_;
return result;
}
};
/*!

View File

@ -0,0 +1,18 @@
//
// ForceInline.h
// Clock Signal
//
// Created by Thomas Harte on 01/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ForceInline_h
#define ForceInline_h
#ifdef __GNUC__
#define forceinline __attribute__((always_inline)) inline
#elif _MSC_VER
#define forceinline __forceinline
#endif
#endif /* ForceInline_h */

View File

@ -0,0 +1,173 @@
//
// CRTC6845.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef CRTC6845_hpp
#define CRTC6845_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include <cstdint>
#include <cstdio>
namespace Motorola {
namespace CRTC {
struct BusState {
bool display_enable;
bool hsync;
bool vsync;
bool cursor;
uint16_t refresh_address;
uint16_t row_address;
};
class BusHandler {
public:
void perform_bus_cycle(const BusState &) {}
};
template <class T> class CRTC6845 {
public:
CRTC6845(T &bus_handler) : bus_handler_(bus_handler) {}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
while(cyles_remaining--) {
// check for end of horizontal sync
if(hsync_down_counter_) {
hsync_down_counter_--;
if(!hsync_down_counter_) {
bus_state_.hsync = false;
}
}
// check for end of line
bool is_end_of_line = character_counter_ == registers_[0];
// increment counter
character_counter_++;
// check for start of horizontal sync
if(character_counter_ == registers_[2]) {
hsync_down_counter_ = registers_[3] & 15;
if(hsync_down_counter_) bus_state_.hsync = true;
}
// check for end of visible characters
if(character_counter_ == registers_[1]) {
bus_state_.refresh_address++;
character_is_visible_ = false;
} else {
// update refresh address
if(character_is_visible_) {
bus_state_.refresh_address++;
}
}
// check for end-of-line
if(is_end_of_line) {
character_counter_ = 0;
character_is_visible_ = true;
// check for end of vertical sync
if(vsync_down_counter_) {
vsync_down_counter_--;
if(!vsync_down_counter_) {
bus_state_.vsync = false;
}
}
if(is_in_adjustment_period_) {
line_counter_++;
if(line_counter_ == registers_[5]) {
line_counter_ = 0;
is_in_adjustment_period_ = false;
line_is_visible_ = true;
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
} else {
// advance vertical counter
if(bus_state_.row_address == registers_[9]) {
line_address_ = bus_state_.refresh_address;
bus_state_.row_address = 0;
line_counter_++;
// check for end of visible lines
if(line_counter_ == registers_[6]) {
line_is_visible_ = false;
}
// check for start of vertical sync
if(line_counter_ == registers_[7]) {
bus_state_.vsync = true;
vsync_down_counter_ = 16; // TODO
}
// check for entry into the overflow area
if(line_counter_ == registers_[4]) {
if(registers_[5]) {
is_in_adjustment_period_ = true;
} else {
line_is_visible_ = true;
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
bus_state_.row_address = 0;
line_counter_ = 0;
}
} else {
bus_state_.row_address++;
bus_state_.refresh_address = line_address_;
}
}
}
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle(bus_state_);
}
}
void select_register(uint8_t r) {
selected_register_ = (int)r & 15;
}
uint8_t get_status() {
return 0xff;
}
uint8_t get_register() {
return registers_[selected_register_];
}
void set_register(uint8_t value) {
registers_[selected_register_] = value;
}
private:
T &bus_handler_;
BusState bus_state_;
uint8_t registers_[16];
int selected_register_;
uint8_t character_counter_;
uint8_t line_counter_;
bool character_is_visible_, line_is_visible_;
int hsync_down_counter_;
int vsync_down_counter_;
bool is_in_adjustment_period_;
uint16_t line_address_;
};
}
}
#endif /* CRTC6845_hpp */

72
Components/8255/i8255.hpp Normal file
View File

@ -0,0 +1,72 @@
//
// i8255.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef i8255_hpp
#define i8255_hpp
namespace Intel {
namespace i8255 {
class PortHandler {
public:
void set_value(int port, uint8_t value) {}
uint8_t get_value(int port) { return 0xff; }
};
// TODO: most of the implementation below. Right now it just blindly passes data in all directions,
// ignoring operation mode. But at least it establishes proper ownership and hand-off of decision making.
template <class T> class i8255 {
public:
i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {}
void set_register(int address, uint8_t value) {
switch(address & 3) {
case 0: outputs_[0] = value; port_handler_.set_value(0, value); break;
case 1: outputs_[1] = value; port_handler_.set_value(1, value); break;
case 2: outputs_[2] = value; port_handler_.set_value(2, value); break;
case 3:
if(value & 0x80) {
control_ = value;
} else {
if(value & 1) {
outputs_[2] |= 1 << ((value >> 1)&7);
} else {
outputs_[2] &= ~(1 << ((value >> 1)&7));
}
}
update_outputs();
break;
}
}
uint8_t get_register(int address) {
switch(address & 3) {
case 0: return port_handler_.get_value(0);
case 1: return port_handler_.get_value(1);
case 2: return port_handler_.get_value(2);
case 3: return control_;
}
return 0xff;
}
private:
void update_outputs() {
port_handler_.set_value(0, outputs_[0]);
port_handler_.set_value(1, outputs_[1]);
port_handler_.set_value(2, outputs_[2]);
}
uint8_t control_;
uint8_t outputs_[3];
T &port_handler_;
};
}
}
#endif /* i8255_hpp */

View File

@ -166,6 +166,8 @@ void AY38910::evaluate_output_volume() {
);
}
#pragma mark - Register manipulation
void AY38910::select_register(uint8_t r) {
selected_register_ = r & 0xf;
}
@ -227,12 +229,22 @@ uint8_t AY38910::get_register_value() {
return registers_[selected_register_] | register_masks[selected_register_];
}
#pragma mark - Port handling
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) {
registers_[port_b ? 15 : 14] = value;
update_bus();
}
#pragma mark - Bus handling
void AY38910::set_data_input(uint8_t r) {
data_input_ = r;
update_bus();
}
uint8_t AY38910::get_data_output() {
@ -240,25 +252,25 @@ uint8_t AY38910::get_data_output() {
}
void AY38910::set_control_lines(ControlLines control_lines) {
ControlState new_state;
switch((int)control_lines) {
default: new_state = Inactive; break;
default: control_state_ = Inactive; break;
case (int)(BCDIR | BC2 | BC1):
case BCDIR:
case BC1: new_state = LatchAddress; break;
case (int)(BDIR | BC2 | BC1):
case BDIR:
case BC1: control_state_ = LatchAddress; break;
case (int)(BC2 | BC1): new_state = Read; break;
case (int)(BCDIR | BC2): new_state = Write; break;
case (int)(BC2 | BC1): control_state_ = Read; break;
case (int)(BDIR | BC2): control_state_ = Write; break;
}
if(new_state != control_state_) {
control_state_ = new_state;
switch(new_state) {
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;
}
update_bus();
}
void AY38910::update_bus() {
switch(control_state_) {
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;
}
}

View File

@ -29,7 +29,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
enum ControlLines {
BC1 = (1 << 0),
BC2 = (1 << 1),
BCDIR = (1 << 2)
BDIR = (1 << 2)
};
/// Sets the value the AY would read from its data lines if it were not outputting.
@ -38,7 +38,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
/// Gets the value that would appear on the data lines if only the AY is outputting.
uint8_t get_data_output();
/// Sets the
/// Sets the current control line state, as a bit field.
void set_control_lines(ControlLines control_lines);
/*!
@ -47,6 +47,12 @@ 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.
*/
void set_port_input(bool port_b, uint8_t value);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
void get_samples(unsigned int number_of_samples, int16_t *target);
@ -88,6 +94,8 @@ class AY38910: public ::Outputs::Filter<AY38910> {
int16_t output_volume_;
inline void evaluate_output_volume();
inline void update_bus();
};
};

View File

@ -0,0 +1,703 @@
//
// AmstradCPC.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "AmstradCPC.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/8255/i8255.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Components/6845/CRTC6845.hpp"
#include "../../Storage/Tape/Tape.hpp"
using namespace AmstradCPC;
/*!
Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period
of 52 and occasionally adjusts or makes decisions based on bit 4.
Hsync and vsync signals are expected to come directly from the CRTC; they are not decoded from a composite stream.
*/
class InterruptTimer {
public:
InterruptTimer() : timer_(0), interrupt_request_(false) {}
/*!
Indicates that a new hsync pulse has been recognised. Per documentation
difficulties, it is not presently clear to me whether this should be
the leading or trailing edge of horizontal sync.
*/
inline void signal_hsync() {
// Increment the timer and if it has hit 52 then reset it and
// set the interrupt request line to true.
timer_++;
if(timer_ == 52) {
timer_ = 0;
interrupt_request_ = true;
}
// If a vertical sync has previously been indicated then after two
// further horizontal syncs the timer should either (i) set the interrupt
// line, if bit 4 is clear; or (ii) reset the timer.
if(reset_counter_) {
reset_counter_--;
if(!reset_counter_) {
if(timer_ < 32) {
interrupt_request_ = true;
}
timer_ = 0;
}
}
}
/// Indicates the leading edge of a new vertical sync.
inline void signal_vsync() {
reset_counter_ = 2;
}
/// Indicates that an interrupt acknowledge has been received from the Z80.
inline void signal_interrupt_acknowledge() {
interrupt_request_ = false;
timer_ &= ~32;
}
/// @returns @c true if an interrupt is currently requested; @c false otherwise.
inline bool get_request() {
return interrupt_request_;
}
/// Resets the timer.
inline void reset_count() {
timer_ = 0;
}
private:
int reset_counter_;
bool interrupt_request_;
int timer_;
};
/*!
Provides a holder for an AY-3-8910 and its current cycles-since-updated count.
Therefore acts both to store an AY and to bookkeep this emulator's idiomatic
deferred clocking for this component.
*/
class AYDeferrer {
public:
/// Constructs a new AY instance and sets its clock rate.
inline void setup_output() {
ay_.reset(new GI::AY38910);
ay_->set_input_rate(1000000);
}
/// Destructs the AY.
inline void close_output() {
ay_.reset();
}
/// Adds @c half_cycles half cycles to the amount of time that has passed.
inline void run_for(HalfCycles half_cycles) {
cycles_since_update_ += half_cycles;
}
/// Issues a request to the AY to perform all processing up to the current time.
inline void flush() {
ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4)));
ay_->flush();
}
/// @returns the speaker the AY is using for output.
std::shared_ptr<Outputs::Speaker> get_speaker() {
return ay_;
}
/// @returns the AY itself.
GI::AY38910 *ay() {
return ay_.get();
}
private:
std::shared_ptr<GI::AY38910> ay_;
HalfCycles cycles_since_update_;
};
/*!
Provides the mechanism of receipt for the CRTC outputs. In practice has the gate array's
video fetching and serialisation logic built in. So this is responsible for all video
generation and therefore owns details such as the current palette.
*/
class CRTCBusHandler {
public:
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
cycles_(0),
was_enabled_(false),
was_sync_(false),
pixel_data_(nullptr),
pixel_pointer_(nullptr),
was_hsync_(false),
ram_(ram),
interrupt_timer_(interrupt_timer),
pixel_divider_(1),
mode_(2),
next_mode_(2) {
build_mode_tables();
}
/*!
The CRTC entry function; takes the current bus state and determines what output
to produce based on the current palette and mode.
*/
inline void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
// Sync is taken to override pixels, and is combined as a simple OR.
bool is_sync = state.hsync || state.vsync;
// If a transition between sync/border/pixels just occurred, flush whatever was
// in progress to the CRT and reset counting.
if(state.display_enable != was_enabled_ || is_sync != was_sync_) {
if(was_sync_) {
crt_->output_sync(cycles_ * 16);
} else {
if(was_enabled_) {
if(cycles_) {
crt_->output_data(cycles_ * 16, pixel_divider_);
pixel_pointer_ = pixel_data_ = nullptr;
}
} else {
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
if(colour_pointer) *colour_pointer = border_;
crt_->output_level(cycles_ * 16);
}
}
cycles_ = 0;
was_sync_ = is_sync;
was_enabled_ = state.display_enable;
}
// increment cycles since state changed
cycles_++;
// collect some more pixels if output is ongoing
if(!is_sync && state.display_enable) {
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320);
}
if(pixel_pointer_) {
// the CPC shuffles output lines as:
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
// ... so form the real access address.
uint16_t address =
(uint16_t)(
((state.refresh_address & 0x3ff) << 1) |
((state.row_address & 0x7) << 11) |
((state.refresh_address & 0x3000) << 2)
);
// fetch two bytes and translate into pixels
switch(mode_) {
case 0:
((uint16_t *)pixel_pointer_)[0] = mode0_output_[ram_[address]];
((uint16_t *)pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
pixel_pointer_ += 4;
break;
case 1:
((uint32_t *)pixel_pointer_)[0] = mode1_output_[ram_[address]];
((uint32_t *)pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
pixel_pointer_ += 8;
break;
case 2:
((uint64_t *)pixel_pointer_)[0] = mode2_output_[ram_[address]];
((uint64_t *)pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
pixel_pointer_ += 16;
break;
case 3:
((uint16_t *)pixel_pointer_)[0] = mode3_output_[ram_[address]];
((uint16_t *)pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
pixel_pointer_ += 4;
break;
}
// flush the current buffer pixel if full; the CRTC allows many different display
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour
if(pixel_pointer_ == pixel_data_ + 320) {
crt_->output_data(cycles_ * 16, pixel_divider_);
pixel_pointer_ = pixel_data_ = nullptr;
cycles_ = 0;
}
}
}
// check for a trailing hsync; if one occurred then that's the trigger potentially to change
// modes, and should also be sent on to the interrupt timer
if(was_hsync_ && !state.hsync) {
if(mode_ != next_mode_) {
mode_ = next_mode_;
switch(mode_) {
default:
case 0: pixel_divider_ = 4; break;
case 1: pixel_divider_ = 2; break;
case 2: pixel_divider_ = 1; break;
}
}
interrupt_timer_.signal_hsync();
}
// check for a leading vsync; that also needs to be communicated to the interrupt timer
if(!was_vsync_ && state.vsync) {
interrupt_timer_.signal_vsync();
}
// update current state for edge detection next time around
was_vsync_ = state.vsync;
was_hsync_ = state.hsync;
}
/// Constructs an appropriate CRT for video output.
void setup_output(float aspect_ratio) {
crt_.reset(new Outputs::CRT::CRT(1024, 16, Outputs::CRT::DisplayType::PAL50, 1));
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"uint sample = texture(texID, coordinate).r;"
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}");
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
}
/// Destructs the CRT.
void close_output() {
crt_.reset();
}
/// @returns the CRT.
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
return crt_;
}
/*!
Sets the next video mode. Per the documentation, mode changes take effect only at the end of line,
not immediately. So next means "as of the end of this line".
*/
void set_next_mode(int mode) {
next_mode_ = mode;
}
/// @returns the current value of the CRTC's vertical sync output.
bool get_vsync() const {
return was_vsync_;
}
/// Palette management: selects a pen to modify.
void select_pen(int pen) {
pen_ = pen;
}
/// Palette management: sets the colour of the selected pen.
void set_colour(uint8_t colour) {
if(pen_ & 16) {
border_ = mapped_palette_value(colour);
// TODO: should flush any border currently in progress
} else {
palette_[pen_] = mapped_palette_value(colour);
// TODO: no need for a full regeneration, of every mode, every time
build_mode_tables();
}
}
private:
void build_mode_tables() {
for(int c = 0; c < 256; c++) {
// prepare mode 0
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
mode0_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)];
mode0_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)];
// prepare mode 1
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
mode1_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)];
mode1_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)];
mode1_pixels[2] = palette_[((c & 0x20) >> 5) | ((c & 0x02) >> 0)];
mode1_pixels[3] = palette_[((c & 0x10) >> 4) | ((c & 0x01) << 1)];
// prepare mode 2
uint8_t *mode2_pixels = (uint8_t *)&mode2_output_[c];
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
mode2_pixels[3] = palette_[((c & 0x10) >> 4)];
mode2_pixels[4] = palette_[((c & 0x08) >> 3)];
mode2_pixels[5] = palette_[((c & 0x04) >> 2)];
mode2_pixels[6] = palette_[((c & 0x03) >> 1)];
mode2_pixels[7] = palette_[((c & 0x01) >> 0)];
// prepare mode 3
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
mode3_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)];
mode3_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)];
}
}
uint8_t mapped_palette_value(uint8_t colour) {
#define COL(r, g, b) (r << 4) | (g << 2) | b
static const uint8_t mapping[32] = {
COL(1, 1, 1), COL(1, 1, 1), COL(0, 2, 1), COL(2, 2, 1),
COL(0, 0, 1), COL(2, 0, 1), COL(0, 1, 1), COL(2, 1, 1),
COL(2, 0, 1), COL(2, 2, 1), COL(2, 2, 0), COL(2, 2, 2),
COL(2, 0, 0), COL(2, 0, 2), COL(2, 1, 0), COL(2, 1, 1),
COL(0, 0, 1), COL(0, 2, 1), COL(0, 2, 0), COL(0, 2, 2),
COL(0, 0, 0), COL(0, 0, 2), COL(0, 1, 0), COL(0, 1, 2),
COL(1, 0, 1), COL(1, 2, 1), COL(1, 2, 0), COL(1, 2, 2),
COL(1, 0, 0), COL(1, 0, 2), COL(1, 1, 0), COL(1, 1, 2),
};
#undef COL
return mapping[colour];
}
unsigned int cycles_;
bool was_enabled_, was_sync_, was_hsync_, was_vsync_;
std::shared_ptr<Outputs::CRT::CRT> crt_;
uint8_t *pixel_data_, *pixel_pointer_;
uint8_t *ram_;
int next_mode_, mode_;
unsigned int pixel_divider_;
uint16_t mode0_output_[256];
uint32_t mode1_output_[256];
uint64_t mode2_output_[256];
uint16_t mode3_output_[256];
int pen_;
uint8_t palette_[16];
uint8_t border_;
InterruptTimer &interrupt_timer_;
};
/*!
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.
*/
struct KeyboardState {
KeyboardState() : rows{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} {}
uint8_t rows[10];
};
/*!
Provides the mechanism of receipt for input and output of the 8255's various ports.
*/
class i8255PortHandler : public Intel::i8255::PortHandler {
public:
i8255PortHandler(
const KeyboardState &key_state,
const CRTCBusHandler &crtc_bus_handler,
AYDeferrer &ay,
Storage::Tape::BinaryTapePlayer &tape_player) :
key_state_(key_state),
crtc_bus_handler_(crtc_bus_handler),
ay_(ay),
tape_player_(tape_player) {}
/// The i8255 will call this to set a new output value of @c value for @c port.
void set_value(int port, uint8_t value) {
switch(port) {
case 0:
// Port A is connected to the AY's data bus.
ay_.ay()->set_data_input(value);
break;
case 1:
// Port B is an input only. So output goes nowehere.
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);
}
// Bit 4 sets the tape motor on or off.
tape_player_.set_motor_control((value & 0x10) ? true : false);
// Bit 5 sets the current tape output level
tape_player_.set_tape_output((value & 0x20) ? true : false);
// Bits 6 and 7 set BDIR and BC1 for the AY.
ay_.ay()->set_control_lines(
(GI::AY38910::ControlLines)(
((value & 0x80) ? GI::AY38910::BDIR : 0) |
((value & 0x40) ? GI::AY38910::BC1 : 0) |
GI::AY38910::BC2
));
} break;
}
}
/// The i8255 will call this to obtain a new input for @c port.
uint8_t get_value(int port) {
switch(port) {
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
case 1: return
(crtc_bus_handler_.get_vsync() ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
0x7e; // Bits unimplemented:
//
// Bit 6: printer ready (1 = not)
// Bit 5: the expansion port /EXP pin, so depends on connected hardware
// Bit 4: 50/60Hz switch (1 = 50Hz)
// Bits 13: distributor ID (111 = Amstrad)
default: return 0xff;
}
}
private:
AYDeferrer &ay_;
const KeyboardState &key_state_;
const CRTCBusHandler &crtc_bus_handler_;
Storage::Tape::BinaryTapePlayer &tape_player_;
};
/*!
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
*/
class ConcreteMachine:
public CPU::Z80::Processor<ConcreteMachine>,
public Machine {
public:
ConcreteMachine() :
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses
crtc_(crtc_bus_handler_),
crtc_bus_handler_(ram_, interrupt_timer_),
i8255_(i8255_port_handler_),
i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_),
tape_player_(8000000) {
// primary clock is 4Mhz
set_clock_rate(4000000);
}
/// The entry point for performing a partial Z80 machine cycle.
inline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// Amstrad CPC timing scheme: assert WAIT for three out of four cycles
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
set_wait_line(clock_offset_ >= HalfCycles(2));
// Update the CRTC once every eight half cycles; aiming for half-cycle 4 as
// per the initial seed to the crtc_counter_, but any time in the final four
// will do as it's safe to conclude that nobody else has touched video RAM
// during that whole window
crtc_counter_ += cycle.length;
int crtc_cycles = crtc_counter_.divide(HalfCycles(8)).as_int();
if(crtc_cycles) crtc_.run_for(Cycles(1));
set_interrupt_line(interrupt_timer_.get_request());
// TODO (in the player, not here): adapt it to accept an input clock rate and
// run_for as HalfCycles
tape_player_.run_for(cycle.length.as_int());
// Pump the AY.
ay_.run_for(cycle.length);
// Stop now if no action is strictly required.
if(!cycle.is_terminal()) return HalfCycles(0);
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 14][address & 16383];
break;
case CPU::Z80::PartialMachineCycle::Write:
write_pointers_[address >> 14][address & 16383] = *cycle.value;
break;
case CPU::Z80::PartialMachineCycle::Output:
// Check for a gate array access.
if((address & 0xc000) == 0x4000) {
switch(*cycle.value >> 6) {
case 0: crtc_bus_handler_.select_pen(*cycle.value & 0x1f); break;
case 1: crtc_bus_handler_.set_colour(*cycle.value & 0x1f); break;
case 2:
// Perform ROM paging.
read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : os_.data();
read_pointers_[3] = (*cycle.value & 8) ? &ram_[49152] : basic_.data();
// Reset the interrupt timer if requested.
if(*cycle.value & 15) interrupt_timer_.reset_count();
// Post the next mode.
crtc_bus_handler_.set_next_mode(*cycle.value & 3);
break;
case 3: printf("RAM paging?\n"); break;
}
}
// Check for a CRTC access
if(!(address & 0x4000)) {
switch((address >> 8) & 3) {
case 0: crtc_.select_register(*cycle.value); break;
case 1: crtc_.set_register(*cycle.value); break;
case 2: case 3: printf("Illegal CRTC write?\n"); break;
}
}
// Check for an 8255 PIO access
if(!(address & 0x800)) {
i8255_.set_register((address >> 8) & 3, *cycle.value);
}
break;
case CPU::Z80::PartialMachineCycle::Input:
// Default to nothing answering
*cycle.value = 0xff;
// Check for a CRTC access
if(!(address & 0x4000)) {
switch((address >> 8) & 3) {
case 0: case 1: printf("Illegal CRTC read?\n"); break;
case 2: *cycle.value = crtc_.get_status(); break;
case 3: *cycle.value = crtc_.get_register(); break;
}
}
// Check for a PIO access
if(!(address & 0x800)) {
*cycle.value = i8255_.get_register((address >> 8) & 3);
}
break;
case CPU::Z80::PartialMachineCycle::Interrupt:
// Nothing is loaded onto the bus during an interrupt acknowledge, but
// the fact of the acknowledge needs to be posted on to the interrupt timer.
*cycle.value = 0xff;
interrupt_timer_.signal_interrupt_acknowledge();
break;
default: break;
}
// This implementation doesn't use time-stuffing; once in-phase waits won't be longer
// than a single cycle so there's no real performance benefit to trying to find the
// next non-wait when a wait cycle comes in, and there'd be no benefit to reproducing
// the Z80's knowledge of where wait cycles occur here.
return HalfCycles(0);
}
/// Another Z80 entry point; indicates that a partcular run request has concluded.
void flush() {
// Just flush the AY.
ay_.flush();
}
/// A CRTMachine function; indicates that outputs should be created now.
void setup_output(float aspect_ratio) {
crtc_bus_handler_.setup_output(aspect_ratio);
ay_.setup_output();
}
/// A CRTMachine function; indicates that outputs should be destroyed now.
void close_output() {
crtc_bus_handler_.close_output();
ay_.close_output();
}
/// @returns the CRT in use.
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
return crtc_bus_handler_.get_crt();
}
/// @returns the speaker in use.
std::shared_ptr<Outputs::Speaker> get_speaker() {
return ay_.get_speaker();
}
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
void run_for(const Cycles cycles) {
CPU::Z80::Processor<ConcreteMachine>::run_for(cycles);
}
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
void configure_as_target(const StaticAnalyser::Target &target) {
// Establish reset memory map as per machine model (or, for now, as a hard-wired 464)
read_pointers_[0] = os_.data();
read_pointers_[1] = &ram_[16384];
read_pointers_[2] = &ram_[32768];
read_pointers_[3] = basic_.data();
write_pointers_[0] = &ram_[0];
write_pointers_[1] = &ram_[16384];
write_pointers_[2] = &ram_[32768];
write_pointers_[3] = &ram_[49152];
// If there are any tapes supplied, use the first of them.
if(!target.tapes.empty()) {
tape_player_.set_tape(target.tapes.front());
}
}
// See header; provides the system ROMs.
void set_rom(ROMType type, std::vector<uint8_t> data) {
// Keep only the two ROMs that are currently of interest.
switch(type) {
case ROMType::OS464: os_ = data; break;
case ROMType::BASIC464: basic_ = data; break;
default: break;
}
}
// 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;
}
// See header; sets all keys to released.
void clear_all_keys() {
memset(key_state_.rows, 0xff, 10);
}
private:
CRTCBusHandler crtc_bus_handler_;
Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_;
AYDeferrer ay_;
i8255PortHandler i8255_port_handler_;
Intel::i8255::i8255<i8255PortHandler> i8255_;
InterruptTimer interrupt_timer_;
Storage::Tape::BinaryTapePlayer tape_player_;
HalfCycles clock_offset_;
HalfCycles crtc_counter_;
HalfCycles half_cycles_since_ay_update_;
uint8_t ram_[65536];
std::vector<uint8_t> os_, basic_;
uint8_t *read_pointers_[4];
uint8_t *write_pointers_[4];
KeyboardState key_state_;
};
// See header; constructs and returns an instance of the Amstrad CPC.
Machine *Machine::AmstradCPC() {
return new ConcreteMachine;
}

View File

@ -0,0 +1,70 @@
//
// AmstradCPC.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef AmstradCPC_hpp
#define AmstradCPC_hpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
namespace AmstradCPC {
enum ROMType: uint8_t {
OS464, OS664, OS6128,
BASIC464, BASIC664, BASIC6128,
AMSDOS
};
enum Key: uint16_t {
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
k1 = (l << 4) | 0x07,\
k2 = (l << 4) | 0x06,\
k3 = (l << 4) | 0x05,\
k4 = (l << 4) | 0x04,\
k5 = (l << 4) | 0x03,\
k6 = (l << 4) | 0x02,\
k7 = (l << 4) | 0x01,\
k8 = (l << 4) | 0x00,
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
#undef Line
};
/*!
Models an Amstrad CPC, a CRT-outputting machine that can accept configuration targets.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine {
public:
/// Creates an returns an Amstrad CPC on the heap.
static Machine *AmstradCPC();
/// Sets the contents of rom @c type to @c data. Assumed to be a setup step; has no effect once a machine is running.
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
/// Indicates that @c key is either pressed or released, according to @c is_pressed.
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
/// Indicates that all keys are now released.
virtual void clear_all_keys() = 0;
};
}
#endif /* AmstradCPC_hpp */

View File

@ -22,7 +22,7 @@ namespace CRTMachine {
*/
class Machine {
public:
Machine() : clock_is_unlimited_(false) {}
Machine() : clock_is_unlimited_(false), delegate_(nullptr) {}
/*!
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
@ -57,7 +57,7 @@ class Machine {
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
void set_delegate(Delegate *delegate) { this->delegate_ = delegate; }
void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected:
void set_clock_rate(double clock_rate) {

View File

@ -244,7 +244,7 @@ void Machine::VIA::run_for(const Cycles cycles) {
void Machine::VIA::update_ay() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
#pragma mark - TapePlayer

View File

@ -46,6 +46,10 @@
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; };
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; };
4B38F3441F2EB3E900D9235D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3421F2EB3E900D9235D /* StaticAnalyser.cpp */; };
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
4B38F34C1F2EC3CA00D9235D /* CSAmstradCPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F34B1F2EC3CA00D9235D /* CSAmstradCPC.mm */; };
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */; };
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; };
@ -532,6 +536,13 @@
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
4B38F3421F2EB3E900D9235D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/AmstradCPC/StaticAnalyser.cpp; sourceTree = "<group>"; };
4B38F3431F2EB3E900D9235D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/AmstradCPC/StaticAnalyser.hpp; sourceTree = "<group>"; };
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; };
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; };
4B38F34A1F2EC3CA00D9235D /* CSAmstradCPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAmstradCPC.h; sourceTree = "<group>"; };
4B38F34B1F2EC3CA00D9235D /* CSAmstradCPC.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAmstradCPC.mm; sourceTree = "<group>"; };
4B38F34E1F2EC6BA00D9235D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AmstradCPCOptions.xib"; sourceTree = SOURCE_ROOT; };
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; };
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; };
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
@ -644,6 +655,7 @@
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = "<group>"; };
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = "<group>"; };
4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
@ -985,11 +997,13 @@
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = "<group>"; };
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
@ -1190,11 +1204,13 @@
4B2A53981D117D36003C6002 /* Wrappers */ = {
isa = PBXGroup;
children = (
4B38F34A1F2EC3CA00D9235D /* CSAmstradCPC.h */,
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
4B2A539B1D117D36003C6002 /* CSElectron.h */,
4BCF1FA61DADC5250039D2E7 /* CSOric.h */,
4B2A539D1D117D36003C6002 /* CSVic20.h */,
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
4B38F34B1F2EC3CA00D9235D /* CSAmstradCPC.mm */,
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
4B2A539C1D117D36003C6002 /* CSElectron.mm */,
4BCF1FA71DADC5250039D2E7 /* CSOric.mm */,
@ -1249,6 +1265,24 @@
name = Outputs;
sourceTree = "<group>";
};
4B38F3451F2EB41800D9235D /* AmstradCPC */ = {
isa = PBXGroup;
children = (
4B38F3421F2EB3E900D9235D /* StaticAnalyser.cpp */,
4B38F3431F2EB3E900D9235D /* StaticAnalyser.hpp */,
);
name = AmstradCPC;
sourceTree = "<group>";
};
4B38F3491F2EC12000D9235D /* AmstradCPC */ = {
isa = PBXGroup;
children = (
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */,
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */,
);
name = AmstradCPC;
sourceTree = "<group>";
};
4B3940E81DA83C8700427841 /* Concurrency */ = {
isa = PBXGroup;
children = (
@ -1330,6 +1364,7 @@
4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */,
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */,
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */,
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
@ -1911,6 +1946,7 @@
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */,
4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */,
4B1E85741D170228001EF87D /* Typer.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
4B2E2D9E1C3A070900138695 /* Electron */,
@ -1998,6 +2034,8 @@
4B1E85791D174DEC001EF87D /* 6532 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
4BE845221F2FF7F400A5EA22 /* 6845 */,
4BD9137C1F3115AC009BCF85 /* 8255 */,
);
name = Components;
path = ../../Components;
@ -2086,6 +2124,14 @@
name = Updater;
sourceTree = "<group>";
};
4BD9137C1F3115AC009BCF85 /* 8255 */ = {
isa = PBXGroup;
children = (
4BD9137D1F311BC5009BCF85 /* i8255.hpp */,
);
name = 8255;
sourceTree = "<group>";
};
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
isa = PBXGroup;
children = (
@ -2095,6 +2141,14 @@
path = Resources;
sourceTree = "<group>";
};
4BE845221F2FF7F400A5EA22 /* 6845 */ = {
isa = PBXGroup;
children = (
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */,
);
name = 6845;
sourceTree = "<group>";
};
4BE9A6B21EDE294200CBCB47 /* Zexall */ = {
isa = PBXGroup;
children = (
@ -2157,6 +2211,7 @@
4B5A12581DD55873007A2231 /* Disassembler */,
4BCF1FAC1DADD41F0039D2E7 /* Oric */,
4B14978C1EE4AC6200CE2596 /* ZX80/81 */,
4B38F3451F2EB41800D9235D /* AmstradCPC */,
);
name = StaticAnalyser;
sourceTree = "<group>";
@ -2165,6 +2220,7 @@
isa = PBXGroup;
children = (
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.h */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
@ -2294,6 +2350,7 @@
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */,
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
);
@ -2614,6 +2671,7 @@
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */,
@ -2676,6 +2734,8 @@
4B8805FE1DD02552003085B1 /* Tape.cpp in Sources */,
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */,
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */,
4B38F34C1F2EC3CA00D9235D /* CSAmstradCPC.mm in Sources */,
4B38F3441F2EB3E900D9235D /* StaticAnalyser.cpp in Sources */,
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */,
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
@ -2783,6 +2843,14 @@
name = OricOptions.xib;
sourceTree = "<group>";
};
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4B38F34E1F2EC6BA00D9235D /* Base */,
);
name = AmstradCPCOptions.xib;
sourceTree = "<group>";
};
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
</constraints>
</view>
<connections>
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
</connections>
<point key="canvasLocation" x="175" y="30"/>
</window>
</objects>
</document>

View File

@ -224,6 +224,24 @@
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cdt</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Amstrad CPC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>

View File

@ -43,8 +43,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (instancetype)init {
self = [super init];
if(self)
{
if(self) {
_machineDelegate.machine = self;
self.machine->set_delegate(&_machineDelegate);
_speakerDelegate.machine = self;

View File

@ -14,6 +14,7 @@
#include "StaticAnalyser.hpp"
#import "CSAmstradCPC.h"
#import "CSAtari2600.h"
#import "CSElectron.h"
#import "CSOric.h"
@ -22,16 +23,13 @@
#import "Clock_Signal-Swift.h"
@implementation CSStaticAnalyser
{
@implementation CSStaticAnalyser {
StaticAnalyser::Target _target;
}
- (instancetype)initWithFileAtURL:(NSURL *)url
{
- (instancetype)initWithFileAtURL:(NSURL *)url {
self = [super init];
if(self)
{
if(self) {
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([url fileSystemRepresentation]);
if(!targets.size()) return nil;
_target = targets.front();
@ -42,34 +40,31 @@
return self;
}
- (NSString *)optionsPanelNibName
{
switch(_target.machine)
{
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
case StaticAnalyser::Target::Oric: return @"OricOptions";
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
case StaticAnalyser::Target::ZX8081: return @"ZX8081Options";
- (NSString *)optionsPanelNibName {
switch(_target.machine) {
case StaticAnalyser::Target::AmstradCPC: return @"AmstradCPCOptions";
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
case StaticAnalyser::Target::Oric: return @"OricOptions";
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
case StaticAnalyser::Target::ZX8081: return @"ZX8081Options";
default: return nil;
}
}
- (CSMachine *)newMachine
{
switch(_target.machine)
{
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] init];
- (CSMachine *)newMachine {
switch(_target.machine) {
case StaticAnalyser::Target::AmstradCPC: return [[CSAmstradCPC alloc] init];
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] init];
default: return nil;
}
}
- (void)applyToMachine:(CSMachine *)machine
{
- (void)applyToMachine:(CSMachine *)machine {
[machine applyTarget:_target];
}

View File

@ -0,0 +1,14 @@
//
// CSAmstradCPC.h
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
@interface CSAmstradCPC : CSMachine <CSKeyboardMachine>
@end

View File

@ -0,0 +1,146 @@
//
// CSAmstradCPC.m
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import "CSAmstradCPC.h"
#include "AmstradCPC.hpp"
#import "CSMachine+Subclassing.h"
#import "NSData+StdVector.h"
#import "NSBundle+DataResource.h"
@implementation CSAmstradCPC {
std::unique_ptr<AmstradCPC::Machine> _amstradCPC;
}
- (CRTMachine::Machine * const)machine {
if(!_amstradCPC) {
_amstradCPC.reset(AmstradCPC::Machine::AmstradCPC());
}
return _amstradCPC.get();
}
- (instancetype)init {
self = [super init];
if(self) {
[self machine];
NSDictionary *roms = @{
@(AmstradCPC::ROMType::OS464) : @"os464",
@(AmstradCPC::ROMType::OS664) : @"os664",
@(AmstradCPC::ROMType::OS6128) : @"os6128",
@(AmstradCPC::ROMType::BASIC464) : @"basic464",
@(AmstradCPC::ROMType::BASIC664) : @"basic664",
@(AmstradCPC::ROMType::BASIC6128) : @"basic6128",
@(AmstradCPC::ROMType::AMSDOS) : @"amsdos",
};
for(NSNumber *key in roms.allKeys) {
AmstradCPC::ROMType type = (AmstradCPC::ROMType)key.integerValue;
NSString *name = roms[key];
NSData *data = [self rom:name];
if(data) {
_amstradCPC->set_rom(type, data.stdVector8);
} else {
NSLog(@"Amstrad CPC ROM missing: %@", name);
}
}
}
return self;
}
- (NSData *)rom:(NSString *)name {
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/AmstradCPC"];
}
- (NSString *)userDefaultsPrefix { return @"amstradCPC"; }
#pragma mark - Keyboard Mapping
- (void)clearAllKeys {
@synchronized(self) {
_amstradCPC->clear_all_keys();
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key) {
case VK_ANSI_0: _amstradCPC->set_key_state(AmstradCPC::Key::Key0, isPressed); break;
case VK_ANSI_1: _amstradCPC->set_key_state(AmstradCPC::Key::Key1, isPressed); break;
case VK_ANSI_2: _amstradCPC->set_key_state(AmstradCPC::Key::Key2, isPressed); break;
case VK_ANSI_3: _amstradCPC->set_key_state(AmstradCPC::Key::Key3, isPressed); break;
case VK_ANSI_4: _amstradCPC->set_key_state(AmstradCPC::Key::Key4, isPressed); break;
case VK_ANSI_5: _amstradCPC->set_key_state(AmstradCPC::Key::Key5, isPressed); break;
case VK_ANSI_6: _amstradCPC->set_key_state(AmstradCPC::Key::Key6, isPressed); break;
case VK_ANSI_7: _amstradCPC->set_key_state(AmstradCPC::Key::Key7, isPressed); break;
case VK_ANSI_8: _amstradCPC->set_key_state(AmstradCPC::Key::Key8, isPressed); break;
case VK_ANSI_9: _amstradCPC->set_key_state(AmstradCPC::Key::Key9, isPressed); break;
case VK_ANSI_Q: _amstradCPC->set_key_state(AmstradCPC::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _amstradCPC->set_key_state(AmstradCPC::Key::KeyW, isPressed); break;
case VK_ANSI_E: _amstradCPC->set_key_state(AmstradCPC::Key::KeyE, isPressed); break;
case VK_ANSI_R: _amstradCPC->set_key_state(AmstradCPC::Key::KeyR, isPressed); break;
case VK_ANSI_T: _amstradCPC->set_key_state(AmstradCPC::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _amstradCPC->set_key_state(AmstradCPC::Key::KeyY, isPressed); break;
case VK_ANSI_U: _amstradCPC->set_key_state(AmstradCPC::Key::KeyU, isPressed); break;
case VK_ANSI_I: _amstradCPC->set_key_state(AmstradCPC::Key::KeyI, isPressed); break;
case VK_ANSI_O: _amstradCPC->set_key_state(AmstradCPC::Key::KeyO, isPressed); break;
case VK_ANSI_P: _amstradCPC->set_key_state(AmstradCPC::Key::KeyP, isPressed); break;
case VK_ANSI_A: _amstradCPC->set_key_state(AmstradCPC::Key::KeyA, isPressed); break;
case VK_ANSI_S: _amstradCPC->set_key_state(AmstradCPC::Key::KeyS, isPressed); break;
case VK_ANSI_D: _amstradCPC->set_key_state(AmstradCPC::Key::KeyD, isPressed); break;
case VK_ANSI_F: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF, isPressed); break;
case VK_ANSI_G: _amstradCPC->set_key_state(AmstradCPC::Key::KeyG, isPressed); break;
case VK_ANSI_H: _amstradCPC->set_key_state(AmstradCPC::Key::KeyH, isPressed); break;
case VK_ANSI_J: _amstradCPC->set_key_state(AmstradCPC::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _amstradCPC->set_key_state(AmstradCPC::Key::KeyK, isPressed); break;
case VK_ANSI_L: _amstradCPC->set_key_state(AmstradCPC::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _amstradCPC->set_key_state(AmstradCPC::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _amstradCPC->set_key_state(AmstradCPC::Key::KeyX, isPressed); break;
case VK_ANSI_C: _amstradCPC->set_key_state(AmstradCPC::Key::KeyC, isPressed); break;
case VK_ANSI_V: _amstradCPC->set_key_state(AmstradCPC::Key::KeyV, isPressed); break;
case VK_ANSI_B: _amstradCPC->set_key_state(AmstradCPC::Key::KeyB, isPressed); break;
case VK_ANSI_N: _amstradCPC->set_key_state(AmstradCPC::Key::KeyN, isPressed); break;
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_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_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_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_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_F12: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed); break;
default:
// printf("%02x\n", key);
break;
}
}
}
@end

View File

@ -87,6 +87,7 @@ class Speaker {
Ensures any deferred processing occurs now.
*/
void flush() {
if(!queued_functions_) return;
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
queued_functions_.reset();
_queue->enqueue([queued_functions] {

View File

@ -0,0 +1,11 @@
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository. Per http://www.worldofspectrum.org/permits/amstrad-roms.txt, Amstrad themselves allow redistribution but the status of Locomotive's input is unclear.
Expected files:
amsdos.rom
basic464.rom
basic664.rom
basic6128.rom
os464.rom
os664.rom
os6128.rom

View File

@ -0,0 +1,23 @@
//
// AmstradCPC.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
void StaticAnalyser::AmstradCPC::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination) {
Target target;
target.machine = Target::AmstradCPC;
target.probability = 1.0;
target.disks = disks;
target.tapes = tapes;
target.cartridges = cartridges;
destination.push_back(target);
}

View File

@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
#define StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace AmstradCPC {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
}
}
#endif /* StaticAnalyser_AmstradCPC_StaticAnalyser_hpp */

View File

@ -12,6 +12,7 @@
// Analysers
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
@ -40,30 +41,28 @@
typedef int TargetPlatformType;
enum class TargetPlatform: TargetPlatformType {
Acorn = 1 << 0,
Atari2600 = 1 << 1,
Commodore = 1 << 2,
Oric = 1 << 3,
ZX8081 = 1 << 4,
AmstradCPC = 1 << 1,
Atari2600 = 1 << 2,
Commodore = 1 << 3,
Oric = 1 << 4,
ZX8081 = 1 << 5,
AllTape = Acorn | Commodore | Oric | ZX8081,
AllTape = Acorn | Commodore | Oric | ZX8081 | AmstradCPC,
};
using namespace StaticAnalyser;
std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
{
std::list<Target> StaticAnalyser::GetTargets(const char *file_name) {
std::list<Target> targets;
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
const char *mixed_case_extension = strrchr(file_name, '.');
char *lowercase_extension = nullptr;
if(mixed_case_extension)
{
if(mixed_case_extension) {
lowercase_extension = strdup(mixed_case_extension+1);
char *parser = lowercase_extension;
while(*parser)
{
while(*parser) {
*parser = (char)tolower(*parser);
parser++;
}
@ -86,18 +85,17 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
} catch(...) {}
#define Format(extension, list, class, platforms) \
if(!strcmp(lowercase_extension, extension)) \
{ \
if(!strcmp(lowercase_extension, extension)) { \
TryInsert(list, class, platforms) \
}
if(lowercase_extension)
{
if(lowercase_extension) {
Format("80", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("cdt", tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("csw", tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64
Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD
@ -108,14 +106,11 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
Format("p81", tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
if(!strcmp(lowercase_extension, "prg"))
{
if(!strcmp(lowercase_extension, "prg")) {
// try instantiating as a ROM; failing that accept as a tape
try {
Insert(cartridges, Cartridge::PRG, TargetPlatform::Commodore)
}
catch(...)
{
} catch(...) {
try {
Insert(tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) {}
@ -135,11 +130,12 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them. (TODO)
if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::ZX8081) ZX8081::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::ZX8081) ZX8081::AddTargets(disks, tapes, cartridges, targets);
free(lowercase_extension);
}

View File

@ -52,10 +52,11 @@ enum class ZX8081MemoryModel {
*/
struct Target {
enum {
AmstradCPC,
Atari2600,
Electron,
Vic20,
Oric,
Vic20,
ZX8081
} machine;
float probability;

View File

@ -18,7 +18,8 @@ DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, size_t length
window_length_(clocks_per_bit),
offset_history_pointer_(0),
offset_history_(length_of_history, 0),
offset_(0) {}
offset_(0),
delegate_(nullptr) {}
void DigitalPhaseLockedLoop::run_for(const Cycles cycles) {
offset_ += cycles.as_int();

View File

@ -56,12 +56,14 @@ void TZX::get_next_pulses() {
return;
}
// printf("TZX %ld\n", ftell(file_));
switch(chunk_id) {
case 0x10: get_standard_speed_data_block(); break;
case 0x11: get_turbo_speed_data_block(); break;
case 0x12: get_pure_tone_data_block(); break;
case 0x13: get_pulse_sequence(); break;
case 0x19: get_generalised_data_block(); break;
case 0x20: get_pause(); break;
case 0x30: {
// Text description. Ripe for ignoring.
@ -160,33 +162,67 @@ void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_pe
}
void TZX::get_standard_speed_data_block() {
__unused uint16_t pause_after_block = fgetc16le();
uint16_t data_length = fgetc16le();
if(!data_length) return;
DataBlock data_block;
data_block.length_of_pilot_pulse = 2168;
data_block.length_of_sync_first_pulse = 667;
data_block.length_of_sync_second_pulse = 735;
data_block.length_of_zero_bit_pulse = 855;
data_block.length_of_one_bit_pulse = 1710;
data_block.number_of_bits_in_final_byte = 8;
data_block.pause_after_block = fgetc16le();
data_block.data_length = fgetc16le();
if(!data_block.data_length) return;
uint8_t first_byte = (uint8_t)fgetc(file_);
__unused int pilot_tone_pulses = (first_byte < 128) ? 8063 : 3223;
data_block.length_of_pilot_tone = (first_byte < 128) ? 8063 : 3223;
ungetc(first_byte, file_);
// TODO: output pilot_tone_pulses pulses
// TODO: output data_length bytes, in the Spectrum encoding
fseek(file_, data_length, SEEK_CUR);
// TODO: output a pause of length paused_after_block ms
get_data_block(data_block);
}
void TZX::get_turbo_speed_data_block() {
__unused uint16_t length_of_pilot_pulse = fgetc16le();
__unused uint16_t length_of_sync_first_pulse = fgetc16le();
__unused uint16_t length_of_sync_second_pulse = fgetc16le();
__unused uint16_t length_of_zero_bit_pulse = fgetc16le();
__unused uint16_t length_of_one_bit_pulse = fgetc16le();
__unused uint16_t length_of_pilot_tone = fgetc16le();
__unused uint8_t number_of_bits_in_final_byte = (uint8_t)fgetc(file_);
__unused uint16_t pause_after_block = fgetc16le();
uint16_t data_length = fgetc16le();
DataBlock data_block;
data_block.length_of_pilot_pulse = fgetc16le();
data_block.length_of_sync_first_pulse = fgetc16le();
data_block.length_of_sync_second_pulse = fgetc16le();
data_block.length_of_zero_bit_pulse = fgetc16le();
data_block.length_of_one_bit_pulse = fgetc16le();
data_block.length_of_pilot_tone = fgetc16le();
data_block.number_of_bits_in_final_byte = (uint8_t)fgetc(file_);
data_block.pause_after_block = fgetc16le();
data_block.data_length = fgetc16le();
data_block.data_length |= (long)(fgetc(file_) << 16);
// TODO output as described
fseek(file_, data_length, SEEK_CUR);
get_data_block(data_block);
}
void TZX::get_data_block(const DataBlock &data_block) {
// Output pilot tone.
for(unsigned int c = 0; c < data_block.length_of_pilot_tone; c++) {
post_pulse(data_block.length_of_pilot_pulse);
}
// Output sync pulses.
post_pulse(data_block.length_of_sync_first_pulse);
post_pulse(data_block.length_of_sync_second_pulse);
// Output data.
for(unsigned int c = 0; c < data_block.data_length; c++) {
uint8_t next_byte = (uint8_t)fgetc(file_);
unsigned int bits = (c != data_block.data_length-1) ? 8 : data_block.number_of_bits_in_final_byte;
while(bits--) {
unsigned int pulse_length = (next_byte & 0x80) ? data_block.length_of_one_bit_pulse : data_block.length_of_zero_bit_pulse;
next_byte <<= 1;
post_pulse(pulse_length);
post_pulse(pulse_length);
}
}
// Output gap.
post_gap(data_block.pause_after_block);
}
void TZX::get_pure_tone_data_block() {
@ -203,6 +239,15 @@ void TZX::get_pulse_sequence() {
}
}
void TZX::get_pause() {
uint16_t duration = fgetc16le();
if(!duration) {
// TODO (maybe): post a 'pause the tape' suggestion
} else {
post_gap(duration);
}
}
#pragma mark - Output
void TZX::post_pulse(unsigned int length) {

View File

@ -42,8 +42,22 @@ class TZX: public PulseQueuedTape, public Storage::FileHolder {
void get_pure_tone_data_block();
void get_pulse_sequence();
void get_generalised_data_block();
void get_pause();
struct DataBlock {
unsigned int length_of_pilot_pulse;
unsigned int length_of_sync_first_pulse;
unsigned int length_of_sync_second_pulse;
unsigned int length_of_zero_bit_pulse;
unsigned int length_of_one_bit_pulse;
unsigned int length_of_pilot_tone;
unsigned int number_of_bits_in_final_byte;
unsigned int pause_after_block;
long data_length;
};
void get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data);
void get_data_block(const DataBlock &);
void post_pulse(unsigned int length);
void post_gap(unsigned int milliseconds);

View File

@ -69,7 +69,8 @@ Shifter::Shifter() :
pll_(PLLClockRate / 4800, 15),
was_high_(false),
input_pattern_(0),
input_bit_counter_(0) {
input_bit_counter_(0),
delegate_(nullptr) {
pll_.set_delegate(this);
}

View File

@ -110,7 +110,7 @@ void TapePlayer::process_next_event() {
#pragma mark - Binary Player
BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
TapePlayer(input_clock_rate), motor_is_running_(false), input_level_(false)
TapePlayer(input_clock_rate), motor_is_running_(false), input_level_(false), delegate_(nullptr)
{}
void BinaryTapePlayer::set_motor_control(bool enabled) {