diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp
index 515d3216b..097b1c6db 100644
--- a/ClockReceiver/ClockReceiver.hpp
+++ b/ClockReceiver/ClockReceiver.hpp
@@ -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;
+		}
 };
 
 /*!
diff --git a/ClockReceiver/ForceInline.h b/ClockReceiver/ForceInline.h
new file mode 100644
index 000000000..786e41c2d
--- /dev/null
+++ b/ClockReceiver/ForceInline.h
@@ -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 */
diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp
new file mode 100644
index 000000000..13d1a2ffa
--- /dev/null
+++ b/Components/6845/CRTC6845.hpp
@@ -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 */
diff --git a/Components/8255/i8255.hpp b/Components/8255/i8255.hpp
new file mode 100644
index 000000000..e188d669c
--- /dev/null
+++ b/Components/8255/i8255.hpp
@@ -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 */
diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp
index 22769b805..1a3f0b33e 100644
--- a/Components/AY38910/AY38910.cpp
+++ b/Components/AY38910/AY38910.cpp
@@ -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;
 	}
 }
diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp
index 4b35e395a..8065990de 100644
--- a/Components/AY38910/AY38910.hpp
+++ b/Components/AY38910/AY38910.hpp
@@ -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();
 };
 
 };
diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp
new file mode 100644
index 000000000..f8efa5c6a
--- /dev/null
+++ b/Machines/AmstradCPC/AmstradCPC.cpp
@@ -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;
+}
diff --git a/Machines/AmstradCPC/AmstradCPC.hpp b/Machines/AmstradCPC/AmstradCPC.hpp
new file mode 100644
index 000000000..964d6b22a
--- /dev/null
+++ b/Machines/AmstradCPC/AmstradCPC.hpp
@@ -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 */
diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp
index 5ea8d856e..96f2ba7fd 100644
--- a/Machines/CRTMachine.hpp
+++ b/Machines/CRTMachine.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) {
diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp
index 10e4f6b7e..74160be0d 100644
--- a/Machines/Oric/Oric.cpp
+++ b/Machines/Oric/Oric.cpp
@@ -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
diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index 2412376ca..ac1236c91 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -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 = (
diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib b/OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
new file mode 100644
index 000000000..39bd7024b
--- /dev/null
+++ b/OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib	
@@ -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>
diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist
index eb8a76d93..cd9bb6f73 100644
--- a/OSBindings/Mac/Clock Signal/Info.plist	
+++ b/OSBindings/Mac/Clock Signal/Info.plist	
@@ -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>
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm
index 6ed1e1d79..9fbc7ffbb 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
@@ -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;
diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm
index 72a5dee94..7a3b12b09 100644
--- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm	
+++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm	
@@ -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];
 }
 
diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
new file mode 100644
index 000000000..1286a6046
--- /dev/null
+++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h	
@@ -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
diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
new file mode 100644
index 000000000..ad42574bd
--- /dev/null
+++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm	
@@ -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
diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp
index 7d9215f50..a64082d42 100644
--- a/Outputs/Speaker.hpp
+++ b/Outputs/Speaker.hpp
@@ -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] {
diff --git a/ROMImages/AmstradCPC/readme.txt b/ROMImages/AmstradCPC/readme.txt
new file mode 100644
index 000000000..ebe897eff
--- /dev/null
+++ b/ROMImages/AmstradCPC/readme.txt
@@ -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
\ No newline at end of file
diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp
new file mode 100644
index 000000000..b66621960
--- /dev/null
+++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp
@@ -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);
+}
diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.hpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.hpp
new file mode 100644
index 000000000..f798f5bcd
--- /dev/null
+++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.hpp
@@ -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 */
diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp
index 0d437a692..ff1502617 100644
--- a/StaticAnalyser/StaticAnalyser.cpp
+++ b/StaticAnalyser/StaticAnalyser.cpp
@@ -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);
 	}
diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp
index 982dc1315..ac1aca5ee 100644
--- a/StaticAnalyser/StaticAnalyser.hpp
+++ b/StaticAnalyser/StaticAnalyser.hpp
@@ -52,10 +52,11 @@ enum class ZX8081MemoryModel {
 */
 struct Target {
 	enum  {
+		AmstradCPC,
 		Atari2600,
 		Electron,
-		Vic20,
 		Oric,
+		Vic20,
 		ZX8081
 	} machine;
 	float probability;
diff --git a/Storage/Disk/DigitalPhaseLockedLoop.cpp b/Storage/Disk/DigitalPhaseLockedLoop.cpp
index 9e99aa054..8348049c9 100644
--- a/Storage/Disk/DigitalPhaseLockedLoop.cpp
+++ b/Storage/Disk/DigitalPhaseLockedLoop.cpp
@@ -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();
diff --git a/Storage/Tape/Formats/TZX.cpp b/Storage/Tape/Formats/TZX.cpp
index 6814b8bcd..efa3b3073 100644
--- a/Storage/Tape/Formats/TZX.cpp
+++ b/Storage/Tape/Formats/TZX.cpp
@@ -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) {
diff --git a/Storage/Tape/Formats/TZX.hpp b/Storage/Tape/Formats/TZX.hpp
index d0a3eedfd..aeedf8ce9 100644
--- a/Storage/Tape/Formats/TZX.hpp
+++ b/Storage/Tape/Formats/TZX.hpp
@@ -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);
diff --git a/Storage/Tape/Parsers/Acorn.cpp b/Storage/Tape/Parsers/Acorn.cpp
index 3cf875198..519fa3fe7 100644
--- a/Storage/Tape/Parsers/Acorn.cpp
+++ b/Storage/Tape/Parsers/Acorn.cpp
@@ -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);
 }
 
diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp
index f59314889..280b02100 100644
--- a/Storage/Tape/Tape.cpp
+++ b/Storage/Tape/Tape.cpp
@@ -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) {