From db6d9b59d0c5e4ad66779c07e771f52b56f47250 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 19 Dec 2017 21:53:04 -0500
Subject: [PATCH 1/5] Attempts to implement TSX support for the MSX.

---
 Machines/MSX/MSX.cpp                   | 58 +++++++++++++++++-----
 OSBindings/Mac/Clock Signal/Info.plist |  3 ++
 StaticAnalyser/MSX/StaticAnalyser.cpp  |  3 ++
 StaticAnalyser/StaticAnalyser.cpp      |  1 +
 Storage/Tape/Formats/TZX.cpp           | 68 ++++++++++++++++++++++++--
 Storage/Tape/Formats/TZX.hpp           |  2 +
 6 files changed, 119 insertions(+), 16 deletions(-)

diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp
index cb69a7899..189683e3a 100644
--- a/Machines/MSX/MSX.cpp
+++ b/Machines/MSX/MSX.cpp
@@ -17,6 +17,8 @@
 #include "../../Components/8255/i8255.hpp"
 #include "../../Components/AY38910/AY38910.hpp"
 
+#include "../../Storage/Tape/Tape.hpp"
+
 #include "../CRTMachine.hpp"
 #include "../ConfigurationTarget.hpp"
 #include "../KeyboardMachine.hpp"
@@ -61,15 +63,32 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
 		Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 };
 
-struct AYPortHandler: public GI::AY38910::PortHandler {
-	void set_port_output(bool port_b, uint8_t value) {
-//		printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value);
-	}
+class AYPortHandler: public GI::AY38910::PortHandler {
+	public:
+		AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {}
 
-	uint8_t get_port_input(bool port_b) {
-//		printf("AY port %c input\n", port_b ? 'b' : 'a');
-		return 0xff;
-	}
+		void set_port_output(bool port_b, uint8_t value) {
+			if(port_b) {
+				// Bits 0–3: touchpad handshaking (?)
+				// Bit 4—5: monostable timer pulses
+				// Bit 6: joystick select
+				// Bit 7: code LED, if any
+			}
+		}
+
+		uint8_t get_port_input(bool port_b) {
+			if(!port_b) {
+				// Bits 0–5: Joystick (up, down, left, right, A, B)
+				// Bit 6: keyboard switch (not universal)
+
+				// Bit 7: tape input
+				return 0x7f | (tape_player_.get_input() ? 0x80 : 0x00);
+			}
+			return 0xff;
+		}
+
+	private:
+		Storage::Tape::BinaryTapePlayer &tape_player_;
 };
 
 class ConcreteMachine:
@@ -86,7 +105,9 @@ class ConcreteMachine:
 			audio_toggle_(audio_queue_),
 			mixer_(ay_, audio_toggle_),
 			speaker_(mixer_),
-			i8255_port_handler_(*this, audio_toggle_) {
+			tape_player_(3579545 * 2),
+			i8255_port_handler_(*this, audio_toggle_, tape_player_),
+			ay_port_handler_(tape_player_) {
 			set_clock_rate(3579545);
 			std::memset(unpopulated_, 0xff, sizeof(unpopulated_));
 			clear_all_keys();
@@ -130,6 +151,11 @@ class ConcreteMachine:
 					memory_slots_[1].read_pointers[(c >> 14) + base] = cartridge_.data() + c;
 				}
 			}
+
+			if(!media.tapes.empty()) {
+				tape_player_.set_tape(media.tapes.front());
+			}
+
 			return true;
 		}
 
@@ -222,6 +248,9 @@ class ConcreteMachine:
 				default: break;
 			}
 
+			// Update the tape. (TODO: allow for sleeping)
+			tape_player_.run_for(cycle.length.as_int());
+
 			// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
 			// but otherwise runs without pause.
 			HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
