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:
commit
a54ccd1969
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
18
ClockReceiver/ForceInline.h
Normal file
18
ClockReceiver/ForceInline.h
Normal 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 */
|
173
Components/6845/CRTC6845.hpp
Normal file
173
Components/6845/CRTC6845.hpp
Normal 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
72
Components/8255/i8255.hpp
Normal 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 */
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
};
|
||||
|
703
Machines/AmstradCPC/AmstradCPC.cpp
Normal file
703
Machines/AmstradCPC/AmstradCPC.cpp
Normal 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 1–3: 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;
|
||||
}
|
70
Machines/AmstradCPC/AmstradCPC.hpp
Normal file
70
Machines/AmstradCPC/AmstradCPC.hpp
Normal 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 */
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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 = (
|
||||
|
48
OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
Normal file
48
OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
Normal 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>
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
14
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
Normal file
14
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
Normal 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
|
146
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
Normal file
146
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
Normal 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
|
@ -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] {
|
||||
|
11
ROMImages/AmstradCPC/readme.txt
Normal file
11
ROMImages/AmstradCPC/readme.txt
Normal 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
|
23
StaticAnalyser/AmstradCPC/StaticAnalyser.cpp
Normal file
23
StaticAnalyser/AmstradCPC/StaticAnalyser.cpp
Normal 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);
|
||||
}
|
27
StaticAnalyser/AmstradCPC/StaticAnalyser.hpp
Normal file
27
StaticAnalyser/AmstradCPC/StaticAnalyser.hpp
Normal 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 */
|
@ -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);
|
||||
}
|
||||
|
@ -52,10 +52,11 @@ enum class ZX8081MemoryModel {
|
||||
*/
|
||||
struct Target {
|
||||
enum {
|
||||
AmstradCPC,
|
||||
Atari2600,
|
||||
Electron,
|
||||
Vic20,
|
||||
Oric,
|
||||
Vic20,
|
||||
ZX8081
|
||||
} machine;
|
||||
float probability;
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user