@@ -299,8 +328,8 @@ class ConcreteMachine:
 
 		class i8255PortHandler: public Intel::i8255::PortHandler {
 			public:
-				i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle) :
-					machine_(machine), audio_toggle_(audio_toggle) {}
+				i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) :
+					machine_(machine), audio_toggle_(audio_toggle), tape_player_(tape_player) {}
 
 				void set_value(int port, uint8_t value) {
 					switch(port) {
@@ -309,7 +338,9 @@ class ConcreteMachine:
 							// TODO:
 							//	b6	caps lock LED
 							//	b5 	audio output
-							//	b4	cassette motor relay
+
+							//	b4: cassette motor relay
+							tape_player_.set_motor_control(!!(value & 0x10));
 
 							//	b7: keyboard click
 							bool new_audio_level = !!(value & 0x80);
@@ -335,6 +366,7 @@ class ConcreteMachine:
 			private:
 				ConcreteMachine &machine_;
 				AudioToggle &audio_toggle_;
+				Storage::Tape::BinaryTapePlayer &tape_player_;
 		};
 
 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
@@ -347,6 +379,8 @@ class ConcreteMachine:
 		Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle> mixer_;
 		Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle>> speaker_;
 
+		Storage::Tape::BinaryTapePlayer tape_player_;
+
 		i8255PortHandler i8255_port_handler_;
 		AYPortHandler ay_port_handler_;
 
diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist
index ee2011d8a..1025bbc5f 100644
--- a/OSBindings/Mac/Clock Signal/Info.plist	
+++ b/OSBindings/Mac/Clock Signal/Info.plist	
@@ -265,6 +265,7 @@
 			<key>CFBundleTypeExtensions</key>
 			<array>
 				<string>cas</string>
+				<string>tsx</string>
 			</array>
 			<key>CFBundleTypeIconFile</key>
 			<string>cassette</string>
@@ -272,6 +273,8 @@
 			<string>MSX 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/StaticAnalyser/MSX/StaticAnalyser.cpp b/StaticAnalyser/MSX/StaticAnalyser.cpp
index c7bed821e..a04756523 100644
--- a/StaticAnalyser/MSX/StaticAnalyser.cpp
+++ b/StaticAnalyser/MSX/StaticAnalyser.cpp
@@ -65,6 +65,9 @@ void StaticAnalyser::MSX::AddTargets(const Media &media, std::list<Target> &dest
 
 	target.media.cartridges = MSXCartridgesFrom(media.cartridges);
 
+	// TODO: tape parsing. Be dumb for now.
+	target.media.tapes = media.tapes;
+
 	if(!target.media.empty()) {
 		target.machine = Target::MSX;
 		target.probability = 1.0;
diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp
index b828e7c66..36750ea75 100644
--- a/StaticAnalyser/StaticAnalyser.cpp
+++ b/StaticAnalyser/StaticAnalyser.cpp
@@ -114,6 +114,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
 		Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD
 		Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore)
 		Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric)
+		Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)												// TSX
 		Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)											// TZX
 		Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)											// UEF (tape)
 
diff --git a/Storage/Tape/Formats/TZX.cpp b/Storage/Tape/Formats/TZX.cpp
index 6bc5ce7ea..a528bdbf9 100644
--- a/Storage/Tape/Formats/TZX.cpp
+++ b/Storage/Tape/Formats/TZX.cpp
@@ -76,6 +76,8 @@ void TZX::get_next_pulses() {
 			case 0x31:	ignore_message_block();				break;
 			case 0x33:	get_hardware_type();				break;
 
+			case 0x4b:	get_kansas_city_block();			break;
+
 			default:
 				// In TZX each chunk has a different way of stating or implying its length,
 				// so there is no route past an unimplemented chunk.
@@ -203,9 +205,7 @@ void TZX::get_turbo_speed_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);
-	}
+	post_pulses(data_block.length_of_pilot_tone, data_block.length_of_pilot_pulse);
 
 	// Output sync pulses.
 	post_pulse(data_block.length_of_sync_first_pulse);
@@ -237,7 +237,7 @@ void TZX::get_pure_tone_data_block() {
 	uint16_t length_of_pulse = file_.get16le();
 	uint16_t nunber_of_pulses = file_.get16le();
 
-	while(nunber_of_pulses--) post_pulse(length_of_pulse);
+	post_pulses(nunber_of_pulses, length_of_pulse);
 }
 
 void TZX::get_pure_data_block() {
@@ -267,8 +267,68 @@ void TZX::get_pause() {
 	}
 }
 
+void TZX::get_kansas_city_block() {
+	uint32_t block_length = file_.get32le();
+
+	const uint16_t pause_after_block = file_.get16le();
+	const uint16_t pilot_pulse_duration = file_.get16le();
+	const uint16_t pilot_length = file_.get16le();
+	uint16_t pulse_durations[2];
+	pulse_durations[0] = file_.get16le();
+	pulse_durations[1] = file_.get16le();
+	const uint8_t packed_pulse_counts = file_.get8();
+	const unsigned int pulse_counts[2] = {
+		static_cast<unsigned int>((((packed_pulse_counts >> 4) - 1) & 15) + 1),
+		static_cast<unsigned int>((((packed_pulse_counts & 15) - 1) & 15) + 1)
+	};
+	const uint8_t padding_flags = file_.get8();
+
+	const unsigned int number_of_leading_pulses = ((padding_flags >> 6)&3) * pulse_counts[(padding_flags >> 5) & 1];
+	const unsigned int leading_pulse_length = pulse_durations[(padding_flags >> 5) & 1];
+
+	const unsigned int number_of_trailing_pulses = ((padding_flags >> 3)&3) * pulse_counts[(padding_flags >> 2) & 1];
+	const unsigned int trailing_pulse_length = pulse_durations[(padding_flags >> 2) & 1];
+
+	block_length -= 12;
+
+	// Output pilot tone.
+	post_pulses(pilot_length, pilot_pulse_duration);
+
+	// Output data.
+	while(block_length--) {
+		post_pulses(number_of_leading_pulses, leading_pulse_length);
+
+		uint8_t new_byte = file_.get8();
+		int bits = 8;
+		if(padding_flags & 1) {
+			// Output MSB first.
+			while(bits--) {
+				int bit = (new_byte >> 7) & 1;
+				new_byte <<= 1;
+				post_pulses(pulse_counts[bit], pulse_durations[bit]);
+			}
+		} else {
+			// Output LSB first.
+			while(bits--) {
+				int bit = new_byte & 1;
+				new_byte >>= 1;
+				post_pulses(pulse_counts[bit], pulse_durations[bit]);
+			}
+		}
+
+		post_pulses(number_of_trailing_pulses, trailing_pulse_length);
+	}
+
+	// Output gap.
+	post_gap(pause_after_block);
+}
+
 // MARK: - Output
 
+void TZX::post_pulses(unsigned int count, unsigned int length) {
+	while(count--) post_pulse(length);
+}
+
 void TZX::post_pulse(unsigned int length) {
 	post_pulse(Storage::Time(length, StandardTZXClock));
 }
diff --git a/Storage/Tape/Formats/TZX.hpp b/Storage/Tape/Formats/TZX.hpp
index 49ac58c21..6d061b582 100644
--- a/Storage/Tape/Formats/TZX.hpp
+++ b/Storage/Tape/Formats/TZX.hpp
@@ -46,6 +46,7 @@ class TZX: public PulseQueuedTape {
 		void get_pure_data_block();
 		void get_generalised_data_block();
 		void get_pause();
+		void get_kansas_city_block();
 
 		void get_hardware_type();
 
@@ -80,6 +81,7 @@ class TZX: public PulseQueuedTape {
 		void get_data_block(const DataBlock &);
 		void get_data(const Data &);
 
+		void post_pulses(unsigned int count, unsigned int length);
 		void post_pulse(unsigned int length);
 		void post_gap(unsigned int milliseconds);
 

From 0bb24075b63abd9fdd517e5565a5671ac42a3841 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 19 Dec 2017 22:17:42 -0500
Subject: [PATCH 2/5] Immediate fixes: TSX is seemingly TZX 1.21; the tape
 motor control works the other way around.

Input is not yet being recognised.
---
 Machines/MSX/MSX.cpp         | 4 ++--
 Storage/Tape/Formats/TZX.cpp | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp
index 189683e3a..b94958040 100644
--- a/Machines/MSX/MSX.cpp
+++ b/Machines/MSX/MSX.cpp
@@ -82,7 +82,7 @@ class AYPortHandler: public GI::AY38910::PortHandler {
 				// Bit 6: keyboard switch (not universal)
 
 				// Bit 7: tape input
-				return 0x7f | (tape_player_.get_input() ? 0x80 : 0x00);
+				return 0x7f | (tape_player_.get_input() ? 0x00 : 0x80);
 			}
 			return 0xff;
 		}
@@ -340,7 +340,7 @@ class ConcreteMachine:
 							//	b5 	audio output
 
 							//	b4: cassette motor relay
-							tape_player_.set_motor_control(!!(value & 0x10));
+							tape_player_.set_motor_control(!(value & 0x10));
 
 							//	b7: keyboard click
 							bool new_audio_level = !!(value & 0x80);
diff --git a/Storage/Tape/Formats/TZX.cpp b/Storage/Tape/Formats/TZX.cpp
index a528bdbf9..047124597 100644
--- a/Storage/Tape/Formats/TZX.cpp
+++ b/Storage/Tape/Formats/TZX.cpp
@@ -28,7 +28,7 @@ TZX::TZX(const char *file_name) :
 	uint8_t minor_version = file_.get8();
 
 	// Reject if an incompatible version
-	if(major_version != 1 || minor_version > 20)  throw ErrorNotTZX;
+	if(major_version != 1 || minor_version > 21)  throw ErrorNotTZX;
 
 	virtual_reset();
 }
@@ -53,7 +53,7 @@ void TZX::get_next_pulses() {
 			return;
 		}
 
-//		printf("TZX %ld\n", ftell(file_));
+		printf("TZX %02x\n", chunk_id);
 		switch(chunk_id) {
 			case 0x10:	get_standard_speed_data_block();	break;
 			case 0x11:	get_turbo_speed_data_block();		break;

From f17758e7f9de050317d651bb2069b92103f0639c Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 20 Dec 2017 21:03:24 -0500
Subject: [PATCH 3/5] Attempts better to deal with large numbers.

---
 Storage/TimedEventLoop.cpp | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp
index 2e187197a..bd9bea643 100644
--- a/Storage/TimedEventLoop.cpp
+++ b/Storage/TimedEventLoop.cpp
@@ -65,18 +65,29 @@ void TimedEventLoop::jump_to_next_event() {
 
 void TimedEventLoop::set_next_event_time_interval(Time interval) {
 	// Calculate [interval]*[input clock rate] + [subcycles until this event].
-	int64_t denominator = (int64_t)interval.clock_rate * (int64_t)subcycles_until_event_.clock_rate;
+	int64_t denominator = static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.clock_rate);
 	int64_t numerator =
-		(int64_t)subcycles_until_event_.clock_rate * (int64_t)input_clock_rate_ * (int64_t)interval.length +
-		(int64_t)interval.clock_rate * (int64_t)subcycles_until_event_.length;
+		static_cast<int64_t>(subcycles_until_event_.clock_rate) * static_cast<int64_t>(input_clock_rate_) * static_cast<int64_t>(interval.length) +
+		static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.length);
 
-	// Simplify if necessary.
-	if(denominator > std::numeric_limits<unsigned int>::max()) {
+	// Simplify if necessary: try just simplifying the interval and recalculating; if that doesn't
+	// work then try simplifying the whole thing.
+	if(numerator < 0 || denominator < 0 || denominator > std::numeric_limits<unsigned int>::max()) {
+		interval.simplify();
+		denominator = static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.clock_rate);
+		numerator =
+			static_cast<int64_t>(subcycles_until_event_.clock_rate) * static_cast<int64_t>(input_clock_rate_) * static_cast<int64_t>(interval.length) +
+			static_cast<int64_t>(interval.clock_rate) * static_cast<int64_t>(subcycles_until_event_.length);
+	}
+
+	if(numerator < 0 || denominator < 0 || denominator > std::numeric_limits<unsigned int>::max()) {
 		int64_t common_divisor = NumberTheory::greatest_common_divisor(numerator % denominator, denominator);
 		denominator /= common_divisor;
 		numerator /= common_divisor;
 	}
 
+	// TODO: if that doesn't work then reduce precision.
+
 	// So this event will fire in the integral number of cycles from now, putting us at the remainder
 	// number of subcycles
 	assert(cycles_until_event_ == 0);

From b9b107ee851f07b12a11ffe7e61711a5d78ebc87 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 20 Dec 2017 21:16:54 -0500
Subject: [PATCH 4/5] Switches KeyGrave and KeyQuote, correcting a
 disarrangement.

---
 Machines/MSX/Keyboard.hpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Machines/MSX/Keyboard.hpp b/Machines/MSX/Keyboard.hpp
index 4cdfd1c26..4daec52ea 100644
--- a/Machines/MSX/Keyboard.hpp
+++ b/Machines/MSX/Keyboard.hpp
@@ -20,7 +20,7 @@ enum Key: uint16_t {
 
 	Line(0, Key7,				Key6,					Key5,					Key4,				Key3,			Key2,				Key1,			Key0)
 	Line(1, KeySemicolon,		KeyRightSquareBracket,	KeyLeftSquareBracket,	KeyBackSlash,		KeyEquals,		KeyMinus,			Key9,			Key8)
-	Line(2, KeyB,				KeyA,					KeyNA,					KeyForwardSlash,	KeyFullStop,	KeyComma,			KeyQuote,		KeyGrave)
+	Line(2, KeyB,				KeyA,					KeyNA,					KeyForwardSlash,	KeyFullStop,	KeyComma,			KeyGrave,		KeyQuote)
 	Line(3, KeyJ,				KeyI,					KeyH,					KeyG,				KeyF,			KeyE,				KeyD,			KeyC)
 	Line(4, KeyR,				KeyQ,					KeyP,					KeyO,				KeyN,			KeyM,				KeyL,			KeyK)
 	Line(5, KeyZ,				KeyY,					KeyX,					KeyW,				KeyV,			KeyU,				KeyT,			KeyS)

From 28fb1ce2ae5f215b759c5309f0da7644875165ac Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 20 Dec 2017 21:39:17 -0500
Subject: [PATCH 5/5] Removes unnecessary logging.

---
 Storage/Tape/Formats/TZX.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Storage/Tape/Formats/TZX.cpp b/Storage/Tape/Formats/TZX.cpp
index 047124597..4c47c6082 100644
--- a/Storage/Tape/Formats/TZX.cpp
+++ b/Storage/Tape/Formats/TZX.cpp
@@ -53,7 +53,6 @@ void TZX::get_next_pulses() {
 			return;
 		}
 
-		printf("TZX %02x\n", chunk_id);
 		switch(chunk_id) {
 			case 0x10:	get_standard_speed_data_block();	break;
 			case 0x11:	get_turbo_speed_data_block();		break;