mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			321 Commits
		
	
	
		
			2018-05-24
			...
			2018-09-09
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ab02f82470 | ||
|  | 1e3318816c | ||
|  | 3a3dec92c7 | ||
|  | 5a5fc1ae1a | ||
|  | 8d79a1e381 | ||
|  | d70f5da94e | ||
|  | 05d4274019 | ||
|  | afeec09902 | ||
|  | 0526ac2ee2 | ||
|  | 6725ee2190 | ||
|  | 8b661fb90f | ||
|  | dab7d3db1b | ||
|  | 1cba3d48d9 | ||
|  | d53b38ec7e | ||
|  | 5d0f47eda2 | ||
|  | 2e04c4442c | ||
|  | f639cdc8ad | ||
|  | 71ec7624ca | ||
|  | 0599d9602e | ||
|  | 234bef2a88 | ||
|  | adb574e1cd | ||
|  | 1f491e764e | ||
|  | 114a43a662 | ||
|  | 5547c39c91 | ||
|  | 97a89aaf4d | ||
|  | 61e46399dc | ||
|  | e802f6ecc2 | ||
|  | 4209f0e044 | ||
|  | 33576aa2c4 | ||
|  | 17bf1a64bf | ||
|  | f8d46f8f3d | ||
|  | 8787d85e64 | ||
|  | 7f0f17f435 | ||
|  | 0e7f54f375 | ||
|  | b3bdfa9f46 | ||
|  | 592ec69d36 | ||
|  | 60e00ddd02 | ||
|  | 6806193dc2 | ||
|  | c35dca783f | ||
|  | 901e0d65b9 | ||
|  | ddf45a0010 | ||
|  | 1eca4463b3 | ||
|  | be01203cc1 | ||
|  | 4d1d19a464 | ||
|  | 760817eb3b | ||
|  | cb47575860 | ||
|  | 434d184503 | ||
|  | 7374c665e8 | ||
|  | 10c930a59d | ||
|  | 60ab6f0c2a | ||
|  | a13eb351da | ||
|  | 4b91910fab | ||
|  | f46d52364c | ||
|  | 878c63dcd2 | ||
|  | 261fb3d4f8 | ||
|  | b63e0cff72 | ||
|  | 5d6e479338 | ||
|  | 90094529a5 | ||
|  | aed4c0539e | ||
|  | 8b50ab2593 | ||
|  | 95164b79c9 | ||
|  | 6f838fe190 | ||
|  | bb680b40d8 | ||
|  | e3f6da6994 | ||
|  | e46bde35f5 | ||
|  | 32338bea4d | ||
|  | 5c881bd19d | ||
|  | 1a44ef0469 | ||
|  | ebce9a2e51 | ||
|  | 633af4d404 | ||
|  | 76a73c835c | ||
|  | c1d1c451ef | ||
|  | 3be30d8c71 | ||
|  | d4c1244485 | ||
|  | c61b9dca17 | ||
|  | 39bf682016 | ||
|  | 60ac9b49ea | ||
|  | a8bb18e2cf | ||
|  | 1852786609 | ||
|  | 31df8c7e91 | ||
|  | 832939f5b7 | ||
|  | c2d9e1ec81 | ||
|  | 673b915ee8 | ||
|  | 032a62dfff | ||
|  | f2d78182a3 | ||
|  | de68e70246 | ||
|  | e07447eb9a | ||
|  | 5cdeb58571 | ||
|  | ce14cc8677 | ||
|  | bcd0479074 | ||
|  | d72dd8c4ff | ||
|  | f7ce86fef8 | ||
|  | 55f2fccf5e | ||
|  | c939a274be | ||
|  | 101fb5d7bf | ||
|  | 3c51e335c3 | ||
|  | 33ea90678c | ||
|  | 11ae2c64ba | ||
|  | 26624d7652 | ||
|  | 85fb4773b0 | ||
|  | 099d66804e | ||
|  | 086596c28e | ||
|  | 3aeb4213fe | ||
|  | 558b96bc05 | ||
|  | e97cc40a2c | ||
|  | 94503ed771 | ||
|  | c4f86cc324 | ||
|  | 70c4d6b9b3 | ||
|  | 78c7137427 | ||
|  | 74a2f717b3 | ||
|  | 98bb5bd9f1 | ||
|  | c91eaaf8da | ||
|  | a36f37d240 | ||
|  | c773d3501a | ||
|  | 5810f9b3f9 | ||
|  | 3f56683342 | ||
|  | 16ccbdefd6 | ||
|  | a533d09fe7 | ||
|  | e9aaa5bbdf | ||
|  | ecb26e3281 | ||
|  | 5aa0b17720 | ||
|  | 632b37ecec | ||
|  | c905de2e40 | ||
|  | bc2afe69e1 | ||
|  | 894998b163 | ||
|  | 51192d8397 | ||
|  | 3c33ccd730 | ||
|  | 3e35109d63 | ||
|  | 99c770eab4 | ||
|  | 34aa78b7ce | ||
|  | 8cca9c2055 | ||
|  | 85ce21c79f | ||
|  | d19d949b9c | ||
|  | 1cb3713b84 | ||
|  | 689850d698 | ||
|  | c572a52049 | ||
|  | 41765e00c4 | ||
|  | 080aa0acc5 | ||
|  | 5e7c46a72a | ||
|  | 5f2b9b2d5a | ||
|  | 5c4506a9db | ||
|  | 55a6431fb3 | ||
|  | ede2696a77 | ||
|  | 59b9e39022 | ||
|  | 6b2970f2f2 | ||
|  | 6a73fe7d65 | ||
|  | 1362906f94 | ||
|  | 8f4042c4bb | ||
|  | c05b6397b0 | ||
|  | 8d18808efe | ||
|  | 09950d9414 | ||
|  | badbbdf155 | ||
|  | 2832792fed | ||
|  | efa45b9504 | ||
|  | 523749edf8 | ||
|  | 5a0499e8a7 | ||
|  | 258c8b5900 | ||
|  | 24b861f056 | ||
|  | 29f7f4d432 | ||
|  | 21080a1149 | ||
|  | 1d068fd09b | ||
|  | 92065813ef | ||
|  | 3e9ef6b8cb | ||
|  | c9451a5382 | ||
|  | 2be3b027db | ||
|  | e339d169c5 | ||
|  | 87001f86ee | ||
|  | 58484e8f37 | ||
|  | 94f68f9d55 | ||
|  | 3f6944de54 | ||
|  | 00cb4d26b3 | ||
|  | 774d8668bf | ||
|  | 8503589828 | ||
|  | 0f95ef2059 | ||
|  | efd812cf22 | ||
|  | 736e14c83e | ||
|  | 57f161e64c | ||
|  | 0897210969 | ||
|  | 7e58a44771 | ||
|  | e8f847d288 | ||
|  | a0f817108e | ||
|  | 3862fdb44c | ||
|  | 3e2d271566 | ||
|  | c97c5fa03a | ||
|  | fa63f7ffc3 | ||
|  | bfccadd356 | ||
|  | 5b3512f1df | ||
|  | 6e34e60f8a | ||
|  | a391d0f4ae | ||
|  | abc5c50b2e | ||
|  | 1fcb461c42 | ||
|  | abca38a548 | ||
|  | b4be2cd063 | ||
|  | 2d83eeb9c4 | ||
|  | 4d9e897cc3 | ||
|  | be664b5695 | ||
|  | c3751066b7 | ||
|  | 77feee8197 | ||
|  | f75af3b45e | ||
|  | 1471a35bb8 | ||
|  | 555c2a4377 | ||
|  | 16bef0dcd5 | ||
|  | cd464fc7de | ||
|  | 5b88207477 | ||
|  | df8c896193 | ||
|  | 5d3e1f7084 | ||
|  | 59f8eeb05a | ||
|  | 0b14850467 | ||
|  | f72e260915 | ||
|  | 640a84d456 | ||
|  | 04f6cb1750 | ||
|  | 87d688b7e3 | ||
|  | 26141a59b0 | ||
|  | a93f8103ad | ||
|  | 4a3d7c338a | ||
|  | 55ab305dbf | ||
|  | e48ba89721 | ||
|  | 9bb55b6b61 | ||
|  | c33308bdc5 | ||
|  | 44a33941bf | ||
|  | cc34cd2133 | ||
|  | 52c9f9e89e | ||
|  | 2363deb19c | ||
|  | 1c6af279b2 | ||
|  | 6e96275e1c | ||
|  | 9968342a11 | ||
|  | c248ecde48 | ||
|  | 370952ab33 | ||
|  | 154c89e041 | ||
|  | d45f1a793d | ||
|  | 9800951f18 | ||
|  | 17251997c2 | ||
|  | 5ab4cfee84 | ||
|  | a9eb0d02c6 | ||
|  | 1f8b69a5b0 | ||
|  | 8b83f58d7a | ||
|  | 9a91ae38c1 | ||
|  | ad57caed5e | ||
|  | 283ed8dbae | ||
|  | acb74185d5 | ||
|  | 7a5d16ccf8 | ||
|  | adca862166 | ||
|  | 1bdc718527 | ||
|  | 685a80f95b | ||
|  | 62eef8cb40 | ||
|  | 6ed3a49fe1 | ||
|  | 17702bfb89 | ||
|  | 292e02702a | ||
|  | 5a56d8a5d0 | ||
|  | 3da1d5700c | ||
|  | d437e06e15 | ||
|  | 6a3702a5c7 | ||
|  | 83a654540a | ||
|  | 678bd93c52 | ||
|  | 1bf0c1891a | ||
|  | b899a22c7d | ||
|  | 1bd6bbca8d | ||
|  | 14a2e470e4 | ||
|  | 41dcf1de42 | ||
|  | 0c65385c82 | ||
|  | 4aaf43150a | ||
|  | f05ee525cb | ||
|  | 1172c4fd97 | ||
|  | 15deef50c8 | ||
|  | 7728adfc5a | ||
|  | eff67f2250 | ||
|  | 64e3cf5de2 | ||
|  | 31a6d620e8 | ||
|  | dfd37e7dec | ||
|  | 8d8f244bf5 | ||
|  | 037b4802db | ||
|  | 51da21b844 | ||
|  | 0be19d8de7 | ||
|  | f26e4734b3 | ||
|  | f1b430338e | ||
|  | 2954373115 | ||
|  | 42d21ea3a9 | ||
|  | 7d761f145f | ||
|  | 27657fcde0 | ||
|  | 3ea2a4ccb8 | ||
|  | a1c60152d4 | ||
|  | 69da00fcfb | ||
|  | c4108efc5c | ||
|  | d576ff1172 | ||
|  | af54666c23 | ||
|  | e0b75b6e3d | ||
|  | 12c59ede09 | ||
|  | b4d0d4fff6 | ||
|  | a694844190 | ||
|  | 28f2d331a8 | ||
|  | dde9b73a22 | ||
|  | fb4bb21bf6 | ||
|  | 744c35b617 | ||
|  | 9ac21a4e71 | ||
|  | 94359e9c75 | ||
|  | 076fa55651 | ||
|  | d380595ad4 | ||
|  | d84b8700a3 | ||
|  | 80b281d9f1 | ||
|  | 69dc3cc4d8 | ||
|  | 1a9cea050e | ||
|  | 0833412df9 | ||
|  | 35e84ff1a8 | ||
|  | 8dd7c6ef23 | ||
|  | a26ab7086d | ||
|  | b2464598d0 | ||
|  | 6812a001d8 | ||
|  | 6c16754a6b | ||
|  | 75f9e3caeb | ||
|  | ad5afe21ee | ||
|  | 8a566cc1dd | ||
|  | 928aab13dc | ||
|  | f3fe711542 | ||
|  | db8d8d8404 | ||
|  | 6220ccb5d3 | ||
|  | 20843305dd | ||
|  | 8f6c0f6a8d | ||
|  | ede2df7e70 | ||
|  | d45231c1a8 | ||
|  | 772812b35f | ||
|  | f443fd44b5 | 
							
								
								
									
										6
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | [*] | ||||||
|  | charset = utf-8 | ||||||
|  | indent_style = tab | ||||||
|  | indent_size = 4 | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  |  | ||||||
| @@ -24,13 +24,13 @@ namespace Activity { | |||||||
| class Observer { | class Observer { | ||||||
| 	public: | 	public: | ||||||
| 		/// Announces to the receiver that there is an LED of name @c name. | 		/// Announces to the receiver that there is an LED of name @c name. | ||||||
| 		virtual void register_led(const std::string &name) = 0; | 		virtual void register_led(const std::string &name) {} | ||||||
|  |  | ||||||
| 		/// Announces to the receiver that there is a drive of name @c name. | 		/// Announces to the receiver that there is a drive of name @c name. | ||||||
| 		virtual void register_drive(const std::string &name) = 0; | 		virtual void register_drive(const std::string &name) {} | ||||||
|  |  | ||||||
| 		/// Informs the receiver of the new state of the LED with name @c name. | 		/// Informs the receiver of the new state of the LED with name @c name. | ||||||
| 		virtual void set_led_status(const std::string &name, bool lit) = 0; | 		virtual void set_led_status(const std::string &name, bool lit) {} | ||||||
|  |  | ||||||
| 		enum class DriveEvent { | 		enum class DriveEvent { | ||||||
| 			StepNormal, | 			StepNormal, | ||||||
| @@ -39,11 +39,10 @@ class Observer { | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		/// Informs the receiver that the named event just occurred for the drive with name @c name. | 		/// Informs the receiver that the named event just occurred for the drive with name @c name. | ||||||
| 		virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0; | 		virtual void announce_drive_event(const std::string &name, DriveEvent event) {} | ||||||
|  |  | ||||||
| 		/// Informs the receiver of the motor-on status of the drive with name @c name. | 		/// Informs the receiver of the motor-on status of the drive with name @c name. | ||||||
| 		virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0; | 		virtual void set_drive_motor_status(const std::string &name, bool is_on) {} | ||||||
|  |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| // |  | ||||||
| //  MultiConfigurationTarget.cpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 29/01/2018. |  | ||||||
| //  Copyright 2018 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "MultiConfigurationTarget.hpp" |  | ||||||
|  |  | ||||||
| using namespace Analyser::Dynamic; |  | ||||||
|  |  | ||||||
| MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { |  | ||||||
| 	for(const auto &machine: machines) { |  | ||||||
| 		ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); |  | ||||||
| 		if(configuration_target) targets_.push_back(configuration_target); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { |  | ||||||
| 	bool inserted = false; |  | ||||||
| 	for(const auto &target : targets_) { |  | ||||||
| 		inserted |= target->insert_media(media); |  | ||||||
| 	} |  | ||||||
| 	return inserted; |  | ||||||
| } |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| // |  | ||||||
| //  MultiConfigurationTarget.hpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 29/01/2018. |  | ||||||
| //  Copyright 2018 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef MultiConfigurationTarget_hpp |  | ||||||
| #define MultiConfigurationTarget_hpp |  | ||||||
|  |  | ||||||
| #include "../../../../Machines/ConfigurationTarget.hpp" |  | ||||||
| #include "../../../../Machines/DynamicMachine.hpp" |  | ||||||
|  |  | ||||||
| #include <memory> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace Analyser { |  | ||||||
| namespace Dynamic { |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	Provides a class that multiplexes the configuration target interface to multiple machines. |  | ||||||
|  |  | ||||||
| 	Makes a static internal copy of the list of machines; makes no guarantees about the |  | ||||||
| 	order of delivered messages. |  | ||||||
| */ |  | ||||||
| struct MultiConfigurationTarget: public ConfigurationTarget::Machine { |  | ||||||
| 	public: |  | ||||||
| 		MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); |  | ||||||
|  |  | ||||||
| 		// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override; |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override; |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		std::vector<ConfigurationTarget::Machine *> targets_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* MultiConfigurationTarget_hpp */ |  | ||||||
| @@ -25,14 +25,14 @@ class MultiJoystick: public Inputs::Joystick { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		std::vector<DigitalInput> get_inputs() override { | 		std::vector<Input> &get_inputs() override { | ||||||
| 			std::vector<DigitalInput> inputs; | 			if(inputs.empty()) { | ||||||
|  | 				for(const auto &joystick: joysticks_) { | ||||||
| 			for(const auto &joystick: joysticks_) { | 					std::vector<Input> joystick_inputs = joystick->get_inputs(); | ||||||
| 				std::vector<DigitalInput> joystick_inputs = joystick->get_inputs(); | 					for(const auto &input: joystick_inputs) { | ||||||
| 				for(const auto &input: joystick_inputs) { | 						if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { | ||||||
| 					if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { | 							inputs.push_back(input); | ||||||
| 						inputs.push_back(input); | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @@ -40,11 +40,18 @@ class MultiJoystick: public Inputs::Joystick { | |||||||
| 			return inputs; | 			return inputs; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | 		void set_input(const Input &digital_input, bool is_active) override { | ||||||
| 			for(const auto &joystick: joysticks_) { | 			for(const auto &joystick: joysticks_) { | ||||||
| 				joystick->set_digital_input(digital_input, is_active); | 				joystick->set_input(digital_input, is_active); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void set_input(const Input &digital_input, float value) override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->set_input(digital_input, value); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void reset_all_inputs() override { | 		void reset_all_inputs() override { | ||||||
| 			for(const auto &joystick: joysticks_) { | 			for(const auto &joystick: joysticks_) { | ||||||
| 				joystick->reset_all_inputs(); | 				joystick->reset_all_inputs(); | ||||||
| @@ -52,6 +59,7 @@ class MultiJoystick: public Inputs::Joystick { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		std::vector<Input> inputs; | ||||||
| 		std::vector<Inputs::Joystick *> joysticks_; | 		std::vector<Inputs::Joystick *> joysticks_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  MultiMediaTarget.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiMediaTarget.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		MediaTarget::Machine *media_target = machine->media_target(); | ||||||
|  | 		if(media_target) targets_.push_back(media_target); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) { | ||||||
|  | 	bool inserted = false; | ||||||
|  | 	for(const auto &target : targets_) { | ||||||
|  | 		inserted |= target->insert_media(media); | ||||||
|  | 	} | ||||||
|  | 	return inserted; | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  MultiMediaTarget.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiMediaTarget_hpp | ||||||
|  | #define MultiMediaTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/MediaTarget.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the media target interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | struct MultiMediaTarget: public MediaTarget::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard MediaTarget::Machine interface; see there for documentation. | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<MediaTarget::Machine *> targets_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiMediaTarget_hpp */ | ||||||
| @@ -15,10 +15,10 @@ using namespace Analyser::Dynamic; | |||||||
| MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | ||||||
| 	machines_(std::move(machines)), | 	machines_(std::move(machines)), | ||||||
| 	configurable_(machines_), | 	configurable_(machines_), | ||||||
| 	configuration_target_(machines_), |  | ||||||
| 	crt_machine_(machines_, machines_mutex_), | 	crt_machine_(machines_, machines_mutex_), | ||||||
| 	joystick_machine_(machines), | 	joystick_machine_(machines), | ||||||
| 	keyboard_machine_(machines_) { | 	keyboard_machine_(machines_), | ||||||
|  | 	media_target_(machines_) { | ||||||
| 	crt_machine_.set_delegate(this); | 	crt_machine_.set_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -26,11 +26,11 @@ Activity::Source *MultiMachine::activity_source() { | |||||||
| 	return nullptr; // TODO | 	return nullptr; // TODO | ||||||
| } | } | ||||||
|  |  | ||||||
| ConfigurationTarget::Machine *MultiMachine::configuration_target() { | MediaTarget::Machine *MultiMachine::media_target() { | ||||||
| 	if(has_picked_) { | 	if(has_picked_) { | ||||||
| 		return machines_.front()->configuration_target(); | 		return machines_.front()->media_target(); | ||||||
| 	} else { | 	} else { | ||||||
| 		return &configuration_target_; | 		return &media_target_; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,10 +12,10 @@ | |||||||
| #include "../../../Machines/DynamicMachine.hpp" | #include "../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
| #include "Implementation/MultiConfigurable.hpp" | #include "Implementation/MultiConfigurable.hpp" | ||||||
| #include "Implementation/MultiConfigurationTarget.hpp" |  | ||||||
| #include "Implementation/MultiCRTMachine.hpp" | #include "Implementation/MultiCRTMachine.hpp" | ||||||
| #include "Implementation/MultiJoystickMachine.hpp" | #include "Implementation/MultiJoystickMachine.hpp" | ||||||
| #include "Implementation/MultiKeyboardMachine.hpp" | #include "Implementation/MultiKeyboardMachine.hpp" | ||||||
|  | #include "Implementation/MultiMediaTarget.hpp" | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| @@ -51,11 +51,11 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | |||||||
| 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | ||||||
|  |  | ||||||
| 		Activity::Source *activity_source() override; | 		Activity::Source *activity_source() override; | ||||||
| 		ConfigurationTarget::Machine *configuration_target() override; | 		Configurable::Device *configurable_device() override; | ||||||
| 		CRTMachine::Machine *crt_machine() override; | 		CRTMachine::Machine *crt_machine() override; | ||||||
| 		JoystickMachine::Machine *joystick_machine() override; | 		JoystickMachine::Machine *joystick_machine() override; | ||||||
| 		KeyboardMachine::Machine *keyboard_machine() override; | 		KeyboardMachine::Machine *keyboard_machine() override; | ||||||
| 		Configurable::Device *configurable_device() override; | 		MediaTarget::Machine *media_target() override; | ||||||
| 		void *raw_pointer() override; | 		void *raw_pointer() override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| @@ -65,10 +65,10 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | |||||||
| 		std::mutex machines_mutex_; | 		std::mutex machines_mutex_; | ||||||
|  |  | ||||||
| 		MultiConfigurable configurable_; | 		MultiConfigurable configurable_; | ||||||
| 		MultiConfigurationTarget configuration_target_; |  | ||||||
| 		MultiCRTMachine crt_machine_; | 		MultiCRTMachine crt_machine_; | ||||||
| 		MultiJoystickMachine joystick_machine_; | 		MultiJoystickMachine joystick_machine_; | ||||||
| 		MultiKeyboardMachine keyboard_machine_; | 		MultiKeyboardMachine keyboard_machine_; | ||||||
|  | 		MultiMediaTarget media_target_; | ||||||
|  |  | ||||||
| 		void pick_first(); | 		void pick_first(); | ||||||
| 		bool has_picked_ = false; | 		bool has_picked_ = false; | ||||||
|   | |||||||
| @@ -69,13 +69,13 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me | |||||||
| 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | ||||||
|  |  | ||||||
| 	// if there are any tapes, attempt to get data from the first | 	// if there are any tapes, attempt to get data from the first | ||||||
| 	if(media.tapes.size() > 0) { | 	if(!media.tapes.empty()) { | ||||||
| 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | ||||||
| 		std::vector<File> files = GetFiles(tape); | 		std::vector<File> files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
|  |  | ||||||
| 		// continue if there are any files | 		// continue if there are any files | ||||||
| 		if(files.size()) { | 		if(!files.empty()) { | ||||||
| 			bool is_basic = true; | 			bool is_basic = true; | ||||||
|  |  | ||||||
| 			// protected files are always for *RUNning only | 			// protected files are always for *RUNning only | ||||||
| @@ -103,7 +103,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(media.disks.size() > 0) { | 	if(!media.disks.empty()) { | ||||||
| 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | ||||||
| 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | ||||||
| 		dfs_catalogue = GetDFSCatalogue(disk); | 		dfs_catalogue = GetDFSCatalogue(disk); | ||||||
|   | |||||||
| @@ -18,7 +18,9 @@ namespace AppleII { | |||||||
| struct Target: public ::Analyser::Static::Target { | struct Target: public ::Analyser::Static::Target { | ||||||
| 	enum class Model { | 	enum class Model { | ||||||
| 		II, | 		II, | ||||||
| 		IIplus | 		IIplus, | ||||||
|  | 		IIe, | ||||||
|  | 		EnhancedIIe | ||||||
| 	}; | 	}; | ||||||
| 	enum class DiskController { | 	enum class DiskController { | ||||||
| 		None, | 		None, | ||||||
| @@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target { | |||||||
| 		ThirteenSector | 		ThirteenSector | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Model model = Model::IIplus; | 	Model model = Model::IIe; | ||||||
| 	DiskController disk_controller = DiskController::None; | 	DiskController disk_controller = DiskController::None; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
|  |  | ||||||
| 		// only one mapped item is allowed | 		// only one mapped item is allowed | ||||||
| 		if(segments.size() != 1) continue; | 		if(segments.size() != 1) continue; | ||||||
|  |  | ||||||
| 		// which must be 8, 12, 16, 24 or 32 kb in size |  | ||||||
| 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 		const std::size_t data_size = segment.data.size(); | 		const std::size_t data_size = segment.data.size(); | ||||||
| 		const std::size_t overflow = data_size&8191; |  | ||||||
| 		if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue; |  | ||||||
| 		if(data_size < 8192) continue; |  | ||||||
|  |  | ||||||
| 		// the two bytes that will be first must be 0xaa and 0x55, either way around | 		// the two bytes that will be first must be 0xaa and 0x55, either way around | ||||||
| 		auto *start = &segment.data[0]; | 		auto *start = &segment.data[0]; | ||||||
| @@ -34,19 +29,24 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 		if(start[0] == start[1]) continue; | 		if(start[0] == start[1]) continue; | ||||||
|  |  | ||||||
| 		// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. | 		// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. | ||||||
| 		if(!overflow) { |  | ||||||
| 			coleco_cartridges.push_back(cartridge); | 		// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if | ||||||
|  | 		// this image is within a short distance of 32kb. | ||||||
|  | 		std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  |  | ||||||
|  | 		size_t target_size; | ||||||
|  | 		if(data_size >= 32*1024 && data_size < 32*1024 + 512) { | ||||||
|  | 			target_size = 32 * 1024; | ||||||
| 		} else { | 		} else { | ||||||
| 			// Size down to a multiple of 8kb and apply the start address. | 			target_size = data_size + ((8192 - (data_size & 8191)) & 8191); | ||||||
| 			std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; |  | ||||||
|  |  | ||||||
| 			std::vector<uint8_t> truncated_data; |  | ||||||
| 			std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191; |  | ||||||
| 			truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); |  | ||||||
| 			output_segments.emplace_back(0x8000, truncated_data); |  | ||||||
|  |  | ||||||
| 			coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> truncated_data; | ||||||
|  | 		truncated_data = segment.data; | ||||||
|  | 		truncated_data.resize(target_size); | ||||||
|  | 		output_segments.emplace_back(0x8000, truncated_data); | ||||||
|  |  | ||||||
|  | 		coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return coleco_cartridges; | 	return coleco_cartridges; | ||||||
|   | |||||||
| @@ -111,7 +111,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med | |||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::vector<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(tape_files.size()) { | 		if(!tape_files.empty()) { | ||||||
| 			for(const auto &file : tape_files) { | 			for(const auto &file : tape_files) { | ||||||
| 				if(file.data_type == File::MachineCode) { | 				if(file.data_type == File::MachineCode) { | ||||||
| 					std::vector<uint16_t> entry_points = {file.starting_address}; | 					std::vector<uint16_t> entry_points = {file.starting_address}; | ||||||
|   | |||||||
							
								
								
									
										81
									
								
								ClockReceiver/ClockDeferrer.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								ClockReceiver/ClockDeferrer.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | // | ||||||
|  | //  ClockDeferrer.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/08/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ClockDeferrer_h | ||||||
|  | #define ClockDeferrer_h | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A ClockDeferrer maintains a list of ordered actions and the times at which | ||||||
|  | 	they should happen, and divides a total execution period up into the portions | ||||||
|  | 	that occur between those actions, triggering each action when it is reached. | ||||||
|  | */ | ||||||
|  | template <typename TimeUnit> class ClockDeferrer { | ||||||
|  | 	public: | ||||||
|  | 		/// Constructs a ClockDeferrer that will call target(period) in between deferred actions. | ||||||
|  | 		ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Schedules @c action to occur in @c delay units of time. | ||||||
|  |  | ||||||
|  | 			Actions must be scheduled in the order they will occur. It is undefined behaviour | ||||||
|  | 			to schedule them out of order. | ||||||
|  | 		*/ | ||||||
|  | 		void defer(TimeUnit delay, const std::function<void(void)> &action) { | ||||||
|  | 			pending_actions_.emplace_back(delay, action); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Runs for @c length units of time. | ||||||
|  |  | ||||||
|  | 			The constructor-supplied target will be called with one or more periods that add up to @c length; | ||||||
|  | 			any scheduled actions will be called between periods. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(TimeUnit length) { | ||||||
|  | 			// If there are no pending actions, just run for the entire length. | ||||||
|  | 			// This should be the normal branch. | ||||||
|  | 			if(pending_actions_.empty()) { | ||||||
|  | 				target_(length); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Divide the time to run according to the pending actions. | ||||||
|  | 			while(length > TimeUnit(0)) { | ||||||
|  | 				TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay); | ||||||
|  | 				target_(next_period); | ||||||
|  | 				length -= next_period; | ||||||
|  |  | ||||||
|  | 				off_t performances = 0; | ||||||
|  | 				for(auto &action: pending_actions_) { | ||||||
|  | 					action.delay -= next_period; | ||||||
|  | 					if(!action.delay) { | ||||||
|  | 						action.action(); | ||||||
|  | 						++performances; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				if(performances) { | ||||||
|  | 					pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::function<void(TimeUnit)> target_; | ||||||
|  |  | ||||||
|  | 		// The list of deferred actions. | ||||||
|  | 		struct DeferredAction { | ||||||
|  | 			TimeUnit delay; | ||||||
|  | 			std::function<void(void)> action; | ||||||
|  |  | ||||||
|  | 			DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {} | ||||||
|  | 		}; | ||||||
|  | 		std::vector<DeferredAction> pending_actions_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* ClockDeferrer_h */ | ||||||
| @@ -52,79 +52,79 @@ | |||||||
| */ | */ | ||||||
| template <class T> class WrappedInt { | template <class T> class WrappedInt { | ||||||
| 	public: | 	public: | ||||||
| 		inline WrappedInt(int l) : length_(l) {} | 		constexpr WrappedInt(int l) : length_(l) {} | ||||||
| 		inline WrappedInt() : length_(0) {} | 		constexpr WrappedInt() : length_(0) {} | ||||||
|  |  | ||||||
| 		inline T &operator =(const T &rhs) { | 		T &operator =(const T &rhs) { | ||||||
| 			length_ = rhs.length_; | 			length_ = rhs.length_; | ||||||
| 			return *this; | 			return *this; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator +=(const T &rhs) { | 		T &operator +=(const T &rhs) { | ||||||
| 			length_ += rhs.length_; | 			length_ += rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator -=(const T &rhs) { | 		T &operator -=(const T &rhs) { | ||||||
| 			length_ -= rhs.length_; | 			length_ -= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator ++() { | 		T &operator ++() { | ||||||
| 			++ length_; | 			++ length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator ++(int) { | 		T &operator ++(int) { | ||||||
| 			length_ ++; | 			length_ ++; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator --() { | 		T &operator --() { | ||||||
| 			-- length_; | 			-- length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator --(int) { | 		T &operator --(int) { | ||||||
| 			length_ --; | 			length_ --; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator %=(const T &rhs) { | 		T &operator %=(const T &rhs) { | ||||||
| 			length_ %= rhs.length_; | 			length_ %= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator &=(const T &rhs) { | 		T &operator &=(const T &rhs) { | ||||||
| 			length_ &= rhs.length_; | 			length_ &= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | 		constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | ||||||
| 		inline T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | 		constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | ||||||
|  |  | ||||||
| 		inline T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | 		constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | ||||||
| 		inline T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | 		constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | ||||||
|  |  | ||||||
| 		inline T operator -() const						{	return T(- length_);				} | 		constexpr T operator -() const						{	return T(- length_);				} | ||||||
|  |  | ||||||
| 		inline bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | 		constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | ||||||
| 		inline bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} | 		constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} | ||||||
| 		inline bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} | 		constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} | ||||||
| 		inline bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} | 		constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} | ||||||
| 		inline bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} | 		constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} | ||||||
| 		inline bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} | 		constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} | ||||||
|  |  | ||||||
| 		inline bool operator !() const					{	return !length_;					} | 		constexpr bool operator !() const					{	return !length_;					} | ||||||
| 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | ||||||
|  |  | ||||||
| 		inline int as_int() const { return length_; } | 		constexpr int as_int() const { return length_; } | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | 			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. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline T divide(const T &divisor) { | 		T divide(const T &divisor) { | ||||||
| 			T result(length_ / divisor.length_); | 			T result(length_ / divisor.length_); | ||||||
| 			length_ %= divisor.length_; | 			length_ %= divisor.length_; | ||||||
| 			return result; | 			return result; | ||||||
| @@ -134,7 +134,7 @@ template <class T> class WrappedInt { | |||||||
| 			Flushes the value in @c this. The current value is returned, and the internal value | 			Flushes the value in @c this. The current value is returned, and the internal value | ||||||
| 			is reset to zero. | 			is reset to zero. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline T flush() { | 		T flush() { | ||||||
| 			T result(length_); | 			T result(length_); | ||||||
| 			length_ = 0; | 			length_ = 0; | ||||||
| 			return result; | 			return result; | ||||||
| @@ -150,34 +150,34 @@ template <class T> class WrappedInt { | |||||||
| /// Describes an integer number of whole cycles: pairs of clock signal transitions. | /// Describes an integer number of whole cycles: pairs of clock signal transitions. | ||||||
| class Cycles: public WrappedInt<Cycles> { | class Cycles: public WrappedInt<Cycles> { | ||||||
| 	public: | 	public: | ||||||
| 		inline Cycles(int l) : WrappedInt<Cycles>(l) {} | 		constexpr Cycles(int l) : WrappedInt<Cycles>(l) {} | ||||||
| 		inline Cycles() : WrappedInt<Cycles>() {} | 		constexpr Cycles() : WrappedInt<Cycles>() {} | ||||||
| 		inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} | 		constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Describes an integer number of half cycles: single clock signal transitions. | /// Describes an integer number of half cycles: single clock signal transitions. | ||||||
| class HalfCycles: public WrappedInt<HalfCycles> { | class HalfCycles: public WrappedInt<HalfCycles> { | ||||||
| 	public: | 	public: | ||||||
| 		inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | 		constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | ||||||
| 		inline HalfCycles() : WrappedInt<HalfCycles>() {} | 		constexpr HalfCycles() : WrappedInt<HalfCycles>() {} | ||||||
|  |  | ||||||
| 		inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | 		constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | ||||||
| 		inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | 		constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | ||||||
|  |  | ||||||
| 		/// @returns The number of whole cycles completely covered by this span of half cycles. | 		/// @returns The number of whole cycles completely covered by this span of half cycles. | ||||||
| 		inline Cycles cycles() { | 		constexpr Cycles cycles() const { | ||||||
| 			return Cycles(length_ >> 1); | 			return Cycles(length_ >> 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. | 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. | ||||||
| 		inline Cycles flush_cycles() { | 		Cycles flush_cycles() { | ||||||
| 			Cycles result(length_ >> 1); | 			Cycles result(length_ >> 1); | ||||||
| 			length_ &= 1; | 			length_ &= 1; | ||||||
| 			return result; | 			return result; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. | 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. | ||||||
| 		inline HalfCycles flush() { | 		HalfCycles flush() { | ||||||
| 			HalfCycles result(length_); | 			HalfCycles result(length_); | ||||||
| 			length_ = 0; | 			length_ = 0; | ||||||
| 			return result; | 			return result; | ||||||
| @@ -187,7 +187,7 @@ class HalfCycles: public WrappedInt<HalfCycles> { | |||||||
| 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | 			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. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline Cycles divide_cycles(const Cycles &divisor) { | 		Cycles divide_cycles(const Cycles &divisor) { | ||||||
| 			HalfCycles half_divisor = HalfCycles(divisor); | 			HalfCycles half_divisor = HalfCycles(divisor); | ||||||
| 			Cycles result(length_ / half_divisor.length_); | 			Cycles result(length_ / half_divisor.length_); | ||||||
| 			length_ %= half_divisor.length_; | 			length_ %= half_divisor.length_; | ||||||
| @@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T { | |||||||
| 	public: | 	public: | ||||||
| 		using T::T; | 		using T::T; | ||||||
|  |  | ||||||
| 		using T::run_for; |  | ||||||
| 		inline void run_for(const HalfCycles half_cycles) { | 		inline void run_for(const HalfCycles half_cycles) { | ||||||
| 			half_cycles_ += half_cycles; | 			half_cycles_ += half_cycles; | ||||||
| 			T::run_for(half_cycles_.flush_cycles()); | 			T::run_for(half_cycles_.flush_cycles()); | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								ClockReceiver/ClockingHintSource.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								ClockReceiver/ClockingHintSource.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | // | ||||||
|  | //  ClockingHintSource.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/08/2017. | ||||||
|  | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ClockingHintSource_hpp | ||||||
|  | #define ClockingHintSource_hpp | ||||||
|  |  | ||||||
|  | namespace ClockingHint { | ||||||
|  |  | ||||||
|  | enum class Preference { | ||||||
|  | 	/// The component doesn't currently require a clock signal. | ||||||
|  | 	None, | ||||||
|  | 	/// The component can be clocked only immediate prior to (explicit) accesses. | ||||||
|  | 	JustInTime, | ||||||
|  | 	/// The component require real-time clocking. | ||||||
|  | 	RealTime | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Source; | ||||||
|  |  | ||||||
|  | struct Observer { | ||||||
|  | 	/// Called to inform an observer that the component @c component has changed its clocking requirements. | ||||||
|  | 	virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	An clocking hint source is any component that can provide hints as to the type of | ||||||
|  | 	clocking required for accurate emulation. A disk controller is an archetypal example. | ||||||
|  |  | ||||||
|  | 	Types of clocking are: | ||||||
|  |  | ||||||
|  | 		- none: | ||||||
|  | 			a component that acts and reacts to direct contact but does not have a state that autonomously evolves. | ||||||
|  | 			E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command. | ||||||
|  |  | ||||||
|  | 		- just-in-time: | ||||||
|  | 			a component that has an evolving state but can receive clock updates only immediately before a | ||||||
|  | 			direct contact. This is possibly the most common kind of component. | ||||||
|  |  | ||||||
|  | 		- real-time: | ||||||
|  | 			a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example | ||||||
|  | 			so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of | ||||||
|  | 			the machine rather than waiting to be called upon, or because there's some other non-obvious relationship | ||||||
|  | 			at play. | ||||||
|  |  | ||||||
|  | 	A clocking hint source can signal changes in preferred clocking to an observer. | ||||||
|  |  | ||||||
|  | 	This is intended to allow for performance improvements to machines with components that can be messaged selectively. | ||||||
|  | 	The observer callout is virtual so the intended use case is that a machine holds a component that might go through | ||||||
|  | 	periods of different clocking requirements. | ||||||
|  |  | ||||||
|  | 	Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that | ||||||
|  | 	the saved or deferred ::run_fors add up to a substantial amount. | ||||||
|  |  | ||||||
|  | 	The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency. | ||||||
|  | */ | ||||||
|  | class Source { | ||||||
|  | 	public: | ||||||
|  | 		/// Registers @c observer as the new clocking observer. | ||||||
|  | 		void set_clocking_hint_observer(Observer *observer) { | ||||||
|  | 			observer_ = observer; | ||||||
|  | 			update_clocking_observer(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// @returns the current preferred clocking strategy. | ||||||
|  | 		virtual Preference preferred_clocking() = 0; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Observer *observer_ = nullptr; | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		/*! | ||||||
|  | 			Provided for subclasses; call this whenever the clocking preference might have changed. | ||||||
|  | 			This will notify the observer if there is one. | ||||||
|  | 		*/ | ||||||
|  | 		void update_clocking_observer() { | ||||||
|  | 			if(!observer_) return; | ||||||
|  | 			observer_->set_component_prefers_clocking(this, preferred_clocking()); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ClockingHintSource_h */ | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| // |  | ||||||
| //  Sleeper.h |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 20/08/2017. |  | ||||||
| //  Copyright 2017 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef Sleeper_hpp |  | ||||||
| #define Sleeper_hpp |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing |  | ||||||
| 	any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example. |  | ||||||
|  |  | ||||||
| 	A sleeper will signal sleeps and wakes to an observer. |  | ||||||
|  |  | ||||||
| 	This is intended to allow for performance improvements to machines with components that can sleep. The observer |  | ||||||
| 	callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions |  | ||||||
| 	into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that |  | ||||||
| 	the saved ::run_fors add up to a substantial amount. |  | ||||||
|  |  | ||||||
| 	By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint, |  | ||||||
| 	not a command. |  | ||||||
| */ |  | ||||||
| class Sleeper { |  | ||||||
| 	public: |  | ||||||
| 		Sleeper() : sleep_observer_(nullptr) {} |  | ||||||
|  |  | ||||||
| 		class SleepObserver { |  | ||||||
| 			public: |  | ||||||
| 				/// Called to inform an observer that the component @c component has either gone to sleep or become awake. |  | ||||||
| 				virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0; |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		/// Registers @c observer as the new sleep observer; |  | ||||||
| 		void set_sleep_observer(SleepObserver *observer) { |  | ||||||
| 			sleep_observer_ = observer; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/// @returns @c true if the component is currently sleeping; @c false otherwise. |  | ||||||
| 		virtual bool is_sleeping() = 0; |  | ||||||
|  |  | ||||||
| 	protected: |  | ||||||
| 		/// Provided for subclasses; send sleep announcements to the sleep_observer_. |  | ||||||
| 		SleepObserver *sleep_observer_; |  | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified, |  | ||||||
| 			if one exists. |  | ||||||
|  |  | ||||||
| 			@c is_sleeping will be called only if there is an observer. |  | ||||||
| 		*/ |  | ||||||
| 		void update_sleep_observer() { |  | ||||||
| 			if(!sleep_observer_) return; |  | ||||||
| 			sleep_observer_->set_component_is_sleeping(this, is_sleeping()); |  | ||||||
| 		} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif /* Sleeper_h */ |  | ||||||
| @@ -7,7 +7,9 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "1770.hpp" | #include "1770.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" | #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| using namespace WD; | using namespace WD; | ||||||
|  |  | ||||||
| @@ -25,10 +27,10 @@ void WD1770::set_register(int address, uint8_t value) { | |||||||
| 			if((value&0xf0) == 0xd0) { | 			if((value&0xf0) == 0xd0) { | ||||||
| 				if(value == 0xd0) { | 				if(value == 0xd0) { | ||||||
| 					// Force interrupt **immediately**. | 					// Force interrupt **immediately**. | ||||||
| 					printf("Force interrupt immediately\n"); | 					LOG("Force interrupt immediately"); | ||||||
| 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | ||||||
| 				} else { | 				} else { | ||||||
| 					printf("!!!TODO: force interrupt!!!\n"); | 					ERROR("!!!TODO: force interrupt!!!"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.type = Status::One; | 						status.type = Status::One; | ||||||
| 					}); | 					}); | ||||||
| @@ -193,7 +195,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 	// Wait for a new command, branch to the appropriate handler. | 	// Wait for a new command, branch to the appropriate handler. | ||||||
| 	case 0: | 	case 0: | ||||||
| 	wait_for_command: | 	wait_for_command: | ||||||
| 		printf("Idle...\n"); | 		LOG("Idle..."); | ||||||
| 		set_data_mode(DataMode::Scanning); | 		set_data_mode(DataMode::Scanning); | ||||||
| 		index_hole_count_ = 0; | 		index_hole_count_ = 0; | ||||||
|  |  | ||||||
| @@ -209,7 +211,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			status.interrupt_request = false; | 			status.interrupt_request = false; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		printf("Starting %02x\n", command_); | 		LOG("Starting " << std::hex << command_ << std::endl); | ||||||
|  |  | ||||||
| 		if(!(command_ & 0x80)) goto begin_type_1; | 		if(!(command_ & 0x80)) goto begin_type_1; | ||||||
| 		if(!(command_ & 0x40)) goto begin_type_2; | 		if(!(command_ & 0x40)) goto begin_type_2; | ||||||
| @@ -327,7 +329,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(header_[0] == track_) { | 			if(header_[0] == track_) { | ||||||
| 				printf("Reached track %d\n", track_); | 				LOG("Reached track " << std::dec << track_); | ||||||
| 				update_status([] (Status &status) { | 				update_status([] (Status &status) { | ||||||
| 					status.crc_error = false; | 					status.crc_error = false; | ||||||
| 				}); | 				}); | ||||||
| @@ -396,20 +398,20 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		READ_ID(); | 		READ_ID(); | ||||||
|  |  | ||||||
| 		if(index_hole_count_ == 5) { | 		if(index_hole_count_ == 5) { | ||||||
| 			printf("Failed to find sector %d\n", sector_); | 			LOG("Failed to find sector " << std::dec << sector_); | ||||||
| 			update_status([] (Status &status) { | 			update_status([] (Status &status) { | ||||||
| 				status.record_not_found = true; | 				status.record_not_found = true; | ||||||
| 			}); | 			}); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		if(distance_into_section_ == 7) { | 		if(distance_into_section_ == 7) { | ||||||
| 			printf("Considering %d/%d\n", header_[0], header_[2]); | 			LOG("Considering " << std::dec << header_[0] << "/" << header_[2]); | ||||||
| 			set_data_mode(DataMode::Scanning); | 			set_data_mode(DataMode::Scanning); | ||||||
| 			if(		header_[0] == track_ && header_[2] == sector_ && | 			if(		header_[0] == track_ && header_[2] == sector_ && | ||||||
| 					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { | 					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { | ||||||
| 				printf("Found %d/%d\n", header_[0], header_[2]); | 				LOG("Found " << std::dec << header_[0] << "/" << header_[2]); | ||||||
| 				if(get_crc_generator().get_value()) { | 				if(get_crc_generator().get_value()) { | ||||||
| 					printf("CRC error; back to searching\n"); | 					LOG("CRC error; back to searching"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.crc_error = true; | 						status.crc_error = true; | ||||||
| 					}); | 					}); | ||||||
| @@ -465,7 +467,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		distance_into_section_++; | 		distance_into_section_++; | ||||||
| 		if(distance_into_section_ == 2) { | 		if(distance_into_section_ == 2) { | ||||||
| 			if(get_crc_generator().get_value()) { | 			if(get_crc_generator().get_value()) { | ||||||
| 				printf("CRC error; terminating\n"); | 				LOG("CRC error; terminating"); | ||||||
| 				update_status([this] (Status &status) { | 				update_status([this] (Status &status) { | ||||||
| 					status.crc_error = true; | 					status.crc_error = true; | ||||||
| 				}); | 				}); | ||||||
| @@ -476,7 +478,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 				sector_++; | 				sector_++; | ||||||
| 				goto test_type2_write_protection; | 				goto test_type2_write_protection; | ||||||
| 			} | 			} | ||||||
| 			printf("Finished reading sector %d\n", sector_); | 			LOG("Finished reading sector " << std::dec << sector_); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		goto type2_check_crc; | 		goto type2_check_crc; | ||||||
| @@ -558,7 +560,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			sector_++; | 			sector_++; | ||||||
| 			goto test_type2_write_protection; | 			goto test_type2_write_protection; | ||||||
| 		} | 		} | ||||||
| 		printf("Wrote sector %d\n", sector_); | 		LOG("Wrote sector " << std::dec << sector_); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
|  |  | ||||||
| 		/// Runs the controller for @c number_of_cycles cycles. | 		/// Runs the controller for @c number_of_cycles cycles. | ||||||
| 		void run_for(const Cycles cycles); | 		void run_for(const Cycles cycles); | ||||||
| 		using Storage::Disk::Controller::run_for; |  | ||||||
|  |  | ||||||
| 		enum Flag: uint8_t { | 		enum Flag: uint8_t { | ||||||
| 			NotReady		= 0x80, | 			NotReady		= 0x80, | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ template <class BusHandler> class MOS6560 { | |||||||
| 				speaker_(audio_generator_) | 				speaker_(audio_generator_) | ||||||
| 		{ | 		{ | ||||||
| 			crt_->set_svideo_sampling_function( | 			crt_->set_svideo_sampling_function( | ||||||
| 				"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | 				"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||||
| 				"{" | 				"{" | ||||||
| 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | ||||||
|  |  | ||||||
| @@ -125,10 +125,10 @@ template <class BusHandler> class MOS6560 { | |||||||
| 				19,		86,		123,	59, | 				19,		86,		123,	59, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t ntsc_chrominances[16] = { | 			const uint8_t ntsc_chrominances[16] = { | ||||||
| 				255,	255,	7,		71, | 				255,	255,	121,	57, | ||||||
| 				25,		86,		48,		112, | 				103,	42,		80,		16, | ||||||
| 				0,		119,	7,		71, | 				0,		9,		121,	57, | ||||||
| 				25,		86,		48,		112, | 				103,	42,		80,		16, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t *chrominances; | 			const uint8_t *chrominances; | ||||||
| 			Outputs::CRT::DisplayType display_type; | 			Outputs::CRT::DisplayType display_type; | ||||||
|   | |||||||
| @@ -7,9 +7,8 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "i8272.hpp" | #include "i8272.hpp" | ||||||
| //#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdio> | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| using namespace Intel::i8272; | using namespace Intel::i8272; | ||||||
|  |  | ||||||
| @@ -83,8 +82,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : | |||||||
| 	posit_event(static_cast<int>(Event8272::CommandByte)); | 	posit_event(static_cast<int>(Event8272::CommandByte)); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool i8272::is_sleeping() { | ClockingHint::Preference i8272::preferred_clocking() { | ||||||
| 	return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); | 	const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking(); | ||||||
|  | 	if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking; | ||||||
|  | 	return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::run_for(Cycles cycles) { | void i8272::run_for(Cycles cycles) { | ||||||
| @@ -113,7 +114,7 @@ void i8272::run_for(Cycles cycles) { | |||||||
| 				while(steps--) { | 				while(steps--) { | ||||||
| 					// Perform a step. | 					// Perform a step. | ||||||
| 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | ||||||
| 					printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); | 					LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position)); | ||||||
| 					select_drive(c); | 					select_drive(c); | ||||||
| 					get_drive().step(Storage::Disk::HeadPosition(direction)); | 					get_drive().step(Storage::Disk::HeadPosition(direction)); | ||||||
| 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | ||||||
| @@ -159,7 +160,7 @@ void i8272::run_for(Cycles cycles) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | ||||||
| 	if(is_sleeping_) update_sleep_observer(); | 	if(is_sleeping_) update_clocking_observer(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::set_register(int address, uint8_t value) { | void i8272::set_register(int address, uint8_t value) { | ||||||
| @@ -198,7 +199,7 @@ uint8_t i8272::get_register(int address) { | |||||||
|  |  | ||||||
| #define MS_TO_CYCLES(x)			x * 8000 | #define MS_TO_CYCLES(x)			x * 8000 | ||||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | ||||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; | #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return; | ||||||
|  |  | ||||||
| #define PASTE(x, y) x##y | #define PASTE(x, y) x##y | ||||||
| #define CONCAT(x, y) PASTE(x, y) | #define CONCAT(x, y) PASTE(x, y) | ||||||
| @@ -257,7 +258,7 @@ uint8_t i8272::get_register(int address) { | |||||||
| 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | ||||||
| 			head_timers_running_++;	\ | 			head_timers_running_++;	\ | ||||||
| 			is_sleeping_ = false;	\ | 			is_sleeping_ = false;	\ | ||||||
| 			update_sleep_observer();	\ | 			update_clocking_observer();	\ | ||||||
| 		}	\ | 		}	\ | ||||||
| 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | ||||||
| 	} | 	} | ||||||
| @@ -384,17 +385,17 @@ void i8272::posit_event(int event_type) { | |||||||
| 		// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the | 		// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the | ||||||
| 		// values in the internal registers. | 		// values in the internal registers. | ||||||
| 			index_hole_limit_ = 2; | 			index_hole_limit_ = 2; | ||||||
| //			printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_); | //			LOG("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_); | ||||||
| 		find_next_sector: | 		find_next_sector: | ||||||
| 			FIND_HEADER(); | 			FIND_HEADER(); | ||||||
| 			if(!index_hole_limit_) { | 			if(!index_hole_limit_) { | ||||||
| 				// Two index holes have passed wihout finding the header sought. | 				// Two index holes have passed wihout finding the header sought. | ||||||
| //				printf("Not found\n"); | //				LOG("Not found"); | ||||||
| 				SetNoData(); | 				SetNoData(); | ||||||
| 				goto abort; | 				goto abort; | ||||||
| 			} | 			} | ||||||
| 			index_hole_count_ = 0; | 			index_hole_count_ = 0; | ||||||
| //			printf("Header\n"); | //			LOG("Header"); | ||||||
| 			READ_HEADER(); | 			READ_HEADER(); | ||||||
| 			if(index_hole_count_) { | 			if(index_hole_count_) { | ||||||
| 				// This implies an index hole was sighted within the header. Error out. | 				// This implies an index hole was sighted within the header. Error out. | ||||||
| @@ -405,11 +406,11 @@ void i8272::posit_event(int event_type) { | |||||||
| 				// This implies a CRC error in the header; mark as such but continue. | 				// This implies a CRC error in the header; mark as such but continue. | ||||||
| 				SetDataError(); | 				SetDataError(); | ||||||
| 			} | 			} | ||||||
| //			printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); | //			LOG("Considering << PADHEX(2) << header_[0] << " " << header_[1] << " " << header_[2] << " " << header_[3] << " [" << get_crc_generator().get_value() << "]"); | ||||||
| 			if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector; | 			if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector; | ||||||
|  |  | ||||||
| 			// Branch to whatever is supposed to happen next | 			// Branch to whatever is supposed to happen next | ||||||
| //			printf("Proceeding\n"); | //			LOG("Proceeding"); | ||||||
| 			switch(command_[0] & 0x1f) { | 			switch(command_[0] & 0x1f) { | ||||||
| 				case CommandReadData: | 				case CommandReadData: | ||||||
| 				case CommandReadDeletedData: | 				case CommandReadDeletedData: | ||||||
| @@ -423,7 +424,13 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs the read data or read deleted data command. | 	// Performs the read data or read deleted data command. | ||||||
| 	read_data: | 	read_data: | ||||||
| 			printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | 			LOG(PADHEX(2) << "Read [deleted] data [" | ||||||
|  | 				<< static_cast<int>(command_[2]) << " " | ||||||
|  | 				<< static_cast<int>(command_[3]) << " " | ||||||
|  | 				<< static_cast<int>(command_[4]) << " " | ||||||
|  | 				<< static_cast<int>(command_[5]) << " ... " | ||||||
|  | 				<< static_cast<int>(command_[6]) << " " | ||||||
|  | 				<< static_cast<int>(command_[8]) << "]"); | ||||||
| 		read_next_data: | 		read_next_data: | ||||||
| 			goto read_write_find_header; | 			goto read_write_find_header; | ||||||
|  |  | ||||||
| @@ -507,7 +514,13 @@ void i8272::posit_event(int event_type) { | |||||||
| 			goto post_st012chrn; | 			goto post_st012chrn; | ||||||
|  |  | ||||||
| 	write_data: | 	write_data: | ||||||
| 			printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | 			LOG(PADHEX(2) << "Write [deleted] data [" | ||||||
|  | 				<< static_cast<int>(command_[2]) << " " | ||||||
|  | 				<< static_cast<int>(command_[3]) << " " | ||||||
|  | 				<< static_cast<int>(command_[4]) << " " | ||||||
|  | 				<< static_cast<int>(command_[5]) << " ... " | ||||||
|  | 				<< static_cast<int>(command_[6]) << " " | ||||||
|  | 				<< static_cast<int>(command_[8]) << "]"); | ||||||
|  |  | ||||||
| 			if(get_drive().get_is_read_only()) { | 			if(get_drive().get_is_read_only()) { | ||||||
| 				SetNotWriteable(); | 				SetNotWriteable(); | ||||||
| @@ -542,7 +555,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 				goto write_loop; | 				goto write_loop; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			printf("Wrote %d bytes\n", distance_into_section_); | 			LOG("Wrote " << PADDEC(0) << distance_into_section_ << " bytes"); | ||||||
| 			write_crc(); | 			write_crc(); | ||||||
| 			expects_input_ = false; | 			expects_input_ = false; | ||||||
| 			WAIT_FOR_EVENT(Event::DataWritten); | 			WAIT_FOR_EVENT(Event::DataWritten); | ||||||
| @@ -558,7 +571,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// Performs the read ID command. | 	// Performs the read ID command. | ||||||
| 	read_id: | 	read_id: | ||||||
| 		// Establishes the drive and head being addressed, and whether in double density mode. | 		// Establishes the drive and head being addressed, and whether in double density mode. | ||||||
| 			printf("Read ID [%02x %02x]\n", command_[0], command_[1]); | 			LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]"); | ||||||
|  |  | ||||||
| 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | ||||||
| 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | ||||||
| @@ -580,7 +593,11 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs read track. | 	// Performs read track. | ||||||
| 	read_track: | 	read_track: | ||||||
| 			printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]); | 			LOG(PADHEX(2) << "Read track [" | ||||||
|  | 				<< static_cast<int>(command_[2]) << " " | ||||||
|  | 				<< static_cast<int>(command_[3]) << " " | ||||||
|  | 				<< static_cast<int>(command_[4]) << " " | ||||||
|  | 				<< static_cast<int>(command_[5]) << "]"); | ||||||
|  |  | ||||||
| 			// Wait for the index hole. | 			// Wait for the index hole. | ||||||
| 			WAIT_FOR_EVENT(Event::IndexHole); | 			WAIT_FOR_EVENT(Event::IndexHole); | ||||||
| @@ -621,7 +638,7 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs format [/write] track. | 	// Performs format [/write] track. | ||||||
| 	format_track: | 	format_track: | ||||||
| 			printf("Format track\n"); | 			LOG("Format track"); | ||||||
| 			if(get_drive().get_is_read_only()) { | 			if(get_drive().get_is_read_only()) { | ||||||
| 				SetNotWriteable(); | 				SetNotWriteable(); | ||||||
| 				goto abort; | 				goto abort; | ||||||
| @@ -665,7 +682,12 @@ void i8272::posit_event(int event_type) { | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); | 			LOG(PADHEX(2) << "W:" | ||||||
|  | 				<< static_cast<int>(header_[0]) << " " | ||||||
|  | 				<< static_cast<int>(header_[1]) << " " | ||||||
|  | 				<< static_cast<int>(header_[2]) << " " | ||||||
|  | 				<< static_cast<int>(header_[3]) << ", " | ||||||
|  | 				<< get_crc_generator().get_value()); | ||||||
| 			write_crc(); | 			write_crc(); | ||||||
|  |  | ||||||
| 			// Write the sector body. | 			// Write the sector body. | ||||||
| @@ -697,15 +719,15 @@ void i8272::posit_event(int event_type) { | |||||||
| 		goto post_st012chrn; | 		goto post_st012chrn; | ||||||
|  |  | ||||||
| 	scan_low: | 	scan_low: | ||||||
| 		printf("Scan low unimplemented!!\n"); | 		ERROR("Scan low unimplemented!!"); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
| 	scan_low_or_equal: | 	scan_low_or_equal: | ||||||
| 		printf("Scan low or equal unimplemented!!\n"); | 		ERROR("Scan low or equal unimplemented!!"); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
| 	scan_high_or_equal: | 	scan_high_or_equal: | ||||||
| 		printf("Scan high or equal unimplemented!!\n"); | 		ERROR("Scan high or equal unimplemented!!"); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
| 	// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work | 	// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work | ||||||
| @@ -720,7 +742,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 				if(drives_[drive].phase != Drive::Seeking) { | 				if(drives_[drive].phase != Drive::Seeking) { | ||||||
| 					drives_seeking_++; | 					drives_seeking_++; | ||||||
| 					is_sleeping_ = false; | 					is_sleeping_ = false; | ||||||
| 					update_sleep_observer(); | 					update_clocking_observer(); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | ||||||
| @@ -736,11 +758,11 @@ void i8272::posit_event(int event_type) { | |||||||
| 				// up in run_for understands to mean 'keep going until track 0 is active'). | 				// up in run_for understands to mean 'keep going until track 0 is active'). | ||||||
| 				if(command_.size() > 2) { | 				if(command_.size() > 2) { | ||||||
| 					drives_[drive].target_head_position = command_[2]; | 					drives_[drive].target_head_position = command_[2]; | ||||||
| 					printf("Seek to %02x\n", command_[2]); | 					LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2])); | ||||||
| 				} else { | 				} else { | ||||||
| 					drives_[drive].target_head_position = -1; | 					drives_[drive].target_head_position = -1; | ||||||
| 					drives_[drive].head_position = 0; | 					drives_[drive].head_position = 0; | ||||||
| 					printf("Recalibrate\n"); | 					LOG("Recalibrate"); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Check whether any steps are even needed; if not then mark as completed already. | 				// Check whether any steps are even needed; if not then mark as completed already. | ||||||
| @@ -753,7 +775,7 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs sense interrupt status. | 	// Performs sense interrupt status. | ||||||
| 	sense_interrupt_status: | 	sense_interrupt_status: | ||||||
| 			printf("Sense interrupt status\n"); | 			LOG("Sense interrupt status"); | ||||||
| 			{ | 			{ | ||||||
| 				// Find the first drive that is in the CompletedSeeking state. | 				// Find the first drive that is in the CompletedSeeking state. | ||||||
| 				int found_drive = -1; | 				int found_drive = -1; | ||||||
| @@ -781,7 +803,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// Performs specify. | 	// Performs specify. | ||||||
| 	specify: | 	specify: | ||||||
| 		// Just store the values, and terminate the command. | 		// Just store the values, and terminate the command. | ||||||
| 			printf("Specify\n"); | 			LOG("Specify"); | ||||||
| 			step_rate_time_ = 16 - (command_[1] >> 4);			// i.e. 1 to 16ms | 			step_rate_time_ = 16 - (command_[1] >> 4);			// i.e. 1 to 16ms | ||||||
| 			head_unload_time_ = (command_[1] & 0x0f) << 4;		// i.e. 16 to 240ms | 			head_unload_time_ = (command_[1] & 0x0f) << 4;		// i.e. 16 to 240ms | ||||||
| 			head_load_time_ = command_[2] & ~1;					// i.e. 2 to 254 ms in increments of 2ms | 			head_load_time_ = command_[2] & ~1;					// i.e. 2 to 254 ms in increments of 2ms | ||||||
| @@ -792,7 +814,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
|  |  | ||||||
| 	sense_drive_status: | 	sense_drive_status: | ||||||
| 			printf("Sense drive status\n"); | 			LOG("Sense drive status"); | ||||||
| 			{ | 			{ | ||||||
| 				int drive = command_[1] & 3; | 				int drive = command_[1] & 3; | ||||||
| 				select_drive(drive); | 				select_drive(drive); | ||||||
| @@ -831,11 +853,11 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the | 	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the | ||||||
| 	// last thing in it will be returned first. | 	// last thing in it will be returned first. | ||||||
| 	post_result: | 	post_result: | ||||||
| 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | 			LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; "); | ||||||
| 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | ||||||
| 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | 				LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c])); | ||||||
| 			} | 			} | ||||||
| 			printf("\n"); | 			LOGNBR(std::endl); | ||||||
|  |  | ||||||
| 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | ||||||
| 			is_executing_ = false; | 			is_executing_ = false; | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 		void set_dma_acknowledge(bool dack); | 		void set_dma_acknowledge(bool dack); | ||||||
| 		void set_terminal_count(bool tc); | 		void set_terminal_count(bool tc); | ||||||
|  |  | ||||||
| 		bool is_sleeping(); | 		ClockingHint::Preference preferred_clocking() override; | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		virtual void select_drive(int number) = 0; | 		virtual void select_drive(int number) = 0; | ||||||
| @@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 			ResultEmpty = (1 << 5), | 			ResultEmpty = (1 << 5), | ||||||
| 			NoLongerReady = (1 << 6) | 			NoLongerReady = (1 << 6) | ||||||
| 		}; | 		}; | ||||||
| 		void posit_event(int type); | 		void posit_event(int type) override; | ||||||
| 		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); | 		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); | ||||||
| 		int resume_point_ = 0; | 		int resume_point_ = 0; | ||||||
| 		bool is_access_command_ = false; | 		bool is_access_command_ = false; | ||||||
|   | |||||||
| @@ -496,17 +496,25 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 								} | 								} | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// Paint sprites and check for collisions. | 							// Paint sprites and check for collisions, but only if at least one sprite is active | ||||||
|  | 							// on this line. | ||||||
| 							if(sprite_set.active_sprite_slot) { | 							if(sprite_set.active_sprite_slot) { | ||||||
| 								int sprite_pixels_left = pixels_left; | 								int sprite_pixels_left = pixels_left; | ||||||
| 								const int shift_advance = sprites_magnified_ ? 1 : 2; | 								const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  |  | ||||||
| 								const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | 								static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||||
| 								const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | 								static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||||
|  |  | ||||||
| 								while(sprite_pixels_left--) { | 								while(sprite_pixels_left--) { | ||||||
|  | 									// sprite_colour is the colour that's going to reach the display after sprite logic has been | ||||||
|  | 									// applied; by default assume that nothing is going to be drawn. | ||||||
| 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | ||||||
|  |  | ||||||
|  | 									// The sprite_mask is used to keep track of whether two sprites have both sought to output | ||||||
|  | 									// a pixel at the same location, and to feed that into the status register's sprite | ||||||
|  | 									// collision bit. | ||||||
| 									int sprite_mask = 0; | 									int sprite_mask = 0; | ||||||
|  |  | ||||||
| 									int c = sprite_set.active_sprite_slot; | 									int c = sprite_set.active_sprite_slot; | ||||||
| 									while(c--) { | 									while(c--) { | ||||||
| 										SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | 										SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
| @@ -517,15 +525,24 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 										} else if(sprite.shift_position < 32) { | 										} else if(sprite.shift_position < 32) { | ||||||
| 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | ||||||
| 											mask = (mask >> 7) & 1; | 											mask = (mask >> 7) & 1; | ||||||
| 											status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; |  | ||||||
| 											sprite_mask |= mask; |  | ||||||
| 											sprite.shift_position += shift_advance; |  | ||||||
|  |  | ||||||
| 											mask &= colour_masks[sprite.info[3]&15]; | 											// Ignore the right half of whatever was collected if sprites are not in 16x16 mode. | ||||||
| 											sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | 											if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { | ||||||
|  | 												// If any previous sprite has been painted in this column and this sprite | ||||||
|  | 												// has this pixel set, set the sprite collision status bit. | ||||||
|  | 												status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; | ||||||
|  | 												sprite_mask |= mask; | ||||||
|  |  | ||||||
|  | 												// Check that the sprite colour is not transparent | ||||||
|  | 												mask &= colour_masks[sprite.info[3]&15]; | ||||||
|  | 												sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | ||||||
|  | 											} | ||||||
|  |  | ||||||
|  | 											sprite.shift_position += shift_advance; | ||||||
| 										} | 										} | ||||||
| 									} | 									} | ||||||
|  |  | ||||||
|  | 									// Output whichever sprite colour was on top. | ||||||
| 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | ||||||
| 									output_column_++; | 									output_column_++; | ||||||
| 								} | 								} | ||||||
|   | |||||||
| @@ -225,14 +225,10 @@ uint8_t AY38910::get_register_value() { | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	if(selected_register_ > 15) return 0xff; | 	if(selected_register_ > 15) return 0xff; | ||||||
| 	switch(selected_register_) { | 	return registers_[selected_register_] & register_masks[selected_register_]; | ||||||
| 		default:	return registers_[selected_register_] & register_masks[selected_register_]; |  | ||||||
| 		case 14:	return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0]; |  | ||||||
| 		case 15:	return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1]; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // MARK: - Port handling | // MARK: - Port querying | ||||||
|  |  | ||||||
| uint8_t AY38910::get_port_output(bool port_b) { | uint8_t AY38910::get_port_output(bool port_b) { | ||||||
| 	return registers_[port_b ? 15 : 14]; | 	return registers_[port_b ? 15 : 14]; | ||||||
| @@ -250,11 +246,16 @@ void AY38910::set_data_input(uint8_t r) { | |||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t AY38910::get_data_output() { | uint8_t AY38910::get_data_output() { | ||||||
| 	if(control_state_ == Read && selected_register_ >= 14) { | 	if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) { | ||||||
| 		if(port_handler_) { | 		// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the | ||||||
| 			return port_handler_->get_port_input(selected_register_ == 15); | 		// value returned to the CPU when reading it is the and of the output value and any input. | ||||||
| 		} else { | 		// If it's defined as input then you just get the input. | ||||||
| 			return 0xff; | 		const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff; | ||||||
|  |  | ||||||
|  | 		switch(selected_register_) { | ||||||
|  | 			default: 	break; | ||||||
|  | 			case 14:	return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff); | ||||||
|  | 			case 15:	return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return data_output_; | 	return data_output_; | ||||||
| @@ -276,8 +277,10 @@ void AY38910::set_control_lines(ControlLines control_lines) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::update_bus() { | void AY38910::update_bus() { | ||||||
|  | 	// Assume no output, unless this turns out to be a read. | ||||||
|  | 	data_output_ = 0xff; | ||||||
| 	switch(control_state_) { | 	switch(control_state_) { | ||||||
| 		default: break; | 		default: 			break; | ||||||
| 		case LatchAddress:	select_register(data_input_);			break; | 		case LatchAddress:	select_register(data_input_);			break; | ||||||
| 		case Write:			set_register_value(data_input_);		break; | 		case Write:			set_register_value(data_input_);		break; | ||||||
| 		case Read:			data_output_ = get_register_value();	break; | 		case Read:			data_output_ = get_register_value();	break; | ||||||
|   | |||||||
| @@ -92,9 +92,8 @@ class AY38910: public ::Outputs::Speaker::SampleSource { | |||||||
| 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|  |  | ||||||
| 		int selected_register_ = 0; | 		int selected_register_ = 0; | ||||||
| 		uint8_t registers_[16]; | 		uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | 		uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||||||
| 		uint8_t port_inputs_[2]; |  | ||||||
|  |  | ||||||
| 		int master_divider_ = 0; | 		int master_divider_ = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,12 +19,13 @@ namespace  { | |||||||
| 	const uint8_t input_flux = 0x1; | 	const uint8_t input_flux = 0x1; | ||||||
| } | } | ||||||
|  |  | ||||||
| DiskII::DiskII() : | DiskII::DiskII(int clock_rate) : | ||||||
|  | 	clock_rate_(clock_rate), | ||||||
| 	inputs_(input_command), | 	inputs_(input_command), | ||||||
| 	drives_{{2045454, 300, 1}, {2045454, 300, 1}} | 	drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}} | ||||||
| { | { | ||||||
| 	drives_[0].set_sleep_observer(this); | 	drives_[0].set_clocking_hint_observer(this); | ||||||
| 	drives_[1].set_sleep_observer(this); | 	drives_[1].set_clocking_hint_observer(this); | ||||||
| 	drives_[active_drive_].set_event_delegate(this); | 	drives_[active_drive_].set_event_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -72,72 +73,90 @@ void DiskII::select_drive(int drive) { | |||||||
| 	drives_[active_drive_].set_motor_on(motor_is_enabled_); | 	drives_[active_drive_].set_motor_on(motor_is_enabled_); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk. | ||||||
|  |  | ||||||
| void DiskII::run_for(const Cycles cycles) { | void DiskII::run_for(const Cycles cycles) { | ||||||
| 	if(is_sleeping()) return; | 	if(preferred_clocking() == ClockingHint::Preference::None) return; | ||||||
|  |  | ||||||
| 	if(!controller_can_sleep_) { | 	int integer_cycles = cycles.as_int(); | ||||||
| 		int integer_cycles = cycles.as_int(); | 	while(integer_cycles--) { | ||||||
| 		while(integer_cycles--) { | 		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); | ||||||
| 			const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); | 		if(flux_duration_) { | ||||||
| 			inputs_ |= input_flux; | 			--flux_duration_; | ||||||
| 			state_ = state_machine_[static_cast<std::size_t>(address)]; | 			if(!flux_duration_) inputs_ |= input_flux; | ||||||
| 			switch(state_ & 0xf) { |  | ||||||
| 				default:	shift_register_ = 0;													break;	// clear |  | ||||||
| 				case 0x8:																			break;	// nop |  | ||||||
|  |  | ||||||
| 				case 0x9:	shift_register_ = static_cast<uint8_t>(shift_register_ << 1);			break;	// shift left, bringing in a zero |  | ||||||
| 				case 0xd:	shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1);		break;	// shift left, bringing in a one |  | ||||||
|  |  | ||||||
| 				case 0xa:	// shift right, bringing in write protected status |  | ||||||
| 					shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); |  | ||||||
|  |  | ||||||
| 					// If the controller is in the sense write protect loop but the register will never change, |  | ||||||
| 					// short circuit further work and return now. |  | ||||||
| 					if(shift_register_ == is_write_protected() ? 0xff : 0x00) { |  | ||||||
| 						if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles)); |  | ||||||
| 						if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles)); |  | ||||||
| 						set_controller_can_sleep(); |  | ||||||
| 						return; |  | ||||||
| 					} |  | ||||||
| 				break; |  | ||||||
| 				case 0xb:	shift_register_ = data_input_;											break;	// load data register from data bus |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Currently writing? |  | ||||||
| 			if(inputs_&input_mode) { |  | ||||||
| 				// state_ & 0x80 should be the current level sent to the disk; |  | ||||||
| 				// therefore transitions in that bit should become flux transitions |  | ||||||
| 				drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80)); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// TODO: surely there's a less heavyweight solution than this? |  | ||||||
| 			if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1)); |  | ||||||
| 			if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); |  | ||||||
| 		} | 		} | ||||||
| 	} else { | 		state_ = state_machine_[static_cast<std::size_t>(address)]; | ||||||
| 		if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles); | 		switch(state_ & 0xf) { | ||||||
| 		if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles); | 			default:	shift_register_ = 0;													break;	// clear | ||||||
|  | 			case 0x8:																			break;	// nop | ||||||
|  |  | ||||||
|  | 			case 0x9:	shift_register_ = static_cast<uint8_t>(shift_register_ << 1);			break;	// shift left, bringing in a zero | ||||||
|  | 			case 0xd:	shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1);		break;	// shift left, bringing in a one | ||||||
|  |  | ||||||
|  | 			case 0xa:	// shift right, bringing in write protected status | ||||||
|  | 				shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); | ||||||
|  |  | ||||||
|  | 				// If the controller is in the sense write protect loop but the register will never change, | ||||||
|  | 				// short circuit further work and return now. | ||||||
|  | 				if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) { | ||||||
|  | 					if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles)); | ||||||
|  | 					if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles)); | ||||||
|  | 					decide_clocking_preference(); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 			break; | ||||||
|  | 			case 0xb:	shift_register_ = data_input_;											break;	// load data register from data bus | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Currently writing? | ||||||
|  | 		if(inputs_&input_mode) { | ||||||
|  | 			// state_ & 0x80 should be the current level sent to the disk; | ||||||
|  | 			// therefore transitions in that bit should become flux transitions | ||||||
|  | 			drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: surely there's a less heavyweight solution than inline updates? | ||||||
|  | 		if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1)); | ||||||
|  | 		if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	set_controller_can_sleep(); | 	// Per comp.sys.apple2.programmer there is a delay between the controller | ||||||
|  | 	// motor switch being flipped and the drive motor actually switching off. | ||||||
|  | 	// This models that, accepting overrun as a risk. | ||||||
|  | 	if(motor_off_time_ >= 0) { | ||||||
|  | 		motor_off_time_ -= cycles.as_int(); | ||||||
|  | 		if(motor_off_time_ < 0) { | ||||||
|  | 			set_control(Control::Motor, false); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	decide_clocking_preference(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskII::set_controller_can_sleep() { | void DiskII::decide_clocking_preference() { | ||||||
| 	// Permit the controller to sleep if it's in sense write protect mode, and the shift register | 	ClockingHint::Preference prior_preference = clocking_preference_; | ||||||
| 	// has already filled with the result of shifting eight times. |  | ||||||
| 	bool controller_could_sleep = controller_can_sleep_; | 	// If in read mode, clocking is either: | ||||||
| 	controller_can_sleep_ = | 	// | ||||||
| 		( | 	//	just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or | ||||||
| 			(inputs_ == input_flux) && | 	//	none, given that drives are not running, the shift register has already emptied and there's no flux about to be received. | ||||||
| 			!motor_is_enabled_ && | 	if(!(inputs_ & ~input_flux)) { | ||||||
| 			!shift_register_ | 		clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; | ||||||
| 		) || | 	} | ||||||
| 		( |  | ||||||
| 			(inputs_ == (input_command | input_flux)) && | 	// If in writing mode, clocking is real time. | ||||||
| 			(shift_register_ == (is_write_protected() ? 0xff : 0x00)) | 	if(inputs_ & input_mode) { | ||||||
| 		); | 		clocking_preference_ = ClockingHint::Preference::RealTime; | ||||||
| 	if(controller_could_sleep != controller_can_sleep_) | 	} | ||||||
| 		update_sleep_observer(); |  | ||||||
|  | 	// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that | ||||||
|  | 	// corresponds to the current write protect status. Otherwise it is none. | ||||||
|  | 	if((inputs_ & ~input_flux) == input_command) { | ||||||
|  | 		clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Announce a change if there was one. | ||||||
|  | 	if(prior_preference != clocking_preference_) | ||||||
|  | 		update_clocking_observer(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool DiskII::is_write_protected() { | bool DiskII::is_write_protected() { | ||||||
| @@ -195,18 +214,19 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv | |||||||
| void DiskII::process_event(const Storage::Disk::Track::Event &event) { | void DiskII::process_event(const Storage::Disk::Track::Event &event) { | ||||||
| 	if(event.type == Storage::Disk::Track::Event::FluxTransition) { | 	if(event.type == Storage::Disk::Track::Event::FluxTransition) { | ||||||
| 		inputs_ &= ~input_flux; | 		inputs_ &= ~input_flux; | ||||||
| 		set_controller_can_sleep(); | 		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles. | ||||||
|  | 		decide_clocking_preference(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { | void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { | ||||||
| 	drive_is_sleeping_[0] = drives_[0].is_sleeping(); | 	drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None; | ||||||
| 	drive_is_sleeping_[1] = drives_[1].is_sleeping(); | 	drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None; | ||||||
| 	update_sleep_observer(); | 	decide_clocking_preference(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool DiskII::is_sleeping() { | ClockingHint::Preference DiskII::preferred_clocking() { | ||||||
| 	return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1]; | 	return clocking_preference_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskII::set_data_input(uint8_t input) { | void DiskII::set_data_input(uint8_t input) { | ||||||
| @@ -227,9 +247,12 @@ int DiskII::read_address(int address) { | |||||||
|  |  | ||||||
| 		case 0x8: | 		case 0x8: | ||||||
| 			shift_register_ = 0; | 			shift_register_ = 0; | ||||||
| 			set_control(Control::Motor, false); | 			motor_off_time_ = clock_rate_; | ||||||
|  | 		break; | ||||||
|  | 		case 0x9: | ||||||
|  | 			set_control(Control::Motor, true); | ||||||
|  | 			motor_off_time_ = -1; | ||||||
| 		break; | 		break; | ||||||
| 		case 0x9:	set_control(Control::Motor, true);	break; |  | ||||||
|  |  | ||||||
| 		case 0xa:	select_drive(0);					break; | 		case 0xa:	select_drive(0);					break; | ||||||
| 		case 0xb:	select_drive(1);					break; | 		case 0xb:	select_drive(1);					break; | ||||||
| @@ -243,11 +266,11 @@ int DiskII::read_address(int address) { | |||||||
| 		break; | 		break; | ||||||
| 		case 0xf: | 		case 0xf: | ||||||
| 			if(!(inputs_ & input_mode)) | 			if(!(inputs_ & input_mode)) | ||||||
| 				drives_[active_drive_].begin_writing(Storage::Time(1, 2045454), false); | 				drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false); | ||||||
| 			inputs_ |= input_mode; | 			inputs_ |= input_mode; | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| 	set_controller_can_sleep(); | 	decide_clocking_preference(); | ||||||
| 	return (address & 1) ? 0xff : shift_register_; | 	return (address & 1) ? 0xff : shift_register_; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -255,3 +278,7 @@ void DiskII::set_activity_observer(Activity::Observer *observer) { | |||||||
| 	drives_[0].set_activity_observer(observer, "Drive 1", true); | 	drives_[0].set_activity_observer(observer, "Drive 1", true); | ||||||
| 	drives_[1].set_activity_observer(observer, "Drive 2", true); | 	drives_[1].set_activity_observer(observer, "Drive 2", true); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | Storage::Disk::Drive &DiskII::get_drive(int index) { | ||||||
|  | 	return drives_[index]; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
| #define DiskII_hpp | #define DiskII_hpp | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "../../ClockReceiver/Sleeper.hpp" | #include "../../ClockReceiver/ClockingHintSource.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../Storage/Disk/Disk.hpp" | ||||||
| #include "../../Storage/Disk/Drive.hpp" | #include "../../Storage/Disk/Drive.hpp" | ||||||
| @@ -28,10 +28,10 @@ namespace Apple { | |||||||
| */ | */ | ||||||
| class DiskII: | class DiskII: | ||||||
| 	public Storage::Disk::Drive::EventDelegate, | 	public Storage::Disk::Drive::EventDelegate, | ||||||
| 	public Sleeper::SleepObserver, | 	public ClockingHint::Source, | ||||||
| 	public Sleeper { | 	public ClockingHint::Observer { | ||||||
| 	public: | 	public: | ||||||
| 		DiskII(); | 		DiskII(int clock_rate); | ||||||
|  |  | ||||||
| 		/// Sets the current external value of the data bus. | 		/// Sets the current external value of the data bus. | ||||||
| 		void set_data_input(uint8_t input); | 		void set_data_input(uint8_t input); | ||||||
| @@ -76,11 +76,15 @@ class DiskII: | |||||||
| 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | ||||||
|  |  | ||||||
| 		// As per Sleeper. | 		// As per Sleeper. | ||||||
| 		bool is_sleeping() override; | 		ClockingHint::Preference preferred_clocking() override; | ||||||
|  |  | ||||||
| 		// The Disk II functions as a potential target for @c Activity::Sources. | 		// The Disk II functions as a potential target for @c Activity::Sources. | ||||||
| 		void set_activity_observer(Activity::Observer *observer); | 		void set_activity_observer(Activity::Observer *observer); | ||||||
|  |  | ||||||
|  | 		// Returns the Storage::Disk::Drive in use for drive @c index. | ||||||
|  | 		// *NOT FOR HARDWARE EMULATION USAGE*. | ||||||
|  | 		Storage::Disk::Drive &get_drive(int index); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		enum class Control { | 		enum class Control { | ||||||
| 			P0, P1, P2, P3, | 			P0, P1, P2, P3, | ||||||
| @@ -95,7 +99,9 @@ class DiskII: | |||||||
|  |  | ||||||
| 		uint8_t trigger_address(int address, uint8_t value); | 		uint8_t trigger_address(int address, uint8_t value); | ||||||
| 		void process_event(const Storage::Disk::Track::Event &event) override; | 		void process_event(const Storage::Disk::Track::Event &event) override; | ||||||
| 		void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; | ||||||
|  |  | ||||||
|  | 		const int clock_rate_ = 0; | ||||||
|  |  | ||||||
| 		uint8_t state_ = 0; | 		uint8_t state_ = 0; | ||||||
| 		uint8_t inputs_ = 0; | 		uint8_t inputs_ = 0; | ||||||
| @@ -103,18 +109,20 @@ class DiskII: | |||||||
|  |  | ||||||
| 		int stepper_mask_ = 0; | 		int stepper_mask_ = 0; | ||||||
| 		int stepper_position_ = 0; | 		int stepper_position_ = 0; | ||||||
|  | 		int motor_off_time_ = -1; | ||||||
|  |  | ||||||
| 		bool is_write_protected(); | 		bool is_write_protected(); | ||||||
| 		std::array<uint8_t, 256> state_machine_; | 		std::array<uint8_t, 256> state_machine_; | ||||||
| 		Storage::Disk::Drive drives_[2]; | 		Storage::Disk::Drive drives_[2]; | ||||||
| 		bool drive_is_sleeping_[2]; | 		bool drive_is_sleeping_[2]; | ||||||
| 		bool controller_can_sleep_ = false; |  | ||||||
| 		int active_drive_ = 0; | 		int active_drive_ = 0; | ||||||
| 		bool motor_is_enabled_ = false; | 		bool motor_is_enabled_ = false; | ||||||
|  |  | ||||||
| 		void set_controller_can_sleep(); | 		void decide_clocking_preference(); | ||||||
|  | 		ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime; | ||||||
|  |  | ||||||
| 		uint8_t data_input_ = 0; | 		uint8_t data_input_ = 0; | ||||||
|  | 		int flux_duration_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,28 +21,73 @@ class Joystick { | |||||||
| 	public: | 	public: | ||||||
| 		virtual ~Joystick() {} | 		virtual ~Joystick() {} | ||||||
|  |  | ||||||
| 		struct DigitalInput { | 		/*! | ||||||
|  | 			Defines a single input, any individually-measured thing — a fire button or | ||||||
|  | 			other digital control, an analogue axis, or a button with a symbol on it. | ||||||
|  | 		*/ | ||||||
|  | 		struct Input { | ||||||
|  | 			/// Defines the broad type of the input. | ||||||
| 			enum Type { | 			enum Type { | ||||||
| 				Up, Down, Left, Right, Fire, | 				// Half-axis inputs. | ||||||
|  | 				Up, Down, Left, Right, | ||||||
|  | 				// Full-axis inputs. | ||||||
|  | 				Horizontal, Vertical, | ||||||
|  | 				// Fire buttons. | ||||||
|  | 				Fire, | ||||||
|  | 				// Other labelled keys. | ||||||
| 				Key | 				Key | ||||||
| 			} type; | 			}; | ||||||
| 			union { | 			const Type type; | ||||||
|  |  | ||||||
|  | 			bool is_digital_axis() const { | ||||||
|  | 				return type < Type::Horizontal; | ||||||
|  | 			} | ||||||
|  | 			bool is_analogue_axis() const { | ||||||
|  | 				return type >= Type::Horizontal && type < Type::Fire; | ||||||
|  | 			} | ||||||
|  | 			bool is_axis() const { | ||||||
|  | 				return type < Type::Fire; | ||||||
|  | 			} | ||||||
|  | 			bool is_button() const { | ||||||
|  | 				return type >= Type::Fire; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			enum Precision { | ||||||
|  | 				Analogue, Digital | ||||||
|  | 			}; | ||||||
|  | 			Precision precision() const { | ||||||
|  | 				return is_analogue_axis() ? Precision::Analogue : Precision::Digital; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/*! | ||||||
|  | 				Holds extra information pertaining to the input. | ||||||
|  |  | ||||||
|  | 				@c Type::Key inputs declare the symbol printed on them. | ||||||
|  |  | ||||||
|  | 				All other types of input have an associated index, indicating whether they | ||||||
|  | 				are the zeroth, first, second, third, etc of those things. E.g. a joystick | ||||||
|  | 				may have two fire buttons, which will be buttons 0 and 1. | ||||||
|  | 			*/ | ||||||
|  | 			union Info { | ||||||
| 				struct { | 				struct { | ||||||
| 					int index; | 					size_t index; | ||||||
| 				} control; | 				} control; | ||||||
| 				struct { | 				struct { | ||||||
| 					wchar_t symbol; | 					wchar_t symbol; | ||||||
| 				} key; | 				} key; | ||||||
| 			} info; | 			}; | ||||||
|  | 			Info info; | ||||||
|  | 			// TODO: Find a way to make the above safely const; may mean not using a union. | ||||||
|  |  | ||||||
| 			DigitalInput(Type type, int index = 0) : type(type) { | 			Input(Type type, size_t index = 0) : | ||||||
|  | 				type(type) { | ||||||
| 				info.control.index = index; | 				info.control.index = index; | ||||||
| 			} | 			} | ||||||
| 			DigitalInput(wchar_t symbol) : type(Key) { | 			Input(wchar_t symbol) : type(Key) { | ||||||
| 				info.key.symbol = symbol; | 				info.key.symbol = symbol; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			bool operator == (const DigitalInput &rhs) { | 			bool operator == (const Input &rhs) { | ||||||
| 				if(rhs.type != type) return false; | 				if(rhs.type != type) return false; | ||||||
| 				if(rhs.type == Key) { | 				if(rhs.type == Key) { | ||||||
| 					return rhs.info.key.symbol == info.key.symbol; | 					return rhs.info.key.symbol == info.key.symbol; | ||||||
| @@ -52,15 +97,139 @@ class Joystick { | |||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		virtual std::vector<DigitalInput> get_inputs() = 0; | 		/// @returns The list of all inputs defined on this joystick. | ||||||
|  | 		virtual std::vector<Input> &get_inputs() = 0; | ||||||
|  |  | ||||||
| 		// Host interface. | 		/*! | ||||||
| 		virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0; | 			Sets the digital value of @c input. This may have direct effect or | ||||||
|  | 			influence an analogue value; e.g. if the caller declares that ::Left is | ||||||
|  | 			active but this joystick has only an analogue horizontal axis, this will | ||||||
|  | 			cause a change to that analogue value. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_input(const Input &input, bool is_active) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the analogue value of @c input. If the input is actually digital, | ||||||
|  | 			or if there is a digital input with a corresponding meaning (e.g. ::Left | ||||||
|  | 			versus the horizontal axis), this may cause a digital input to be set. | ||||||
|  |  | ||||||
|  | 			@c value should be in the range [0.0, 1.0]. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_input(const Input &input, float value) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets all inputs to their resting state. | ||||||
|  | 		*/ | ||||||
| 		virtual void reset_all_inputs() { | 		virtual void reset_all_inputs() { | ||||||
| 			for(const auto &input: get_inputs()) { | 			for(const auto &input: get_inputs()) { | ||||||
| 				set_digital_input(input, false); | 				if(input.precision() == Input::Precision::Digital) | ||||||
|  | 					set_input(input, false); | ||||||
|  | 				else | ||||||
|  | 					set_input(input, 0.5f); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Gets the number of input fire buttons. | ||||||
|  |  | ||||||
|  | 			This is cached by default, but it's virtual so overridable. | ||||||
|  | 		*/ | ||||||
|  | 		virtual int get_number_of_fire_buttons() { | ||||||
|  | 			if(number_of_buttons_ >= 0) return number_of_buttons_; | ||||||
|  |  | ||||||
|  | 			number_of_buttons_ = 0; | ||||||
|  | 			for(const auto &input: get_inputs()) { | ||||||
|  | 				if(input.type == Input::Type::Fire) ++number_of_buttons_; | ||||||
|  | 			} | ||||||
|  | 			return number_of_buttons_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int number_of_buttons_ = -1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	ConcreteJoystick is the class that it's expected most machines will actually subclass; | ||||||
|  | 	it accepts a set of Inputs at construction and thereby is able to provide the | ||||||
|  | 	promised analogue <-> digital mapping of Joystick. | ||||||
|  | */ | ||||||
|  | class ConcreteJoystick: public Joystick { | ||||||
|  | 	public: | ||||||
|  | 		ConcreteJoystick(const std::vector<Input> &inputs) : inputs_(inputs) { | ||||||
|  | 			// Size and populate stick_types_, which is used for digital <-> analogue conversion. | ||||||
|  | 			for(const auto &input: inputs_) { | ||||||
|  | 				const bool is_digital_axis = input.is_digital_axis(); | ||||||
|  | 				const bool is_analogue_axis = input.is_analogue_axis(); | ||||||
|  | 				if(is_digital_axis || is_analogue_axis) { | ||||||
|  | 					const size_t required_size = static_cast<size_t>(input.info.control.index+1); | ||||||
|  | 					if(stick_types_.size() < required_size) { | ||||||
|  | 						stick_types_.resize(required_size); | ||||||
|  | 					} | ||||||
|  | 					stick_types_[static_cast<size_t>(input.info.control.index)] = is_digital_axis ? StickType::Digital : StickType::Analogue; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<Input> &get_inputs() override final { | ||||||
|  | 			return inputs_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_input(const Input &input, bool is_active) override final { | ||||||
|  | 			// If this is a digital setting to a digital property, just pass it along. | ||||||
|  | 			if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) { | ||||||
|  | 				did_set_input(input, is_active); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Otherwise this is logically to an analogue axis; for now just use some | ||||||
|  | 			// convenient hard-coded values. TODO: make these a function of time. | ||||||
|  | 			using Type = Joystick::Input::Type; | ||||||
|  | 			switch(input.type) { | ||||||
|  | 				default: 			did_set_input(input, is_active ? 1.0f : 0.0f);													break; | ||||||
|  | 				case Type::Left:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f);		break; | ||||||
|  | 				case Type::Right:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f);		break; | ||||||
|  | 				case Type::Up:		did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f);		break; | ||||||
|  | 				case Type::Down:	did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f);		break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_input(const Input &input, float value) override final { | ||||||
|  | 			// If this is an analogue setting to an analogue property, just pass it along. | ||||||
|  | 			if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) { | ||||||
|  | 				did_set_input(input, value); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs. | ||||||
|  | 			using Type = Joystick::Input::Type; | ||||||
|  | 			switch(input.type) { | ||||||
|  | 				default: 			did_set_input(input, value > 0.5f);											break; | ||||||
|  | 				case Type::Horizontal: | ||||||
|  | 					did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f); | ||||||
|  | 					did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f); | ||||||
|  | 				break; | ||||||
|  | 				case Type::Vertical: | ||||||
|  | 					did_set_input(Input(Type::Up, input.info.control.index), value <= 0.25f); | ||||||
|  | 					did_set_input(Input(Type::Down, input.info.control.index), value >= 0.75f); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		virtual void did_set_input(const Input &input, float value) { | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		virtual void did_set_input(const Input &input, bool value) { | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<Input> inputs_; | ||||||
|  |  | ||||||
|  | 		enum class StickType { | ||||||
|  | 			Digital, | ||||||
|  | 			Analogue | ||||||
|  | 		}; | ||||||
|  | 		std::vector<StickType> stick_types_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,8 +21,9 @@ | |||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
|  | #include "../JoystickMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
| @@ -37,12 +38,11 @@ | |||||||
|  |  | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
| enum ROMType: int { | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 	OS464 = 0,	BASIC464, | 	return Configurable::standard_options( | ||||||
| 	OS664,		BASIC664, | 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite) | ||||||
| 	OS6128,		BASIC6128, | 	); | ||||||
| 	AMSDOS | } | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | ||||||
| @@ -192,36 +192,51 @@ class CRTCBusHandler { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6); | 			bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6); | ||||||
|  | 			bool is_colour_burst = (cycles_into_hsync_ >= 7 && cycles_into_hsync_ < 11); | ||||||
|  |  | ||||||
| 			// Sync is taken to override pixels, and is combined as a simple OR. | 			// Sync is taken to override pixels, and is combined as a simple OR. | ||||||
| 			bool is_sync = is_hsync || state.vsync; | 			bool is_sync = is_hsync || state.vsync; | ||||||
|  | 			bool is_blank = !is_sync && state.hsync; | ||||||
|  |  | ||||||
|  | 			OutputMode output_mode; | ||||||
|  | 			if(is_sync) { | ||||||
|  | 				output_mode = OutputMode::Sync; | ||||||
|  | 			} else if(is_colour_burst) { | ||||||
|  | 				output_mode = OutputMode::ColourBurst; | ||||||
|  | 			} else if(is_blank) { | ||||||
|  | 				output_mode = OutputMode::Blank; | ||||||
|  | 			} else if(state.display_enable) { | ||||||
|  | 				output_mode = OutputMode::Pixels; | ||||||
|  | 			} else { | ||||||
|  | 				output_mode = OutputMode::Border; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// If a transition between sync/border/pixels just occurred, flush whatever was | 			// If a transition between sync/border/pixels just occurred, flush whatever was | ||||||
| 			// in progress to the CRT and reset counting. | 			// in progress to the CRT and reset counting. | ||||||
| 			if(state.display_enable != was_enabled_ || is_sync != was_sync_) { | 			if(output_mode != previous_output_mode_) { | ||||||
| 				if(was_sync_) { | 				if(cycles_) { | ||||||
| 					crt_->output_sync(cycles_ * 16); | 					switch(previous_output_mode_) { | ||||||
| 				} else { | 						default: | ||||||
| 					if(was_enabled_) { | 						case OutputMode::Blank:			crt_->output_blank(cycles_ * 16);					break; | ||||||
| 						if(cycles_) { | 						case OutputMode::Sync:			crt_->output_sync(cycles_ * 16);					break; | ||||||
|  | 						case OutputMode::Border:		output_border(cycles_);								break; | ||||||
|  | 						case OutputMode::ColourBurst:	crt_->output_default_colour_burst(cycles_ * 16);	break; | ||||||
|  | 						case OutputMode::Pixels: | ||||||
| 							crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_); | 							crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_); | ||||||
| 							pixel_pointer_ = pixel_data_ = nullptr; | 							pixel_pointer_ = pixel_data_ = nullptr; | ||||||
| 						} | 						break; | ||||||
| 					} else { |  | ||||||
| 						output_border(cycles_); |  | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				cycles_ = 0; | 				cycles_ = 0; | ||||||
| 				was_sync_ = is_sync; | 				previous_output_mode_ = output_mode; | ||||||
| 				was_enabled_ = state.display_enable; |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// increment cycles since state changed | 			// increment cycles since state changed | ||||||
| 			cycles_++; | 			cycles_++; | ||||||
|  |  | ||||||
| 			// collect some more pixels if output is ongoing | 			// collect some more pixels if output is ongoing | ||||||
| 			if(!is_sync && state.display_enable) { | 			if(previous_output_mode_ == OutputMode::Pixels) { | ||||||
| 				if(!pixel_data_) { | 				if(!pixel_data_) { | ||||||
| 					pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8); | 					pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8); | ||||||
| 				} | 				} | ||||||
| @@ -317,7 +332,7 @@ class CRTCBusHandler { | |||||||
| 					"uint sample = texture(texID, coordinate).r;" | 					"uint sample = texture(texID, coordinate).r;" | ||||||
| 					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" | 					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" | ||||||
| 				"}"); | 				"}"); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); | 			crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f)); | ||||||
| 			crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | 			crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -349,7 +364,7 @@ class CRTCBusHandler { | |||||||
| 			if(pen_ & 16) { | 			if(pen_ & 16) { | ||||||
| 				// If border is[/was] currently being output, flush what should have been | 				// If border is[/was] currently being output, flush what should have been | ||||||
| 				// drawn in the old colour. | 				// drawn in the old colour. | ||||||
| 				if(!was_sync_ && !was_enabled_) { | 				if(previous_output_mode_ == OutputMode::Border) { | ||||||
| 					output_border(cycles_); | 					output_border(cycles_); | ||||||
| 					cycles_ = 0; | 					cycles_ = 0; | ||||||
| 				} | 				} | ||||||
| @@ -506,9 +521,16 @@ class CRTCBusHandler { | |||||||
| 			return mapping[colour]; | 			return mapping[colour]; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		enum class OutputMode { | ||||||
|  | 			Sync, | ||||||
|  | 			Blank, | ||||||
|  | 			ColourBurst, | ||||||
|  | 			Border, | ||||||
|  | 			Pixels | ||||||
|  | 		} previous_output_mode_ = OutputMode::Sync; | ||||||
| 		unsigned int cycles_ = 0; | 		unsigned int cycles_ = 0; | ||||||
|  |  | ||||||
| 		bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false; | 		bool was_hsync_ = false, was_vsync_ = false; | ||||||
| 		int cycles_into_hsync_ = 0; | 		int cycles_into_hsync_ = 0; | ||||||
|  |  | ||||||
| 		std::unique_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
| @@ -537,24 +559,28 @@ class CRTCBusHandler { | |||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Holds and vends the current keyboard state, acting as the AY's port handler. | 	Holds and vends the current keyboard state, acting as the AY's port handler. | ||||||
|  | 	Also owns the joysticks. | ||||||
| */ | */ | ||||||
| class KeyboardState: public GI::AY38910::PortHandler { | class KeyboardState: public GI::AY38910::PortHandler { | ||||||
| 	public: | 	public: | ||||||
| 		KeyboardState() : rows_{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} {} | 		KeyboardState() { | ||||||
|  | 			joysticks_.emplace_back(new Joystick(rows_[9])); | ||||||
|  | 			joysticks_.emplace_back(new Joystick(joy2_state_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the row currently being reported to the AY. | 			Sets the row currently being reported to the AY. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_row(int row) { | 		void set_row(int row) { | ||||||
| 			row_ = row; | 			row_ = static_cast<size_t>(row); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Reports the state of the currently-selected row as Port A to the AY. | 			Reports the state of the currently-selected row as Port A to the AY. | ||||||
| 		*/ | 		*/ | ||||||
| 		uint8_t get_port_input(bool port_b) { | 		uint8_t get_port_input(bool port_b) { | ||||||
| 			if(!port_b && row_ < 10) { | 			if(!port_b && row_ < sizeof(rows_)) { | ||||||
| 				return rows_[row_]; | 				return (row_ == 6) ? rows_[row_] & joy2_state_ : rows_[row_]; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return 0xff; | 			return 0xff; | ||||||
| @@ -565,7 +591,7 @@ class KeyboardState: public GI::AY38910::PortHandler { | |||||||
| 		*/ | 		*/ | ||||||
| 		void set_is_pressed(bool is_pressed, int line, int key) { | 		void set_is_pressed(bool is_pressed, int line, int key) { | ||||||
| 			int mask = 1 << key; | 			int mask = 1 << key; | ||||||
| 			assert(line < 10); | 			assert(static_cast<size_t>(line) < sizeof(rows_)); | ||||||
| 			if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask; | 			if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -573,12 +599,52 @@ class KeyboardState: public GI::AY38910::PortHandler { | |||||||
| 			Sets all keys as currently unpressed. | 			Sets all keys as currently unpressed. | ||||||
| 		*/ | 		*/ | ||||||
| 		void clear_all_keys() { | 		void clear_all_keys() { | ||||||
| 			memset(rows_, 0xff, 10); | 			memset(rows_, 0xff, sizeof(rows_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { | ||||||
|  | 			return joysticks_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		uint8_t rows_[10]; | 		uint8_t joy2_state_ = 0xff; | ||||||
| 		int row_; | 		uint8_t rows_[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; | ||||||
|  | 		size_t row_ = 0; | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  |  | ||||||
|  | 		class Joystick: public Inputs::ConcreteJoystick { | ||||||
|  | 			public: | ||||||
|  | 				Joystick(uint8_t &state) : | ||||||
|  | 					ConcreteJoystick({ | ||||||
|  | 						Input(Input::Up), | ||||||
|  | 						Input(Input::Down), | ||||||
|  | 						Input(Input::Left), | ||||||
|  | 						Input(Input::Right), | ||||||
|  | 						Input(Input::Fire, 0), | ||||||
|  | 						Input(Input::Fire, 1), | ||||||
|  | 					}), | ||||||
|  | 					state_(state) {} | ||||||
|  |  | ||||||
|  | 				void did_set_input(const Input &input, bool is_active) override { | ||||||
|  | 					uint8_t mask = 0; | ||||||
|  | 					switch(input.type) { | ||||||
|  | 						default: return; | ||||||
|  | 						case Input::Up:		mask = 0x01;	break; | ||||||
|  | 						case Input::Down:	mask = 0x02;	break; | ||||||
|  | 						case Input::Left:	mask = 0x04;	break; | ||||||
|  | 						case Input::Right:	mask = 0x08;	break; | ||||||
|  | 						case Input::Fire: | ||||||
|  | 							if(input.info.control.index >= 2) return; | ||||||
|  | 							mask = input.info.control.index ? 0x20 : 0x10; | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(is_active) state_ &= ~mask; else state_ |= mask; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				uint8_t &state_; | ||||||
|  | 		}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -688,17 +754,19 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| /*! | /*! | ||||||
| 	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. | 	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. | ||||||
| */ | */ | ||||||
| class ConcreteMachine: | template <bool has_fdc> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public Sleeper::SleepObserver, | 	public ClockingHint::Observer, | ||||||
|  | 	public Configurable::Device, | ||||||
|  | 	public JoystickMachine::Machine, | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			crtc_bus_handler_(ram_, interrupt_timer_), | 			crtc_bus_handler_(ram_, interrupt_timer_), | ||||||
| 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), | 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), | ||||||
| @@ -714,13 +782,62 @@ class ConcreteMachine: | |||||||
| 			Memory::Fuzz(ram_, sizeof(ram_)); | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
|  |  | ||||||
| 			// register this class as the sleep observer for the FDC and tape | 			// register this class as the sleep observer for the FDC and tape | ||||||
| 			fdc_.set_sleep_observer(this); | 			fdc_.set_clocking_hint_observer(this); | ||||||
| 			fdc_is_sleeping_ = fdc_.is_sleeping(); | 			tape_player_.set_clocking_hint_observer(this); | ||||||
|  |  | ||||||
| 			tape_player_.set_sleep_observer(this); |  | ||||||
| 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); |  | ||||||
|  |  | ||||||
|  | 			// install the keyboard state class as the AY port handler | ||||||
| 			ay_.ay().set_port_handler(&key_state_); | 			ay_.ay().set_port_handler(&key_state_); | ||||||
|  |  | ||||||
|  | 			// construct the list of necessary ROMs | ||||||
|  | 			std::vector<std::string> required_roms = {"amsdos.rom"}; | ||||||
|  | 			std::string model_number; | ||||||
|  | 			switch(target.model) { | ||||||
|  | 				default: | ||||||
|  | 					model_number = "6128"; | ||||||
|  | 					has_128k_ = true; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: | ||||||
|  | 					model_number = "464"; | ||||||
|  | 					has_128k_ = false; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: | ||||||
|  | 					model_number = "664"; | ||||||
|  | 					has_128k_ = false; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			required_roms.push_back("os" + model_number + ".rom"); | ||||||
|  | 			required_roms.push_back("basic" + model_number + ".rom"); | ||||||
|  |  | ||||||
|  | 			// fetch and verify the ROMs | ||||||
|  | 			const auto roms = rom_fetcher("AmstradCPC", required_roms); | ||||||
|  |  | ||||||
|  | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
|  | 				auto &data = roms[index]; | ||||||
|  | 				if(!data) throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				roms_[static_cast<int>(index)] = std::move(*data); | ||||||
|  | 				roms_[static_cast<int>(index)].resize(16384); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Establish default memory map | ||||||
|  | 			upper_rom_is_paged_ = true; | ||||||
|  | 			upper_rom_ = ROMType::BASIC; | ||||||
|  |  | ||||||
|  | 			write_pointers_[0] = &ram_[0]; | ||||||
|  | 			write_pointers_[1] = &ram_[16384]; | ||||||
|  | 			write_pointers_[2] = &ram_[32768]; | ||||||
|  | 			write_pointers_[3] = &ram_[49152]; | ||||||
|  |  | ||||||
|  | 			read_pointers_[0] = roms_[ROMType::OS].data(); | ||||||
|  | 			read_pointers_[1] = write_pointers_[1]; | ||||||
|  | 			read_pointers_[2] = write_pointers_[2]; | ||||||
|  | 			read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
|  |  | ||||||
|  | 			// Type whatever is required. | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The entry point for performing a partial Z80 machine cycle. | 		/// The entry point for performing a partial Z80 machine cycle. | ||||||
| @@ -749,7 +866,7 @@ class ConcreteMachine: | |||||||
| 			ay_.run_for(cycle.length); | 			ay_.run_for(cycle.length); | ||||||
|  |  | ||||||
| 			// Clock the FDC, if connected, using a lazy scale by two | 			// Clock the FDC, if connected, using a lazy scale by two | ||||||
| 			if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int())); | 			time_since_fdc_update_ += cycle.length; | ||||||
|  |  | ||||||
| 			// Update typing activity | 			// Update typing activity | ||||||
| 			if(typer_) typer_->run_for(cycle.length); | 			if(typer_) typer_->run_for(cycle.length); | ||||||
| @@ -775,8 +892,8 @@ class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for an upper ROM selection | 					// Check for an upper ROM selection | ||||||
| 					if(has_fdc_ && !(address&0x2000)) { | 					if(has_fdc && !(address&0x2000)) { | ||||||
| 						upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : rom_model_ + 1; | 						upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC; | ||||||
| 						if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); | 						if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -795,12 +912,14 @@ class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for an FDC access | 					// Check for an FDC access | ||||||
| 					if(has_fdc_ && (address & 0x580) == 0x100) { | 					if(has_fdc && (address & 0x580) == 0x100) { | ||||||
|  | 						flush_fdc(); | ||||||
| 						fdc_.set_register(address & 1, *cycle.value); | 						fdc_.set_register(address & 1, *cycle.value); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for a disk motor access | 					// Check for a disk motor access | ||||||
| 					if(has_fdc_ && !(address & 0x580)) { | 					if(has_fdc && !(address & 0x580)) { | ||||||
|  | 						flush_fdc(); | ||||||
| 						fdc_.set_motor_on(!!(*cycle.value)); | 						fdc_.set_motor_on(!!(*cycle.value)); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| @@ -814,7 +933,8 @@ class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for an FDC access | 					// Check for an FDC access | ||||||
| 					if(has_fdc_ && (address & 0x580) == 0x100) { | 					if(has_fdc && (address & 0x580) == 0x100) { | ||||||
|  | 						flush_fdc(); | ||||||
| 						*cycle.value &= fdc_.get_register(address & 1); | 						*cycle.value &= fdc_.get_register(address & 1); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -858,6 +978,7 @@ class ConcreteMachine: | |||||||
| 			// Just flush the AY. | 			// Just flush the AY. | ||||||
| 			ay_.update(); | 			ay_.update(); | ||||||
| 			ay_.flush(); | 			ay_.flush(); | ||||||
|  | 			flush_fdc(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// A CRTMachine function; indicates that outputs should be created now. | 		/// A CRTMachine function; indicates that outputs should be created now. | ||||||
| @@ -885,50 +1006,6 @@ class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final  { |  | ||||||
| 			auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target); |  | ||||||
|  |  | ||||||
| 			switch(cpc_target->model) { |  | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: |  | ||||||
| 					rom_model_ = ROMType::OS464; |  | ||||||
| 					has_128k_ = false; |  | ||||||
| 					has_fdc_ = false; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: |  | ||||||
| 					rom_model_ = ROMType::OS664; |  | ||||||
| 					has_128k_ = false; |  | ||||||
| 					has_fdc_ = true; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC6128: |  | ||||||
| 					rom_model_ = ROMType::OS6128; |  | ||||||
| 					has_128k_ = true; |  | ||||||
| 					has_fdc_ = true; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Establish default memory map |  | ||||||
| 			upper_rom_is_paged_ = true; |  | ||||||
| 			upper_rom_ = rom_model_ + 1; |  | ||||||
|  |  | ||||||
| 			write_pointers_[0] = &ram_[0]; |  | ||||||
| 			write_pointers_[1] = &ram_[16384]; |  | ||||||
| 			write_pointers_[2] = &ram_[32768]; |  | ||||||
| 			write_pointers_[3] = &ram_[49152]; |  | ||||||
|  |  | ||||||
| 			read_pointers_[0] = roms_[rom_model_].data(); |  | ||||||
| 			read_pointers_[1] = write_pointers_[1]; |  | ||||||
| 			read_pointers_[2] = write_pointers_[2]; |  | ||||||
| 			read_pointers_[3] = roms_[upper_rom_].data(); |  | ||||||
|  |  | ||||||
| 			// Type whatever is required. |  | ||||||
| 			if(!cpc_target->loading_command.empty()) { |  | ||||||
| 				type_string(cpc_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			// If there are any tapes supplied, use the first of them. | 			// If there are any tapes supplied, use the first of them. | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| @@ -943,37 +1020,15 @@ class ConcreteMachine: | |||||||
| 				if(c == 4) break; | 				if(c == 4) break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc_); | 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final { | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { | 			fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None; | ||||||
| 			auto roms = roms_with_names( | 			tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; | ||||||
| 				"AmstradCPC", |  | ||||||
| 				{ |  | ||||||
| 					"os464.rom",	"basic464.rom", |  | ||||||
| 					"os664.rom",	"basic664.rom", |  | ||||||
| 					"os6128.rom",	"basic6128.rom", |  | ||||||
| 					"amsdos.rom" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				auto &data = roms[index]; |  | ||||||
| 				if(!data) return false; |  | ||||||
| 				roms_[static_cast<int>(index)] = std::move(*data); |  | ||||||
| 				roms_[static_cast<int>(index)].resize(16384); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final { | 		// MARK: - Keyboard | ||||||
| 			fdc_is_sleeping_ = fdc_.is_sleeping(); |  | ||||||
| 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| // MARK: - Keyboard |  | ||||||
|  |  | ||||||
| 		void type_string(const std::string &string) override final { | 		void type_string(const std::string &string) override final { | ||||||
| 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | ||||||
| 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| @@ -1003,9 +1058,37 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		// MARK: - Activity Source | 		// MARK: - Activity Source | ||||||
| 		void set_activity_observer(Activity::Observer *observer) override { | 		void set_activity_observer(Activity::Observer *observer) override { | ||||||
| 			if(has_fdc_) fdc_.set_activity_observer(observer); | 			if(has_fdc) fdc_.set_activity_observer(observer); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Configuration options. | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | ||||||
|  | 			return AmstradCPC::get_options(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
|  | 			Configurable::Display display; | ||||||
|  | 			if(Configurable::get_display(selections_by_option, display)) { | ||||||
|  | 				set_video_signal_configurable(display); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_accurate_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_display_selection(selection_set, Configurable::Display::RGB); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_user_friendly_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_display_selection(selection_set, Configurable::Display::RGB); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Joysticks | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return key_state_.get_joysticks(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		inline void write_to_gate_array(uint8_t value) { | 		inline void write_to_gate_array(uint8_t value) { | ||||||
| @@ -1014,7 +1097,7 @@ class ConcreteMachine: | |||||||
| 				case 1: crtc_bus_handler_.set_colour(value & 0x1f);		break; | 				case 1: crtc_bus_handler_.set_colour(value & 0x1f);		break; | ||||||
| 				case 2: | 				case 2: | ||||||
| 					// Perform ROM paging. | 					// Perform ROM paging. | ||||||
| 					read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data(); | 					read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[ROMType::OS].data(); | ||||||
|  |  | ||||||
| 					upper_rom_is_paged_ = !(value & 8); | 					upper_rom_is_paged_ = !(value & 8); | ||||||
| 					read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; | 					read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; | ||||||
| @@ -1063,6 +1146,14 @@ class ConcreteMachine: | |||||||
| 		Intel::i8255::i8255<i8255PortHandler> i8255_; | 		Intel::i8255::i8255<i8255PortHandler> i8255_; | ||||||
|  |  | ||||||
| 		FDC fdc_; | 		FDC fdc_; | ||||||
|  | 		HalfCycles time_since_fdc_update_; | ||||||
|  | 		void flush_fdc() { | ||||||
|  | 			// Clock the FDC, if connected, using a lazy scale by two | ||||||
|  | 			if(has_fdc && !fdc_is_sleeping_) { | ||||||
|  | 				fdc_.run_for(Cycles(time_since_fdc_update_.as_int())); | ||||||
|  | 			} | ||||||
|  | 			time_since_fdc_update_ = HalfCycles(0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		InterruptTimer interrupt_timer_; | 		InterruptTimer interrupt_timer_; | ||||||
| 		Storage::Tape::BinaryTapePlayer tape_player_; | 		Storage::Tape::BinaryTapePlayer tape_player_; | ||||||
| @@ -1073,13 +1164,16 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		uint8_t ram_[128 * 1024]; | 		uint8_t ram_[128 * 1024]; | ||||||
|  |  | ||||||
| 		std::vector<uint8_t> roms_[7]; | 		bool fdc_is_sleeping_; | ||||||
| 		int rom_model_; |  | ||||||
| 		bool has_fdc_, fdc_is_sleeping_; |  | ||||||
| 		bool tape_player_is_sleeping_; | 		bool tape_player_is_sleeping_; | ||||||
| 		bool has_128k_; | 		bool has_128k_; | ||||||
|  |  | ||||||
|  | 		enum ROMType: int { | ||||||
|  | 			AMSDOS = 0, OS = 1, BASIC = 2 | ||||||
|  | 		}; | ||||||
|  | 		std::vector<uint8_t> roms_[3]; | ||||||
| 		bool upper_rom_is_paged_; | 		bool upper_rom_is_paged_; | ||||||
| 		int upper_rom_; | 		ROMType upper_rom_; | ||||||
|  |  | ||||||
| 		uint8_t *ram_pages_[4]; | 		uint8_t *ram_pages_[4]; | ||||||
| 		uint8_t *read_pointers_[4]; | 		uint8_t *read_pointers_[4]; | ||||||
| @@ -1094,8 +1188,13 @@ class ConcreteMachine: | |||||||
| using namespace AmstradCPC; | using namespace AmstradCPC; | ||||||
|  |  | ||||||
| // See header; constructs and returns an instance of the Amstrad CPC. | // See header; constructs and returns an instance of the Amstrad CPC. | ||||||
| Machine *Machine::AmstradCPC() { | Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new AmstradCPC::ConcreteMachine; | 	using Target = Analyser::Static::AmstradCPC::Target; | ||||||
|  | 	const Target *const cpc_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	switch(cpc_target->model) { | ||||||
|  | 		default: 					return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher); | ||||||
|  | 		case Target::Model::CPC464:	return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,8 +9,18 @@ | |||||||
| #ifndef AmstradCPC_hpp | #ifndef AmstradCPC_hpp | ||||||
| #define AmstradCPC_hpp | #define AmstradCPC_hpp | ||||||
|  |  | ||||||
|  | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | /// @returns The options available for an Amstrad CPC. | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models an Amstrad CPC. | 	Models an Amstrad CPC. | ||||||
| */ | */ | ||||||
| @@ -19,7 +29,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Amstrad CPC. | 		/// Creates and returns an Amstrad CPC. | ||||||
| 		static Machine *AmstradCPC(); | 		static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,8 +9,9 @@ | |||||||
| #include "AppleII.hpp" | #include "AppleII.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
|  | #include "../JoystickMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
| #include "../Utility/MemoryFuzzer.hpp" | #include "../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Utility/StringSerialiser.hpp" | #include "../Utility/StringSerialiser.hpp" | ||||||
| @@ -19,12 +20,14 @@ | |||||||
| #include "../../Components/AudioToggle/AudioToggle.hpp" | #include "../../Components/AudioToggle/AudioToggle.hpp" | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| #include "Card.hpp" | #include "Card.hpp" | ||||||
| #include "DiskIICard.hpp" | #include "DiskIICard.hpp" | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
| #include "../../Analyser/Static/AppleII/Target.hpp" | #include "../../Analyser/Static/AppleII/Target.hpp" | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <array> | #include <array> | ||||||
| @@ -32,31 +35,35 @@ | |||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| class ConcreteMachine: | #define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) | ||||||
|  |  | ||||||
|  | template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public Inputs::Keyboard, | 	public Inputs::Keyboard, | ||||||
| 	public AppleII::Machine, | 	public AppleII::Machine, | ||||||
| 	public Activity::Source, | 	public Activity::Source, | ||||||
|  | 	public JoystickMachine::Machine, | ||||||
| 	public AppleII::Card::Delegate { | 	public AppleII::Card::Delegate { | ||||||
| 	private: | 	private: | ||||||
| 		struct VideoBusHandler : public AppleII::Video::BusHandler { | 		struct VideoBusHandler : public AppleII::Video::BusHandler { | ||||||
| 			public: | 			public: | ||||||
| 				VideoBusHandler(uint8_t *ram) : ram_(ram) {} | 				VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} | ||||||
|  |  | ||||||
| 				uint8_t perform_read(uint16_t address) { | 				void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { | ||||||
| 					return ram_[address]; | 					memcpy(base_target, &ram_[address], count); | ||||||
|  | 					memcpy(auxiliary_target, &aux_ram_[address], count); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 			private: | 			private: | ||||||
| 				uint8_t *ram_; | 				uint8_t *ram_, *aux_ram_; | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
| 		VideoBusHandler video_bus_handler_; | 		VideoBusHandler video_bus_handler_; | ||||||
| 		std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_; | 		std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie()>> video_; | ||||||
| 		int cycles_into_current_line_ = 0; | 		int cycles_into_current_line_ = 0; | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
|  |  | ||||||
| @@ -76,17 +83,24 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		uint8_t ram_[65536], aux_ram_[65536]; | 		uint8_t ram_[65536], aux_ram_[65536]; | ||||||
| 		std::vector<uint8_t> apple2_rom_, apple2plus_rom_, rom_; | 		std::vector<uint8_t> rom_; | ||||||
| 		std::vector<uint8_t> character_rom_; | 		std::vector<uint8_t> character_rom_; | ||||||
| 		uint8_t keyboard_input_ = 0x00; | 		uint8_t keyboard_input_ = 0x00; | ||||||
|  | 		bool key_is_down_ = false; | ||||||
|  |  | ||||||
|  | 		uint8_t get_keyboard_input() { | ||||||
|  | 			if(string_serialiser_) { | ||||||
|  | 				return string_serialiser_->head() | 0x80; | ||||||
|  | 			} else { | ||||||
|  | 				return keyboard_input_; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
| 		Audio::Toggle audio_toggle_; | 		Audio::Toggle audio_toggle_; | ||||||
| 		Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_; | 		Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_; | ||||||
| 		Cycles cycles_since_audio_update_; | 		Cycles cycles_since_audio_update_; | ||||||
|  |  | ||||||
| 		ROMMachine::ROMFetcher rom_fetcher_; |  | ||||||
|  |  | ||||||
| 		// MARK: - Cards | 		// MARK: - Cards | ||||||
| 		std::array<std::unique_ptr<AppleII::Card>, 7> cards_; | 		std::array<std::unique_ptr<AppleII::Card>, 7> cards_; | ||||||
| 		Cycles cycles_since_card_update_; | 		Cycles cycles_since_card_update_; | ||||||
| @@ -121,11 +135,48 @@ class ConcreteMachine: | |||||||
| 			pick_card_messaging_group(card); | 			pick_card_messaging_group(card); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Memory Map | 		AppleII::DiskIICard *diskii_card() { | ||||||
| 		struct MemoryBlock { | 			return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get()); | ||||||
| 			uint8_t *read_pointer = nullptr; | 		} | ||||||
| 			uint8_t *write_pointer = nullptr; |  | ||||||
| 		} memory_blocks_[4];	// The IO page isn't included. | 		// MARK: - Memory Map. | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			The Apple II's paging mechanisms are byzantine to say the least. Painful is | ||||||
|  | 			another appropriate adjective. | ||||||
|  |  | ||||||
|  | 			On a II and II+ there are five distinct zones of memory: | ||||||
|  |  | ||||||
|  | 			0000 to c000	:	the main block of RAM | ||||||
|  | 			c000 to d000	:	the IO area, including card ROMs | ||||||
|  | 			d000 to e000	:	the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card | ||||||
|  | 			e000 onward		:	the rest of ROM, also potentially replaced with RAM by a language card | ||||||
|  |  | ||||||
|  | 			On a IIe with auxiliary memory the following orthogonal changes also need to be factored in: | ||||||
|  |  | ||||||
|  | 			0000 to 0200 	:	can be paged independently of the rest of RAM, other than part of the language card area which pages with it | ||||||
|  | 			0400 to 0800	:	the text screen, can be configured to write to auxiliary RAM | ||||||
|  | 			2000 to 4000	:	the graphics screen, which can be configured to write to auxiliary RAM | ||||||
|  | 			c100 to d000	:	can be used to page an additional 3.75kb of ROM, replacing the IO area | ||||||
|  | 			c300 to c400	:	can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest | ||||||
|  | 			c800 to d000	:	can contain ROM separately from the region below c800 | ||||||
|  |  | ||||||
|  | 			If dealt with as individual blocks in the inner loop, that would therefore imply mapping | ||||||
|  | 			an address to one of 13 potential pageable zones. So I've gone reductive and surrendered | ||||||
|  | 			to paging every 6502 page of memory independently. It makes the paging events more expensive, | ||||||
|  | 			but hopefully more clear. | ||||||
|  | 		*/ | ||||||
|  | 		uint8_t *read_pages_[256];	// each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory | ||||||
|  | 		uint8_t *write_pages_[256];	// as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. | ||||||
|  | 		void page(int start, int end, uint8_t *read, uint8_t *write) { | ||||||
|  | 			for(int position = start; position < end; ++position) { | ||||||
|  | 				read_pages_[position] = read; | ||||||
|  | 				if(read) read += 256; | ||||||
|  |  | ||||||
|  | 				write_pages_[position] = write; | ||||||
|  | 				if(write) write += 256; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// MARK: - The language card. | 		// MARK: - The language card. | ||||||
| 		struct { | 		struct { | ||||||
| @@ -136,29 +187,128 @@ class ConcreteMachine: | |||||||
| 		} language_card_; | 		} language_card_; | ||||||
| 		bool has_language_card_ = true; | 		bool has_language_card_ = true; | ||||||
| 		void set_language_card_paging() { | 		void set_language_card_paging() { | ||||||
| 			if(has_language_card_ && !language_card_.write) { | 			uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; | ||||||
| 				memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; | 			uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data(); | ||||||
| 				memory_blocks_[3].write_pointer = &ram_[56*1024]; |  | ||||||
| 			} else { | 			page(0xd0, 0xe0, | ||||||
| 				memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr; | 				language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom, | ||||||
|  | 				language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]); | ||||||
|  |  | ||||||
|  | 			page(0xe0, 0x100, | ||||||
|  | 				language_card_.read ? &ram[0xe000] : &rom[0x1000], | ||||||
|  | 				language_card_.write ? nullptr : &ram[0xe000]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK - The IIe's ROM controls. | ||||||
|  | 		bool internal_CX_rom_ = false; | ||||||
|  | 		bool slot_C3_rom_ = false; | ||||||
|  | 		bool internal_c8_rom_ = false; | ||||||
|  |  | ||||||
|  | 		void set_card_paging() { | ||||||
|  | 			page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); | ||||||
|  |  | ||||||
|  | 			if(!internal_CX_rom_) { | ||||||
|  | 				if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(has_language_card_ && language_card_.read) { | 			page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr); | ||||||
| 				memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; | 		} | ||||||
| 				memory_blocks_[3].read_pointer = &ram_[56*1024]; |  | ||||||
|  | 		// MARK - The IIe's auxiliary RAM controls. | ||||||
|  | 		bool alternative_zero_page_ = false; | ||||||
|  | 		void set_zero_page_paging() { | ||||||
|  | 			if(alternative_zero_page_) { | ||||||
|  | 				read_pages_[0] = aux_ram_; | ||||||
| 			} else { | 			} else { | ||||||
| 				memory_blocks_[2].read_pointer = rom_.data(); | 				read_pages_[0] = ram_; | ||||||
| 				memory_blocks_[3].read_pointer = rom_.data() + 0x1000; | 			} | ||||||
|  | 			read_pages_[1] = read_pages_[0] + 256; | ||||||
|  | 			write_pages_[0] = read_pages_[0]; | ||||||
|  | 			write_pages_[1] = read_pages_[1]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool read_auxiliary_memory_ = false; | ||||||
|  | 		bool write_auxiliary_memory_ = false; | ||||||
|  | 		void set_main_paging() { | ||||||
|  | 			page(0x02, 0xc0, | ||||||
|  | 				read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200], | ||||||
|  | 				write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]); | ||||||
|  |  | ||||||
|  | 			if(video_ && video_->get_80_store()) { | ||||||
|  | 				bool use_aux_ram = video_->get_page2(); | ||||||
|  | 				page(0x04, 0x08, | ||||||
|  | 					use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400], | ||||||
|  | 					use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]); | ||||||
|  |  | ||||||
|  | 				if(video_->get_high_resolution()) { | ||||||
|  | 					page(0x20, 0x40, | ||||||
|  | 						use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000], | ||||||
|  | 						use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK - typing | 		// MARK - typing | ||||||
| 		std::unique_ptr<Utility::StringSerialiser> string_serialiser_; | 		std::unique_ptr<Utility::StringSerialiser> string_serialiser_; | ||||||
|  |  | ||||||
|  | 		// MARK - joysticks | ||||||
|  | 		class Joystick: public Inputs::ConcreteJoystick { | ||||||
|  | 			public: | ||||||
|  | 				Joystick() : | ||||||
|  | 					ConcreteJoystick({ | ||||||
|  | 						Input(Input::Horizontal), | ||||||
|  | 						Input(Input::Vertical), | ||||||
|  |  | ||||||
|  | 						// The Apple II offers three buttons between two joysticks; | ||||||
|  | 						// this emulator puts three buttons on each joystick and | ||||||
|  | 						// combines them. | ||||||
|  | 						Input(Input::Fire, 0), | ||||||
|  | 						Input(Input::Fire, 1), | ||||||
|  | 						Input(Input::Fire, 2), | ||||||
|  | 					}) {} | ||||||
|  |  | ||||||
|  | 					void did_set_input(const Input &input, float value) override { | ||||||
|  | 						if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical)) | ||||||
|  | 							axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					void did_set_input(const Input &input, bool value) override { | ||||||
|  | 						if(input.type == Input::Type::Fire && input.info.control.index < 3) { | ||||||
|  | 							buttons[input.info.control.index] = value; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 				bool buttons[3] = {false, false, false}; | ||||||
|  | 				float axes[2] = {0.5f, 0.5f}; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input | ||||||
|  | 		// to begin a charge and discharge cycle **if they are not already charging**. | ||||||
|  | 		// The greater the analogue input, the faster they will charge and therefore the sooner | ||||||
|  | 		// they will discharge. | ||||||
|  | 		// | ||||||
|  | 		// This emulator models that with analogue_charge_ being essentially the amount of time, | ||||||
|  | 		// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue | ||||||
|  | 		// inputs were already partially charged then they gain a bias in analogue_biases_. | ||||||
|  | 		// | ||||||
|  | 		// It's a little indirect, but it means only having to increment the one value in the | ||||||
|  | 		// main loop. | ||||||
|  | 		float analogue_charge_ = 0.0f; | ||||||
|  | 		float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f}; | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | 		bool analogue_channel_is_discharged(size_t channel) { | ||||||
|  | 			return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// The IIe has three keys that are wired directly to the same input as the joystick buttons. | ||||||
|  | 		bool open_apple_is_pressed_ = false; | ||||||
|  | 		bool closed_apple_is_pressed_ = false; | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(): | 		ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): | ||||||
| 		 	m6502_(*this), | 			m6502_(*this), | ||||||
| 		 	video_bus_handler_(ram_), | 		 	video_bus_handler_(ram_, aux_ram_), | ||||||
| 		 	audio_toggle_(audio_queue_), | 		 	audio_toggle_(audio_queue_), | ||||||
| 		 	speaker_(audio_toggle_) { | 		 	speaker_(audio_toggle_) { | ||||||
| 		 	// The system's master clock rate. | 		 	// The system's master clock rate. | ||||||
| @@ -180,6 +330,66 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			// Also, start with randomised memory contents. | 			// Also, start with randomised memory contents. | ||||||
| 			Memory::Fuzz(ram_, sizeof(ram_)); | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
|  | 			Memory::Fuzz(aux_ram_, sizeof(aux_ram_)); | ||||||
|  |  | ||||||
|  | 			// Add a couple of joysticks. | ||||||
|  | 		 	joysticks_.emplace_back(new Joystick); | ||||||
|  | 		 	joysticks_.emplace_back(new Joystick); | ||||||
|  |  | ||||||
|  | 			// Pick the required ROMs. | ||||||
|  | 			using Target = Analyser::Static::AppleII::Target; | ||||||
|  | 			std::vector<std::string> rom_names; | ||||||
|  | 			size_t rom_size = 12*1024; | ||||||
|  | 			switch(target.model) { | ||||||
|  | 				default: | ||||||
|  | 					rom_names.push_back("apple2-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2o.rom"); | ||||||
|  | 				break; | ||||||
|  | 				case Target::Model::IIplus: | ||||||
|  | 					rom_names.push_back("apple2-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2.rom"); | ||||||
|  | 				break; | ||||||
|  | 				case Target::Model::IIe: | ||||||
|  | 					rom_size += 3840; | ||||||
|  | 					rom_names.push_back("apple2eu-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2eu.rom"); | ||||||
|  | 				break; | ||||||
|  | 				case Target::Model::EnhancedIIe: | ||||||
|  | 					rom_size += 3840; | ||||||
|  | 					rom_names.push_back("apple2e-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2e.rom"); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			const auto roms = rom_fetcher("AppleII", rom_names); | ||||||
|  |  | ||||||
|  | 			if(!roms[0] || !roms[1]) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			rom_ = std::move(*roms[1]); | ||||||
|  | 			if(rom_.size() > rom_size) { | ||||||
|  | 				rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			character_rom_ = std::move(*roms[0]); | ||||||
|  |  | ||||||
|  | 			if(target.disk_controller != Target::DiskController::None) { | ||||||
|  | 				// Apple recommended slot 6 for the (first) Disk II. | ||||||
|  | 				install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Set up the default memory blocks. On a II or II+ these values will never change. | ||||||
|  | 			// On a IIe they'll be affected by selection of auxiliary RAM. | ||||||
|  | 			set_main_paging(); | ||||||
|  | 			set_zero_page_paging(); | ||||||
|  |  | ||||||
|  | 			// Set the whole card area to initially backed by nothing. | ||||||
|  | 			page(0xc0, 0xd0, nullptr, nullptr); | ||||||
|  |  | ||||||
|  | 			// Set proper values for the language card/ROM area. | ||||||
|  | 			set_language_card_paging(); | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -187,7 +397,7 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override { | 		void setup_output(float aspect_ratio) override { | ||||||
| 			video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_)); | 			video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie()>(video_bus_handler_)); | ||||||
| 			video_->set_character_rom(character_rom_); | 			video_->set_character_rom(character_rom_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -203,7 +413,7 @@ class ConcreteMachine: | |||||||
| 			return &speaker_; | 			return &speaker_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) { | ||||||
| 			++ cycles_since_video_update_; | 			++ cycles_since_video_update_; | ||||||
| 			++ cycles_since_card_update_; | 			++ cycles_since_card_update_; | ||||||
| 			cycles_since_audio_update_ += Cycles(7); | 			cycles_since_audio_update_ += Cycles(7); | ||||||
| @@ -221,30 +431,23 @@ class ConcreteMachine: | |||||||
| 				++ stretched_cycles_since_card_update_; | 				++ stretched_cycles_since_card_update_; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 				There are five distinct zones of memory on an Apple II: |  | ||||||
|  |  | ||||||
| 				0000 to 0200	:	the zero and stack pages, which can be paged independently on a IIe |  | ||||||
| 				0200 to c000	:	the main block of RAM, which can be paged on a IIe |  | ||||||
| 				c000 to d000	:	the IO area, including card ROMs |  | ||||||
| 				d000 to e000	:	the low ROM area, which can contain indepdently-paged RAM with a language card |  | ||||||
| 				e000 onward		:	the rest of ROM, also potentially replaced with RAM by a language card |  | ||||||
| 			*/ |  | ||||||
| 			MemoryBlock *block = nullptr; |  | ||||||
| 			if(address < 0x200) block = &memory_blocks_[0]; |  | ||||||
| 			else if(address < 0xc000) { |  | ||||||
| 				if(address < 0x6000 && !isReadOperation(operation)) update_video(); |  | ||||||
| 				block = &memory_blocks_[1]; |  | ||||||
| 				address -= 0x200; |  | ||||||
| 			} |  | ||||||
| 			else if(address < 0xd000) block = nullptr; |  | ||||||
| 			else if(address < 0xe000) {block = &memory_blocks_[2]; address -= 0xd000; } |  | ||||||
| 			else { block = &memory_blocks_[3]; address -= 0xe000; } |  | ||||||
|  |  | ||||||
| 			bool has_updated_cards = false; | 			bool has_updated_cards = false; | ||||||
| 			if(block) { | 			if(read_pages_[address >> 8]) { | ||||||
| 				if(isReadOperation(operation)) *value = block->read_pointer[address]; | 				if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; | ||||||
| 				else if(block->write_pointer) block->write_pointer[address] = *value; | 				else { | ||||||
|  | 					if(address >= 0x200 && address < 0x6000) update_video(); | ||||||
|  | 					if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if(is_iie() && address >= 0xc300 && address < 0xd000) { | ||||||
|  | 					bool internal_c8_rom = internal_c8_rom_; | ||||||
|  | 					internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_; | ||||||
|  | 					internal_c8_rom &= (address != 0xcfff); | ||||||
|  | 					if(internal_c8_rom != internal_c8_rom_) { | ||||||
|  | 						internal_c8_rom_ = internal_c8_rom; | ||||||
|  | 						set_card_paging(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				// Assume a vapour read unless it turns out otherwise; this is a little | 				// Assume a vapour read unless it turns out otherwise; this is a little | ||||||
| 				// wasteful but works for now. | 				// wasteful but works for now. | ||||||
| @@ -271,27 +474,171 @@ class ConcreteMachine: | |||||||
| 								default: break; | 								default: break; | ||||||
|  |  | ||||||
| 								case 0xc000: | 								case 0xc000: | ||||||
| 									if(string_serialiser_) { | 									*value = get_keyboard_input(); | ||||||
| 										*value = string_serialiser_->head() | 0x80; | 								break; | ||||||
| 									} else { | 								case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007: | ||||||
| 										*value = keyboard_input_; | 								case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f: | ||||||
|  | 									*value = (*value & 0x80) | (get_keyboard_input() & 0x7f); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case 0xc061:	// Switch input 0. | ||||||
|  | 									*value &= 0x7f; | ||||||
|  | 									if( | ||||||
|  | 										static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] || | ||||||
|  | 										(is_iie() && open_apple_is_pressed_) | ||||||
|  | 									) | ||||||
|  | 										*value |= 0x80; | ||||||
|  | 								break; | ||||||
|  | 								case 0xc062:	// Switch input 1. | ||||||
|  | 									*value &= 0x7f; | ||||||
|  | 									if( | ||||||
|  | 										static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] || | ||||||
|  | 										(is_iie() && closed_apple_is_pressed_) | ||||||
|  | 									) | ||||||
|  | 										*value |= 0x80; | ||||||
|  | 								break; | ||||||
|  | 								case 0xc063:	// Switch input 2. | ||||||
|  | 									*value &= 0x7f; | ||||||
|  | 									if(static_cast<Joystick *>(joysticks_[0].get())->buttons[2] || static_cast<Joystick *>(joysticks_[1].get())->buttons[0]) | ||||||
|  | 										*value |= 0x80; | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case 0xc064:	// Analogue input 0. | ||||||
|  | 								case 0xc065:	// Analogue input 1. | ||||||
|  | 								case 0xc066:	// Analogue input 2. | ||||||
|  | 								case 0xc067: {	// Analogue input 3. | ||||||
|  | 									const size_t input = address - 0xc064; | ||||||
|  | 									*value &= 0x7f; | ||||||
|  | 									if(!analogue_channel_is_discharged(input)) { | ||||||
|  | 										*value |= 0x80; | ||||||
| 									} | 									} | ||||||
|  | 								} break; | ||||||
|  |  | ||||||
|  | 								// The IIe-only state reads follow... | ||||||
|  | #define IIeSwitchRead(s)	*value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); | ||||||
|  | 								case 0xc011:	IIeSwitchRead(language_card_.bank1);										break; | ||||||
|  | 								case 0xc012:	IIeSwitchRead(language_card_.read);											break; | ||||||
|  | 								case 0xc013:	IIeSwitchRead(read_auxiliary_memory_);										break; | ||||||
|  | 								case 0xc014:	IIeSwitchRead(write_auxiliary_memory_);										break; | ||||||
|  | 								case 0xc015:	IIeSwitchRead(internal_CX_rom_);											break; | ||||||
|  | 								case 0xc016:	IIeSwitchRead(alternative_zero_page_);										break; | ||||||
|  | 								case 0xc017:	IIeSwitchRead(slot_C3_rom_);												break; | ||||||
|  | 								case 0xc018:	IIeSwitchRead(video_->get_80_store());										break; | ||||||
|  | 								case 0xc019:	IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_));	break; | ||||||
|  | 								case 0xc01a:	IIeSwitchRead(video_->get_text());											break; | ||||||
|  | 								case 0xc01b:	IIeSwitchRead(video_->get_mixed());											break; | ||||||
|  | 								case 0xc01c:	IIeSwitchRead(video_->get_page2());											break; | ||||||
|  | 								case 0xc01d:	IIeSwitchRead(video_->get_high_resolution());								break; | ||||||
|  | 								case 0xc01e:	IIeSwitchRead(video_->get_alternative_character_set());						break; | ||||||
|  | 								case 0xc01f:	IIeSwitchRead(video_->get_80_columns());									break; | ||||||
|  | #undef IIeSwitchRead | ||||||
|  |  | ||||||
|  | 								case 0xc07f: | ||||||
|  | 									if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00); | ||||||
| 								break; | 								break; | ||||||
| 							} | 							} | ||||||
| 						} else { | 						} else { | ||||||
| 							// Write-only switches. | 							// Write-only switches. All IIe as currently implemented. | ||||||
|  | 							if(is_iie()) { | ||||||
|  | 								switch(address) { | ||||||
|  | 									default: break; | ||||||
|  |  | ||||||
|  | 									case 0xc000: | ||||||
|  | 									case 0xc001: | ||||||
|  | 										update_video(); | ||||||
|  | 										video_->set_80_store(!!(address&1)); | ||||||
|  | 										set_main_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc002: | ||||||
|  | 									case 0xc003: | ||||||
|  | 										read_auxiliary_memory_ = !!(address&1); | ||||||
|  | 										set_main_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc004: | ||||||
|  | 									case 0xc005: | ||||||
|  | 										write_auxiliary_memory_ = !!(address&1); | ||||||
|  | 										set_main_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc006: | ||||||
|  | 									case 0xc007: | ||||||
|  | 										internal_CX_rom_ = !!(address&1); | ||||||
|  | 										set_card_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc008: | ||||||
|  | 									case 0xc009: | ||||||
|  | 										// The alternative zero page setting affects both bank 0 and any RAM | ||||||
|  | 										// that's paged as though it were on a language card. | ||||||
|  | 										alternative_zero_page_ = !!(address&1); | ||||||
|  | 										set_zero_page_paging(); | ||||||
|  | 										set_language_card_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc00a: | ||||||
|  | 									case 0xc00b: | ||||||
|  | 										slot_C3_rom_ = !!(address&1); | ||||||
|  | 										set_card_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc00c: | ||||||
|  | 									case 0xc00d: | ||||||
|  | 										update_video(); | ||||||
|  | 										video_->set_80_columns(!!(address&1)); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc00e: | ||||||
|  | 									case 0xc00f: | ||||||
|  | 										update_video(); | ||||||
|  | 										video_->set_alternative_character_set(!!(address&1)); | ||||||
|  | 									break; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
| 						} | 						} | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
| 					/* Read-write switches. */ | 					case 0xc070: {	// Permit analogue inputs that are currently discharged to begin a charge cycle. | ||||||
| 					case 0xc050:	update_video();		video_->set_graphics_mode();	break; | 									// Ensure those that were still charging retain that state. | ||||||
| 					case 0xc051:	update_video();		video_->set_text_mode();		break; | 						for(size_t c = 0; c < 4; ++c) { | ||||||
| 					case 0xc052:	update_video();		video_->set_mixed_mode(false);	break; | 							if(analogue_channel_is_discharged(c)) { | ||||||
| 					case 0xc053:	update_video();		video_->set_mixed_mode(true);	break; | 								analogue_biases_[c] = 0.0f; | ||||||
| 					case 0xc054:	update_video();		video_->set_video_page(0);		break; | 							} else { | ||||||
| 					case 0xc055:	update_video();		video_->set_video_page(1);		break; | 								analogue_biases_[c] += analogue_charge_; | ||||||
| 					case 0xc056:	update_video();		video_->set_low_resolution();	break; | 							} | ||||||
| 					case 0xc057:	update_video();		video_->set_high_resolution();	break; | 						} | ||||||
|  | 						analogue_charge_ = 0.0f; | ||||||
|  | 					} break; | ||||||
|  |  | ||||||
|  | 					/* Switches triggered by reading or writing. */ | ||||||
|  | 					case 0xc050: | ||||||
|  | 					case 0xc051: | ||||||
|  | 						update_video(); | ||||||
|  | 						video_->set_text(!!(address&1)); | ||||||
|  | 					break; | ||||||
|  | 					case 0xc052:	update_video();		video_->set_mixed(false);			break; | ||||||
|  | 					case 0xc053:	update_video();		video_->set_mixed(true);			break; | ||||||
|  | 					case 0xc054: | ||||||
|  | 					case 0xc055: | ||||||
|  | 						update_video(); | ||||||
|  | 						video_->set_page2(!!(address&1)); | ||||||
|  | 						set_main_paging(); | ||||||
|  | 					break; | ||||||
|  | 					case 0xc056: | ||||||
|  | 					case 0xc057: | ||||||
|  | 						update_video(); | ||||||
|  | 						video_->set_high_resolution(!!(address&1)); | ||||||
|  | 						set_main_paging(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0xc05e: | ||||||
|  | 					case 0xc05f: | ||||||
|  | 						if(is_iie()) { | ||||||
|  | 							update_video(); | ||||||
|  | 							video_->set_annunciator_3(!(address&1)); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 					case 0xc010: | 					case 0xc010: | ||||||
| 						keyboard_input_ &= 0x7f; | 						keyboard_input_ &= 0x7f; | ||||||
| @@ -299,9 +646,15 @@ class ConcreteMachine: | |||||||
| 							if(!string_serialiser_->advance()) | 							if(!string_serialiser_->advance()) | ||||||
| 								string_serialiser_.reset(); | 								string_serialiser_.reset(); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
|  | 						// On the IIe, reading C010 returns additional key info. | ||||||
|  | 						if(is_iie() && isReadOperation(operation)) { | ||||||
|  | 							*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f); | ||||||
|  | 						} | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
| 					case 0xc030: | 					case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037: | ||||||
|  | 					case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f: | ||||||
| 						update_audio(); | 						update_audio(); | ||||||
| 						audio_toggle_.set_output(!audio_toggle_.get_output()); | 						audio_toggle_.set_output(!audio_toggle_.get_output()); | ||||||
| 					break; | 					break; | ||||||
| @@ -330,6 +683,7 @@ class ConcreteMachine: | |||||||
| 						// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." | 						// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." | ||||||
| 						language_card_.pre_write = isReadOperation(operation) ? (address&1) : false; | 						language_card_.pre_write = isReadOperation(operation) ? (address&1) : false; | ||||||
|  |  | ||||||
|  | 						// Apply whatever the net effect of all that is to the memory map. | ||||||
| 						set_language_card_paging(); | 						set_language_card_paging(); | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| @@ -338,7 +692,7 @@ class ConcreteMachine: | |||||||
| 					Communication with cards follows. | 					Communication with cards follows. | ||||||
| 				*/ | 				*/ | ||||||
|  |  | ||||||
| 				if(address >= 0xc090 && address < 0xc800) { | 				if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) { | ||||||
| 					// If this is a card access, figure out which card is at play before determining | 					// If this is a card access, figure out which card is at play before determining | ||||||
| 					// the totality of who needs messaging. | 					// the totality of who needs messaging. | ||||||
| 					size_t card_number = 0; | 					size_t card_number = 0; | ||||||
| @@ -363,7 +717,7 @@ class ConcreteMachine: | |||||||
| 					// If the selected card is a just-in-time card, update the just-in-time cards, | 					// If the selected card is a just-in-time card, update the just-in-time cards, | ||||||
| 					// and then message it specifically. | 					// and then message it specifically. | ||||||
| 					const bool is_read = isReadOperation(operation); | 					const bool is_read = isReadOperation(operation); | ||||||
| 					AppleII::Card *const target = cards_[card_number].get(); | 					AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get(); | ||||||
| 					if(target && !is_every_cycle_card(target)) { | 					if(target && !is_every_cycle_card(target)) { | ||||||
| 						update_just_in_time_cards(); | 						update_just_in_time_cards(); | ||||||
| 						target->perform_bus_operation(select, is_read, address, value); | 						target->perform_bus_operation(select, is_read, address, value); | ||||||
| @@ -390,6 +744,9 @@ class ConcreteMachine: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// Update analogue charge level. | ||||||
|  | 			analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f); | ||||||
|  |  | ||||||
| 			return Cycles(1); | 			return Cycles(1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -400,49 +757,50 @@ class ConcreteMachine: | |||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"AppleII", |  | ||||||
| 				{ |  | ||||||
| 					"apple2o.rom", |  | ||||||
| 					"apple2.rom", |  | ||||||
| 					"apple2-character.rom" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			if(!roms[0] || !roms[1] || !roms[2]) return false; |  | ||||||
|  |  | ||||||
| 			apple2_rom_ = std::move(*roms[0]); |  | ||||||
| 			apple2plus_rom_ = std::move(*roms[1]); |  | ||||||
|  |  | ||||||
| 			character_rom_ = std::move(*roms[2]); |  | ||||||
|  |  | ||||||
| 			rom_fetcher_ = roms_with_names; |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles) override { | 		void run_for(const Cycles cycles) override { | ||||||
| 			m6502_.run_for(cycles); | 			m6502_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void reset_all_keys() override { | ||||||
|  | 			open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_key_pressed(Key key, char value, bool is_pressed) override { | 		void set_key_pressed(Key key, char value, bool is_pressed) override { | ||||||
| 			if(key == Key::F12) { | 			switch(key) { | ||||||
| 				m6502_.set_reset_line(is_pressed); | 				default: break; | ||||||
|  | 				case Key::F12: | ||||||
|  | 					m6502_.set_reset_line(is_pressed); | ||||||
|  | 				return; | ||||||
|  | 				case Key::LeftOption: | ||||||
|  | 					open_apple_is_pressed_ = is_pressed; | ||||||
|  | 				return; | ||||||
|  | 				case Key::RightOption: | ||||||
|  | 					closed_apple_is_pressed_ = is_pressed; | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(is_pressed) { | 			// If no ASCII value is supplied, look for a few special cases. | ||||||
| 				// If no ASCII value is supplied, look for a few special cases. | 			if(!value) { | ||||||
| 				if(!value) { | 				switch(key) { | ||||||
| 					switch(key) { | 					case Key::Left:			value = 0x08;	break; | ||||||
| 						case Key::Left:		value = 8;	break; | 					case Key::Right:		value = 0x15;	break; | ||||||
| 						case Key::Right:	value = 21;	break; | 					case Key::Down:			value = 0x0a;	break; | ||||||
| 						case Key::Down:		value = 10;	break; | 					case Key::Up:			value = 0x0b;	break; | ||||||
| 						default: break; | 					case Key::BackSpace:	value = 0x7f;	break; | ||||||
| 					} | 					default: return; | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 				keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80); | 			// Prior to the IIe, the keyboard could produce uppercase only. | ||||||
|  | 			if(!is_iie()) value = static_cast<char>(toupper(value)); | ||||||
|  |  | ||||||
|  | 			if(is_pressed) { | ||||||
|  | 				keyboard_input_ = static_cast<uint8_t>(value | 0x80); | ||||||
|  | 				key_is_down_ = true; | ||||||
|  | 			} else { | ||||||
|  | 				if((keyboard_input_ & 0x7f) == value) { | ||||||
|  | 					key_is_down_ = false; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -454,32 +812,11 @@ class ConcreteMachine: | |||||||
| 			string_serialiser_.reset(new Utility::StringSerialiser(string, true)); | 			string_serialiser_.reset(new Utility::StringSerialiser(string, true)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: ConfigurationTarget | 		// MARK: MediaTarget | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			using Target = Analyser::Static::AppleII::Target; |  | ||||||
| 			auto *const apple_target = dynamic_cast<const Target *>(target); |  | ||||||
|  |  | ||||||
| 			if(apple_target->disk_controller != Target::DiskController::None) { |  | ||||||
| 				// Apple recommended slot 6 for the (first) Disk II. |  | ||||||
| 				install_card(6, new AppleII::DiskIICard(rom_fetcher_, apple_target->disk_controller == Target::DiskController::SixteenSector)); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			rom_ = (apple_target->model == Target::Model::II) ? apple2_rom_ : apple2plus_rom_; |  | ||||||
| 			if(rom_.size() > 12*1024) { |  | ||||||
| 				rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Set up the default memory blocks. |  | ||||||
| 			memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_; |  | ||||||
| 			memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200]; |  | ||||||
| 			set_language_card_paging(); |  | ||||||
|  |  | ||||||
| 			insert_media(apple_target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| 			if(!media.disks.empty() && cards_[5]) { | 			if(!media.disks.empty()) { | ||||||
| 				dynamic_cast<AppleII::DiskIICard *>(cards_[5].get())->set_disk(media.disks[0], 0); | 				auto diskii = diskii_card(); | ||||||
|  | 				if(diskii) diskii->set_disk(media.disks[0], 0); | ||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @@ -490,14 +827,27 @@ class ConcreteMachine: | |||||||
| 				if(card) card->set_activity_observer(observer); | 				if(card) card->set_activity_observer(observer); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: JoystickMachine | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return joysticks_; | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| using namespace AppleII; | using namespace AppleII; | ||||||
|  |  | ||||||
| Machine *Machine::AppleII() { | Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new ConcreteMachine; | 	using Target = Analyser::Static::AppleII::Target; | ||||||
|  | 	const Target *const appleii_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	switch(appleii_target->model) { | ||||||
|  | 		default: return nullptr; | ||||||
|  | 		case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher); | ||||||
|  | 		case Target::Model::IIplus: return new ConcreteMachine<Target::Model::IIplus>(*appleii_target, rom_fetcher); | ||||||
|  | 		case Target::Model::IIe: return new ConcreteMachine<Target::Model::IIe>(*appleii_target, rom_fetcher); | ||||||
|  | 		case Target::Model::EnhancedIIe: return new ConcreteMachine<Target::Model::EnhancedIIe>(*appleii_target, rom_fetcher); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,6 +9,13 @@ | |||||||
| #ifndef AppleII_hpp | #ifndef AppleII_hpp | ||||||
| #define AppleII_hpp | #define AppleII_hpp | ||||||
|  |  | ||||||
|  | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace AppleII { | namespace AppleII { | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| @@ -16,7 +23,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an AppleII. | 		/// Creates and returns an AppleII. | ||||||
| 		static Machine *AppleII(); | 		static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ | |||||||
|  |  | ||||||
| using namespace AppleII; | using namespace AppleII; | ||||||
|  |  | ||||||
| DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) { | DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { | ||||||
| 	auto roms = rom_fetcher( | 	const auto roms = rom_fetcher( | ||||||
| 		"DiskII", | 		"DiskII", | ||||||
| 		{ | 		{ | ||||||
| 			is_16_sector ? "boot-16.rom" : "boot-13.rom", | 			is_16_sector ? "boot-16.rom" : "boot-13.rom", | ||||||
| @@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec | |||||||
| 	boot_ = std::move(*roms[0]); | 	boot_ = std::move(*roms[0]); | ||||||
| 	diskii_.set_state_machine(*roms[1]); | 	diskii_.set_state_machine(*roms[1]); | ||||||
| 	set_select_constraints(None); | 	set_select_constraints(None); | ||||||
| 	diskii_.set_sleep_observer(this); | 	diskii_.set_clocking_hint_observer(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) { | void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) { | ||||||
| @@ -41,7 +41,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add | |||||||
| } | } | ||||||
|  |  | ||||||
| void DiskIICard::run_for(Cycles cycles, int stretches) { | void DiskIICard::run_for(Cycles cycles, int stretches) { | ||||||
| 	if(diskii_is_sleeping_) return; | 	if(diskii_clocking_preference_ == ClockingHint::Preference::None) return; | ||||||
| 	diskii_.run_for(Cycles(cycles.as_int() * 2)); | 	diskii_.run_for(Cycles(cycles.as_int() * 2)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -53,7 +53,11 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) { | |||||||
| 	diskii_.set_activity_observer(observer); | 	diskii_.set_activity_observer(observer); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { | void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { | ||||||
| 	diskii_is_sleeping_ = is_sleeping; | 	diskii_clocking_preference_ = preference; | ||||||
| 	set_select_constraints(is_sleeping ? (IO | Device) : 0); | 	set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Storage::Disk::Drive &DiskIICard::get_drive(int drive) { | ||||||
|  | 	return diskii_.get_drive(drive); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
|  |  | ||||||
| #include "../../Components/DiskII/DiskII.hpp" | #include "../../Components/DiskII/DiskII.hpp" | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../Storage/Disk/Disk.hpp" | ||||||
| #include "../../ClockReceiver/Sleeper.hpp" | #include "../../ClockReceiver/ClockingHintSource.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <memory> | #include <memory> | ||||||
| @@ -22,7 +22,7 @@ | |||||||
|  |  | ||||||
| namespace AppleII { | namespace AppleII { | ||||||
|  |  | ||||||
| class DiskIICard: public Card, public Sleeper::SleepObserver { | class DiskIICard: public Card, public ClockingHint::Observer { | ||||||
| 	public: | 	public: | ||||||
| 		DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector); | 		DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector); | ||||||
|  |  | ||||||
| @@ -32,12 +32,13 @@ class DiskIICard: public Card, public Sleeper::SleepObserver { | |||||||
| 		void set_activity_observer(Activity::Observer *observer) override; | 		void set_activity_observer(Activity::Observer *observer) override; | ||||||
|  |  | ||||||
| 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | ||||||
|  | 		Storage::Disk::Drive &get_drive(int drive); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override; | ||||||
| 		std::vector<uint8_t> boot_; | 		std::vector<uint8_t> boot_; | ||||||
| 		Apple::DiskII diskii_; | 		Apple::DiskII diskii_; | ||||||
| 		bool diskii_is_sleeping_ = false; | 		ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,122 +10,330 @@ | |||||||
|  |  | ||||||
| using namespace AppleII::Video; | using namespace AppleII::Video; | ||||||
|  |  | ||||||
| namespace { | VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) : | ||||||
|  | 	crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)), | ||||||
|  | 	is_iie_(is_iie), | ||||||
|  | 	deferrer_(std::move(target)) { | ||||||
|  |  | ||||||
| struct ScaledByteFiller { | 	// Set a composite sampling function that assumes one byte per pixel input, and | ||||||
| 	ScaledByteFiller() { | 	// accepts any non-zero value as being fully on, zero being fully off. | ||||||
| 		VideoBase::setup_tables(); |  | ||||||
| 	} |  | ||||||
| } throwaway; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| VideoBase::VideoBase() : |  | ||||||
| 	crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) { |  | ||||||
|  |  | ||||||
| 	// Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte. |  | ||||||
| 	crt_->set_composite_sampling_function( | 	crt_->set_composite_sampling_function( | ||||||
| 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | ||||||
| 		"{" | 		"{" | ||||||
| 			"uint texValue = texture(sampler, coordinate).r;" | 			"return clamp(texture(sampler, coordinate).r, 0.0, 0.7);" | ||||||
| 			"texValue >>= int(icoordinate.x) % 7;" |  | ||||||
| 			"return float(texValue & 1u);" |  | ||||||
| 		"}"); | 		"}"); | ||||||
| 	crt_->set_integer_coordinate_multiplier(7.0f); |  | ||||||
|  |  | ||||||
| 	// Show only the centre 75% of the TV frame. | 	// Show only the centre 75% of the TV frame. | ||||||
| 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
| 	crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f)); | 	crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f)); | ||||||
|  | 	crt_->set_immediate_default_phase(0.0f); | ||||||
|  |  | ||||||
|  | 	character_zones[0].xor_mask = 0; | ||||||
|  | 	character_zones[0].address_mask = 0x3f; | ||||||
|  | 	character_zones[1].xor_mask = 0; | ||||||
|  | 	character_zones[1].address_mask = 0x3f; | ||||||
|  | 	character_zones[2].xor_mask = 0; | ||||||
|  | 	character_zones[2].address_mask = 0x3f; | ||||||
|  | 	character_zones[3].xor_mask = 0; | ||||||
|  | 	character_zones[3].address_mask = 0x3f; | ||||||
|  |  | ||||||
|  | 	if(is_iie) { | ||||||
|  | 		character_zones[0].xor_mask = | ||||||
|  | 		character_zones[2].xor_mask = | ||||||
|  | 		character_zones[3].xor_mask = 0xff; | ||||||
|  | 		character_zones[2].address_mask = | ||||||
|  | 		character_zones[3].address_mask = 0xff; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Outputs::CRT::CRT *VideoBase::get_crt() { | Outputs::CRT::CRT *VideoBase::get_crt() { | ||||||
| 	return crt_.get(); | 	return crt_.get(); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t VideoBase::scaled_byte[256]; | /* | ||||||
| uint16_t VideoBase::low_resolution_patterns[2][16]; | 	Rote setters and getters. | ||||||
|  | */ | ||||||
| void VideoBase::setup_tables() { | void VideoBase::set_alternative_character_set(bool alternative_character_set) { | ||||||
| 	for(int c = 0; c < 128; ++c) { | 	set_alternative_character_set_ = alternative_character_set; | ||||||
| 		const uint16_t value = | 	deferrer_.defer(Cycles(2), [=] { | ||||||
| 			((c & 0x01) ? 0x0003 : 0x0000) | | 		alternative_character_set_ = alternative_character_set; | ||||||
| 			((c & 0x02) ? 0x000c : 0x0000) | | 		if(alternative_character_set) { | ||||||
| 			((c & 0x04) ? 0x0030 : 0x0000) | | 			character_zones[1].address_mask = 0xff; | ||||||
| 			((c & 0x08) ? 0x0140 : 0x0000) | | 			character_zones[1].xor_mask = 0; | ||||||
| 			((c & 0x10) ? 0x0600 : 0x0000) | | 		} else { | ||||||
| 			((c & 0x20) ? 0x1800 : 0x0000) | | 			character_zones[1].address_mask = 0x3f; | ||||||
| 			((c & 0x40) ? 0x6000 : 0x0000); | 			character_zones[1].xor_mask = flash_mask(); | ||||||
|  |  | ||||||
| 		uint8_t *const table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]); |  | ||||||
| 		table_entry[0] = static_cast<uint8_t>(value & 0xff); |  | ||||||
| 		table_entry[1] = static_cast<uint8_t>(value >> 8); |  | ||||||
| 	} |  | ||||||
| 	for(int c = 128; c < 256; ++c) { |  | ||||||
| 		uint8_t *const source_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c & 0x7f]); |  | ||||||
| 		uint8_t *const destination_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]); |  | ||||||
|  |  | ||||||
| 		destination_table_entry[0] = static_cast<uint8_t>(source_table_entry[0] << 1); |  | ||||||
| 		destination_table_entry[1] = static_cast<uint8_t>((source_table_entry[1] << 1) | (source_table_entry[0] >> 6)); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for(int c = 0; c < 16; ++c) { |  | ||||||
| 		// Produce the whole 28-bit pattern that would cover two columns. |  | ||||||
| 		const int reversed_c = ((c&0x1) ? 0x8 : 0x0) | ((c&0x2) ? 0x4 : 0x0) | ((c&0x4) ? 0x2 : 0x0) | ((c&0x8) ? 0x1 : 0x0); |  | ||||||
| 		int pattern = 0; |  | ||||||
| 		for(int l = 0; l < 7; ++l) { |  | ||||||
| 			pattern <<= 4; |  | ||||||
| 			pattern |= reversed_c; |  | ||||||
| 		} | 		} | ||||||
|  | 	}); | ||||||
| 		// Pack that 28-bit pattern into the appropriate look-up tables. |  | ||||||
| 		uint8_t *const left_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[0][c]); |  | ||||||
| 		uint8_t *const right_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[1][c]); |  | ||||||
| 		left_entry[0] = static_cast<uint8_t>(pattern);; |  | ||||||
| 		left_entry[1] = static_cast<uint8_t>(pattern >> 7); |  | ||||||
| 		right_entry[0] = static_cast<uint8_t>(pattern >> 14); |  | ||||||
| 		right_entry[1] = static_cast<uint8_t>(pattern >> 21); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_graphics_mode() { | bool VideoBase::get_alternative_character_set() { | ||||||
| 	use_graphics_mode_ = true; | 	return set_alternative_character_set_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_text_mode() { | void VideoBase::set_80_columns(bool columns_80) { | ||||||
| 	use_graphics_mode_ = false; | 	set_columns_80_ = columns_80; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		columns_80_ = columns_80; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_mixed_mode(bool mixed_mode) { | bool VideoBase::get_80_columns() { | ||||||
| 	mixed_mode_ = mixed_mode; | 	return set_columns_80_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_video_page(int page) { | void VideoBase::set_80_store(bool store_80) { | ||||||
| 	video_page_ = page; | 	set_store_80_ = store_80_ = store_80; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_low_resolution() { | bool VideoBase::get_80_store() { | ||||||
| 	graphics_mode_ = GraphicsMode::LowRes; | 	return set_store_80_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_high_resolution() { | void VideoBase::set_page2(bool page2) { | ||||||
| 	graphics_mode_ = GraphicsMode::HighRes; | 	set_page2_ = page2_ = page2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_page2() { | ||||||
|  | 	return set_page2_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_text(bool text) { | ||||||
|  | 	set_text_ = text; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		text_ = text; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_text() { | ||||||
|  | 	return set_text_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_mixed(bool mixed) { | ||||||
|  | 	set_mixed_ = mixed; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		mixed_ = mixed; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_mixed() { | ||||||
|  | 	return set_mixed_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_high_resolution(bool high_resolution) { | ||||||
|  | 	set_high_resolution_ = high_resolution; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		high_resolution_ = high_resolution; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_high_resolution() { | ||||||
|  | 	return set_high_resolution_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_annunciator_3(bool annunciator_3) { | ||||||
|  | 	set_annunciator_3_ = annunciator_3; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		annunciator_3_ = annunciator_3; | ||||||
|  | 		high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_annunciator_3() { | ||||||
|  | 	return set_annunciator_3_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) { | void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) { | ||||||
| 	character_rom_ = character_rom; | 	character_rom_ = character_rom; | ||||||
|  |  | ||||||
| 	// Bytes in the character ROM are stored in reverse bit order. Reverse them | 	// Flip all character contents based on the second line of the $ graphic. | ||||||
| 	// ahead of time so as to be able to use the same scaling table as for | 	if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) { | ||||||
| 	// high-resolution graphics. | 		for(auto &graphic : character_rom_) { | ||||||
| 	for(auto &byte : character_rom_) { | 			graphic = | ||||||
| 		byte = | 				((graphic & 0x01) ? 0x40 : 0x00) | | ||||||
| 			((byte & 0x40) ? 0x01 : 0x00) | | 				((graphic & 0x02) ? 0x20 : 0x00) | | ||||||
| 			((byte & 0x20) ? 0x02 : 0x00) | | 				((graphic & 0x04) ? 0x10 : 0x00) | | ||||||
| 			((byte & 0x10) ? 0x04 : 0x00) | | 				((graphic & 0x08) ? 0x08 : 0x00) | | ||||||
| 			((byte & 0x08) ? 0x08 : 0x00) | | 				((graphic & 0x10) ? 0x04 : 0x00) | | ||||||
| 			((byte & 0x04) ? 0x10 : 0x00) | | 				((graphic & 0x20) ? 0x02 : 0x00) | | ||||||
| 			((byte & 0x02) ? 0x20 : 0x00) | | 				((graphic & 0x40) ? 0x01 : 0x00); | ||||||
| 			((byte & 0x01) ? 0x40 : 0x00) | | 		} | ||||||
| 			(byte & 0x80); | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		const int character = source[c] & character_zones[source[c] >> 6].address_mask; | ||||||
|  | 		const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask; | ||||||
|  | 		const std::size_t character_address = static_cast<std::size_t>(character << 3) + pixel_row; | ||||||
|  | 		const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask; | ||||||
|  |  | ||||||
|  | 		// The character ROM is output MSB to LSB rather than LSB to MSB. | ||||||
|  | 		target[0] = target[1] = character_pattern & 0x40; | ||||||
|  | 		target[2] = target[3] = character_pattern & 0x20; | ||||||
|  | 		target[4] = target[5] = character_pattern & 0x10; | ||||||
|  | 		target[6] = target[7] = character_pattern & 0x08; | ||||||
|  | 		target[8] = target[9] = character_pattern & 0x04; | ||||||
|  | 		target[10] = target[11] = character_pattern & 0x02; | ||||||
|  | 		target[12] = target[13] = character_pattern & 0x01; | ||||||
|  | 		graphics_carry_ = character_pattern & 0x01; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, size_t pixel_row) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		const std::size_t character_addresses[2] = { | ||||||
|  | 			static_cast<std::size_t>( | ||||||
|  | 				(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3 | ||||||
|  | 			) + pixel_row, | ||||||
|  | 			static_cast<std::size_t>( | ||||||
|  | 				(source[c] & character_zones[source[c] >> 6].address_mask) << 3 | ||||||
|  | 			) + pixel_row | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const uint8_t character_patterns[2] = { | ||||||
|  | 			static_cast<uint8_t>( | ||||||
|  | 				character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask | ||||||
|  | 			), | ||||||
|  | 			static_cast<uint8_t>( | ||||||
|  | 				character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask | ||||||
|  | 			) | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// The character ROM is output MSB to LSB rather than LSB to MSB. | ||||||
|  | 		target[0] = character_patterns[0] & 0x40; | ||||||
|  | 		target[1] = character_patterns[0] & 0x20; | ||||||
|  | 		target[2] = character_patterns[0] & 0x10; | ||||||
|  | 		target[3] = character_patterns[0] & 0x08; | ||||||
|  | 		target[4] = character_patterns[0] & 0x04; | ||||||
|  | 		target[5] = character_patterns[0] & 0x02; | ||||||
|  | 		target[6] = character_patterns[0] & 0x01; | ||||||
|  | 		target[7] = character_patterns[1] & 0x40; | ||||||
|  | 		target[8] = character_patterns[1] & 0x20; | ||||||
|  | 		target[9] = character_patterns[1] & 0x10; | ||||||
|  | 		target[10] = character_patterns[1] & 0x08; | ||||||
|  | 		target[11] = character_patterns[1] & 0x04; | ||||||
|  | 		target[12] = character_patterns[1] & 0x02; | ||||||
|  | 		target[13] = character_patterns[1] & 0x01; | ||||||
|  | 		graphics_carry_ = character_patterns[1] & 0x01; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const { | ||||||
|  | 	const int row_shift = row&4; | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this | ||||||
|  | 		// 14-sample output window is starting at the beginning of a colour cycle or halfway through. | ||||||
|  | 		if((column + static_cast<int>(c))&1) { | ||||||
|  | 			target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8; | ||||||
|  | 			target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 8; | ||||||
|  | 		} else { | ||||||
|  | 			target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2; | ||||||
|  | 			target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 2; | ||||||
|  | 		} | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_fat_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const { | ||||||
|  | 	const int row_shift = row&4; | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		// Fat low-resolution mode appears not to do anything to try to make odd and | ||||||
|  | 		// even columns compatible. | ||||||
|  | 		target[0] = target[1] = target[8] = target[9] = (source[c] >> row_shift) & 1; | ||||||
|  | 		target[2] = target[3] = target[10] = target[11] = (source[c] >> row_shift) & 2; | ||||||
|  | 		target[4] = target[5] = target[12] = target[13] = (source[c] >> row_shift) & 4; | ||||||
|  | 		target[6] = target[7] = (source[c] >> row_shift) & 8; | ||||||
|  | 		graphics_carry_ = (source[c] >> row_shift) & 4; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, int column, int row) const { | ||||||
|  | 	const int row_shift = row&4; | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		if((column + static_cast<int>(c))&1) { | ||||||
|  | 			target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2; | ||||||
|  | 			target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4; | ||||||
|  | 			target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8; | ||||||
|  | 			target[3] = (auxiliary_source[c] >> row_shift) & 1; | ||||||
|  |  | ||||||
|  | 			target[8] = target[12] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[9] = target[13] = (source[c] >> row_shift) & 8; | ||||||
|  | 			target[10] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[7] = target[11] = (source[c] >> row_shift) & 2; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 8; | ||||||
|  | 		} else { | ||||||
|  | 			target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8; | ||||||
|  | 			target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1; | ||||||
|  | 			target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2; | ||||||
|  | 			target[3] = (auxiliary_source[c] >> row_shift) & 4; | ||||||
|  |  | ||||||
|  | 			target[8] = target[12] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[9] = target[13] = (source[c] >> row_shift) & 2; | ||||||
|  | 			target[10] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[7] = target[11] = (source[c] >> row_shift) & 8; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 2; | ||||||
|  | 		} | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_high_resolution(uint8_t *target, const uint8_t *const source, size_t length) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel. | ||||||
|  | 		// If there is a delay, the previous output level is held to bridge the gap. | ||||||
|  | 		// Delays may be ignored on a IIe if Annunciator 3 is set; that's the state that | ||||||
|  | 		// high_resolution_mask_ models. | ||||||
|  | 		if(source[c] & high_resolution_mask_ & 0x80) { | ||||||
|  | 			target[0] = graphics_carry_; | ||||||
|  | 			target[1] = target[2] = source[c] & 0x01; | ||||||
|  | 			target[3] = target[4] = source[c] & 0x02; | ||||||
|  | 			target[5] = target[6] = source[c] & 0x04; | ||||||
|  | 			target[7] = target[8] = source[c] & 0x08; | ||||||
|  | 			target[9] = target[10] = source[c] & 0x10; | ||||||
|  | 			target[11] = target[12] = source[c] & 0x20; | ||||||
|  | 			target[13] = source[c] & 0x40; | ||||||
|  | 		} else { | ||||||
|  | 			target[0] = target[1] = source[c] & 0x01; | ||||||
|  | 			target[2] = target[3] = source[c] & 0x02; | ||||||
|  | 			target[4] = target[5] = source[c] & 0x04; | ||||||
|  | 			target[6] = target[7] = source[c] & 0x08; | ||||||
|  | 			target[8] = target[9] = source[c] & 0x10; | ||||||
|  | 			target[10] = target[11] = source[c] & 0x20; | ||||||
|  | 			target[12] = target[13] = source[c] & 0x40; | ||||||
|  | 		} | ||||||
|  | 		graphics_carry_ = source[c] & 0x40; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_double_high_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		target[0] = auxiliary_source[c] & 0x01; | ||||||
|  | 		target[1] = auxiliary_source[c] & 0x02; | ||||||
|  | 		target[2] = auxiliary_source[c] & 0x04; | ||||||
|  | 		target[3] = auxiliary_source[c] & 0x08; | ||||||
|  | 		target[4] = auxiliary_source[c] & 0x10; | ||||||
|  | 		target[5] = auxiliary_source[c] & 0x20; | ||||||
|  | 		target[6] = auxiliary_source[c] & 0x40; | ||||||
|  | 		target[7] = source[c] & 0x01; | ||||||
|  | 		target[8] = source[c] & 0x02; | ||||||
|  | 		target[9] = source[c] & 0x04; | ||||||
|  | 		target[10] = source[c] & 0x08; | ||||||
|  | 		target[11] = source[c] & 0x10; | ||||||
|  | 		target[12] = source[c] & 0x20; | ||||||
|  | 		target[13] = source[c] & 0x40; | ||||||
|  |  | ||||||
|  | 		graphics_carry_ = auxiliary_source[c] & 0x40; | ||||||
|  | 		target += 14; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,7 +11,9 @@ | |||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" | #include "../../Outputs/CRT/CRT.hpp" | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockDeferrer.hpp" | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace AppleII { | namespace AppleII { | ||||||
| @@ -19,26 +21,126 @@ namespace Video { | |||||||
|  |  | ||||||
| class BusHandler { | class BusHandler { | ||||||
| 	public: | 	public: | ||||||
| 		uint8_t perform_read(uint16_t address) { | 		/*! | ||||||
| 			return 0xff; | 			Requests fetching of the @c count bytes starting from @c address. | ||||||
|  |  | ||||||
|  | 			The handler should write the values from base memory to @c base_target, and those | ||||||
|  | 			from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory, | ||||||
|  | 			it needn't write anything to auxiliary_target. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class VideoBase { | class VideoBase { | ||||||
| 	public: | 	public: | ||||||
| 		VideoBase(); | 		VideoBase(bool is_iie, std::function<void(Cycles)> &&target); | ||||||
| 		static void setup_tables(); |  | ||||||
|  |  | ||||||
| 		/// @returns The CRT this video feed is feeding. | 		/// @returns The CRT this video feed is feeding. | ||||||
| 		Outputs::CRT::CRT *get_crt(); | 		Outputs::CRT::CRT *get_crt(); | ||||||
|  |  | ||||||
| 		// Inputs for the various soft switches. | 		/* | ||||||
| 		void set_graphics_mode(); | 			Descriptions for the setters below are taken verbatim from | ||||||
| 		void set_text_mode(); | 			the Apple IIe Technical Reference. Addresses are the conventional | ||||||
| 		void set_mixed_mode(bool); | 			locations within the Apple II memory map. Only those which affect | ||||||
| 		void set_video_page(int); | 			video output are implemented here. | ||||||
| 		void set_low_resolution(); |  | ||||||
| 		void set_high_resolution(); | 			Those registers which don't exist on a II/II+ are marked. | ||||||
|  | 		*/ | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for ALTCHAR ($C00E/$C00F; triggers on write only): | ||||||
|  |  | ||||||
|  | 			* Off: display text using primary character set. | ||||||
|  | 			* On: display text using alternate character set. | ||||||
|  |  | ||||||
|  | 			Doesn't exist on a II/II+. | ||||||
|  | 		*/ | ||||||
|  | 		void set_alternative_character_set(bool); | ||||||
|  | 		bool get_alternative_character_set(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for 80COL ($C00C/$C00D; triggers on write only). | ||||||
|  |  | ||||||
|  | 			* Off: display 40 columns. | ||||||
|  | 			* On: display 80 columns. | ||||||
|  |  | ||||||
|  | 			Doesn't exist on a II/II+. | ||||||
|  | 		*/ | ||||||
|  | 		void set_80_columns(bool); | ||||||
|  | 		bool get_80_columns(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for 80STORE ($C000/$C001; triggers on write only). | ||||||
|  |  | ||||||
|  | 			* Off: cause PAGE2 to select auxiliary RAM. | ||||||
|  | 			* On: cause PAGE2 to switch main RAM areas. | ||||||
|  |  | ||||||
|  | 			Doesn't exist on a II/II+. | ||||||
|  | 		*/ | ||||||
|  | 		void set_80_store(bool); | ||||||
|  | 		bool get_80_store(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for PAGE2 ($C054/$C055; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: select Page 1. | ||||||
|  | 			* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. | ||||||
|  |  | ||||||
|  | 			80STORE doesn't exist on a II/II+; therefore this always selects | ||||||
|  | 			either Page 1 or Page 2 on those machines. | ||||||
|  | 		*/ | ||||||
|  | 		void set_page2(bool); | ||||||
|  | 		bool get_page2(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for TEXT ($C050/$C051; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: display graphics or, if MIXED on, mixed. | ||||||
|  | 			* On: display text. | ||||||
|  | 		*/ | ||||||
|  | 		void set_text(bool); | ||||||
|  | 		bool get_text(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for MIXED ($C052/$C053; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: display only text or only graphics. | ||||||
|  | 			* On: if TEXT off, display text and graphics. | ||||||
|  | 		*/ | ||||||
|  | 		void set_mixed(bool); | ||||||
|  | 		bool get_mixed(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for HIRES ($C056/$C057; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: if TEXT off, display low-resolution graphics. | ||||||
|  | 			* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. | ||||||
|  |  | ||||||
|  | 			DHIRES doesn't exist on a II/II+; therefore this always selects | ||||||
|  | 			either high- or low-resolution graphics on those machines. | ||||||
|  |  | ||||||
|  | 			Despite Apple's documentation, the IIe also supports double low-resolution | ||||||
|  | 			graphics, which are the 80-column analogue to ordinary low-resolution 40-column | ||||||
|  | 			low-resolution graphics. | ||||||
|  | 		*/ | ||||||
|  | 		void set_high_resolution(bool); | ||||||
|  | 		bool get_high_resolution(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for annunciator 3. | ||||||
|  |  | ||||||
|  | 			* On: turn on annunciator 3. | ||||||
|  | 			* Off: turn off annunciator 3. | ||||||
|  |  | ||||||
|  | 			This exists on both the II/II+ and the IIe, but has no effect on | ||||||
|  | 			video on the older machines. It's intended to be used on the IIe | ||||||
|  | 			to confirm double-high resolution mode but has side effects in | ||||||
|  | 			selecting mixed mode output and discarding high-resolution | ||||||
|  | 			delay bits. | ||||||
|  | 		*/ | ||||||
|  | 		void set_annunciator_3(bool); | ||||||
|  | 		bool get_annunciator_3(); | ||||||
|  |  | ||||||
| 		// Setup for text mode. | 		// Setup for text mode. | ||||||
| 		void set_character_rom(const std::vector<uint8_t> &); | 		void set_character_rom(const std::vector<uint8_t> &); | ||||||
| @@ -46,167 +148,121 @@ class VideoBase { | |||||||
| 	protected: | 	protected: | ||||||
| 		std::unique_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
| 		int video_page_ = 0; | 		// State affecting output video stream generation. | ||||||
|  | 		uint8_t *pixel_pointer_ = nullptr; | ||||||
|  |  | ||||||
|  | 		// State affecting logical state. | ||||||
| 		int row_ = 0, column_ = 0, flash_ = 0; | 		int row_ = 0, column_ = 0, flash_ = 0; | ||||||
| 		uint16_t *pixel_pointer_ = nullptr; | 		uint8_t flash_mask() { | ||||||
|  | 			return static_cast<uint8_t>((flash_ / flash_length) * 0xff); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Enumerates all Apple II and IIe display modes. | ||||||
|  | 		enum class GraphicsMode { | ||||||
|  | 			Text = 0, | ||||||
|  | 			DoubleText, | ||||||
|  | 			HighRes, | ||||||
|  | 			DoubleHighRes, | ||||||
|  | 			LowRes, | ||||||
|  | 			DoubleLowRes, | ||||||
|  | 			FatLowRes | ||||||
|  | 		}; | ||||||
|  | 		bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; } | ||||||
|  | 		bool is_double_mode(GraphicsMode m) { return !!(static_cast<int>(m)&1); } | ||||||
|  |  | ||||||
|  | 		// Various soft-switch values. | ||||||
|  | 		bool alternative_character_set_ = false, set_alternative_character_set_ = false; | ||||||
|  | 		bool columns_80_ = false, set_columns_80_ = false; | ||||||
|  | 		bool store_80_ = false, set_store_80_ = false; | ||||||
|  | 		bool page2_ = false, set_page2_ = false; | ||||||
|  | 		bool text_ = true, set_text_ = true; | ||||||
|  | 		bool mixed_ = false, set_mixed_ = false; | ||||||
|  | 		bool high_resolution_ = false, set_high_resolution_ = false; | ||||||
|  | 		bool annunciator_3_ = false, set_annunciator_3_ = false; | ||||||
|  |  | ||||||
|  | 		// Graphics carry is the final level output in a fetch window; | ||||||
|  | 		// it carries on into the next if it's high resolution with | ||||||
|  | 		// the delay bit set. | ||||||
|  | 		mutable uint8_t graphics_carry_ = 0; | ||||||
|  | 		bool was_double_ = false; | ||||||
|  | 		uint8_t high_resolution_mask_ = 0xff; | ||||||
|  |  | ||||||
|  | 		// This holds a copy of the character ROM. The regular character | ||||||
|  | 		// set is assumed to be in the first 64*8 bytes; the alternative | ||||||
|  | 		// is in the 128*8 bytes after that. | ||||||
| 		std::vector<uint8_t> character_rom_; | 		std::vector<uint8_t> character_rom_; | ||||||
|  |  | ||||||
| 		enum class GraphicsMode { | 		// Memory is fetched ahead of time into this array; | ||||||
| 			LowRes, | 		// this permits the correct delay between fetching | ||||||
| 			HighRes, | 		// without having to worry about a rolling buffer. | ||||||
| 			Text | 		std::array<uint8_t, 40> base_stream_; | ||||||
| 		} graphics_mode_ = GraphicsMode::LowRes; | 		std::array<uint8_t, 40> auxiliary_stream_; | ||||||
| 		bool use_graphics_mode_ = false; |  | ||||||
| 		bool mixed_mode_ = false; |  | ||||||
| 		uint16_t graphics_carry_ = 0; |  | ||||||
|  |  | ||||||
| 		static uint16_t scaled_byte[256]; | 		bool is_iie_ = false; | ||||||
| 		static uint16_t low_resolution_patterns[2][16]; | 		static const int flash_length = 8406; | ||||||
|  |  | ||||||
|  | 		// Describes the current text mode mapping from in-memory character index | ||||||
|  | 		// to output character. | ||||||
|  | 		struct CharacterMapping { | ||||||
|  | 			uint8_t address_mask; | ||||||
|  | 			uint8_t xor_mask; | ||||||
|  | 		}; | ||||||
|  | 		CharacterMapping character_zones[4]; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column text to @c target, using @c length bytes from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_text(uint8_t *target, const uint8_t *source, size_t length, size_t pixel_row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_double_text(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, size_t pixel_row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_double_low_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, int column, int row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_high_resolution(uint8_t *target, const uint8_t *source, size_t length) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_double_high_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column "fat low resolution" graphics to @c target, drawing @c length columns from @c source. | ||||||
|  |  | ||||||
|  | 			Fat low-resolution mode is like regular low-resolution mode except that data is shifted out on the 7M | ||||||
|  | 			clock rather than the 14M. | ||||||
|  | 		*/ | ||||||
|  | 		void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; | ||||||
|  |  | ||||||
|  | 		// Maintain a ClockDeferrer for delayed mode switches. | ||||||
|  | 		ClockDeferrer<Cycles> deferrer_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| template <class BusHandler> class Video: public VideoBase { | template <class BusHandler, bool is_iie> class Video: public VideoBase { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs an instance of the video feed; a CRT is also created. | 		/// Constructs an instance of the video feed; a CRT is also created. | ||||||
| 		Video(BusHandler &bus_handler) : | 		Video(BusHandler &bus_handler) : | ||||||
| 			VideoBase(), | 			VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }), | ||||||
| 			bus_handler_(bus_handler) {} | 			bus_handler_(bus_handler) {} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Advances time by @c cycles; expects to be fed by the CPU clock. | 			Runs video for @c cycles. | ||||||
| 			Implicitly adds an extra half a colour clock at the end of every |  | ||||||
| 			line. |  | ||||||
| 		*/ | 		*/ | ||||||
| 		void run_for(const Cycles cycles) { | 		void run_for(Cycles cycles) { | ||||||
| 			/* | 			deferrer_.run_for(cycles); | ||||||
| 				Addressing scheme used throughout is that column 0 is the first column with pixels in it; |  | ||||||
| 				row 0 is the first row with pixels in it. |  | ||||||
|  |  | ||||||
| 				A frame is oriented around 65 cycles across, 262 lines down. |  | ||||||
| 			*/ |  | ||||||
| 			const int first_sync_line = 220;	// A complete guess. Information needed. |  | ||||||
| 			const int first_sync_column = 49;	// Also a guess. |  | ||||||
|  |  | ||||||
| 			int int_cycles = cycles.as_int(); |  | ||||||
| 			while(int_cycles) { |  | ||||||
| 				const int cycles_this_line = std::min(65 - column_, int_cycles); |  | ||||||
|  |  | ||||||
| 				if(row_ >= first_sync_line && row_ < first_sync_line + 3) { |  | ||||||
| 					crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 7); |  | ||||||
| 				} else { |  | ||||||
| 					const int ending_column = column_ + cycles_this_line; |  | ||||||
| 					const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text; |  | ||||||
|  |  | ||||||
| 					// The first 40 columns are submitted to the CRT only upon completion; |  | ||||||
| 					// they'll be either graphics or blank, depending on which side we are |  | ||||||
| 					// of line 192. |  | ||||||
| 					if(column_ < 40) { |  | ||||||
| 						if(row_ < 192) { |  | ||||||
| 							if(!column_) { |  | ||||||
| 								pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2)); |  | ||||||
| 								graphics_carry_ = 0; |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							const int pixel_end = std::min(40, ending_column); |  | ||||||
| 							const int character_row = row_ >> 3; |  | ||||||
| 							const int pixel_row = row_ & 7; |  | ||||||
| 							const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); |  | ||||||
| 							const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address); |  | ||||||
| 							const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); |  | ||||||
| 							const int row_shift = (row_&4); |  | ||||||
|  |  | ||||||
| 							GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text; |  | ||||||
| 							switch(pixel_mode) { |  | ||||||
| 								case GraphicsMode::Text: { |  | ||||||
| 									const uint8_t inverses[] = { |  | ||||||
| 										0xff, |  | ||||||
| 										static_cast<uint8_t>((flash_ / flash_length) * 0xff), |  | ||||||
| 										0x00, |  | ||||||
| 										0x00 |  | ||||||
| 									}; |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)); |  | ||||||
| 										const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row); |  | ||||||
|  |  | ||||||
| 										const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6]; |  | ||||||
| 										pixel_pointer_[c] = scaled_byte[character_pattern & 0x7f]; |  | ||||||
| 									} |  | ||||||
| 								} break; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::LowRes: |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)); |  | ||||||
| 										pixel_pointer_[c] = low_resolution_patterns[c&1][(character >> row_shift)&0xf]; |  | ||||||
| 									} |  | ||||||
| 								break; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::HighRes: |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c)); |  | ||||||
| 										pixel_pointer_[c] = scaled_byte[graphic]; |  | ||||||
| 										if(graphic & 0x80) { |  | ||||||
| 											reinterpret_cast<uint8_t *>(&pixel_pointer_[c])[0] |= graphics_carry_; |  | ||||||
| 										} |  | ||||||
| 										graphics_carry_ = (graphic >> 6) & 1; |  | ||||||
| 									} |  | ||||||
| 								break; |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							if(ending_column >= 40) { |  | ||||||
| 								crt_->output_data(280, 80); |  | ||||||
| 							} |  | ||||||
| 						} else { |  | ||||||
| 							if(ending_column >= 40) { |  | ||||||
| 								crt_->output_blank(280); |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					/* |  | ||||||
| 						The left border, sync, right border pattern doesn't depend on whether |  | ||||||
| 						there were pixels this row and is output as soon as it is known. |  | ||||||
| 					*/ |  | ||||||
|  |  | ||||||
| 					const int first_blank_start = std::max(40, column_); |  | ||||||
| 					const int first_blank_end = std::min(first_sync_column, ending_column); |  | ||||||
| 					if(first_blank_end > first_blank_start) { |  | ||||||
| 						crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 7); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					const int sync_start = std::max(first_sync_column, column_); |  | ||||||
| 					const int sync_end = std::min(first_sync_column + 4, ending_column); |  | ||||||
| 					if(sync_end > sync_start) { |  | ||||||
| 						crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 7); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					int second_blank_start; |  | ||||||
| 					if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) { |  | ||||||
| 						const int colour_burst_start = std::max(first_sync_column + 4, column_); |  | ||||||
| 						const int colour_burst_end = std::min(first_sync_column + 7, ending_column); |  | ||||||
| 						if(colour_burst_end > colour_burst_start) { |  | ||||||
| 							crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 7); |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						second_blank_start = std::max(first_sync_column + 7, column_); |  | ||||||
| 					} else { |  | ||||||
| 						second_blank_start = std::max(first_sync_column + 4, column_); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					if(ending_column > second_blank_start) { |  | ||||||
| 						crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 7); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				int_cycles -= cycles_this_line; |  | ||||||
| 				column_ = (column_ + cycles_this_line) % 65; |  | ||||||
| 				if(!column_) { |  | ||||||
| 					row_ = (row_ + 1) % 262; |  | ||||||
| 					flash_ = (flash_ + 1) % (2 * flash_length); |  | ||||||
|  |  | ||||||
| 					// Add an extra half a colour cycle of blank; this isn't counted in the run_for |  | ||||||
| 					// count explicitly but is promised. |  | ||||||
| 					crt_->output_blank(1); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -244,22 +300,294 @@ template <class BusHandler> class Video: public VideoBase { | |||||||
|  |  | ||||||
| 			// Calculate the address and return the value. | 			// Calculate the address and return the value. | ||||||
| 			uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25); | 			uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25); | ||||||
| 			return bus_handler_.perform_read(read_address); | 			uint8_t value, aux_value; | ||||||
|  | 			bus_handler_.perform_read(read_address, 1, &value, &aux_value); | ||||||
|  | 			return value; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool get_is_vertical_blank(Cycles offset) { | ||||||
|  | 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||||
|  | 			// (so what was column 0 is now column 25). | ||||||
|  | 			int mapped_column = column_ + offset.as_int(); | ||||||
|  |  | ||||||
|  | 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||||
|  | 			// (so what was column 0 is now column 25). | ||||||
|  | 			mapped_column += 25; | ||||||
|  |  | ||||||
|  | 			// Apply carry into the row counter and test it for location. | ||||||
|  | 			int mapped_row = row_ + (mapped_column / 65); | ||||||
|  | 			return (mapped_row % 262) >= 192; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		/*! | ||||||
|  | 			Advances time by @c cycles; expects to be fed by the CPU clock. | ||||||
|  | 			Implicitly adds an extra half a colour clock at the end of | ||||||
|  | 			line. | ||||||
|  | 		*/ | ||||||
|  | 		void advance(Cycles cycles) { | ||||||
|  | 			/* | ||||||
|  | 				Addressing scheme used throughout is that column 0 is the first column with pixels in it; | ||||||
|  | 				row 0 is the first row with pixels in it. | ||||||
|  |  | ||||||
|  | 				A frame is oriented around 65 cycles across, 262 lines down. | ||||||
|  | 			*/ | ||||||
|  | 			static const int first_sync_line = 220;		// A complete guess. Information needed. | ||||||
|  | 			static const int first_sync_column = 49;	// Also a guess. | ||||||
|  | 			static const int sync_length = 4;			// One of the two likely candidates. | ||||||
|  |  | ||||||
|  | 			int int_cycles = cycles.as_int(); | ||||||
|  | 			while(int_cycles) { | ||||||
|  | 				const int cycles_this_line = std::min(65 - column_, int_cycles); | ||||||
|  | 				const int ending_column = column_ + cycles_this_line; | ||||||
|  |  | ||||||
|  | 				if(row_ >= first_sync_line && row_ < first_sync_line + 3) { | ||||||
|  | 					// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising | ||||||
|  | 					// pulses (and hencce keep hsync approximately where it should be during vsync). | ||||||
|  | 					const int blank_start = std::max(first_sync_column - sync_length, column_); | ||||||
|  | 					const int blank_end = std::min(first_sync_column, ending_column); | ||||||
|  | 					if(blank_end > blank_start) { | ||||||
|  | 						if(blank_start > column_) { | ||||||
|  | 							crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14); | ||||||
|  | 						} | ||||||
|  | 						crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14); | ||||||
|  | 						if(blank_end < ending_column) { | ||||||
|  | 							crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					const GraphicsMode line_mode = graphics_mode(row_); | ||||||
|  |  | ||||||
|  | 					// Determine whether there's any fetching to do. Fetching occurs during the first | ||||||
|  | 					// 40 columns of rows prior to 192. | ||||||
|  | 					if(row_ < 192 && column_ < 40) { | ||||||
|  | 						const int character_row = row_ >> 3; | ||||||
|  | 						const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); | ||||||
|  |  | ||||||
|  | 						// Grab the memory contents that'll be needed momentarily. | ||||||
|  | 						const int fetch_end = std::min(40, ending_column); | ||||||
|  | 						uint16_t fetch_address; | ||||||
|  | 						switch(line_mode) { | ||||||
|  | 							default: | ||||||
|  | 							case GraphicsMode::Text: | ||||||
|  | 							case GraphicsMode::DoubleText: | ||||||
|  | 							case GraphicsMode::LowRes: | ||||||
|  | 							case GraphicsMode::FatLowRes: | ||||||
|  | 							case GraphicsMode::DoubleLowRes: { | ||||||
|  | 								const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address); | ||||||
|  | 								fetch_address = static_cast<uint16_t>(text_address + column_); | ||||||
|  | 							} break; | ||||||
|  |  | ||||||
|  | 							case GraphicsMode::HighRes: | ||||||
|  | 							case GraphicsMode::DoubleHighRes: | ||||||
|  | 								fetch_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_); | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						bus_handler_.perform_read( | ||||||
|  | 							fetch_address, | ||||||
|  | 							static_cast<size_t>(fetch_end - column_), | ||||||
|  | 							&base_stream_[static_cast<size_t>(column_)], | ||||||
|  | 							&auxiliary_stream_[static_cast<size_t>(column_)]); | ||||||
|  | 						// TODO: should character modes be mapped to character pixel outputs here? | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(row_ < 192) { | ||||||
|  | 						// The pixel area is the first 40.5 columns; base contents | ||||||
|  | 						// remain where they would naturally be but auxiliary | ||||||
|  | 						// graphics appear to the left of that. | ||||||
|  | 						if(!column_) { | ||||||
|  | 							pixel_pointer_ = crt_->allocate_write_area(568); | ||||||
|  | 							graphics_carry_ = 0; | ||||||
|  | 							was_double_ = true; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if(column_ < 40) { | ||||||
|  | 							const int pixel_start = std::max(0, column_); | ||||||
|  | 							const int pixel_end = std::min(40, ending_column); | ||||||
|  | 							const int pixel_row = row_ & 7; | ||||||
|  |  | ||||||
|  | 							const bool is_double = Video::is_double_mode(line_mode); | ||||||
|  | 							if(!is_double && was_double_) { | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 0] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 1] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 2] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 3] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 4] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 5] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 6] = 0; | ||||||
|  | 							} | ||||||
|  | 							was_double_ = is_double; | ||||||
|  |  | ||||||
|  | 							switch(line_mode) { | ||||||
|  | 								case GraphicsMode::Text: | ||||||
|  | 									output_text( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										static_cast<size_t>(pixel_row)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::DoubleText: | ||||||
|  | 									output_double_text( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										&auxiliary_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										static_cast<size_t>(pixel_row)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::LowRes: | ||||||
|  | 									output_low_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										pixel_start, | ||||||
|  | 										pixel_row); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::FatLowRes: | ||||||
|  | 									output_fat_low_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										pixel_start, | ||||||
|  | 										pixel_row); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::DoubleLowRes: | ||||||
|  | 									output_double_low_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										&auxiliary_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										pixel_start, | ||||||
|  | 										pixel_row); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::HighRes: | ||||||
|  | 									output_high_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::DoubleHighRes: | ||||||
|  | 									output_double_high_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										&auxiliary_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								default: break; | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							if(pixel_end == 40) { | ||||||
|  | 								if(was_double_) { | ||||||
|  | 									pixel_pointer_[563] = | ||||||
|  | 									pixel_pointer_[564] = | ||||||
|  | 									pixel_pointer_[565] = | ||||||
|  | 									pixel_pointer_[566] = | ||||||
|  | 									pixel_pointer_[567] = 0; | ||||||
|  | 								} else { | ||||||
|  | 									if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) | ||||||
|  | 										pixel_pointer_[567] = graphics_carry_; | ||||||
|  | 									else | ||||||
|  | 										pixel_pointer_[567] = 0; | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								crt_->output_data(568, 568); | ||||||
|  | 								pixel_pointer_ = nullptr; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						if(column_ < 40 && ending_column >= 40) { | ||||||
|  | 							crt_->output_blank(568); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					/* | ||||||
|  | 						The left border, sync, right border pattern doesn't depend on whether | ||||||
|  | 						there were pixels this row and is output as soon as it is known. | ||||||
|  | 					*/ | ||||||
|  |  | ||||||
|  | 					if(column_ < first_sync_column && ending_column >= first_sync_column) { | ||||||
|  | 						crt_->output_blank((first_sync_column - 41)*14 - 1); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) { | ||||||
|  | 						crt_->output_sync(sync_length*14); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					int second_blank_start; | ||||||
|  | 					if(!is_text_mode(graphics_mode(row_+1))) { | ||||||
|  | 						const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); | ||||||
|  | 						const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); | ||||||
|  | 						if(colour_burst_end > colour_burst_start) { | ||||||
|  | 							crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						second_blank_start = std::max(first_sync_column + sync_length + 3, column_); | ||||||
|  | 					} else { | ||||||
|  | 						second_blank_start = std::max(first_sync_column + sync_length, column_); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(ending_column > second_blank_start) { | ||||||
|  | 						crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				int_cycles -= cycles_this_line; | ||||||
|  | 				column_ = (column_ + cycles_this_line) % 65; | ||||||
|  | 				if(!column_) { | ||||||
|  | 					row_ = (row_ + 1) % 262; | ||||||
|  | 					flash_ = (flash_ + 1) % (2 * flash_length); | ||||||
|  | 					if(!alternative_character_set_) { | ||||||
|  | 						character_zones[1].xor_mask = flash_mask(); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Add an extra half a colour cycle of blank; this isn't counted in the run_for | ||||||
|  | 					// count explicitly but is promised. | ||||||
|  | 					crt_->output_blank(2); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		GraphicsMode graphics_mode(int row) { | ||||||
|  | 			if( | ||||||
|  | 				text_ || | ||||||
|  | 				(mixed_ && row >= 160 && row < 192) | ||||||
|  | 			) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text; | ||||||
|  | 			if(high_resolution_) { | ||||||
|  | 				return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; | ||||||
|  | 			} else { | ||||||
|  | 				if(columns_80_) return GraphicsMode::DoubleLowRes; | ||||||
|  | 				if(annunciator_3_) return GraphicsMode::FatLowRes; | ||||||
|  | 				return GraphicsMode::LowRes; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int video_page() { | ||||||
|  | 			return (store_80_ || !page2_) ? 0 : 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		uint16_t get_row_address(int row) { | 		uint16_t get_row_address(int row) { | ||||||
| 			const int character_row = row >> 3; | 			const int character_row = row >> 3; | ||||||
| 			const int pixel_row = row & 7; | 			const int pixel_row = row & 7; | ||||||
| 			const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); | 			const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); | ||||||
|  |  | ||||||
| 			GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text; | 			const GraphicsMode pixel_mode = graphics_mode(row); | ||||||
| 			return (pixel_mode == GraphicsMode::HighRes) ? | 			return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? | ||||||
| 				static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : | 				static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : | ||||||
| 				static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address); | 				static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const int flash_length = 8406; |  | ||||||
| 		BusHandler &bus_handler_; | 		BusHandler &bus_handler_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
| @@ -37,30 +36,27 @@ namespace { | |||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  |  | ||||||
| class Joystick: public Inputs::Joystick { | class Joystick: public Inputs::ConcreteJoystick { | ||||||
| 	public: | 	public: | ||||||
| 		Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : | 		Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : | ||||||
|  | 			ConcreteJoystick({ | ||||||
|  | 				Input(Input::Up), | ||||||
|  | 				Input(Input::Down), | ||||||
|  | 				Input(Input::Left), | ||||||
|  | 				Input(Input::Right), | ||||||
|  | 				Input(Input::Fire) | ||||||
|  | 			}), | ||||||
| 			bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} | 			bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} | ||||||
|  |  | ||||||
| 		std::vector<DigitalInput> get_inputs() override { | 		void did_set_input(const Input &digital_input, bool is_active) override { | ||||||
| 			return { |  | ||||||
| 				DigitalInput(DigitalInput::Up), |  | ||||||
| 				DigitalInput(DigitalInput::Down), |  | ||||||
| 				DigitalInput(DigitalInput::Left), |  | ||||||
| 				DigitalInput(DigitalInput::Right), |  | ||||||
| 				DigitalInput(DigitalInput::Fire) |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { |  | ||||||
| 			switch(digital_input.type) { | 			switch(digital_input.type) { | ||||||
| 				case DigitalInput::Up:		bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active);		break; | 				case Input::Up:		bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active);		break; | ||||||
| 				case DigitalInput::Down:	bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active);		break; | 				case Input::Down:	bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active);		break; | ||||||
| 				case DigitalInput::Left:	bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active);		break; | 				case Input::Left:	bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active);		break; | ||||||
| 				case DigitalInput::Right:	bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active);		break; | 				case Input::Right:	bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active);		break; | ||||||
|  |  | ||||||
| 				// TODO: latching | 				// TODO: latching | ||||||
| 				case DigitalInput::Fire: | 				case Input::Fire: | ||||||
| 					if(is_active) | 					if(is_active) | ||||||
| 						bus_->tia_input_value_[fire_tia_input_] &= ~0x80; | 						bus_->tia_input_value_[fire_tia_input_] &= ~0x80; | ||||||
| 					else | 					else | ||||||
| @@ -79,24 +75,16 @@ class Joystick: public Inputs::Joystick { | |||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public JoystickMachine::Machine, | 	public JoystickMachine::Machine, | ||||||
| 	public Outputs::CRT::Delegate { | 	public Outputs::CRT::Delegate { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() { | 		ConcreteMachine(const Analyser::Static::Atari::Target &target) { | ||||||
| 			set_clock_rate(NTSC_clock_rate); | 			set_clock_rate(NTSC_clock_rate); | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | ||||||
| 			close_output(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target); |  | ||||||
| 			const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data; |  | ||||||
|  |  | ||||||
| 			using PagingModel = Analyser::Static::Atari::Target::PagingModel; | 			using PagingModel = Analyser::Static::Atari::Target::PagingModel; | ||||||
| 			switch(atari_target->paging_model) { | 			switch(target.paging_model) { | ||||||
| 				case PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | 				case PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | ||||||
| 				case PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | 				case PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | ||||||
| 				case PagingModel::CommaVid:			bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | 				case PagingModel::CommaVid:			bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||||
| @@ -108,21 +96,21 @@ class ConcreteMachine: | |||||||
| 				case PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | 				case PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||||
|  |  | ||||||
| 				case PagingModel::Atari8k: | 				case PagingModel::Atari8k: | ||||||
| 					if(atari_target->uses_superchip) { | 					if(target.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| 				case PagingModel::Atari16k: | 				case PagingModel::Atari16k: | ||||||
| 					if(atari_target->uses_superchip) { | 					if(target.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| 				case PagingModel::Atari32k: | 				case PagingModel::Atari32k: | ||||||
| 					if(atari_target->uses_superchip) { | 					if(target.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | ||||||
| @@ -134,8 +122,8 @@ class ConcreteMachine: | |||||||
| 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		~ConcreteMachine() { | ||||||
| 			return false; | 			close_output(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
| @@ -257,8 +245,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Atari2600; | using namespace Atari2600; | ||||||
|  |  | ||||||
| Machine *Machine::Atari2600() { | Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new Atari2600::ConcreteMachine; | 	using Target = Analyser::Static::Atari::Target; | ||||||
|  | 	const Target *const atari_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new Atari2600::ConcreteMachine(*atari_target); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,6 +9,10 @@ | |||||||
| #ifndef Atari2600_cpp | #ifndef Atari2600_cpp | ||||||
| #define Atari2600_cpp | #define Atari2600_cpp | ||||||
|  |  | ||||||
|  | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| #include "Atari2600Inputs.h" | #include "Atari2600Inputs.h" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
| @@ -21,7 +25,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Atari 2600 on the heap. | 		/// Creates and returns an Atari 2600 on the heap. | ||||||
| 		static Machine *Atari2600(); | 		static Machine *Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		/// Sets the switch @c input to @c state. | 		/// Sets the switch @c input to @c state. | ||||||
| 		virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; | 		virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; | ||||||
|   | |||||||
| @@ -204,7 +204,7 @@ template<class T> class Cartridge: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		CPU::MOS6502::Processor<Cartridge<T>, true> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_; | ||||||
| 		std::vector<uint8_t> rom_; | 		std::vector<uint8_t> rom_; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -124,19 +124,19 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { | |||||||
|  |  | ||||||
| 	if(output_mode == OutputMode::NTSC) { | 	if(output_mode == OutputMode::NTSC) { | ||||||
| 		crt_->set_svideo_sampling_function( | 		crt_->set_svideo_sampling_function( | ||||||
| 			"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | 			"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||||
| 			"{" | 			"{" | ||||||
| 				"uint c = texture(texID, coordinate).r;" | 				"uint c = texture(texID, coordinate).r;" | ||||||
| 				"uint y = c & 14u;" | 				"uint y = c & 14u;" | ||||||
| 				"uint iPhase = (c >> 4);" | 				"uint iPhase = (c >> 4);" | ||||||
|  |  | ||||||
| 				"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" | 				"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" | ||||||
| 				"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset));" | 				"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));" | ||||||
| 			"}"); | 			"}"); | ||||||
| 		display_type = Outputs::CRT::DisplayType::NTSC60; | 		display_type = Outputs::CRT::DisplayType::NTSC60; | ||||||
| 	} else { | 	} else { | ||||||
| 		crt_->set_svideo_sampling_function( | 		crt_->set_svideo_sampling_function( | ||||||
| 			"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | 			"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||||
| 			"{" | 			"{" | ||||||
| 				"uint c = texture(texID, coordinate).r;" | 				"uint c = texture(texID, coordinate).r;" | ||||||
| 				"uint y = c & 14u;" | 				"uint y = c & 14u;" | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ namespace CRTMachine { | |||||||
| 	that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate | 	that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate | ||||||
| 	should that clock rate change. | 	should that clock rate change. | ||||||
| */ | */ | ||||||
| class Machine: public ROMMachine::Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		/*! | 		/*! | ||||||
| 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ | |||||||
| #include "../../Components/AY38910/AY38910.hpp"	// For the Super Game Module. | #include "../../Components/AY38910/AY38910.hpp"	// For the Super Game Module. | ||||||
| #include "../../Components/SN76489/SN76489.hpp" | #include "../../Components/SN76489/SN76489.hpp" | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
| @@ -32,30 +31,29 @@ const int sn76489_divider = 2; | |||||||
| namespace Coleco { | namespace Coleco { | ||||||
| namespace Vision { | namespace Vision { | ||||||
|  |  | ||||||
| class Joystick: public Inputs::Joystick { | class Joystick: public Inputs::ConcreteJoystick { | ||||||
| 	public: | 	public: | ||||||
| 		std::vector<DigitalInput> get_inputs() override { | 		Joystick() : | ||||||
| 			return { | 			ConcreteJoystick({ | ||||||
| 				DigitalInput(DigitalInput::Up), | 				Input(Input::Up), | ||||||
| 				DigitalInput(DigitalInput::Down), | 				Input(Input::Down), | ||||||
| 				DigitalInput(DigitalInput::Left), | 				Input(Input::Left), | ||||||
| 				DigitalInput(DigitalInput::Right), | 				Input(Input::Right), | ||||||
|  |  | ||||||
| 				DigitalInput(DigitalInput::Fire, 0), | 				Input(Input::Fire, 0), | ||||||
| 				DigitalInput(DigitalInput::Fire, 1), | 				Input(Input::Fire, 1), | ||||||
|  |  | ||||||
| 				DigitalInput('0'),	DigitalInput('1'),	DigitalInput('2'), | 				Input('0'),	Input('1'),	Input('2'), | ||||||
| 				DigitalInput('3'),	DigitalInput('4'),	DigitalInput('5'), | 				Input('3'),	Input('4'),	Input('5'), | ||||||
| 				DigitalInput('6'),	DigitalInput('7'),	DigitalInput('8'), | 				Input('6'),	Input('7'),	Input('8'), | ||||||
| 				DigitalInput('9'),	DigitalInput('*'),	DigitalInput('#'), | 				Input('9'),	Input('*'),	Input('#'), | ||||||
| 			}; | 			}) {} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | 		void did_set_input(const Input &digital_input, bool is_active) override { | ||||||
| 			switch(digital_input.type) { | 			switch(digital_input.type) { | ||||||
| 				default: return; | 				default: return; | ||||||
|  |  | ||||||
| 				case DigitalInput::Key: | 				case Input::Key: | ||||||
| 					if(!is_active) keypad_ |= 0xf; | 					if(!is_active) keypad_ |= 0xf; | ||||||
| 					else { | 					else { | ||||||
| 						uint8_t mask = 0xf; | 						uint8_t mask = 0xf; | ||||||
| @@ -78,11 +76,11 @@ class Joystick: public Inputs::Joystick { | |||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case DigitalInput::Up: 		if(is_active) direction_ &= ~0x01; else direction_ |= 0x01;	break; | 				case Input::Up: 	if(is_active) direction_ &= ~0x01; else direction_ |= 0x01;	break; | ||||||
| 				case DigitalInput::Right:	if(is_active) direction_ &= ~0x02; else direction_ |= 0x02;	break; | 				case Input::Right:	if(is_active) direction_ &= ~0x02; else direction_ |= 0x02;	break; | ||||||
| 				case DigitalInput::Down:	if(is_active) direction_ &= ~0x04; else direction_ |= 0x04;	break; | 				case Input::Down:	if(is_active) direction_ &= ~0x04; else direction_ |= 0x04;	break; | ||||||
| 				case DigitalInput::Left:	if(is_active) direction_ &= ~0x08; else direction_ |= 0x08;	break; | 				case Input::Left:	if(is_active) direction_ &= ~0x08; else direction_ |= 0x08;	break; | ||||||
| 				case DigitalInput::Fire: | 				case Input::Fire: | ||||||
| 					switch(digital_input.info.control.index) { | 					switch(digital_input.info.control.index) { | ||||||
| 						default: break; | 						default: break; | ||||||
| 						case 0:	if(is_active) direction_ &= ~0x40; else direction_ |= 0x40;	break; | 						case 0:	if(is_active) direction_ &= ~0x40; else direction_ |= 0x40;	break; | ||||||
| @@ -109,11 +107,10 @@ class ConcreteMachine: | |||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public JoystickMachine::Machine { | 	public JoystickMachine::Machine { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), | 			sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), | ||||||
| 			ay_(audio_queue_), | 			ay_(audio_queue_), | ||||||
| @@ -123,6 +120,36 @@ class ConcreteMachine: | |||||||
| 			set_clock_rate(3579545); | 			set_clock_rate(3579545); | ||||||
| 			joysticks_.emplace_back(new Joystick); | 			joysticks_.emplace_back(new Joystick); | ||||||
| 			joysticks_.emplace_back(new Joystick); | 			joysticks_.emplace_back(new Joystick); | ||||||
|  |  | ||||||
|  | 			const auto roms = rom_fetcher( | ||||||
|  | 				"ColecoVision", | ||||||
|  | 				{ "coleco.rom" }); | ||||||
|  |  | ||||||
|  | 			if(!roms[0]) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bios_ = *roms[0]; | ||||||
|  | 			bios_.resize(8192); | ||||||
|  |  | ||||||
|  | 			if(!target.media.cartridges.empty()) { | ||||||
|  | 				const auto &segment = target.media.cartridges.front()->get_segments().front(); | ||||||
|  | 				cartridge_ = segment.data; | ||||||
|  | 				if(cartridge_.size() >= 32768) | ||||||
|  | 					cartridge_address_limit_ = 0xffff; | ||||||
|  | 				else | ||||||
|  | 					cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1); | ||||||
|  |  | ||||||
|  | 				if(cartridge_.size() > 32768) { | ||||||
|  | 					cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384]; | ||||||
|  | 					cartridge_pages_[1] = cartridge_.data(); | ||||||
|  | 					is_megacart_ = true; | ||||||
|  | 				} else { | ||||||
|  | 					cartridge_pages_[0] = cartridge_.data(); | ||||||
|  | 					cartridge_pages_[1] = cartridge_.data() + 16384; | ||||||
|  | 					is_megacart_ = false; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -154,48 +181,6 @@ class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			// Insert the media. |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { |  | ||||||
| 			if(!media.cartridges.empty()) { |  | ||||||
| 				const auto &segment = media.cartridges.front()->get_segments().front(); |  | ||||||
| 				cartridge_ = segment.data; |  | ||||||
| 				if(cartridge_.size() >= 32768) |  | ||||||
| 					cartridge_address_limit_ = 0xffff; |  | ||||||
| 				else |  | ||||||
| 					cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1); |  | ||||||
|  |  | ||||||
| 				if(cartridge_.size() > 32768) { |  | ||||||
| 					cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384]; |  | ||||||
| 					cartridge_pages_[1] = cartridge_.data(); |  | ||||||
| 					is_megacart_ = true; |  | ||||||
| 				} else { |  | ||||||
| 					cartridge_pages_[0] = cartridge_.data(); |  | ||||||
| 					cartridge_pages_[1] = cartridge_.data() + 16384; |  | ||||||
| 					is_megacart_ = false; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"ColecoVision", |  | ||||||
| 				{ "coleco.rom" }); |  | ||||||
|  |  | ||||||
| 			if(!roms[0]) return false; |  | ||||||
|  |  | ||||||
| 			bios_ = *roms[0]; |  | ||||||
| 			bios_.resize(8192); |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// MARK: Z80::BusHandler | 		// MARK: Z80::BusHandler | ||||||
| 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
| 			// The SN76489 will use its ready line to trigger the Z80's wait for three | 			// The SN76489 will use its ready line to trigger the Z80's wait for three | ||||||
| @@ -409,8 +394,8 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Coleco::Vision; | using namespace Coleco::Vision; | ||||||
|  |  | ||||||
| Machine *Machine::ColecoVision() { | Machine *Machine::ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new ConcreteMachine; | 	return new ConcreteMachine(*target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,13 +9,16 @@ | |||||||
| #ifndef ColecoVision_hpp | #ifndef ColecoVision_hpp | ||||||
| #define ColecoVision_hpp | #define ColecoVision_hpp | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace Coleco { | namespace Coleco { | ||||||
| namespace Vision { | namespace Vision { | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
| 		static Machine *ColecoVision(); | 		static Machine *ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,25 @@ | |||||||
| #ifndef Commodore1540_hpp | #ifndef Commodore1540_hpp | ||||||
| #define Commodore1540_hpp | #define Commodore1540_hpp | ||||||
|  |  | ||||||
|  | namespace Commodore { | ||||||
|  | namespace C1540 { | ||||||
|  |  | ||||||
|  | /// Defines the type of drive this 1540 hardware is configured as. | ||||||
|  | enum class Personality { | ||||||
|  | 	C1540, | ||||||
|  | 	C1541 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Implementation note: this is defined up here so that it precedes | ||||||
|  | 	C1540Base.hpp below. The alternative option was to factor it out, | ||||||
|  | 	but the whole point of the C1540.hpp/C1540Base.hpp split is supposed | ||||||
|  | 	to be to create a single file of public interface. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
| #include "../SerialBus.hpp" | #include "../SerialBus.hpp" | ||||||
| #include "../../ROMMachine.hpp" | #include "../../ROMMachine.hpp" | ||||||
| #include "../../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| @@ -20,18 +39,9 @@ namespace C1540 { | |||||||
| /*! | /*! | ||||||
| 	Provides an emulation of the C1540. | 	Provides an emulation of the C1540. | ||||||
| */ | */ | ||||||
| class Machine: public MachineBase, public ROMMachine::Machine { | class Machine: public MachineBase { | ||||||
| 	public: | 	public: | ||||||
| 		enum Personality { | 		Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| 			C1540, |  | ||||||
| 			C1541 |  | ||||||
| 		}; |  | ||||||
| 		Machine(Personality p); |  | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Sets the source for this drive's ROM image. |  | ||||||
| 		*/ |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names); |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the serial bus to which this drive should attach itself. | 			Sets the serial bus to which this drive should attach itself. | ||||||
| @@ -43,9 +53,6 @@ class Machine: public MachineBase, public ROMMachine::Machine { | |||||||
|  |  | ||||||
| 		/// Inserts @c disk into the drive. | 		/// Inserts @c disk into the drive. | ||||||
| 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		Personality personality_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|  |  | ||||||
| using namespace Commodore::C1540; | using namespace Commodore::C1540; | ||||||
|  |  | ||||||
| MachineBase::MachineBase() : | MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 		Storage::Disk::Controller(1000000), | 		Storage::Disk::Controller(1000000), | ||||||
| 		m6502_(*this), | 		m6502_(*this), | ||||||
| 		drive_(new Storage::Disk::Drive(1000000, 300, 2)), | 		drive_(new Storage::Disk::Drive(1000000, 300, 2)), | ||||||
| @@ -38,9 +38,22 @@ MachineBase::MachineBase() : | |||||||
|  |  | ||||||
| 	// attach the only drive there is | 	// attach the only drive there is | ||||||
| 	set_drive(drive_); | 	set_drive(drive_); | ||||||
|  |  | ||||||
|  | 	std::string rom_name; | ||||||
|  | 	switch(personality) { | ||||||
|  | 		case Personality::C1540:	rom_name = "1540.bin";	break; | ||||||
|  | 		case Personality::C1541:	rom_name = "1541.bin";	break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	auto roms = rom_fetcher("Commodore1540", {rom_name}); | ||||||
|  | 	if(!roms[0]) { | ||||||
|  | 		throw ROMMachine::Error::MissingROMs; | ||||||
|  | 	} | ||||||
|  | 	std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size())); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {} | Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
|  | 	MachineBase(personality, rom_fetcher) {} | ||||||
|  |  | ||||||
| void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | ||||||
| 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | ||||||
| @@ -82,19 +95,6 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, | |||||||
| 	return Cycles(1); | 	return Cycles(1); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Machine::set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) { |  | ||||||
| 	std::string rom_name; |  | ||||||
| 	switch(personality_) { |  | ||||||
| 		case Personality::C1540:	rom_name = "1540.bin";	break; |  | ||||||
| 		case Personality::C1541:	rom_name = "1541.bin";	break; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	auto roms = roms_with_names("Commodore1540", {rom_name}); |  | ||||||
| 	if(!roms[0]) return false; |  | ||||||
| 	std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size())); |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | ||||||
| 	drive_->set_disk(disk); | 	drive_->set_disk(disk); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ | |||||||
|  |  | ||||||
| #include "../../../../Storage/Disk/Controller/DiskController.hpp" | #include "../../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
|  |  | ||||||
|  | #include "../C1540.hpp" | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace C1540 { | namespace C1540 { | ||||||
|  |  | ||||||
| @@ -125,7 +127,7 @@ class MachineBase: | |||||||
| 	public Storage::Disk::Controller { | 	public Storage::Disk::Controller { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		MachineBase(); | 		MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); | 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); | ||||||
| @@ -141,7 +143,7 @@ class MachineBase: | |||||||
| 		void set_activity_observer(Activity::Observer *observer); | 		void set_activity_observer(Activity::Observer *observer); | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		CPU::MOS6502::Processor<MachineBase, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_; | ||||||
| 		std::shared_ptr<Storage::Disk::Drive> drive_; | 		std::shared_ptr<Storage::Disk::Drive> drive_; | ||||||
|  |  | ||||||
| 		uint8_t ram_[0x800]; | 		uint8_t ram_[0x800]; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| #include "Keyboard.hpp" | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
| #include "../../../Activity/Source.hpp" | #include "../../../Activity/Source.hpp" | ||||||
| #include "../../ConfigurationTarget.hpp" | #include "../../MediaTarget.hpp" | ||||||
| #include "../../CRTMachine.hpp" | #include "../../CRTMachine.hpp" | ||||||
| #include "../../KeyboardMachine.hpp" | #include "../../KeyboardMachine.hpp" | ||||||
| #include "../../JoystickMachine.hpp" | #include "../../JoystickMachine.hpp" | ||||||
| @@ -62,18 +62,6 @@ enum JoystickInput { | |||||||
| 	Fire = 0x20 | 	Fire = 0x20 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum ROM { |  | ||||||
| 	CharactersDanish = 0, |  | ||||||
| 	CharactersEnglish, |  | ||||||
| 	CharactersJapanese, |  | ||||||
| 	CharactersSwedish, |  | ||||||
| 	KernelDanish, |  | ||||||
| 	KernelJapanese, |  | ||||||
| 	KernelNTSC, |  | ||||||
| 	KernelPAL, |  | ||||||
| 	KernelSwedish |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder; | 	Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder; | ||||||
| 	sensing the presence or absence of a tape and controlling the tape motor; and reading the current | 	sensing the presence or absence of a tape and controlling the tape motor; and reading the current | ||||||
| @@ -257,31 +245,28 @@ class Vic6560BusHandler { | |||||||
| /*! | /*! | ||||||
| 	Interfaces a joystick to the two VIAs. | 	Interfaces a joystick to the two VIAs. | ||||||
| */ | */ | ||||||
| class Joystick: public Inputs::Joystick { | class Joystick: public Inputs::ConcreteJoystick { | ||||||
| 	public: | 	public: | ||||||
| 		Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) : | 		Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) : | ||||||
|  | 			ConcreteJoystick({ | ||||||
|  | 				Input(Input::Up), | ||||||
|  | 				Input(Input::Down), | ||||||
|  | 				Input(Input::Left), | ||||||
|  | 				Input(Input::Right), | ||||||
|  | 				Input(Input::Fire) | ||||||
|  | 			}), | ||||||
| 			user_port_via_port_handler_(user_port_via_port_handler), | 			user_port_via_port_handler_(user_port_via_port_handler), | ||||||
| 			keyboard_via_port_handler_(keyboard_via_port_handler) {} | 			keyboard_via_port_handler_(keyboard_via_port_handler) {} | ||||||
|  |  | ||||||
| 		std::vector<DigitalInput> get_inputs() override { | 		void did_set_input(const Input &digital_input, bool is_active) override { | ||||||
| 			return { |  | ||||||
| 				DigitalInput(DigitalInput::Up), |  | ||||||
| 				DigitalInput(DigitalInput::Down), |  | ||||||
| 				DigitalInput(DigitalInput::Left), |  | ||||||
| 				DigitalInput(DigitalInput::Right), |  | ||||||
| 				DigitalInput(DigitalInput::Fire) |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { |  | ||||||
| 			JoystickInput mapped_input; | 			JoystickInput mapped_input; | ||||||
| 			switch(digital_input.type) { | 			switch(digital_input.type) { | ||||||
| 				default: return; | 				default: return; | ||||||
| 				case DigitalInput::Up: mapped_input = Up;		break; | 				case Input::Up:		mapped_input = Up;		break; | ||||||
| 				case DigitalInput::Down: mapped_input = Down;	break; | 				case Input::Down:	mapped_input = Down;	break; | ||||||
| 				case DigitalInput::Left: mapped_input = Left;	break; | 				case Input::Left:	mapped_input = Left;	break; | ||||||
| 				case DigitalInput::Right: mapped_input = Right;	break; | 				case Input::Right:	mapped_input = Right;	break; | ||||||
| 				case DigitalInput::Fire: mapped_input = Fire;	break; | 				case Input::Fire:	mapped_input = Fire;	break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			user_port_via_port_handler_.set_joystick_state(mapped_input, is_active); | 			user_port_via_port_handler_.set_joystick_state(mapped_input, is_active); | ||||||
| @@ -295,7 +280,7 @@ class Joystick: public Inputs::Joystick { | |||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public JoystickMachine::Machine, | 	public JoystickMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| @@ -304,10 +289,10 @@ class ConcreteMachine: | |||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public Storage::Tape::BinaryTapePlayer::Delegate, | 	public Storage::Tape::BinaryTapePlayer::Delegate, | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public Sleeper::SleepObserver, | 	public ClockingHint::Observer, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 				m6502_(*this), | 				m6502_(*this), | ||||||
| 				user_port_via_port_handler_(new UserPortVIA), | 				user_port_via_port_handler_(new UserPortVIA), | ||||||
| 				keyboard_via_port_handler_(new KeyboardVIA), | 				keyboard_via_port_handler_(new KeyboardVIA), | ||||||
| @@ -331,131 +316,72 @@ class ConcreteMachine: | |||||||
| 			user_port_via_port_handler_->set_interrupt_delegate(this); | 			user_port_via_port_handler_->set_interrupt_delegate(this); | ||||||
| 			keyboard_via_port_handler_->set_interrupt_delegate(this); | 			keyboard_via_port_handler_->set_interrupt_delegate(this); | ||||||
| 			tape_->set_delegate(this); | 			tape_->set_delegate(this); | ||||||
| 			tape_->set_sleep_observer(this); | 			tape_->set_clocking_hint_observer(this); | ||||||
|  |  | ||||||
| 			// install a joystick | 			// install a joystick | ||||||
| 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 			std::vector<std::string> rom_names = { "basic.bin" }; | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { | 			switch(target.region) { | ||||||
| 			rom_fetcher_ = roms_with_names; | 				default: | ||||||
|  | 					rom_names.push_back("characters-english.bin"); | ||||||
| 			auto roms = roms_with_names( | 					rom_names.push_back("kernel-pal.bin"); | ||||||
| 				"Vic20", | 				break; | ||||||
| 				{ | 				case Analyser::Static::Commodore::Target::Region::American: | ||||||
| 					"characters-danish.bin", | 					rom_names.push_back("characters-english.bin"); | ||||||
| 					"characters-english.bin", | 					rom_names.push_back("kernel-ntsc.bin"); | ||||||
| 					"characters-japanese.bin", | 				break; | ||||||
| 					"characters-swedish.bin", | 				case Analyser::Static::Commodore::Target::Region::Danish: | ||||||
| 					"kernel-danish.bin", | 					rom_names.push_back("characters-danish.bin"); | ||||||
| 					"kernel-japanese.bin", | 					rom_names.push_back("kernel-danish.bin"); | ||||||
| 					"kernel-ntsc.bin", | 				break; | ||||||
| 					"kernel-pal.bin", | 				case Analyser::Static::Commodore::Target::Region::Japanese: | ||||||
| 					"kernel-swedish.bin", | 					rom_names.push_back("characters-japanese.bin"); | ||||||
| 					"basic.bin" | 					rom_names.push_back("kernel-japanese.bin"); | ||||||
| 				}); | 				break; | ||||||
|  | 				case Analyser::Static::Commodore::Target::Region::Swedish: | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { | 					rom_names.push_back("characters-swedish.bin"); | ||||||
| 				auto &data = roms[index]; | 					rom_names.push_back("kernel-japanese.bin"); | ||||||
| 				if(!data) return false; | 				break; | ||||||
| 				if(index < 9) roms_[index] = std::move(*data); else basic_rom_ = std::move(*data); |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			const auto roms = rom_fetcher("Vic20", rom_names); | ||||||
|  |  | ||||||
|  | 			for(const auto &rom: roms) { | ||||||
|  | 				if(!rom) { | ||||||
|  | 					throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			basic_rom_ = std::move(*roms[0]); | ||||||
|  | 			character_rom_ = std::move(*roms[1]); | ||||||
|  | 			kernel_rom_ = std::move(*roms[2]); | ||||||
|  |  | ||||||
| 			// Characters ROMs should be 4kb. | 			// Characters ROMs should be 4kb. | ||||||
| 			for(std::size_t index = 0; index < 4; ++index) roms_[index].resize(4096); | 			character_rom_.resize(4096); | ||||||
| 			// Kernel ROMs and the BASIC ROM should be 8kb. | 			// Kernel ROMs and the BASIC ROM should be 8kb. | ||||||
| 			for(std::size_t index = 4; index < roms.size(); ++index) roms_[index].resize(8192); | 			kernel_rom_.resize(8192); | ||||||
|  |  | ||||||
| 			return true; | 			if(target.has_c1540) { | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target); |  | ||||||
|  |  | ||||||
| 			if(!commodore_target_.loading_command.empty()) { |  | ||||||
| 				type_string(commodore_target_.loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(commodore_target_.has_c1540) { |  | ||||||
| 				// construct the 1540 | 				// construct the 1540 | ||||||
| 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); | 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Personality::C1540, rom_fetcher)); | ||||||
|  |  | ||||||
| 				// attach it to the serial bus | 				// attach it to the serial bus | ||||||
| 				c1540_->set_serial_bus(serial_bus_); | 				c1540_->set_serial_bus(serial_bus_); | ||||||
|  |  | ||||||
| 				// give it a means to obtain its ROM |  | ||||||
| 				c1540_->set_rom_fetcher(rom_fetcher_); |  | ||||||
|  |  | ||||||
| 				// give it a little warm up | 				// give it a little warm up | ||||||
| 				c1540_->run_for(Cycles(2000000)); | 				c1540_->run_for(Cycles(2000000)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { |  | ||||||
| 			if(!media.tapes.empty()) { |  | ||||||
| 				tape_->set_tape(media.tapes.front()); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(!media.disks.empty() && c1540_) { |  | ||||||
| 				c1540_->set_disk(media.disks.front()); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(!media.cartridges.empty()) { |  | ||||||
| 				rom_address_ = 0xa000; |  | ||||||
| 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; |  | ||||||
| 				rom_length_ = static_cast<uint16_t>(rom_image.size()); |  | ||||||
|  |  | ||||||
| 				rom_ = rom_image; |  | ||||||
| 				rom_.resize(0x2000); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			set_use_fast_tape(); |  | ||||||
|  |  | ||||||
| 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool is_pressed) override final { |  | ||||||
| 			if(key != KeyRestore) |  | ||||||
| 				keyboard_via_port_handler_->set_key_state(key, is_pressed); |  | ||||||
| 			else |  | ||||||
| 				user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void clear_all_keys() override final { |  | ||||||
| 			keyboard_via_port_handler_->clear_all_keys(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { |  | ||||||
| 			return joysticks_; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_ntsc_6560() { |  | ||||||
| 			set_clock_rate(1022727); |  | ||||||
| 			if(mos6560_) { |  | ||||||
| 				mos6560_->set_output_mode(MOS::MOS6560::OutputMode::NTSC); |  | ||||||
| 				mos6560_->set_clock_rate(1022727); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_pal_6560() { |  | ||||||
| 			set_clock_rate(1108404); |  | ||||||
| 			if(mos6560_) { |  | ||||||
| 				mos6560_->set_output_mode(MOS::MOS6560::OutputMode::PAL); |  | ||||||
| 				mos6560_->set_clock_rate(1108404); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_memory_map(Analyser::Static::Commodore::Target::MemoryModel memory_model, Analyser::Static::Commodore::Target::Region region) { |  | ||||||
| 			// Determine PAL/NTSC | 			// Determine PAL/NTSC | ||||||
| 			if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) { | 			if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) { | ||||||
| 				// NTSC | 				// NTSC | ||||||
| 				set_ntsc_6560(); | 				set_clock_rate(1022727); | ||||||
|  | 				output_mode_ = MOS::MOS6560::OutputMode::NTSC; | ||||||
| 			} else { | 			} else { | ||||||
| 				// PAL | 				// PAL | ||||||
| 				set_pal_6560(); | 				set_clock_rate(1108404); | ||||||
|  | 				output_mode_ = MOS::MOS6560::OutputMode::PAL; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Initialise the memory maps as all pointing to nothing | 			// Initialise the memory maps as all pointing to nothing | ||||||
| @@ -468,7 +394,7 @@ class ConcreteMachine: | |||||||
| 	write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); | 	write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); | ||||||
|  |  | ||||||
| 			// Add 6502-visible RAM as requested | 			// Add 6502-visible RAM as requested | ||||||
| 			switch(memory_model) { | 			switch(target.memory_model) { | ||||||
| 				case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded: | 				case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded: | ||||||
| 					// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000. | 					// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000. | ||||||
| 					set_ram(0x0000, 0x0400); | 					set_ram(0x0000, 0x0400); | ||||||
| @@ -515,39 +441,53 @@ class ConcreteMachine: | |||||||
| 			write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size())); | 			write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size())); | ||||||
|  |  | ||||||
| 			// install the system ROM | 			// install the system ROM | ||||||
| 			ROM character_rom; | 			write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, static_cast<uint16_t>(character_rom_.size())); | ||||||
| 			ROM kernel_rom; | 			write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, static_cast<uint16_t>(character_rom_.size())); | ||||||
| 			switch(region) { | 			write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, static_cast<uint16_t>(kernel_rom_.size())); | ||||||
| 				default: |  | ||||||
| 					character_rom = CharactersEnglish; | 			insert_media(target.media); | ||||||
| 					kernel_rom = KernelPAL; | 			if(!target.loading_command.empty()) { | ||||||
| 				break; | 				type_string(target.loading_command); | ||||||
| 				case Analyser::Static::Commodore::Target::Region::American: | 			} | ||||||
| 					character_rom = CharactersEnglish; | 		} | ||||||
| 					kernel_rom = KernelNTSC; |  | ||||||
| 				break; | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Danish: | 			if(!media.tapes.empty()) { | ||||||
| 					character_rom = CharactersDanish; | 				tape_->set_tape(media.tapes.front()); | ||||||
| 					kernel_rom = KernelDanish; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Japanese: |  | ||||||
| 					character_rom = CharactersJapanese; |  | ||||||
| 					kernel_rom = KernelJapanese; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Swedish: |  | ||||||
| 					character_rom = CharactersSwedish; |  | ||||||
| 					kernel_rom = KernelSwedish; |  | ||||||
| 				break; |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			write_to_map(processor_read_memory_map_, roms_[character_rom].data(), 0x8000, static_cast<uint16_t>(roms_[character_rom].size())); | 			if(!media.disks.empty() && c1540_) { | ||||||
| 			write_to_map(mos6560_bus_handler_.video_memory_map, roms_[character_rom].data(), 0x0000, static_cast<uint16_t>(roms_[character_rom].size())); | 				c1540_->set_disk(media.disks.front()); | ||||||
| 			write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size())); | 			} | ||||||
|  |  | ||||||
| 			// install the inserted ROM if there is one | 			if(!media.cartridges.empty()) { | ||||||
| 			if(!rom_.empty()) { | 				rom_address_ = 0xa000; | ||||||
|  | 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; | ||||||
|  | 				rom_length_ = static_cast<uint16_t>(rom_image.size()); | ||||||
|  |  | ||||||
|  | 				rom_ = rom_image; | ||||||
|  | 				rom_.resize(0x2000); | ||||||
| 				write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_); | 				write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			set_use_fast_tape(); | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
|  | 			if(key != KeyRestore) | ||||||
|  | 				keyboard_via_port_handler_->set_key_state(key, is_pressed); | ||||||
|  | 			else | ||||||
|  | 				user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			keyboard_via_port_handler_->clear_all_keys(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return joysticks_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| @@ -683,8 +623,8 @@ class ConcreteMachine: | |||||||
| 		void setup_output(float aspect_ratio) override final { | 		void setup_output(float aspect_ratio) override final { | ||||||
| 			mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_)); | 			mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_)); | ||||||
| 			mos6560_->set_high_frequency_cutoff(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | 			mos6560_->set_high_frequency_cutoff(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | ||||||
| 			// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set. | 			mos6560_->set_output_mode(output_mode_); | ||||||
| 			set_memory_map(commodore_target_.memory_model, commodore_target_.region); | 			mos6560_->set_clock_rate(get_clock_rate()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void close_output() override final { | 		void close_output() override final { | ||||||
| @@ -749,8 +689,8 @@ class ConcreteMachine: | |||||||
| 			return selection_set; | 			return selection_set; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { | ||||||
| 			tape_is_sleeping_ = is_sleeping; | 			tape_is_sleeping_ = clocking == ClockingHint::Preference::None; | ||||||
| 			set_use_fast_tape(); | 			set_use_fast_tape(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -763,11 +703,7 @@ class ConcreteMachine: | |||||||
| 		void update_video() { | 		void update_video() { | ||||||
| 			mos6560_->run_for(cycles_since_mos6560_update_.flush()); | 			mos6560_->run_for(cycles_since_mos6560_update_.flush()); | ||||||
| 		} | 		} | ||||||
| 		Analyser::Static::Commodore::Target commodore_target_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; |  | ||||||
|  |  | ||||||
| 		std::vector<uint8_t>  roms_[9]; |  | ||||||
|  |  | ||||||
| 		std::vector<uint8_t>  character_rom_; | 		std::vector<uint8_t>  character_rom_; | ||||||
| 		std::vector<uint8_t>  basic_rom_; | 		std::vector<uint8_t>  basic_rom_; | ||||||
| @@ -778,8 +714,6 @@ class ConcreteMachine: | |||||||
| 		uint8_t ram_[0x8000]; | 		uint8_t ram_[0x8000]; | ||||||
| 		uint8_t colour_ram_[0x0400]; | 		uint8_t colour_ram_[0x0400]; | ||||||
|  |  | ||||||
| 		std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_; |  | ||||||
|  |  | ||||||
| 		uint8_t *processor_read_memory_map_[64]; | 		uint8_t *processor_read_memory_map_[64]; | ||||||
| 		uint8_t *processor_write_memory_map_[64]; | 		uint8_t *processor_write_memory_map_[64]; | ||||||
| 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { | 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { | ||||||
| @@ -797,6 +731,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		Cycles cycles_since_mos6560_update_; | 		Cycles cycles_since_mos6560_update_; | ||||||
| 		Vic6560BusHandler mos6560_bus_handler_; | 		Vic6560BusHandler mos6560_bus_handler_; | ||||||
|  | 		MOS::MOS6560::OutputMode output_mode_; | ||||||
| 		std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_; | 		std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_; | ||||||
| 		std::shared_ptr<UserPortVIA> user_port_via_port_handler_; | 		std::shared_ptr<UserPortVIA> user_port_via_port_handler_; | ||||||
| 		std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_; | 		std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_; | ||||||
| @@ -825,8 +760,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Commodore::Vic20; | using namespace Commodore::Vic20; | ||||||
|  |  | ||||||
| Machine *Machine::Vic20() { | Machine *Machine::Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new Vic20::ConcreteMachine; | 	using Target = Analyser::Static::Commodore::Target; | ||||||
|  | 	const Target *const commodore_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new Vic20::ConcreteMachine(*commodore_target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,6 +10,11 @@ | |||||||
| #define Vic20_hpp | #define Vic20_hpp | ||||||
|  |  | ||||||
| #include "../../../Configurable/Configurable.hpp" | #include "../../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace Vic20 { | namespace Vic20 { | ||||||
| @@ -22,7 +27,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns a Vic-20. | 		/// Creates and returns a Vic-20. | ||||||
| 		static Machine *Vic20(); | 		static Machine *Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,38 +0,0 @@ | |||||||
| // |  | ||||||
| //  ConfigurationTarget.h |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 08/09/2016. |  | ||||||
| //  Copyright 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef ConfigurationTarget_hpp |  | ||||||
| #define ConfigurationTarget_hpp |  | ||||||
|  |  | ||||||
| #include "../Analyser/Static/StaticAnalyser.hpp" |  | ||||||
| #include "../Configurable/Configurable.hpp" |  | ||||||
|  |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ConfigurationTarget { |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	A ConfigurationTarget::Machine is anything that can accept a Analyser::Static::Target |  | ||||||
| 	and configure itself appropriately, or accept a list of media subsequently to insert. |  | ||||||
| */ |  | ||||||
| class Machine { |  | ||||||
| 	public: |  | ||||||
| 		/// Instructs the machine to configure itself as described by @c target and insert the included media. |  | ||||||
| 		virtual void configure_as_target(const Analyser::Static::Target *target) = 0; |  | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Requests that the machine insert @c media as a modification to current state |  | ||||||
|  |  | ||||||
| 			@returns @c true if any media was inserted; @c false otherwise. |  | ||||||
| 		*/ |  | ||||||
| 		virtual bool insert_media(const Analyser::Static::Media &media) = 0; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* ConfigurationTarget_h */ |  | ||||||
| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../Configurable/Configurable.hpp" | #include "../Configurable/Configurable.hpp" | ||||||
| #include "../Activity/Source.hpp" | #include "../Activity/Source.hpp" | ||||||
| #include "ConfigurationTarget.hpp" | #include "MediaTarget.hpp" | ||||||
| #include "CRTMachine.hpp" | #include "CRTMachine.hpp" | ||||||
| #include "JoystickMachine.hpp" | #include "JoystickMachine.hpp" | ||||||
| #include "KeyboardMachine.hpp" | #include "KeyboardMachine.hpp" | ||||||
| @@ -27,11 +27,11 @@ struct DynamicMachine { | |||||||
| 	virtual ~DynamicMachine() {} | 	virtual ~DynamicMachine() {} | ||||||
|  |  | ||||||
| 	virtual Activity::Source *activity_source() = 0; | 	virtual Activity::Source *activity_source() = 0; | ||||||
| 	virtual ConfigurationTarget::Machine *configuration_target() = 0; | 	virtual Configurable::Device *configurable_device() = 0; | ||||||
| 	virtual CRTMachine::Machine *crt_machine() = 0; | 	virtual CRTMachine::Machine *crt_machine() = 0; | ||||||
| 	virtual JoystickMachine::Machine *joystick_machine() = 0; | 	virtual JoystickMachine::Machine *joystick_machine() = 0; | ||||||
| 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | ||||||
| 	virtual Configurable::Device *configurable_device() = 0; | 	virtual MediaTarget::Machine *media_target() = 0; | ||||||
|  |  | ||||||
| 	/*! | 	/*! | ||||||
| 		Provides a raw pointer to the underlying machine if and only if this dynamic machine really is | 		Provides a raw pointer to the underlying machine if and only if this dynamic machine really is | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #include "Electron.hpp" | #include "Electron.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| @@ -41,7 +41,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| @@ -49,10 +49,10 @@ class ConcreteMachine: | |||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			m6502_(*this), | 				m6502_(*this), | ||||||
| 			sound_generator_(audio_queue_), | 				sound_generator_(audio_queue_), | ||||||
| 			speaker_(sound_generator_) { | 				speaker_(sound_generator_) { | ||||||
| 			memset(key_states_, 0, sizeof(key_states_)); | 			memset(key_states_, 0, sizeof(key_states_)); | ||||||
| 			for(int c = 0; c < 16; c++) | 			for(int c = 0; c < 16; c++) | ||||||
| 				memset(roms_[c], 0xff, 16384); | 				memset(roms_[c], 0xff, 16384); | ||||||
| @@ -61,60 +61,53 @@ class ConcreteMachine: | |||||||
| 			set_clock_rate(2000000); | 			set_clock_rate(2000000); | ||||||
|  |  | ||||||
| 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | ||||||
|  |  | ||||||
|  | 			std::vector<std::string> rom_names = {"basic.rom", "os.rom"}; | ||||||
|  | 			if(target.has_adfs) { | ||||||
|  | 				rom_names.push_back("ADFS-E00_1.rom"); | ||||||
|  | 				rom_names.push_back("ADFS-E00_2.rom"); | ||||||
|  | 			} | ||||||
|  | 			const size_t dfs_rom_position = rom_names.size(); | ||||||
|  | 			if(target.has_dfs) { | ||||||
|  | 				rom_names.push_back("DFS-1770-2.20.rom"); | ||||||
|  | 			} | ||||||
|  | 			const auto roms = rom_fetcher("Electron", rom_names); | ||||||
|  |  | ||||||
|  | 			for(const auto &rom: roms) { | ||||||
|  | 				if(!rom) { | ||||||
|  | 					throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			set_rom(ROM::BASIC, *roms[0], false); | ||||||
|  | 			set_rom(ROM::OS, *roms[1], false); | ||||||
|  |  | ||||||
|  | 			if(target.has_dfs || target.has_adfs) { | ||||||
|  | 				plus3_.reset(new Plus3); | ||||||
|  |  | ||||||
|  | 				if(target.has_dfs) { | ||||||
|  | 					set_rom(ROM::Slot0, *roms[dfs_rom_position], true); | ||||||
|  | 				} | ||||||
|  | 				if(target.has_adfs) { | ||||||
|  | 					set_rom(ROM::Slot4, *roms[2], true); | ||||||
|  | 					set_rom(ROM::Slot5, *roms[3], true); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  |  | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.should_shift_restart) { | ||||||
|  | 				shift_restart_counter_ = 1000000; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| 			audio_queue_.flush(); | 			audio_queue_.flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final { |  | ||||||
| 			uint8_t *target = nullptr; |  | ||||||
| 			switch(slot) { |  | ||||||
| 				case ROMSlotDFS:	dfs_ = data;			return; |  | ||||||
| 				case ROMSlotADFS1:	adfs1_ = data;			return; |  | ||||||
| 				case ROMSlotADFS2:	adfs2_ = data;			return; |  | ||||||
|  |  | ||||||
| 				case ROMSlotOS:		target = os_;			break; |  | ||||||
| 				default: |  | ||||||
| 					target = roms_[slot]; |  | ||||||
| 					rom_write_masks_[slot] = is_writeable; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Copy in, with mirroring. |  | ||||||
| 			std::size_t rom_ptr = 0; |  | ||||||
| 			while(rom_ptr < 16384) { |  | ||||||
| 				std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size()); |  | ||||||
| 				std::memcpy(&target[rom_ptr], data.data(), size_to_copy); |  | ||||||
| 				rom_ptr += size_to_copy; |  | ||||||
| 			} |  | ||||||
| 			rom_inserted_[slot] = true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"Electron", |  | ||||||
| 				{ |  | ||||||
| 					"DFS-1770-2.20.rom", |  | ||||||
| 					"ADFS-E00_1.rom",	"ADFS-E00_2.rom", |  | ||||||
| 					"basic.rom",		"os.rom" |  | ||||||
| 				}); |  | ||||||
| 			ROMSlot slots[] = { |  | ||||||
| 				ROMSlotDFS, |  | ||||||
| 				ROMSlotADFS1, ROMSlotADFS2, |  | ||||||
| 				ROMSlotBASIC, ROMSlotOS |  | ||||||
| 			}; |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				auto &data = roms[index]; |  | ||||||
| 				if(!data) return false; |  | ||||||
| 				set_rom(slots[index], *data, false); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed) override final { | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
| 			if(key == KeyBreak) { | 			if(key == KeyBreak) { | ||||||
| 				m6502_.set_reset_line(isPressed); | 				m6502_.set_reset_line(isPressed); | ||||||
| @@ -131,32 +124,6 @@ class ConcreteMachine: | |||||||
| 			if(is_holding_shift_) set_key_state(KeyShift, true); | 			if(is_holding_shift_) set_key_state(KeyShift, true); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target); |  | ||||||
|  |  | ||||||
| 			if(!acorn_target->loading_command.empty()) { |  | ||||||
| 				type_string(acorn_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(acorn_target->should_shift_restart) { |  | ||||||
| 				shift_restart_counter_ = 1000000; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(acorn_target->has_dfs || acorn_target->has_adfs) { |  | ||||||
| 				plus3_.reset(new Plus3); |  | ||||||
|  |  | ||||||
| 				if(acorn_target->has_dfs) { |  | ||||||
| 					set_rom(ROMSlot0, dfs_, true); |  | ||||||
| 				} |  | ||||||
| 				if(acorn_target->has_adfs) { |  | ||||||
| 					set_rom(ROMSlot4, adfs1_, true); |  | ||||||
| 					set_rom(ROMSlot5, adfs2_, true); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_.set_tape(media.tapes.front()); | 				tape_.set_tape(media.tapes.front()); | ||||||
| @@ -167,11 +134,11 @@ class ConcreteMachine: | |||||||
| 				plus3_->set_disk(media.disks.front(), 0); | 				plus3_->set_disk(media.disks.front(), 0); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			ROMSlot slot = ROMSlot12; | 			ROM slot = ROM::Slot12; | ||||||
| 			for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) { | 			for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) { | ||||||
| 				const ROMSlot first_slot_tried = slot; | 				const ROM first_slot_tried = slot; | ||||||
| 				while(rom_inserted_[slot]) { | 				while(rom_inserted_[static_cast<int>(slot)]) { | ||||||
| 					slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15); | 					slot = static_cast<ROM>((static_cast<int>(slot) + 1) & 15); | ||||||
| 					if(slot == first_slot_tried) return false; | 					if(slot == first_slot_tried) return false; | ||||||
| 				} | 				} | ||||||
| 				set_rom(slot, cartridge->get_segments().front().data, false); | 				set_rom(slot, cartridge->get_segments().front().data, false); | ||||||
| @@ -258,7 +225,7 @@ class ConcreteMachine: | |||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// latch the paged ROM in case external hardware is being emulated | 							// latch the paged ROM in case external hardware is being emulated | ||||||
| 							active_rom_ = (Electron::ROMSlot)(*value & 0xf); | 							active_rom_ = *value & 0xf; | ||||||
|  |  | ||||||
| 							// apply the ULA's test | 							// apply the ULA's test | ||||||
| 							if(*value & 0x08) { | 							if(*value & 0x08) { | ||||||
| @@ -363,7 +330,7 @@ class ConcreteMachine: | |||||||
| 									} | 									} | ||||||
| 								} | 								} | ||||||
| 								if(basic_is_active_) { | 								if(basic_is_active_) { | ||||||
| 									*value &= roms_[ROMSlotBASIC][address & 16383]; | 									*value &= roms_[static_cast<int>(ROM::BASIC)][address & 16383]; | ||||||
| 								} | 								} | ||||||
| 							} else if(rom_write_masks_[active_rom_]) { | 							} else if(rom_write_masks_[active_rom_]) { | ||||||
| 								roms_[active_rom_][address & 16383] = *value; | 								roms_[active_rom_][address & 16383] = *value; | ||||||
| @@ -480,15 +447,64 @@ class ConcreteMachine: | |||||||
| 			return selection_set; | 			return selection_set; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Activity Source | ||||||
| 		void set_activity_observer(Activity::Observer *observer) override { | 		void set_activity_observer(Activity::Observer *observer) override { | ||||||
| 			activity_observer_ = observer; | 			activity_observer_ = observer; | ||||||
| 			if(activity_observer_) { | 			if(activity_observer_) { | ||||||
| 				activity_observer_->register_led(caps_led); | 				activity_observer_->register_led(caps_led); | ||||||
| 				activity_observer_->set_led_status(caps_led, caps_led_state_); | 				activity_observer_->set_led_status(caps_led, caps_led_state_); | ||||||
|  |  | ||||||
|  | 				if(plus3_) { | ||||||
|  | 					plus3_->set_activity_observer(observer); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		enum class ROM { | ||||||
|  | 			Slot0 = 0, | ||||||
|  | 			Slot1,	Slot2,	Slot3, | ||||||
|  | 			Slot4,	Slot5,	Slot6,	Slot7, | ||||||
|  |  | ||||||
|  | 			Keyboard = 8,	Slot9, | ||||||
|  | 			BASIC = 10,		Slot11, | ||||||
|  |  | ||||||
|  | 			Slot12,	Slot13,	Slot14,	Slot15, | ||||||
|  |  | ||||||
|  | 			OS,		DFS, | ||||||
|  | 			ADFS1,	ADFS2 | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot | ||||||
|  | 			is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. | ||||||
|  | 		*/ | ||||||
|  | 		void set_rom(ROM slot, const std::vector<uint8_t> &data, bool is_writeable) { | ||||||
|  | 			uint8_t *target = nullptr; | ||||||
|  | 			switch(slot) { | ||||||
|  | 				case ROM::DFS:		dfs_ = data;			return; | ||||||
|  | 				case ROM::ADFS1:	adfs1_ = data;			return; | ||||||
|  | 				case ROM::ADFS2:	adfs2_ = data;			return; | ||||||
|  |  | ||||||
|  | 				case ROM::OS:		target = os_;			break; | ||||||
|  | 				default: | ||||||
|  | 					target = roms_[static_cast<int>(slot)]; | ||||||
|  | 					rom_write_masks_[static_cast<int>(slot)] = is_writeable; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Copy in, with mirroring. | ||||||
|  | 			std::size_t rom_ptr = 0; | ||||||
|  | 			while(rom_ptr < 16384) { | ||||||
|  | 				std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size()); | ||||||
|  | 				std::memcpy(&target[rom_ptr], data.data(), size_to_copy); | ||||||
|  | 				rom_ptr += size_to_copy; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(static_cast<int>(slot) < 16) | ||||||
|  | 				rom_inserted_[static_cast<int>(slot)] = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Work deferral updates. | 		// MARK: - Work deferral updates. | ||||||
| 		inline void update_display() { | 		inline void update_display() { | ||||||
| 			if(cycles_since_display_update_ > 0) { | 			if(cycles_since_display_update_ > 0) { | ||||||
| @@ -525,7 +541,7 @@ class ConcreteMachine: | |||||||
| 			m6502_.set_irq_line(interrupt_status_ & 1); | 			m6502_.set_irq_line(interrupt_status_ & 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		// Things that directly constitute the memory map. | 		// Things that directly constitute the memory map. | ||||||
| 		uint8_t roms_[16][16384]; | 		uint8_t roms_[16][16384]; | ||||||
| @@ -535,7 +551,7 @@ class ConcreteMachine: | |||||||
| 		std::vector<uint8_t> dfs_, adfs1_, adfs2_; | 		std::vector<uint8_t> dfs_, adfs1_, adfs2_; | ||||||
|  |  | ||||||
| 		// Paging | 		// Paging | ||||||
| 		ROMSlot active_rom_ = ROMSlot::ROMSlot0; | 		int active_rom_ = static_cast<int>(ROM::Slot0); | ||||||
| 		bool keyboard_is_active_ = false; | 		bool keyboard_is_active_ = false; | ||||||
| 		bool basic_is_active_ = false; | 		bool basic_is_active_ = false; | ||||||
|  |  | ||||||
| @@ -585,8 +601,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| Machine *Machine::Electron() { | Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new Electron::ConcreteMachine; | 	using Target = Analyser::Static::Acorn::Target; | ||||||
|  | 	const Target *const acorn_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new Electron::ConcreteMachine(*acorn_target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,26 +10,15 @@ | |||||||
| #define Electron_hpp | #define Electron_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace Electron { | namespace Electron { | ||||||
|  |  | ||||||
| enum ROMSlot: uint8_t { |  | ||||||
| 	ROMSlot0 = 0, |  | ||||||
| 	ROMSlot1,	ROMSlot2,	ROMSlot3, |  | ||||||
| 	ROMSlot4,	ROMSlot5,	ROMSlot6,	ROMSlot7, |  | ||||||
|  |  | ||||||
| 	ROMSlotKeyboard = 8,	ROMSlot9, |  | ||||||
| 	ROMSlotBASIC = 10,		ROMSlot11, |  | ||||||
|  |  | ||||||
| 	ROMSlot12,	ROMSlot13,	ROMSlot14,	ROMSlot15, |  | ||||||
|  |  | ||||||
| 	ROMSlotOS,		ROMSlotDFS, |  | ||||||
| 	ROMSlotADFS1,	ROMSlotADFS2 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// @returns The options available for an Electron. | /// @returns The options available for an Electron. | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| @@ -44,13 +33,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Electron. | 		/// Creates and returns an Electron. | ||||||
| 		static Machine *Electron(); | 		static Machine *Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot |  | ||||||
| 			is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. |  | ||||||
| 		*/ |  | ||||||
| 		virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,14 +11,12 @@ | |||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| Plus3::Plus3() : WD1770(P1770) { | Plus3::Plus3() : WD1770(P1770) { | ||||||
|  | 	drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2)); | ||||||
|  | 	drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2)); | ||||||
| 	set_control_register(last_control_, 0xff); | 	set_control_register(last_control_, 0xff); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) { | ||||||
| 	if(!drives_[drive]) { |  | ||||||
| 		drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); |  | ||||||
| 		if(drive == selected_drive_) set_drive(drives_[drive]); |  | ||||||
| 	} |  | ||||||
| 	drives_[drive]->set_disk(disk); | 	drives_[drive]->set_disk(disk); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -42,8 +40,8 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if(changes & 0x04) { | 	if(changes & 0x04) { | ||||||
| 		if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); | 		drives_[0]->set_head((control & 0x04) ? 1 : 0); | ||||||
| 		if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); | 		drives_[1]->set_head((control & 0x04) ? 1 : 0); | ||||||
| 	} | 	} | ||||||
| 	if(changes & 0x08) set_is_double_density(!(control & 0x08)); | 	if(changes & 0x08) set_is_double_density(!(control & 0x08)); | ||||||
| } | } | ||||||
| @@ -55,6 +53,9 @@ void Plus3::set_motor_on(bool on) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Plus3::set_activity_observer(Activity::Observer *observer) { | void Plus3::set_activity_observer(Activity::Observer *observer) { | ||||||
| 	drives_[0]->set_activity_observer(observer, "Drive 1", true); | 	size_t index = 0; | ||||||
| 	drives_[1]->set_activity_observer(observer, "Drive 2", true); | 	for(const auto &drive: drives_) { | ||||||
|  | 		drive->set_activity_observer(observer, "Drive " + std::to_string(index+1), true); | ||||||
|  | 		++index; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,17 +18,18 @@ class Plus3 : public WD::WD1770 { | |||||||
| 	public: | 	public: | ||||||
| 		Plus3(); | 		Plus3(); | ||||||
|  |  | ||||||
| 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive); | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive); | ||||||
| 		void set_control_register(uint8_t control); | 		void set_control_register(uint8_t control); | ||||||
| 		void set_activity_observer(Activity::Observer *observer); | 		void set_activity_observer(Activity::Observer *observer); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void set_control_register(uint8_t control, uint8_t changes); | 		void set_control_register(uint8_t control, uint8_t changes); | ||||||
| 		std::shared_ptr<Storage::Disk::Drive> drives_[2]; | 		std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_; | ||||||
| 		int selected_drive_ = 0; | 		int selected_drive_ = 0; | ||||||
| 		uint8_t last_control_ = 0; | 		uint8_t last_control_ = 0; | ||||||
|  |  | ||||||
| 		void set_motor_on(bool on); | 		void set_motor_on(bool on); | ||||||
|  | 		std::string drive_name(size_t drive); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) { | |||||||
| 	std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender); | 	std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender); | ||||||
| 	crt_->set_bookender(std::move(bookender)); | 	crt_->set_bookender(std::move(bookender)); | ||||||
| 	// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. | 	// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. | ||||||
| 	crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); | 	crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); | ||||||
| } | } | ||||||
|  |  | ||||||
| // MARK: - CRT getter | // MARK: - CRT getter | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ using namespace MSX; | |||||||
| DiskROM::DiskROM(const std::vector<uint8_t> &rom) : | DiskROM::DiskROM(const std::vector<uint8_t> &rom) : | ||||||
| 	WD1770(P1793), | 	WD1770(P1793), | ||||||
| 	rom_(rom) { | 	rom_(rom) { | ||||||
|  | 	drives_[0].reset(new Storage::Disk::Drive(8000000, 300, 2)); | ||||||
|  | 	drives_[1].reset(new Storage::Disk::Drive(8000000, 300, 2)); | ||||||
| 	set_is_double_density(true); | 	set_is_double_density(true); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -23,16 +25,16 @@ void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) { | |||||||
| 		break; | 		break; | ||||||
| 		case 0x7ffc: | 		case 0x7ffc: | ||||||
| 			selected_head_ = value & 1; | 			selected_head_ = value & 1; | ||||||
| 			if(drives_[0]) drives_[0]->set_head(selected_head_); | 			drives_[0]->set_head(selected_head_); | ||||||
| 			if(drives_[1]) drives_[1]->set_head(selected_head_); | 			drives_[1]->set_head(selected_head_); | ||||||
| 		break; | 		break; | ||||||
| 		case 0x7ffd: { | 		case 0x7ffd: { | ||||||
| 			selected_drive_ = value & 1; | 			selected_drive_ = value & 1; | ||||||
| 			set_drive(drives_[selected_drive_]); | 			set_drive(drives_[selected_drive_]); | ||||||
|  |  | ||||||
| 			bool drive_motor = !!(value & 0x80); | 			bool drive_motor = !!(value & 0x80); | ||||||
| 			if(drives_[0]) drives_[0]->set_motor_on(drive_motor); | 			drives_[0]->set_motor_on(drive_motor); | ||||||
| 			if(drives_[1]) drives_[1]->set_motor_on(drive_motor); | 			drives_[1]->set_motor_on(drive_motor); | ||||||
| 		} break; | 		} break; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -57,12 +59,6 @@ void DiskROM::run_for(HalfCycles half_cycles) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) { | void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) { | ||||||
| 	if(!drives_[drive]) { |  | ||||||
| 		drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); |  | ||||||
| 		drives_[drive]->set_head(selected_head_); |  | ||||||
| 		if(drive == selected_drive_) set_drive(drives_[drive]); |  | ||||||
| 		drives_[drive]->set_activity_observer(observer_, drive_name(drive), true); |  | ||||||
| 	} |  | ||||||
| 	drives_[drive]->set_disk(disk); | 	drives_[drive]->set_disk(disk); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -72,14 +68,9 @@ void DiskROM::set_head_load_request(bool head_load) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void DiskROM::set_activity_observer(Activity::Observer *observer) { | void DiskROM::set_activity_observer(Activity::Observer *observer) { | ||||||
| 	size_t c = 0; | 	size_t c = 1; | ||||||
| 	observer_ = observer; |  | ||||||
| 	for(auto &drive: drives_) { | 	for(auto &drive: drives_) { | ||||||
| 		if(drive) drive->set_activity_observer(observer, drive_name(c), true); | 		drive->set_activity_observer(observer, "Drive " + std::to_string(c), true); | ||||||
| 		++c; | 		++c; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string DiskROM::drive_name(size_t index) { |  | ||||||
| 	return "Drive " + std::to_string(index); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -41,8 +41,6 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 { | |||||||
| 		std::array<std::shared_ptr<Storage::Disk::Drive>, 2> drives_; | 		std::array<std::shared_ptr<Storage::Disk::Drive>, 2> drives_; | ||||||
|  |  | ||||||
| 		void set_head_load_request(bool head_load) override; | 		void set_head_load_request(bool head_load) override; | ||||||
| 		std::string drive_name(size_t index); |  | ||||||
| 		Activity::Observer *observer_ = nullptr; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -32,7 +32,8 @@ | |||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../JoystickMachine.hpp" | ||||||
|  | #include "../MediaTarget.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | ||||||
| @@ -54,13 +55,19 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
|  |  | ||||||
| class AYPortHandler: public GI::AY38910::PortHandler { | class AYPortHandler: public GI::AY38910::PortHandler { | ||||||
| 	public: | 	public: | ||||||
| 		AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {} | 		AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) { | ||||||
|  | 			joysticks_.emplace_back(new Joystick); | ||||||
|  | 			joysticks_.emplace_back(new Joystick); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_port_output(bool port_b, uint8_t value) { | 		void set_port_output(bool port_b, uint8_t value) { | ||||||
| 			if(port_b) { | 			if(port_b) { | ||||||
| 				// Bits 0-3: touchpad handshaking (?) | 				// Bits 0-3: touchpad handshaking (?) | ||||||
| 				// Bit 4-5: monostable timer pulses | 				// Bit 4-5: monostable timer pulses | ||||||
|  |  | ||||||
| 				// Bit 6: joystick select | 				// Bit 6: joystick select | ||||||
|  | 				selected_joystick_ = (value >> 6) & 1; | ||||||
|  |  | ||||||
| 				// Bit 7: code LED, if any | 				// Bit 7: code LED, if any | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -69,29 +76,75 @@ class AYPortHandler: public GI::AY38910::PortHandler { | |||||||
| 			if(!port_b) { | 			if(!port_b) { | ||||||
| 				// Bits 0-5: Joystick (up, down, left, right, A, B) | 				// Bits 0-5: Joystick (up, down, left, right, A, B) | ||||||
| 				// Bit 6: keyboard switch (not universal) | 				// Bit 6: keyboard switch (not universal) | ||||||
|  |  | ||||||
| 				// Bit 7: tape input | 				// Bit 7: tape input | ||||||
| 				return 0x7f | (tape_player_.get_input() ? 0x00 : 0x80); | 				return | ||||||
|  | 					(static_cast<Joystick *>(joysticks_[selected_joystick_].get())->get_state() & 0x3f) | | ||||||
|  | 					0x40 | | ||||||
|  | 					(tape_player_.get_input() ? 0x00 : 0x80); | ||||||
| 			} | 			} | ||||||
| 			return 0xff; | 			return 0xff; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { | ||||||
|  | 			return joysticks_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Storage::Tape::BinaryTapePlayer &tape_player_; | 		Storage::Tape::BinaryTapePlayer &tape_player_; | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | 		size_t selected_joystick_ = 0; | ||||||
|  | 		class Joystick: public Inputs::ConcreteJoystick { | ||||||
|  | 			public: | ||||||
|  | 				Joystick() : | ||||||
|  | 					ConcreteJoystick({ | ||||||
|  | 						Input(Input::Up), | ||||||
|  | 						Input(Input::Down), | ||||||
|  | 						Input(Input::Left), | ||||||
|  | 						Input(Input::Right), | ||||||
|  | 						Input(Input::Fire, 0), | ||||||
|  | 						Input(Input::Fire, 1), | ||||||
|  | 					}) {} | ||||||
|  |  | ||||||
|  | 				void did_set_input(const Input &input, bool is_active) override { | ||||||
|  | 					uint8_t mask = 0; | ||||||
|  | 					switch(input.type) { | ||||||
|  | 						default: return; | ||||||
|  | 						case Input::Up:		mask = 0x01;	break; | ||||||
|  | 						case Input::Down:	mask = 0x02;	break; | ||||||
|  | 						case Input::Left:	mask = 0x04;	break; | ||||||
|  | 						case Input::Right:	mask = 0x08;	break; | ||||||
|  | 						case Input::Fire: | ||||||
|  | 							if(input.info.control.index >= 2) return; | ||||||
|  | 							mask = input.info.control.index ? 0x20 : 0x10; | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(is_active) state_ &= ~mask; else state_ |= mask; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				uint8_t get_state() { | ||||||
|  | 					return state_; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				uint8_t state_ = 0xff; | ||||||
|  | 		}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
|  | 	public JoystickMachine::Machine, | ||||||
| 	public MemoryMap, | 	public MemoryMap, | ||||||
| 	public Sleeper::SleepObserver, | 	public ClockingHint::Observer, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(): | 		ConcreteMachine(const Analyser::Static::MSX::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			i8255_(i8255_port_handler_), | 			i8255_(i8255_port_handler_), | ||||||
| 			ay_(audio_queue_), | 			ay_(audio_queue_), | ||||||
| @@ -108,10 +161,55 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			ay_.set_port_handler(&ay_port_handler_); | 			ay_.set_port_handler(&ay_port_handler_); | ||||||
| 			speaker_.set_input_rate(3579545.0f / 2.0f); | 			speaker_.set_input_rate(3579545.0f / 2.0f); | ||||||
| 			tape_player_.set_sleep_observer(this); | 			tape_player_.set_clocking_hint_observer(this); | ||||||
|  |  | ||||||
| 			// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC. | 			// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC. | ||||||
| 			mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); | 			mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); | ||||||
|  |  | ||||||
|  | 			// Fetch the necessary ROMs. | ||||||
|  | 			std::vector<std::string> rom_names = {"msx.rom"}; | ||||||
|  | 			if(target.has_disk_drive) { | ||||||
|  | 				rom_names.push_back("disk.rom"); | ||||||
|  | 			} | ||||||
|  | 			const auto roms = rom_fetcher("MSX", rom_names); | ||||||
|  |  | ||||||
|  | 			if(!roms[0] || (target.has_disk_drive && !roms[1])) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			memory_slots_[0].source = std::move(*roms[0]); | ||||||
|  | 			memory_slots_[0].source.resize(32768); | ||||||
|  |  | ||||||
|  | 			for(size_t c = 0; c < 8; ++c) { | ||||||
|  | 				for(size_t slot = 0; slot < 3; ++slot) { | ||||||
|  | 					memory_slots_[slot].read_pointers[c] = unpopulated_; | ||||||
|  | 					memory_slots_[slot].write_pointers[c] = scratch_; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				memory_slots_[3].read_pointers[c] = | ||||||
|  | 				memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			map(0, 0, 0, 32768); | ||||||
|  | 			page_memory(0); | ||||||
|  |  | ||||||
|  | 			// Add a disk cartridge if any disks were supplied. | ||||||
|  | 			if(target.has_disk_drive) { | ||||||
|  | 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); | ||||||
|  | 				memory_slots_[2].source = std::move(*roms[1]); | ||||||
|  | 				memory_slots_[2].source.resize(16384); | ||||||
|  |  | ||||||
|  | 				map(2, 0, 0x4000, 0x2000); | ||||||
|  | 				unmap(2, 0x6000, 0x2000); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Insert the media. | ||||||
|  | 			insert_media(target.media); | ||||||
|  |  | ||||||
|  | 			// Type whatever has been requested. | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -152,25 +250,6 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			auto *const msx_target = dynamic_cast<const Analyser::Static::MSX::Target *>(target); |  | ||||||
|  |  | ||||||
| 			// Add a disk cartridge if any disks were supplied. |  | ||||||
| 			if(msx_target->has_disk_drive) { |  | ||||||
| 				map(2, 0, 0x4000, 0x2000); |  | ||||||
| 				unmap(2, 0x6000, 0x2000); |  | ||||||
| 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Insert the media. |  | ||||||
| 			insert_media(target->media); |  | ||||||
|  |  | ||||||
| 			// Type whatever has been requested. |  | ||||||
| 			if(!msx_target->loading_command.empty()) { |  | ||||||
| 				type_string(msx_target->loading_command); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| 			if(!media.cartridges.empty()) { | 			if(!media.cartridges.empty()) { | ||||||
| 				const auto &segment = media.cartridges.front()->get_segments().front(); | 				const auto &segment = media.cartridges.front()->get_segments().front(); | ||||||
| @@ -467,39 +546,6 @@ class ConcreteMachine: | |||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"MSX", |  | ||||||
| 				{ |  | ||||||
| 					"msx.rom", |  | ||||||
| 					"disk.rom" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			if(!roms[0] || !roms[1]) return false; |  | ||||||
|  |  | ||||||
| 			memory_slots_[0].source = std::move(*roms[0]); |  | ||||||
| 			memory_slots_[0].source.resize(32768); |  | ||||||
|  |  | ||||||
| 			memory_slots_[2].source = std::move(*roms[1]); |  | ||||||
| 			memory_slots_[2].source.resize(16384); |  | ||||||
|  |  | ||||||
| 			for(size_t c = 0; c < 8; ++c) { |  | ||||||
| 				for(size_t slot = 0; slot < 3; ++slot) { |  | ||||||
| 					memory_slots_[slot].read_pointers[c] = unpopulated_; |  | ||||||
| 					memory_slots_[slot].write_pointers[c] = scratch_; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				memory_slots_[3].read_pointers[c] = |  | ||||||
| 				memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			map(0, 0, 0, 32768); |  | ||||||
| 			page_memory(0); |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_keyboard_line(int line) { | 		void set_keyboard_line(int line) { | ||||||
| 			selected_key_line_ = line; | 			selected_key_line_ = line; | ||||||
| 		} | 		} | ||||||
| @@ -555,8 +601,8 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Sleeper | 		// MARK: - Sleeper | ||||||
| 		void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { | ||||||
| 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | 			tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; | ||||||
| 			set_use_fast_tape(); | 			set_use_fast_tape(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -568,6 +614,11 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Joysticks | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return ay_port_handler_.get_joysticks(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		DiskROM *get_disk_rom() { | 		DiskROM *get_disk_rom() { | ||||||
| 			return dynamic_cast<DiskROM *>(memory_slots_[2].handler.get()); | 			return dynamic_cast<DiskROM *>(memory_slots_[2].handler.get()); | ||||||
| @@ -683,8 +734,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace MSX; | using namespace MSX; | ||||||
|  |  | ||||||
| Machine *Machine::MSX() { | Machine *Machine::MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new ConcreteMachine; | 	using Target = Analyser::Static::MSX::Target; | ||||||
|  | 	const Target *const msx_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new ConcreteMachine(*msx_target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,17 +10,22 @@ | |||||||
| #define MSX_hpp | #define MSX_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace MSX { | namespace MSX { | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
| 		static Machine *MSX(); | 		static Machine *MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* MSX_hpp */ | #endif /* MSX_hpp */ | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								Machines/MediaTarget.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Machines/MediaTarget.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  MediaTarget.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 08/09/2016. | ||||||
|  | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MediaTarget_hpp | ||||||
|  | #define MediaTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../Configurable/Configurable.hpp" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace MediaTarget { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A MediaTarget::Machine is anything that can accept new media while running. | ||||||
|  | */ | ||||||
|  | class Machine { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Requests that the machine insert @c media as a modification to current state | ||||||
|  |  | ||||||
|  | 			@returns @c true if any media was inserted; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		virtual bool insert_media(const Analyser::Static::Media &media) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MediaTarget_hpp */ | ||||||
| @@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 { | |||||||
| 		bool get_interrupt_request_line(); | 		bool get_interrupt_request_line(); | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles); | 		void run_for(const Cycles cycles); | ||||||
| 		using WD::WD1770::run_for; |  | ||||||
|  |  | ||||||
| 		enum PagingFlags { | 		enum PagingFlags { | ||||||
| 			/// Indicates that the BASIC ROM should be disabled; if this is set then either | 			/// Indicates that the BASIC ROM should be disabled; if this is set then either | ||||||
| @@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void set_control_register(uint8_t control, uint8_t changes); | 		void set_control_register(uint8_t control, uint8_t changes); | ||||||
| 		void set_head_load_request(bool head_load); | 		void set_head_load_request(bool head_load) override; | ||||||
| 		bool get_drive_is_ready(); | 		bool get_drive_is_ready(); | ||||||
|  |  | ||||||
| 		std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_; | 		std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_; | ||||||
| 		size_t selected_drive_; | 		size_t selected_drive_; | ||||||
| 		bool irq_enable_ = false; | 		bool irq_enable_ = false; | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| @@ -193,7 +193,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { | |||||||
|  |  | ||||||
| template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine: | template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| @@ -201,36 +201,29 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public Storage::Tape::BinaryTapePlayer::Delegate, | 	public Storage::Tape::BinaryTapePlayer::Delegate, | ||||||
| 	public Microdisc::Delegate, | 	public Microdisc::Delegate, | ||||||
| 	public Sleeper::SleepObserver, | 	public ClockingHint::Observer, | ||||||
| 	public Activity::Source, | 	public Activity::Source, | ||||||
| 	public Machine { | 	public Machine { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(const Analyser::Static::Oric::Target *target) : | 		ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 				m6502_(*this), | 				m6502_(*this), | ||||||
| 				rom_type_(target ? target->rom : Analyser::Static::Oric::Target::ROM::BASIC10), |  | ||||||
| 				ay8910_(audio_queue_), | 				ay8910_(audio_queue_), | ||||||
| 				speaker_(ay8910_), | 				speaker_(ay8910_), | ||||||
| 				via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), | 				via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), | ||||||
| 				via_(via_port_handler_) { | 				via_(via_port_handler_), | ||||||
|  | 				diskii_(2000000) { | ||||||
| 			set_clock_rate(1000000); | 			set_clock_rate(1000000); | ||||||
| 			via_port_handler_.set_interrupt_delegate(this); | 			via_port_handler_.set_interrupt_delegate(this); | ||||||
| 			tape_player_.set_delegate(this); | 			tape_player_.set_delegate(this); | ||||||
| 			Memory::Fuzz(ram_, sizeof(ram_)); | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
|  |  | ||||||
| 			if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) { | 			if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) { | ||||||
| 				diskii_.set_sleep_observer(this); | 				diskii_.set_clocking_hint_observer(this); | ||||||
| 			} | 			} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { |  | ||||||
| 			audio_queue_.flush(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			std::vector<std::string> rom_names = {"colour.rom"}; | 			std::vector<std::string> rom_names = {"colour.rom"}; | ||||||
| 			switch(rom_type_) { | 			switch(target.rom) { | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom");	break; | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom");	break; | ||||||
| 				case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom");	break; | ||||||
| @@ -238,13 +231,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			switch(disk_interface) { | 			switch(disk_interface) { | ||||||
| 				default: break; | 				default: break; | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc:	rom_names.push_back("microdisc.rom");	break; | 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc:	rom_names.push_back("microdisc.rom");	break; | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz:	rom_names.push_back("8dos.rom");	break; | 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz:	rom_names.push_back("8dos.rom");		break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			auto roms = roms_with_names("Oric", rom_names); | 			const auto roms = rom_fetcher("Oric", rom_names); | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
| 				if(!roms[index]) return false; | 				if(!roms[index]) { | ||||||
|  | 					throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			colour_rom_ = std::move(*roms[0]); | 			colour_rom_ = std::move(*roms[0]); | ||||||
| @@ -260,8 +255,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 					pravetz_rom_ = std::move(*roms[2]); | 					pravetz_rom_ = std::move(*roms[2]); | ||||||
| 					pravetz_rom_.resize(512); | 					pravetz_rom_.resize(512); | ||||||
|  |  | ||||||
| 					auto state_machine_rom = roms_with_names("DiskII", {"state-machine-16.rom"}); | 					auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"}); | ||||||
| 					if(!state_machine_rom[0]) return false; | 					if(!state_machine_rom[0]) { | ||||||
|  | 						throw ROMMachine::Error::MissingROMs; | ||||||
|  | 					} | ||||||
| 					diskii_.set_state_machine(*state_machine_rom[0]); | 					diskii_.set_state_machine(*state_machine_rom[0]); | ||||||
| 				} break; | 				} break; | ||||||
| 			} | 			} | ||||||
| @@ -270,9 +267,35 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			rom_.resize(16384); | 			rom_.resize(16384); | ||||||
| 			paged_rom_ = rom_.data(); | 			paged_rom_ = rom_.data(); | ||||||
|  |  | ||||||
| 			if(video_output_) video_output_->set_colour_rom(colour_rom_); | 			switch(target.disk_interface) { | ||||||
|  | 				default: break; | ||||||
|  | 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc: | ||||||
|  | 					microdisc_did_change_paging_flags(µdisc_); | ||||||
|  | 					microdisc_.set_delegate(this); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			return true; | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch(target.rom) { | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::BASIC10: | ||||||
|  | 					tape_get_byte_address_ = 0xe630; | ||||||
|  | 					tape_speed_address_ = 0x67; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::BASIC11: | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::Pravetz: | ||||||
|  | 					tape_get_byte_address_ = 0xe6c9; | ||||||
|  | 					tape_speed_address_ = 0x024d; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool is_pressed) override final { | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
| @@ -291,41 +314,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			use_fast_tape_hack_ = activate; | 			use_fast_tape_hack_ = activate; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy ConfigurationTarget::Machine |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target); |  | ||||||
|  |  | ||||||
| 			switch(oric_target->disk_interface) { |  | ||||||
| 				default: break; |  | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc: |  | ||||||
| 					microdisc_did_change_paging_flags(µdisc_); |  | ||||||
| 					microdisc_.set_delegate(this); |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(!oric_target->loading_command.empty()) { |  | ||||||
| 				type_string(oric_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			switch(rom_type_) { |  | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC10: |  | ||||||
| 					tape_get_byte_address_ = 0xe630; |  | ||||||
| 					tape_speed_address_ = 0x67; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC11: |  | ||||||
| 				case Analyser::Static::Oric::Target::ROM::Pravetz: |  | ||||||
| 					tape_get_byte_address_ = 0xe6c9; |  | ||||||
| 					tape_speed_address_ = 0x024d; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			bool inserted = false; | 			bool inserted = false; | ||||||
|  |  | ||||||
| 			if(media.tapes.size()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| 				inserted = true; | 				inserted = true; | ||||||
| 			} | 			} | ||||||
| @@ -410,6 +402,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 										} | 										} | ||||||
| 									} | 									} | ||||||
| 								} else { | 								} else { | ||||||
|  | 									flush_diskii(); | ||||||
| 									const int disk_value = diskii_.read_address(address); | 									const int disk_value = diskii_.read_address(address); | ||||||
| 									if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value); | 									if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value); | ||||||
| 								} | 								} | ||||||
| @@ -444,9 +437,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 					microdisc_.run_for(Cycles(8)); | 					microdisc_.run_for(Cycles(8)); | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz: | 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz: | ||||||
| 					if(!diskii_is_sleeping_) { | 					if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) { | ||||||
| 						diskii_.set_data_input(*value); | 						diskii_.set_data_input(*value); | ||||||
| 						diskii_.run_for(Cycles(2)); | 						diskii_.run_for(Cycles(2)); | ||||||
|  | 					} else { | ||||||
|  | 						cycles_since_diskii_update_ += Cycles(2); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| @@ -457,6 +452,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 		forceinline void flush() { | 		forceinline void flush() { | ||||||
| 			update_video(); | 			update_video(); | ||||||
| 			via_port_handler_.flush(); | 			via_port_handler_.flush(); | ||||||
|  | 			flush_diskii(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine | 		// to satisfy CRTMachine::Machine | ||||||
| @@ -571,18 +567,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final { | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final { | ||||||
| 			diskii_is_sleeping_ = diskii_.is_sleeping(); | 			diskii_clocking_preference_ = diskii_.preferred_clocking(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		const uint16_t basic_invisible_ram_top_ = 0xffff; | 		const uint16_t basic_invisible_ram_top_ = 0xffff; | ||||||
| 		const uint16_t basic_visible_ram_top_ = 0xbfff; | 		const uint16_t basic_visible_ram_top_ = 0xbfff; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		// RAM and ROM | 		// RAM and ROM | ||||||
| 		Analyser::Static::Oric::Target::ROM rom_type_; |  | ||||||
| 		std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_; | 		std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_; | ||||||
| 		uint8_t ram_[65536]; | 		uint8_t ram_[65536]; | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
| @@ -617,9 +612,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
|  |  | ||||||
| 		// the Pravetz/Disk II, if in use | 		// the Pravetz/Disk II, if in use | ||||||
| 		Apple::DiskII diskii_; | 		Apple::DiskII diskii_; | ||||||
|  | 		Cycles cycles_since_diskii_update_; | ||||||
|  | 		void flush_diskii() { | ||||||
|  | 			diskii_.run_for(cycles_since_diskii_update_.flush()); | ||||||
|  | 		} | ||||||
| 		std::vector<uint8_t> pravetz_rom_; | 		std::vector<uint8_t> pravetz_rom_; | ||||||
| 		std::size_t pravetz_rom_base_pointer_ = 0; | 		std::size_t pravetz_rom_base_pointer_ = 0; | ||||||
| 		bool diskii_is_sleeping_ = false; | 		ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; | ||||||
|  |  | ||||||
| 		// Overlay RAM | 		// Overlay RAM | ||||||
| 		uint16_t ram_top_ = basic_visible_ram_top_; | 		uint16_t ram_top_ = basic_visible_ram_top_; | ||||||
| @@ -641,13 +640,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
|  |  | ||||||
| using namespace Oric; | using namespace Oric; | ||||||
|  |  | ||||||
| Machine *Machine::Oric(const Analyser::Static::Target *target_hint) { | Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint); | 	auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint); | ||||||
| 	using DiskInterface = Analyser::Static::Oric::Target::DiskInterface; | 	using DiskInterface = Analyser::Static::Oric::Target::DiskInterface; | ||||||
| 	switch(oric_target->disk_interface) { | 	switch(oric_target->disk_interface) { | ||||||
| 		default:						return new ConcreteMachine<DiskInterface::None>(oric_target); | 		default:						return new ConcreteMachine<DiskInterface::None>(*oric_target, rom_fetcher); | ||||||
| 		case DiskInterface::Microdisc:	return new ConcreteMachine<DiskInterface::Microdisc>(oric_target); | 		case DiskInterface::Microdisc:	return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher); | ||||||
| 		case DiskInterface::Pravetz:	return new ConcreteMachine<DiskInterface::Pravetz>(oric_target); | 		case DiskInterface::Pravetz:	return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace Oric { | namespace Oric { | ||||||
|  |  | ||||||
| @@ -25,7 +26,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Oric. | 		/// Creates and returns an Oric. | ||||||
| 		static Machine *Oric(const Analyser::Static::Target *target_hint); | 		static Machine *Oric(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : | |||||||
| 	crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f); | 	crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f); | ||||||
|  |  | ||||||
| 	set_video_signal(Outputs::CRT::VideoSignal::Composite); | 	set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
| 	crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); | 	crt_->set_visible_area(crt_->get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) { | void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) { | ||||||
|   | |||||||
| @@ -16,13 +16,18 @@ | |||||||
|  |  | ||||||
| namespace ROMMachine { | namespace ROMMachine { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Defines the signature for a function that must be supplied by the host environment in order to give machines | ||||||
|  | 	a route for fetching any system ROMs they might need. | ||||||
|  |  | ||||||
|  | 	The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects | ||||||
|  | 	to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the | ||||||
|  | 	ROM from @c names that corresponds by index, or else are the nullptr | ||||||
|  | */ | ||||||
| typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher; | typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher; | ||||||
|  |  | ||||||
| struct Machine { | enum class Error { | ||||||
| 	/*! | 	MissingROMs | ||||||
| 		Provides the machine with a way to obtain such ROMs as it needs. |  | ||||||
| 	*/ |  | ||||||
| 	virtual bool set_rom_fetcher(const ROMFetcher &rom_with_name) { return true; } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,36 +25,36 @@ namespace { | |||||||
|  |  | ||||||
| ::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { | ::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { | ||||||
| 	error = Machine::Error::None; | 	error = Machine::Error::None; | ||||||
| 	::Machine::DynamicMachine *machine = nullptr; |  | ||||||
| 	switch(target->machine) { |  | ||||||
| 		case Analyser::Machine::AmstradCPC:		machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());				break; |  | ||||||
| 		case Analyser::Machine::AppleII:		machine = new Machine::TypedDynamicMachine<AppleII::Machine>(AppleII::Machine::AppleII());						break; |  | ||||||
| 		case Analyser::Machine::Atari2600:		machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());				break; |  | ||||||
| 		case Analyser::Machine::ColecoVision:	machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision());	break; |  | ||||||
| 		case Analyser::Machine::Electron:		machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());					break; |  | ||||||
| 		case Analyser::Machine::MSX:			machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());									break; |  | ||||||
| 		case Analyser::Machine::Oric:			machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric(target));							break; |  | ||||||
| 		case Analyser::Machine::Vic20:			machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());		break; |  | ||||||
| 		case Analyser::Machine::ZX8081:			machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target));					break; |  | ||||||
|  |  | ||||||
| 		default: | 	Machine::DynamicMachine *machine = nullptr; | ||||||
| 			error = Machine::Error::UnknownMachine; | 	try { | ||||||
| 		return nullptr; | #define BindD(name, m)	case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<name::Machine>(name::Machine::m(target, rom_fetcher));	break; | ||||||
| 	} | #define Bind(m)	BindD(m, m) | ||||||
|  | 		switch(target->machine) { | ||||||
|  | 			Bind(AmstradCPC) | ||||||
|  | 			Bind(AppleII) | ||||||
|  | 			Bind(Atari2600) | ||||||
|  | 			BindD(Coleco::Vision, ColecoVision) | ||||||
|  | 			Bind(Electron) | ||||||
|  | 			Bind(MSX) | ||||||
|  | 			Bind(Oric) | ||||||
|  | 			BindD(Commodore::Vic20, Vic20) | ||||||
|  | 			Bind(ZX8081) | ||||||
|  |  | ||||||
| 	// TODO: this shouldn't depend on CRT machine's inclusion of ROM machine. | 			default: | ||||||
| 	CRTMachine::Machine *crt_machine = machine->crt_machine(); | 				error = Machine::Error::UnknownMachine; | ||||||
| 	if(crt_machine) { |  | ||||||
| 		if(!machine->crt_machine()->set_rom_fetcher(rom_fetcher)) { |  | ||||||
| 			delete machine; |  | ||||||
| 			error = Machine::Error::MissingROM; |  | ||||||
| 			return nullptr; | 			return nullptr; | ||||||
| 		} | 		} | ||||||
| 	} | #undef Bind | ||||||
|  | 	} catch(ROMMachine::Error construction_error) { | ||||||
| 	ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); | 		switch(construction_error) { | ||||||
| 	if(configuration_target) { | 			case ROMMachine::Error::MissingROMs: | ||||||
| 		machine->configuration_target()->configure_as_target(target); | 				error = Machine::Error::MissingROM; | ||||||
|  | 			break; | ||||||
|  | 			default: | ||||||
|  | 				error = Machine::Error::UnknownError; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return machine; | 	return machine; | ||||||
| @@ -129,6 +129,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { | |||||||
| std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() { | std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() { | ||||||
| 	std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; | 	std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; | ||||||
|  |  | ||||||
|  | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ namespace Machine { | |||||||
|  |  | ||||||
| enum class Error { | enum class Error { | ||||||
| 	None, | 	None, | ||||||
|  | 	UnknownError, | ||||||
| 	UnknownMachine, | 	UnknownMachine, | ||||||
| 	MissingROM, | 	MissingROM, | ||||||
| 	NoTargets | 	NoTargets | ||||||
|   | |||||||
| @@ -29,8 +29,8 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine | |||||||
| 			return get<Activity::Source>(); | 			return get<Activity::Source>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ConfigurationTarget::Machine *configuration_target() override { | 		MediaTarget::Machine *media_target() override { | ||||||
| 			return get<ConfigurationTarget::Machine>(); | 			return get<MediaTarget::Machine>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		CRTMachine::Machine *crt_machine() override { | 		CRTMachine::Machine *crt_machine() override { | ||||||
|   | |||||||
| @@ -16,25 +16,20 @@ namespace { | |||||||
| 	The number of bytes of PCM data to allocate at once; if/when more are required, | 	The number of bytes of PCM data to allocate at once; if/when more are required, | ||||||
| 	the class will simply allocate another batch. | 	the class will simply allocate another batch. | ||||||
| */ | */ | ||||||
| const std::size_t StandardAllocationSize = 40; | const std::size_t StandardAllocationSize = 320; | ||||||
|  |  | ||||||
| /// The amount of time a byte takes to output. |  | ||||||
| const std::size_t HalfCyclesPerByte = 8; |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Video::Video() : | Video::Video() : | ||||||
| 	crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) { | 	crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) { | ||||||
|  |  | ||||||
| 	// Set a composite sampling function that assumes 1bpp input. | 	// Set a composite sampling function that assumes two-level input; either a byte is 0, which is black, | ||||||
|  | 	// or it is non-zero, which is white. | ||||||
| 	crt_->set_composite_sampling_function( | 	crt_->set_composite_sampling_function( | ||||||
| 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | ||||||
| 		"{" | 		"{" | ||||||
| 			"uint texValue = texture(sampler, coordinate).r;" | 			"return texture(sampler, coordinate).r;" | ||||||
| 			"texValue <<= int(icoordinate.x) & 7;" |  | ||||||
| 			"return float(texValue & 128u);" |  | ||||||
| 		"}"); | 		"}"); | ||||||
| 	crt_->set_integer_coordinate_multiplier(8.0f); |  | ||||||
|  |  | ||||||
| 	// Show only the centre 80% of the TV frame. | 	// Show only the centre 80% of the TV frame. | ||||||
| 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
| @@ -43,7 +38,7 @@ Video::Video() : | |||||||
|  |  | ||||||
| void Video::run_for(const HalfCycles half_cycles) { | void Video::run_for(const HalfCycles half_cycles) { | ||||||
| 	// Just keep a running total of the amount of time that remains owed to the CRT. | 	// Just keep a running total of the amount of time that remains owed to the CRT. | ||||||
| 	cycles_since_update_ += static_cast<unsigned int>(half_cycles.as_int()); | 	time_since_update_ += half_cycles; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Video::flush() { | void Video::flush() { | ||||||
| @@ -53,29 +48,29 @@ void Video::flush() { | |||||||
| void Video::flush(bool next_sync) { | void Video::flush(bool next_sync) { | ||||||
| 	if(sync_) { | 	if(sync_) { | ||||||
| 		// If in sync, that takes priority. Output the proper amount of sync. | 		// If in sync, that takes priority. Output the proper amount of sync. | ||||||
| 		crt_->output_sync(cycles_since_update_); | 		crt_->output_sync(static_cast<unsigned int>(time_since_update_.as_int())); | ||||||
| 	} else { | 	} else { | ||||||
| 		// If not presently in sync, then... | 		// If not presently in sync, then... | ||||||
|  |  | ||||||
| 		if(line_data_) { | 		if(line_data_) { | ||||||
| 			// If there is output data queued, output it either if it's being interrupted by | 			// If there is output data queued, output it either if it's being interrupted by | ||||||
| 			// sync, or if we're past its end anyway. Otherwise let it be. | 			// sync, or if we're past its end anyway. Otherwise let it be. | ||||||
| 			unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_) * HalfCyclesPerByte; | 			int data_length = static_cast<int>(line_data_pointer_ - line_data_); | ||||||
| 			if(data_length < cycles_since_update_ || next_sync) { | 			if(data_length < time_since_update_.as_int() || next_sync) { | ||||||
| 				unsigned int output_length = std::min(data_length, cycles_since_update_); | 				auto output_length = std::min(data_length, time_since_update_.as_int()); | ||||||
| 				crt_->output_data(output_length, output_length / HalfCyclesPerByte); | 				crt_->output_data(static_cast<unsigned int>(output_length), static_cast<unsigned int>(output_length)); | ||||||
| 				line_data_pointer_ = line_data_ = nullptr; | 				line_data_pointer_ = line_data_ = nullptr; | ||||||
| 				cycles_since_update_ -= output_length; | 				time_since_update_ -= HalfCycles(output_length); | ||||||
| 			} else return; | 			} else return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Any pending pixels being dealt with, pad with the white level. | 		// Any pending pixels being dealt with, pad with the white level. | ||||||
| 		uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1)); | 		uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1)); | ||||||
| 		if(colour_pointer) *colour_pointer = 0xff; | 		if(colour_pointer) *colour_pointer = 0xff; | ||||||
| 		crt_->output_level(cycles_since_update_); | 		crt_->output_level(static_cast<unsigned int>(time_since_update_.as_int())); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cycles_since_update_ = 0; | 	time_since_update_ = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Video::set_sync(bool sync) { | void Video::set_sync(bool sync) { | ||||||
| @@ -101,14 +96,19 @@ void Video::output_byte(uint8_t byte) { | |||||||
| 	if(line_data_) { | 	if(line_data_) { | ||||||
| 		// If the buffer is full, output it now and obtain a new one | 		// If the buffer is full, output it now and obtain a new one | ||||||
| 		if(line_data_pointer_ - line_data_ == StandardAllocationSize) { | 		if(line_data_pointer_ - line_data_ == StandardAllocationSize) { | ||||||
| 			crt_->output_data(StandardAllocationSize * HalfCyclesPerByte, StandardAllocationSize); | 			crt_->output_data(StandardAllocationSize, StandardAllocationSize); | ||||||
| 			cycles_since_update_ -= StandardAllocationSize * HalfCyclesPerByte; | 			time_since_update_ -= StandardAllocationSize; | ||||||
| 			line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); | 			line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); | ||||||
| 			if(!line_data_) return; | 			if(!line_data_) return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		line_data_pointer_[0] = byte; | 		// Convert to one-byte-per-pixel where any non-zero value will act as white. | ||||||
| 		line_data_pointer_ ++; | 		uint8_t mask = 0x80; | ||||||
|  | 		for(int c = 0; c < 8; c++) { | ||||||
|  | 			line_data_pointer_[c] = byte & mask; | ||||||
|  | 			mask >>= 1; | ||||||
|  | 		} | ||||||
|  | 		line_data_pointer_ += 8; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ class Video { | |||||||
| 		bool sync_ = false; | 		bool sync_ = false; | ||||||
| 		uint8_t *line_data_ = nullptr; | 		uint8_t *line_data_ = nullptr; | ||||||
| 		uint8_t *line_data_pointer_ = nullptr; | 		uint8_t *line_data_pointer_ = nullptr; | ||||||
| 		unsigned int cycles_since_update_ = 0; | 		HalfCycles time_since_update_ = 0; | ||||||
| 		std::unique_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
| 		void flush(bool next_sync); | 		void flush(bool next_sync); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| #include "ZX8081.hpp" | #include "ZX8081.hpp" | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| @@ -59,14 +59,14 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
|  |  | ||||||
| template<bool is_zx81> class ConcreteMachine: | template<bool is_zx81> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public Machine { | 	public Machine { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			tape_player_(ZX8081ClockRate), | 			tape_player_(ZX8081ClockRate), | ||||||
| 			ay_(audio_queue_), | 			ay_(audio_queue_), | ||||||
| @@ -74,6 +74,55 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			set_clock_rate(ZX8081ClockRate); | 			set_clock_rate(ZX8081ClockRate); | ||||||
| 			speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f); | 			speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f); | ||||||
| 			clear_all_keys(); | 			clear_all_keys(); | ||||||
|  |  | ||||||
|  | 			const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM; | ||||||
|  | 			const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" }); | ||||||
|  | 			if(!roms[0]) throw ROMMachine::Error::MissingROMs; | ||||||
|  |  | ||||||
|  | 			rom_ = std::move(*roms[0]); | ||||||
|  | 			rom_.resize(use_zx81_rom ? 8192 : 4096); | ||||||
|  |  | ||||||
|  | 			if(is_zx81) { | ||||||
|  | 				tape_trap_address_ = 0x37c; | ||||||
|  | 				tape_return_address_ = 0x380; | ||||||
|  | 				vsync_start_ = HalfCycles(32); | ||||||
|  | 				vsync_end_ = HalfCycles(64); | ||||||
|  | 				automatic_tape_motor_start_address_ = 0x0340; | ||||||
|  | 				automatic_tape_motor_end_address_ = 0x03c3; | ||||||
|  | 			} else { | ||||||
|  | 				tape_trap_address_ = 0x220; | ||||||
|  | 				tape_return_address_ = 0x248; | ||||||
|  | 				vsync_start_ = HalfCycles(26); | ||||||
|  | 				vsync_end_ = HalfCycles(66); | ||||||
|  | 				automatic_tape_motor_start_address_ = 0x0206; | ||||||
|  | 				automatic_tape_motor_end_address_ = 0x024d; | ||||||
|  | 			} | ||||||
|  | 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); | ||||||
|  |  | ||||||
|  | 			switch(target.memory_model) { | ||||||
|  | 				case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded: | ||||||
|  | 					ram_.resize(1024); | ||||||
|  | 					ram_base_ = 16384; | ||||||
|  | 					ram_mask_ = 1023; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB: | ||||||
|  | 					ram_.resize(16384); | ||||||
|  | 					ram_base_ = 16384; | ||||||
|  | 					ram_mask_ = 16383; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB: | ||||||
|  | 					ram_.resize(65536); | ||||||
|  | 					ram_base_ = 8192; | ||||||
|  | 					ram_mask_ = 65535; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			Memory::Fuzz(ram_); | ||||||
|  |  | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -105,7 +154,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 				video_->run_for(cycle.length); | 				video_->run_for(cycle.length); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207)); | 			if(is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207)); | ||||||
| 			if(!tape_advance_delay_) { | 			if(!tape_advance_delay_) { | ||||||
| 				tape_player_.run_for(cycle.length); | 				tape_player_.run_for(cycle.length); | ||||||
| 			} else { | 			} else { | ||||||
| @@ -129,7 +178,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 						set_vsync(false); | 						set_vsync(false); | ||||||
| 					} | 					} | ||||||
| 					if(!(address & 2)) nmi_is_enabled_ = false; | 					if(!(address & 2)) nmi_is_enabled_ = false; | ||||||
| 					if(!(address & 1)) nmi_is_enabled_ = is_zx81_; | 					if(!(address & 1)) nmi_is_enabled_ = is_zx81; | ||||||
|  |  | ||||||
| 					// The below emulates the ZonX AY expansion device. | 					// The below emulates the ZonX AY expansion device. | ||||||
| 					if(is_zx81) { | 					if(is_zx81) { | ||||||
| @@ -157,7 +206,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
|  |  | ||||||
| 					// The below emulates the ZonX AY expansion device. | 					// The below emulates the ZonX AY expansion device. | ||||||
| 					if(is_zx81) { | 					if(is_zx81) { | ||||||
| 						if((address&0xef) == 0x0f) { | 						if((address&0xef) == 0xcf) { | ||||||
| 							value &= ay_read_data(); | 							value &= ay_read_data(); | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| @@ -281,54 +330,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target); |  | ||||||
| 			is_zx81_ = zx8081_target->is_ZX81; |  | ||||||
| 			if(is_zx81_) { |  | ||||||
| 				rom_ = zx81_rom_; |  | ||||||
| 				tape_trap_address_ = 0x37c; |  | ||||||
| 				tape_return_address_ = 0x380; |  | ||||||
| 				vsync_start_ = HalfCycles(32); |  | ||||||
| 				vsync_end_ = HalfCycles(64); |  | ||||||
| 				automatic_tape_motor_start_address_ = 0x0340; |  | ||||||
| 				automatic_tape_motor_end_address_ = 0x03c3; |  | ||||||
| 			} else { |  | ||||||
| 				rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_; |  | ||||||
| 				tape_trap_address_ = 0x220; |  | ||||||
| 				tape_return_address_ = 0x248; |  | ||||||
| 				vsync_start_ = HalfCycles(26); |  | ||||||
| 				vsync_end_ = HalfCycles(66); |  | ||||||
| 				automatic_tape_motor_start_address_ = 0x0206; |  | ||||||
| 				automatic_tape_motor_end_address_ = 0x024d; |  | ||||||
| 			} |  | ||||||
| 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); |  | ||||||
|  |  | ||||||
| 			switch(zx8081_target->memory_model) { |  | ||||||
| 				case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded: |  | ||||||
| 					ram_.resize(1024); |  | ||||||
| 					ram_base_ = 16384; |  | ||||||
| 					ram_mask_ = 1023; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB: |  | ||||||
| 					ram_.resize(16384); |  | ||||||
| 					ram_base_ = 16384; |  | ||||||
| 					ram_mask_ = 16383; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB: |  | ||||||
| 					ram_.resize(65536); |  | ||||||
| 					ram_base_ = 8192; |  | ||||||
| 					ram_mask_ = 65535; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 			Memory::Fuzz(ram_); |  | ||||||
|  |  | ||||||
| 			if(!zx8081_target->loading_command.empty()) { |  | ||||||
| 				type_string(zx8081_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| @@ -339,30 +340,10 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void type_string(const std::string &string) override final { | 		void type_string(const std::string &string) override final { | ||||||
| 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_)); | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81)); | ||||||
| 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			const auto roms = roms_with_names( |  | ||||||
| 				"ZX8081", |  | ||||||
| 				{ |  | ||||||
| 					"zx80.rom",	"zx81.rom", |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				if(!roms[index]) return false; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			zx80_rom_ = std::move(*roms[0]); |  | ||||||
| 			zx81_rom_ = std::move(*roms[1]); |  | ||||||
| 			zx80_rom_.resize(4096); |  | ||||||
| 			zx81_rom_.resize(8192); |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// MARK: - Keyboard | 		// MARK: - Keyboard | ||||||
| 		void set_key_state(uint16_t key, bool is_pressed) override final { | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
| 			if(is_pressed) | 			if(is_pressed) | ||||||
| @@ -436,7 +417,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_; | 		CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_; | ||||||
|  |  | ||||||
| 		std::unique_ptr<Video> video_; | 		std::unique_ptr<Video> video_; | ||||||
| 		std::vector<uint8_t> zx81_rom_, zx80_rom_; |  | ||||||
|  |  | ||||||
| 		uint16_t tape_trap_address_, tape_return_address_; | 		uint16_t tape_trap_address_, tape_return_address_; | ||||||
| 		uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_; | 		uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_; | ||||||
| @@ -456,7 +436,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_; | 		HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_; | ||||||
| 		Storage::Tape::ZX8081::Parser parser_; | 		Storage::Tape::ZX8081::Parser parser_; | ||||||
|  |  | ||||||
| 		bool is_zx81_; |  | ||||||
| 		bool nmi_is_enabled_ = false; | 		bool nmi_is_enabled_ = false; | ||||||
|  |  | ||||||
| 		HalfCycles vsync_start_, vsync_end_; | 		HalfCycles vsync_start_, vsync_end_; | ||||||
| @@ -522,14 +501,12 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| using namespace ZX8081; | using namespace ZX8081; | ||||||
|  |  | ||||||
| // See header; constructs and returns an instance of the ZX80 or 81. | // See header; constructs and returns an instance of the ZX80 or 81. | ||||||
| Machine *Machine::ZX8081(const Analyser::Static::Target *target_hint) { | Machine *Machine::ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint); | 	const Analyser::Static::ZX8081::Target *const zx_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target); | ||||||
|  |  | ||||||
| 	// Instantiate the correct type of machine. | 	// Instantiate the correct type of machine. | ||||||
| 	if(hint->is_ZX81) | 	if(zx_target->is_ZX81)	return new ZX8081::ConcreteMachine<true>(*zx_target, rom_fetcher); | ||||||
| 		return new ZX8081::ConcreteMachine<true>(); | 	else					return new ZX8081::ConcreteMachine<false>(*zx_target, rom_fetcher); | ||||||
| 	else |  | ||||||
| 		return new ZX8081::ConcreteMachine<false>(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
|  |  | ||||||
| @@ -21,7 +22,7 @@ class Machine { | |||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		static Machine *ZX8081(const Analyser::Static::Target *target_hint); | 		static Machine *ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		virtual void set_tape_is_playing(bool is_playing) = 0; | 		virtual void set_tape_is_playing(bool is_playing) = 0; | ||||||
| 		virtual bool get_tape_is_playing() = 0; | 		virtual bool get_tape_is_playing() = 0; | ||||||
|   | |||||||
| @@ -95,14 +95,14 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl | |||||||
| 	those used by the FM and MFM disk encodings. | 	those used by the FM and MFM disk encodings. | ||||||
| */ | */ | ||||||
| struct CCITT: public Generator<uint16_t, 0xffff, 0x0000, false, false> { | struct CCITT: public Generator<uint16_t, 0xffff, 0x0000, false, false> { | ||||||
| 	CCITT() : Generator(0x1021) {} | 	CCITT(): Generator(0x1021) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Provides a generator of "standard 32-bit" CRCs. | 	Provides a generator of "standard 32-bit" CRCs. | ||||||
| */ | */ | ||||||
| struct CRC32: public Generator<uint32_t, 0xffffffff, 0xffffffff, true, true> { | struct CRC32: public Generator<uint32_t, 0xffffffff, 0xffffffff, true, true> { | ||||||
| 	CRC32() : Generator(0x04c11db7) {} | 	CRC32(): Generator(0x04c11db7) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| 	objects = { | 	objects = { | ||||||
|  |  | ||||||
| /* Begin PBXBuildFile section */ | /* Begin PBXBuildFile section */ | ||||||
|  | 		4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; }; | ||||||
| 		4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; }; | 		4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; }; | ||||||
| 		4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | 		4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | ||||||
| 		4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | 		4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | ||||||
| @@ -43,7 +44,6 @@ | |||||||
| 		4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; | 		4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; | ||||||
| 		4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; | 		4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; | ||||||
| 		4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; }; | 		4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; }; | ||||||
| 		4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; }; |  | ||||||
| 		4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; | 		4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; | ||||||
| 		4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; }; | 		4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; }; | ||||||
| 		4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; | 		4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; | ||||||
| @@ -115,6 +115,7 @@ | |||||||
| 		4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; }; | 		4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; }; | ||||||
| 		4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; }; | 		4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; }; | ||||||
| 		4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; }; | 		4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; }; | ||||||
|  | 		4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B08A56720D72BEF0016CE5A /* Activity.xib */; }; | ||||||
| 		4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; | 		4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; | ||||||
| 		4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; | 		4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; | ||||||
| 		4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; | 		4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; | ||||||
| @@ -124,7 +125,6 @@ | |||||||
| 		4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; | 		4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; | ||||||
| 		4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; | 		4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; | ||||||
| 		4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; | 		4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; }; | ||||||
| 		4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; }; |  | ||||||
| 		4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; | 		4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; }; | ||||||
| 		4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; | 		4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; | ||||||
| 		4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; | 		4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; }; | ||||||
| @@ -185,7 +185,6 @@ | |||||||
| 		4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; }; | 		4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; }; | ||||||
| 		4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; }; | 		4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; }; | ||||||
| 		4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; }; | 		4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; }; | ||||||
| 		4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; }; |  | ||||||
| 		4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; | 		4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; }; | ||||||
| 		4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; }; | 		4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; }; | ||||||
| 		4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; }; | 		4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; }; | ||||||
| @@ -214,6 +213,8 @@ | |||||||
| 		4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; }; | 		4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; }; | ||||||
| 		4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; | 		4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; | ||||||
| 		4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; | 		4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; | ||||||
|  | 		4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */; }; | ||||||
|  | 		4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B55DD8120DF06680043F2E5 /* MachinePicker.xib */; }; | ||||||
| 		4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; }; | 		4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; }; | ||||||
| 		4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; | 		4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; | ||||||
| 		4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; }; | 		4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; }; | ||||||
| @@ -309,8 +310,6 @@ | |||||||
| 		4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; | 		4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; | ||||||
| 		4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; | 		4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; | ||||||
| 		4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; }; | 		4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; }; | ||||||
| 		4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */; }; |  | ||||||
| 		4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */; }; |  | ||||||
| 		4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; | 		4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; | ||||||
| 		4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; | 		4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; | ||||||
| 		4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; }; | 		4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; }; | ||||||
| @@ -597,8 +596,8 @@ | |||||||
| 		4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; | 		4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; | ||||||
| 		4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; | 		4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; | ||||||
| 		4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; | 		4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; | ||||||
| 		4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; }; | 		4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; }; | ||||||
| 		4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; }; | 		4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; }; | ||||||
| 		4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | 		4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | ||||||
| 		4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | 		4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | ||||||
| 		4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; }; | 		4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; }; | ||||||
| @@ -607,14 +606,18 @@ | |||||||
| 		4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; | 		4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; | ||||||
| 		4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; | 		4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; | ||||||
| 		4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; }; | 		4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; }; | ||||||
|  | 		4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; }; | ||||||
| 		4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; | 		4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; | ||||||
| 		4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | 		4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | ||||||
| 		4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | 		4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | ||||||
| 		4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; | 		4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; | ||||||
| 		4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; }; | 		4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; }; | ||||||
|  | 		4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; }; | ||||||
| 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; | 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; | ||||||
| 		4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; | 		4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; | ||||||
| 		4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; | 		4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; | ||||||
|  | 		4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; }; | ||||||
|  | 		4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; }; | ||||||
| 		4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; | 		4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; | ||||||
| 		4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; | 		4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; | ||||||
| 		4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; | 		4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; | ||||||
| @@ -644,6 +647,7 @@ | |||||||
| 		4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; }; | 		4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; }; | ||||||
| 		4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; | 		4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; | ||||||
| 		4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; | 		4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; | ||||||
|  | 		4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; }; | ||||||
| 		4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; | 		4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; | ||||||
| 		4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; | 		4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; | ||||||
| 		4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | 		4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | ||||||
| @@ -687,6 +691,7 @@ | |||||||
| /* End PBXCopyFilesBuildPhase section */ | /* End PBXCopyFilesBuildPhase section */ | ||||||
|  |  | ||||||
| /* Begin PBXFileReference section */ | /* Begin PBXFileReference section */ | ||||||
|  | 		4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; }; | ||||||
| 		4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; }; | 		4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; }; | ||||||
| 		4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; }; | 		4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; }; | 		4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -704,6 +709,7 @@ | |||||||
| 		4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; }; | 		4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; }; | ||||||
| 		4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; }; | 		4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; }; | ||||||
| 		4B08A2791EE3957B008B7065 /* TestMachine+ForSubclassEyesOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestMachine+ForSubclassEyesOnly.h"; sourceTree = "<group>"; }; | 		4B08A2791EE3957B008B7065 /* TestMachine+ForSubclassEyesOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestMachine+ForSubclassEyesOnly.h"; sourceTree = "<group>"; }; | ||||||
|  | 		4B08A56820D72BEF0016CE5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Activity.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; }; | 		4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; }; | 		4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; }; | 		4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -716,7 +722,6 @@ | |||||||
| 		4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = "<group>"; }; | 		4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; }; | 		4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; }; | 		4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; }; |  | ||||||
| 		4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; }; | 		4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; }; | ||||||
| 		4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; }; | 		4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; }; | 		4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -823,8 +828,6 @@ | |||||||
| 		4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; }; | 		4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; }; | ||||||
| 		4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; }; | 		4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; }; | ||||||
| 		4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; }; | 		4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; }; | ||||||
| 		4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; }; |  | ||||||
| 		4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; }; |  | ||||||
| 		4B4518731F75E91800926311 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; }; | 		4B4518731F75E91800926311 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B4518741F75E91800926311 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; }; | 		4B4518741F75E91800926311 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B4518751F75E91800926311 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; }; | 		4B4518751F75E91800926311 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -883,6 +886,8 @@ | |||||||
| 		4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; }; | 		4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; }; | ||||||
| 		4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; }; | 		4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; }; | ||||||
| 		4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; }; | 		4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; }; | ||||||
|  | 		4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; }; | ||||||
|  | 		4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; }; | ||||||
| 		4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; }; | 		4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; }; | 		4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; }; | 		4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1002,6 +1007,7 @@ | |||||||
| 		4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | 		4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | 		4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; }; | 		4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; }; | 		4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; }; | 		4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; }; | 		4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1024,12 +1030,10 @@ | |||||||
| 		4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; }; | 		4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; }; | 		4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; }; | 		4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BA141BC2072E8A400A31EC9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; }; |  | ||||||
| 		4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; }; |  | ||||||
| 		4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | 		4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; }; | 		4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; }; | ||||||
| 		4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; }; | 		4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; }; | ||||||
| 		4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; }; | 		4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; }; | 		4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; }; | 		4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; }; | 		4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1037,7 +1041,7 @@ | |||||||
| 		4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; }; | 		4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; }; | 		4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; }; | 		4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; }; | 		4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; 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>"; }; | 		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>"; }; | 		4BB297E51B587D8300A49093 /*  start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; }; | ||||||
| @@ -1326,8 +1330,8 @@ | |||||||
| 		4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; }; | 		4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; }; | 		4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; }; | 		4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiConfigurationTarget.hpp; sourceTree = "<group>"; }; | 		4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiConfigurationTarget.cpp; sourceTree = "<group>"; }; | 		4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; }; | 		4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; }; | 		4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; }; | 		4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; }; | ||||||
| @@ -1345,6 +1349,8 @@ | |||||||
| 		4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; }; | 		4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; }; | 		4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; }; | 		4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; }; | ||||||
|  | 		4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; }; | ||||||
| 		4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; }; | 		4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; }; | 		4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; }; | 		4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1353,10 +1359,13 @@ | |||||||
| 		4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; }; | 		4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; }; | 		4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; }; | 		4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; }; | 		4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; }; | ||||||
| 		4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; }; | 		4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; }; | 		4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; | 		4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; | ||||||
|  | 		4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Rectangle.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Rectangle.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; }; | 		4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; }; | 		4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; }; | 		4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; }; | ||||||
| @@ -1377,6 +1386,7 @@ | |||||||
| 		4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; }; | 		4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; }; | ||||||
| 		4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; }; | 		4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; }; | ||||||
| 		4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; }; | 		4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; }; | ||||||
|  | 		4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; }; | 		4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; }; | 		4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | 		4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1425,6 +1435,7 @@ | |||||||
| 		4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; }; | 		4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; }; | 		4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BEE0A6E1D72496600532C7B /* PRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PRG.hpp; sourceTree = "<group>"; }; | 		4BEE0A6E1D72496600532C7B /* PRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PRG.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BEEE6BC20DC72EA003723BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/CompositeOptions.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; }; | 		4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; }; | ||||||
| 		4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; }; | 		4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; }; | ||||||
| 		4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; }; | 		4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; }; | ||||||
| @@ -1537,14 +1548,15 @@ | |||||||
| 		4B1414631B588A1100E04248 /* Test Binaries */ = { | 		4B1414631B588A1100E04248 /* Test Binaries */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B98A1CD1FFADEC400ADF63B /* MSX ROMs */, |  | ||||||
| 				4B9252CD1E74D28200B76AF1 /* Atari ROMs */, | 				4B9252CD1E74D28200B76AF1 /* Atari ROMs */, | ||||||
| 				4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, | 				4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, | ||||||
|  | 				4B98A1CD1FFADEC400ADF63B /* MSX ROMs */, | ||||||
|  | 				4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */, | ||||||
| 				4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, | 				4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, | ||||||
| 				4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */, | 				4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */, | ||||||
| 				4BE9A6B21EDE294200CBCB47 /* Zexall */, |  | ||||||
| 				4BBF49B41ED2881600AB3669 /* FUSE */, | 				4BBF49B41ED2881600AB3669 /* FUSE */, | ||||||
| 				4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */, | 				4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */, | ||||||
|  | 				4BE9A6B21EDE294200CBCB47 /* Zexall */, | ||||||
| 			); | 			); | ||||||
| 			name = "Test Binaries"; | 			name = "Test Binaries"; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -1770,6 +1782,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B0CCC411C62D0B3001CAC5F /* CRT */, | 				4B0CCC411C62D0B3001CAC5F /* CRT */, | ||||||
| 				4BD060A41FE49D3C006E14BE /* Speaker */, | 				4BD060A41FE49D3C006E14BE /* Speaker */, | ||||||
|  | 				4BD601A920D89F2A00CBCE57 /* Log.hpp */, | ||||||
| 			); | 			); | ||||||
| 			name = Outputs; | 			name = Outputs; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -1841,12 +1854,10 @@ | |||||||
| 		4B4518701F75E91800926311 /* Track */ = { | 		4B4518701F75E91800926311 /* Track */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */, |  | ||||||
| 				4B4518731F75E91800926311 /* PCMSegment.cpp */, | 				4B4518731F75E91800926311 /* PCMSegment.cpp */, | ||||||
| 				4B4518751F75E91800926311 /* PCMTrack.cpp */, | 				4B4518751F75E91800926311 /* PCMTrack.cpp */, | ||||||
| 				4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */, | 				4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */, | ||||||
| 				4B4518771F75E91800926311 /* UnformattedTrack.cpp */, | 				4B4518771F75E91800926311 /* UnformattedTrack.cpp */, | ||||||
| 				4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */, |  | ||||||
| 				4B4518741F75E91800926311 /* PCMSegment.hpp */, | 				4B4518741F75E91800926311 /* PCMSegment.hpp */, | ||||||
| 				4B4518761F75E91800926311 /* PCMTrack.hpp */, | 				4B4518761F75E91800926311 /* PCMTrack.hpp */, | ||||||
| 				4B4518881F75ECB100926311 /* Track.hpp */, | 				4B4518881F75ECB100926311 /* Track.hpp */, | ||||||
| @@ -1986,7 +1997,10 @@ | |||||||
| 				4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, | 				4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, | ||||||
| 				4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, | 				4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, | ||||||
| 				4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */, | 				4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */, | ||||||
|  | 				4B08A56720D72BEF0016CE5A /* Activity.xib */, | ||||||
|  | 				4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */, | ||||||
| 				4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, | 				4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, | ||||||
|  | 				4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */, | ||||||
| 				4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, | 				4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, | ||||||
| 				4B2A332B1DB86821002876E3 /* OricOptions.xib */, | 				4B2A332B1DB86821002876E3 /* OricOptions.xib */, | ||||||
| 				4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */, | 				4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */, | ||||||
| @@ -2005,6 +2019,15 @@ | |||||||
| 			path = Views; | 			path = Views; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4B55DD7F20DF06680043F2E5 /* MachinePicker */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4B55DD8020DF06680043F2E5 /* MachinePicker.swift */, | ||||||
|  | 				4B55DD8120DF06680043F2E5 /* MachinePicker.xib */, | ||||||
|  | 			); | ||||||
|  | 			path = MachinePicker; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = { | 		4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -2384,16 +2407,6 @@ | |||||||
| 			path = Implementation; | 			path = Implementation; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| 		4BA141C02072E8B300A31EC9 /* MachinePicker */ = { |  | ||||||
| 			isa = PBXGroup; |  | ||||||
| 			children = ( |  | ||||||
| 				4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */, |  | ||||||
| 				4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */, |  | ||||||
| 			); |  | ||||||
| 			name = MachinePicker; |  | ||||||
| 			path = "New Group"; |  | ||||||
| 			sourceTree = "<group>"; |  | ||||||
| 		}; |  | ||||||
| 		4BAB62AA1D3272D200DF5BA0 /* Disk */ = { | 		4BAB62AA1D3272D200DF5BA0 /* Disk */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -2754,6 +2767,7 @@ | |||||||
| 		4BB73EA01B587A5100552FC2 /* Clock Signal */ = { | 		4BB73EA01B587A5100552FC2 /* Clock Signal */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
|  | 				4BBFE83B21015D9C00BF1C40 /* Joystick Manager */, | ||||||
| 				4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, | 				4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, | ||||||
| 				4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, | 				4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, | ||||||
| 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | ||||||
| @@ -2763,7 +2777,7 @@ | |||||||
| 				4B643F3D1D77B88000D431D6 /* Document Controller */, | 				4B643F3D1D77B88000D431D6 /* Document Controller */, | ||||||
| 				4B55CE551C3B7D360093A61B /* Documents */, | 				4B55CE551C3B7D360093A61B /* Documents */, | ||||||
| 				4B2A53921D117D36003C6002 /* Machine */, | 				4B2A53921D117D36003C6002 /* Machine */, | ||||||
| 				4BA141C02072E8B300A31EC9 /* MachinePicker */, | 				4B55DD7F20DF06680043F2E5 /* MachinePicker */, | ||||||
| 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, | 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, | ||||||
| 				4BE5F85A1C3E1C2500C43F01 /* Resources */, | 				4BE5F85A1C3E1C2500C43F01 /* Resources */, | ||||||
| 				4BD5F1961D1352A000631CD1 /* Updater */, | 				4BD5F1961D1352A000631CD1 /* Updater */, | ||||||
| @@ -2779,7 +2793,6 @@ | |||||||
| 				4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, | 				4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, | ||||||
| 				4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, | 				4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, | ||||||
| 				4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, | 				4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, | ||||||
| 				4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */, |  | ||||||
| 				4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, | 				4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, | ||||||
| 				4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, | 				4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, | ||||||
| 				4B2AF8681E513FC20027EE29 /* TIATests.mm */, | 				4B2AF8681E513FC20027EE29 /* TIATests.mm */, | ||||||
| @@ -2819,7 +2832,7 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, | 				4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, | ||||||
| 				4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */, | 				4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */, | ||||||
| 				4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, | 				4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, | ||||||
| 				4BBB709C2020109C002FE009 /* DynamicMachine.hpp */, | 				4BBB709C2020109C002FE009 /* DynamicMachine.hpp */, | ||||||
| 				4B7041271F92C26900735E45 /* JoystickMachine.hpp */, | 				4B7041271F92C26900735E45 /* JoystickMachine.hpp */, | ||||||
| @@ -2857,16 +2870,16 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */, | 				4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */, | ||||||
| 				4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */, |  | ||||||
| 				4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */, | 				4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */, | ||||||
| 				4B1B88C6202E469300B67DFF /* MultiJoystickMachine.cpp */, | 				4B1B88C6202E469300B67DFF /* MultiJoystickMachine.cpp */, | ||||||
| 				4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */, | 				4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */, | ||||||
|  | 				4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */, | ||||||
| 				4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */, | 				4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */, | ||||||
| 				4B1B88BF202E3DB200B67DFF /* MultiConfigurable.hpp */, | 				4B1B88BF202E3DB200B67DFF /* MultiConfigurable.hpp */, | ||||||
| 				4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */, |  | ||||||
| 				4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */, | 				4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */, | ||||||
| 				4B1B88C7202E469300B67DFF /* MultiJoystickMachine.hpp */, | 				4B1B88C7202E469300B67DFF /* MultiJoystickMachine.hpp */, | ||||||
| 				4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */, | 				4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */, | ||||||
|  | 				4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */, | ||||||
| 				4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */, | 				4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Implementation; | 			path = Implementation; | ||||||
| @@ -2895,6 +2908,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */, | 				4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */, | ||||||
| 				4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, | 				4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, | ||||||
|  | 				4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */, | ||||||
| 				4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, | 				4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, | ||||||
| 				4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, | 				4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, | ||||||
| 				4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */, | 				4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */, | ||||||
| @@ -2902,6 +2916,7 @@ | |||||||
| 				4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, | 				4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, | ||||||
| 				4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, | 				4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, | ||||||
| 				4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, | 				4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, | ||||||
|  | 				4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */, | ||||||
| 				4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, | 				4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, | ||||||
| 				4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, | 				4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, | ||||||
| 				4BC3B74C1CD194CC00F86E85 /* Shaders */, | 				4BC3B74C1CD194CC00F86E85 /* Shaders */, | ||||||
| @@ -2909,6 +2924,15 @@ | |||||||
| 			path = Internals; | 			path = Internals; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4BBFE83B21015D9C00BF1C40 /* Joystick Manager */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */, | ||||||
|  | 				4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */, | ||||||
|  | 			); | ||||||
|  | 			path = "Joystick Manager"; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4BC3B74C1CD194CC00F86E85 /* Shaders */ = { | 		4BC3B74C1CD194CC00F86E85 /* Shaders */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -3124,8 +3148,9 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, | 				4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, | ||||||
| 				4BB06B211F316A3F00600C7A /* ForceInline.hpp */, | 				4BB06B211F316A3F00600C7A /* ForceInline.hpp */, | ||||||
| 				4BB146C61F49D7D700253439 /* Sleeper.hpp */, | 				4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, | ||||||
| 				4B449C942063389900A095C8 /* TimeTypes.hpp */, | 				4B449C942063389900A095C8 /* TimeTypes.hpp */, | ||||||
|  | 				4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */, | ||||||
| 			); | 			); | ||||||
| 			name = ClockReceiver; | 			name = ClockReceiver; | ||||||
| 			path = ../../ClockReceiver; | 			path = ../../ClockReceiver; | ||||||
| @@ -3277,9 +3302,11 @@ | |||||||
| 				4B2C45421E3C3896002A2389 /* cartridge.png in Resources */, | 				4B2C45421E3C3896002A2389 /* cartridge.png in Resources */, | ||||||
| 				4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, | 				4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, | ||||||
| 				4B79E4451E3AF38600141F11 /* floppy35.png in Resources */, | 				4B79E4451E3AF38600141F11 /* floppy35.png in Resources */, | ||||||
| 				4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */, | 				4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */, | ||||||
|  | 				4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */, | ||||||
| 				4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, | 				4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, | ||||||
| 				4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, | 				4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, | ||||||
|  | 				4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */, | ||||||
| 				4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */, | 				4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */, | ||||||
| 				4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, | 				4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, | ||||||
| 				4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, | 				4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, | ||||||
| @@ -3288,6 +3315,7 @@ | |||||||
| 				4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, | 				4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, | ||||||
| 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | ||||||
| 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | ||||||
|  | 				4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */, | ||||||
| 				4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */, | 				4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */, | ||||||
| 			); | 			); | ||||||
| 			runOnlyForDeploymentPostprocessing = 0; | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
| @@ -3307,6 +3335,7 @@ | |||||||
| 				4BB2998A1B587D8400A49093 /* lseix in Resources */, | 				4BB2998A1B587D8400A49093 /* lseix in Resources */, | ||||||
| 				4BB2994E1B587D8400A49093 /* dexn in Resources */, | 				4BB2994E1B587D8400A49093 /* dexn in Resources */, | ||||||
| 				4BB299971B587D8400A49093 /* nopa in Resources */, | 				4BB299971B587D8400A49093 /* nopa in Resources */, | ||||||
|  | 				4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */, | ||||||
| 				4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */, | 				4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */, | ||||||
| 				4BB299521B587D8400A49093 /* eoray in Resources */, | 				4BB299521B587D8400A49093 /* eoray in Resources */, | ||||||
| 				4BB299411B587D8400A49093 /* cpyb in Resources */, | 				4BB299411B587D8400A49093 /* cpyb in Resources */, | ||||||
| @@ -3610,6 +3639,7 @@ | |||||||
| 				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, | 				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, | ||||||
| 				4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
|  | 				4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */, | ||||||
| 				4B894539201967B4007DE474 /* Tape.cpp in Sources */, | 				4B894539201967B4007DE474 /* Tape.cpp in Sources */, | ||||||
| 				4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */, | 				4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */, | ||||||
| 				4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, | 				4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, | ||||||
| @@ -3626,7 +3656,6 @@ | |||||||
| 				4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */, | 				4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */, | ||||||
| 				4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */, | 				4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */, | ||||||
| 				4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */, | 				4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */, | ||||||
| 				4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */, |  | ||||||
| 				4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */, | 				4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */, | ||||||
| 				4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */, | 				4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */, | ||||||
| 				4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */, | 				4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */, | ||||||
| @@ -3688,7 +3717,7 @@ | |||||||
| 				4BAD13441FF709C700FD114A /* MSX.cpp in Sources */, | 				4BAD13441FF709C700FD114A /* MSX.cpp in Sources */, | ||||||
| 				4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */, | 				4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */, | ||||||
| 				4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */, | 				4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */, | ||||||
| 				4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */, | 				4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, | ||||||
| 				4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */, | 				4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */, | ||||||
| 				4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */, | 				4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */, | ||||||
| 				4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */, | 				4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */, | ||||||
| @@ -3799,7 +3828,6 @@ | |||||||
| 				4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */, | 				4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */, | ||||||
| 				4B894532201967B4007DE474 /* 6502.cpp in Sources */, | 				4B894532201967B4007DE474 /* 6502.cpp in Sources */, | ||||||
| 				4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */, | 				4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */, | ||||||
| 				4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */, |  | ||||||
| 				4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */, | 				4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */, | ||||||
| 				4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */, | 				4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */, | ||||||
| 				4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, | 				4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, | ||||||
| @@ -3815,6 +3843,7 @@ | |||||||
| 				4B4518A21F75FD1C00926311 /* G64.cpp in Sources */, | 				4B4518A21F75FD1C00926311 /* G64.cpp in Sources */, | ||||||
| 				4B89452C201967B4007DE474 /* Tape.cpp in Sources */, | 				4B89452C201967B4007DE474 /* Tape.cpp in Sources */, | ||||||
| 				4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, | 				4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, | ||||||
|  | 				4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */, | ||||||
| 				4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */, | 				4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */, | ||||||
| 				4B89451C201967B4007DE474 /* Disk.cpp in Sources */, | 				4B89451C201967B4007DE474 /* Disk.cpp in Sources */, | ||||||
| 				4B302184208A550100773308 /* DiskII.cpp in Sources */, | 				4B302184208A550100773308 /* DiskII.cpp in Sources */, | ||||||
| @@ -3834,11 +3863,11 @@ | |||||||
| 				4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */, | 				4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */, | ||||||
| 				4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */, | 				4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */, | ||||||
| 				4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, | 				4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, | ||||||
| 				4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */, |  | ||||||
| 				4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, | 				4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, | ||||||
| 				4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, | 				4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, | ||||||
| 				4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, | 				4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, | ||||||
| 				4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, | 				4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, | ||||||
|  | 				4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */, | ||||||
| 				4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, | 				4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, | ||||||
| 				4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */, | 				4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */, | ||||||
| 				4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| @@ -3855,7 +3884,7 @@ | |||||||
| 				4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, | 				4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, | ||||||
| 				4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, | 				4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, | ||||||
| 				4B894528201967B4007DE474 /* Disk.cpp in Sources */, | 				4B894528201967B4007DE474 /* Disk.cpp in Sources */, | ||||||
| 				4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */, | 				4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, | ||||||
| 				4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, | 				4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, | ||||||
| 				4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, | 				4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, | ||||||
| @@ -3900,6 +3929,7 @@ | |||||||
| 				4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, | 				4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, | ||||||
| 				4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, | 				4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, | ||||||
| 				4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, | 				4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, | ||||||
|  | 				4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */, | ||||||
| 				4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, | 				4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, | ||||||
| 				4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */, | 				4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */, | ||||||
| @@ -3951,7 +3981,6 @@ | |||||||
| 				4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */, | 				4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */, | ||||||
| 				4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */, | 				4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */, | ||||||
| 				4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */, | 				4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */, | ||||||
| 				4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */, |  | ||||||
| 				4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */, | 				4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */, | ||||||
| 			); | 			); | ||||||
| 			runOnlyForDeploymentPostprocessing = 0; | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
| @@ -3980,6 +4009,14 @@ | |||||||
| /* End PBXTargetDependency section */ | /* End PBXTargetDependency section */ | ||||||
|  |  | ||||||
| /* Begin PBXVariantGroup section */ | /* Begin PBXVariantGroup section */ | ||||||
|  | 		4B08A56720D72BEF0016CE5A /* Activity.xib */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4B08A56820D72BEF0016CE5A /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = Activity.xib; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */ = { | 		4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -3996,6 +4033,14 @@ | |||||||
| 			name = OricOptions.xib; | 			name = OricOptions.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4B55DD8220DF06680043F2E5 /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = MachinePicker.xib; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { | 		4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -4020,14 +4065,6 @@ | |||||||
| 			name = QuickLoadCompositeOptions.xib; | 			name = QuickLoadCompositeOptions.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| 		4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = { |  | ||||||
| 			isa = PBXVariantGroup; |  | ||||||
| 			children = ( |  | ||||||
| 				4BA141BC2072E8A400A31EC9 /* Base */, |  | ||||||
| 			); |  | ||||||
| 			name = MachinePicker.xib; |  | ||||||
| 			sourceTree = "<group>"; |  | ||||||
| 		}; |  | ||||||
| 		4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = { | 		4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -4036,6 +4073,14 @@ | |||||||
| 			name = MainMenu.xib; | 			name = MainMenu.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BC5FC2F20CDDDEE00410AA0 /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = AppleIIOptions.xib; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = { | 		4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -4044,6 +4089,14 @@ | |||||||
| 			name = QuickLoadOptions.xib; | 			name = QuickLoadOptions.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BEEE6BC20DC72EA003723BF /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = CompositeOptions.xib; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| /* End PBXVariantGroup section */ | /* End PBXVariantGroup section */ | ||||||
|  |  | ||||||
| /* Begin XCBuildConfiguration section */ | /* Begin XCBuildConfiguration section */ | ||||||
|   | |||||||
							
								
								
									
										113
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|  |         <capability name="Aspect ratio constraints" minToolsVersion="5.1"/> | ||||||
|  |         <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="activityPanel" destination="ZW7-Bw-4RP" id="GRG-Q6-RQU"/> | ||||||
|  |             </connections> | ||||||
|  |         </customObject> | ||||||
|  |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|  |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|  |         <window title="Activity" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="NSPanel"> | ||||||
|  |             <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="300" y="76" width="200" height="131"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|  |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|  |                 <rect key="frame" x="0.0" y="0.0" width="200" height="131"/> | ||||||
|  |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|  |                 <subviews> | ||||||
|  |                     <levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ySY-ir-hzb" userLabel="First indicator"> | ||||||
|  |                         <rect key="frame" x="20" y="95" width="17" height="16"/> | ||||||
|  |                         <constraints> | ||||||
|  |                             <constraint firstAttribute="width" secondItem="ySY-ir-hzb" secondAttribute="height" multiplier="1:1" id="UX0-hT-7Td"/> | ||||||
|  |                         </constraints> | ||||||
|  |                         <levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="DhQ-Di-tRT"/> | ||||||
|  |                     </levelIndicator> | ||||||
|  |                     <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tah-UQ-vdf"> | ||||||
|  |                         <rect key="frame" x="44" y="94" width="59" height="17"/> | ||||||
|  |                         <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 1" id="a5P-Ci-RzC"> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                         </textFieldCell> | ||||||
|  |                     </textField> | ||||||
|  |                     <levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncQ-wN-C61" userLabel="Second indicator"> | ||||||
|  |                         <rect key="frame" x="20" y="70" width="17" height="16"/> | ||||||
|  |                         <constraints> | ||||||
|  |                             <constraint firstAttribute="width" secondItem="ncQ-wN-C61" secondAttribute="height" multiplier="1:1" id="176-v3-mVW"/> | ||||||
|  |                         </constraints> | ||||||
|  |                         <levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="jlb-bk-FPd"/> | ||||||
|  |                     </levelIndicator> | ||||||
|  |                     <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="14O-Lq-Npx"> | ||||||
|  |                         <rect key="frame" x="44" y="69" width="61" height="17"/> | ||||||
|  |                         <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 2" id="NE1-CO-pGI"> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                         </textFieldCell> | ||||||
|  |                     </textField> | ||||||
|  |                     <levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0rV-Th-Zwt" userLabel="Third indicator"> | ||||||
|  |                         <rect key="frame" x="20" y="45" width="17" height="16"/> | ||||||
|  |                         <constraints> | ||||||
|  |                             <constraint firstAttribute="width" secondItem="0rV-Th-Zwt" secondAttribute="height" multiplier="1:1" id="Ai8-b3-Nn5"/> | ||||||
|  |                         </constraints> | ||||||
|  |                         <levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="CJy-Jn-eCL"/> | ||||||
|  |                     </levelIndicator> | ||||||
|  |                     <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acy-tT-OFH"> | ||||||
|  |                         <rect key="frame" x="44" y="44" width="61" height="17"/> | ||||||
|  |                         <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 3" id="FSR-y6-7WE"> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                         </textFieldCell> | ||||||
|  |                     </textField> | ||||||
|  |                     <levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bvH-EJ-TYb" userLabel="Fourth indicator"> | ||||||
|  |                         <rect key="frame" x="20" y="20" width="17" height="16"/> | ||||||
|  |                         <constraints> | ||||||
|  |                             <constraint firstAttribute="width" secondItem="bvH-EJ-TYb" secondAttribute="height" multiplier="1:1" id="cKc-q1-2Q4"/> | ||||||
|  |                         </constraints> | ||||||
|  |                         <levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="eoN-hl-30l"/> | ||||||
|  |                     </levelIndicator> | ||||||
|  |                     <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R0g-Oa-VB5"> | ||||||
|  |                         <rect key="frame" x="44" y="19" width="62" height="17"/> | ||||||
|  |                         <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 4" id="aGr-cd-jC0"> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> | ||||||
|  |                         </textFieldCell> | ||||||
|  |                     </textField> | ||||||
|  |                 </subviews> | ||||||
|  |                 <constraints> | ||||||
|  |                     <constraint firstItem="14O-Lq-Npx" firstAttribute="centerY" secondItem="ncQ-wN-C61" secondAttribute="centerY" id="0Ht-U2-sPg"/> | ||||||
|  |                     <constraint firstItem="bvH-EJ-TYb" firstAttribute="top" secondItem="0rV-Th-Zwt" secondAttribute="bottom" constant="9" id="0xw-qA-6vP"/> | ||||||
|  |                     <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="14O-Lq-Npx" secondAttribute="trailing" constant="20" id="5eo-XI-a3W"/> | ||||||
|  |                     <constraint firstItem="Tah-UQ-vdf" firstAttribute="centerY" secondItem="ySY-ir-hzb" secondAttribute="centerY" id="6Hn-ts-mTi"/> | ||||||
|  |                     <constraint firstItem="R0g-Oa-VB5" firstAttribute="leading" secondItem="bvH-EJ-TYb" secondAttribute="trailing" constant="10" id="Dgy-JI-nA1"/> | ||||||
|  |                     <constraint firstItem="R0g-Oa-VB5" firstAttribute="centerY" secondItem="bvH-EJ-TYb" secondAttribute="centerY" id="Gfq-mB-Y1z"/> | ||||||
|  |                     <constraint firstItem="Acy-tT-OFH" firstAttribute="centerY" secondItem="0rV-Th-Zwt" secondAttribute="centerY" id="ImF-rK-oOr"/> | ||||||
|  |                     <constraint firstItem="Acy-tT-OFH" firstAttribute="leading" secondItem="0rV-Th-Zwt" secondAttribute="trailing" constant="10" id="JSU-pZ-l9Q"/> | ||||||
|  |                     <constraint firstItem="ySY-ir-hzb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="KMh-EO-rxE"/> | ||||||
|  |                     <constraint firstItem="0rV-Th-Zwt" firstAttribute="top" secondItem="ncQ-wN-C61" secondAttribute="bottom" constant="9" id="Q2g-yM-nlJ"/> | ||||||
|  |                     <constraint firstItem="ncQ-wN-C61" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="QUI-Hc-Bcl"/> | ||||||
|  |                     <constraint firstItem="0rV-Th-Zwt" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="bKh-4L-mqj"/> | ||||||
|  |                     <constraint firstItem="bvH-EJ-TYb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="cPA-Ls-fLj"/> | ||||||
|  |                     <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Tah-UQ-vdf" secondAttribute="trailing" constant="20" id="igX-7U-TeE"/> | ||||||
|  |                     <constraint firstItem="14O-Lq-Npx" firstAttribute="leading" secondItem="ncQ-wN-C61" secondAttribute="trailing" constant="10" id="jjP-qH-Pqg"/> | ||||||
|  |                     <constraint firstItem="Tah-UQ-vdf" firstAttribute="leading" secondItem="ySY-ir-hzb" secondAttribute="trailing" constant="10" id="lux-Nz-K7E"/> | ||||||
|  |                     <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Acy-tT-OFH" secondAttribute="trailing" constant="20" id="mEe-VT-dNr"/> | ||||||
|  |                     <constraint firstItem="ncQ-wN-C61" firstAttribute="top" secondItem="ySY-ir-hzb" secondAttribute="bottom" constant="9" id="mSc-jj-amw"/> | ||||||
|  |                     <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="R0g-Oa-VB5" secondAttribute="trailing" constant="20" id="sR8-Ph-suC"/> | ||||||
|  |                     <constraint firstItem="ySY-ir-hzb" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="wbj-48-DYq"/> | ||||||
|  |                 </constraints> | ||||||
|  |             </view> | ||||||
|  |             <point key="canvasLocation" x="84" y="115"/> | ||||||
|  |         </window> | ||||||
|  |     </objects> | ||||||
|  | </document> | ||||||
							
								
								
									
										49
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|  |         <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="80" y="150" width="200" height="54"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|  |             <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="Accelerate DOS 3.3" 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 firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/> | ||||||
|  |                     <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 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> | ||||||
| @@ -1,7 +1,9 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/> |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|  |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
|         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> |         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
| @@ -14,8 +16,8 @@ | |||||||
|         <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target"> |         <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|             <rect key="contentRect" x="83" y="102" width="200" height="121"/> |             <rect key="contentRect" x="80" y="150" width="200" height="121"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <view key="contentView" id="aQh-Pm-DEo"> |             <view key="contentView" id="aQh-Pm-DEo"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="200" height="121"/> |                 <rect key="frame" x="0.0" y="0.0" width="200" height="121"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/CompositeOptions.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/CompositeOptions.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|  |         <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="80" y="150" width="200" height="61"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|  |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|  |                 <rect key="frame" x="0.0" y="0.0" width="200" height="61"/> | ||||||
|  |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|  |                 <subviews> | ||||||
|  |                     <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n"> | ||||||
|  |                         <rect key="frame" x="18" y="17" width="165" height="26"/> | ||||||
|  |                         <popUpButtonCell key="cell" type="push" title="RGB Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1"> | ||||||
|  |                             <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|  |                             <font key="font" metaFont="menu"/> | ||||||
|  |                             <menu key="menu" id="L06-TO-EF0"> | ||||||
|  |                                 <items> | ||||||
|  |                                     <menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/> | ||||||
|  |                                     <menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/> | ||||||
|  |                                     <menuItem title="Television" tag="1" id="fFm-fS-rWG"/> | ||||||
|  |                                 </items> | ||||||
|  |                             </menu> | ||||||
|  |                         </popUpButtonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="setDisplayType:" target="ZW7-Bw-4RP" id="PAH-CZ-zlk"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </popUpButton> | ||||||
|  |                 </subviews> | ||||||
|  |                 <constraints> | ||||||
|  |                     <constraint firstItem="rh8-km-57n" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="B6L-VS-2cN"/> | ||||||
|  |                     <constraint firstItem="rh8-km-57n" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="VRo-6R-IKd"/> | ||||||
|  |                     <constraint firstAttribute="bottom" secondItem="rh8-km-57n" secondAttribute="bottom" constant="20" id="jHA-lf-e7V"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="rh8-km-57n" secondAttribute="trailing" constant="20" id="urO-Ac-aqK"/> | ||||||
|  |                 </constraints> | ||||||
|  |             </view> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="displayTypeButton" destination="rh8-km-57n" id="FB2-Zg-VKq"/> | ||||||
|  |             </connections> | ||||||
|  |             <point key="canvasLocation" x="175" y="33.5"/> | ||||||
|  |         </window> | ||||||
|  |     </objects> | ||||||
|  | </document> | ||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -18,8 +18,8 @@ | |||||||
|             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> | ||||||
|             <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> |             <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|             <rect key="contentRect" x="133" y="235" width="600" height="450"/> |             <rect key="contentRect" x="80" y="250" width="600" height="450"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <value key="minSize" type="size" width="228" height="171"/> |             <value key="minSize" type="size" width="228" height="171"/> | ||||||
|             <view key="contentView" id="gIp-Ho-8D9"> |             <view key="contentView" id="gIp-Ho-8D9"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> |                 <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
|         <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> |         <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> | ||||||
| @@ -111,6 +111,17 @@ | |||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> |                             <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> | ||||||
|  |                             <menuItem title="Save Screenshot" keyEquivalent="D" id="BVJ-oQ-hUp"> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                             <menuItem title="Insert..." keyEquivalent="O" id="qQa-kh-4nz"> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="insertMedia:" target="-1" id="9Hs-9J-dlY"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                             <menuItem isSeparatorItem="YES" id="rXU-KX-GkZ"/> | ||||||
|                             <menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK"> |                             <menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK"> | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> |                                 <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> | ||||||
|                                 <connections> |                                 <connections> | ||||||
| @@ -129,22 +140,6 @@ | |||||||
|                     <modifierMask key="keyEquivalentModifierMask"/> |                     <modifierMask key="keyEquivalentModifierMask"/> | ||||||
|                     <menu key="submenu" title="Edit" id="W48-6f-4Dl"> |                     <menu key="submenu" title="Edit" id="W48-6f-4Dl"> | ||||||
|                         <items> |                         <items> | ||||||
|                             <menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg"> |  | ||||||
|                                 <connections> |  | ||||||
|                                     <action selector="undo:" target="-1" id="M6e-cu-g7V"/> |  | ||||||
|                                 </connections> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam"> |  | ||||||
|                                 <connections> |  | ||||||
|                                     <action selector="redo:" target="-1" id="oIA-Rs-6OD"/> |  | ||||||
|                                 </connections> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> |  | ||||||
|                             <menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG"> |  | ||||||
|                                 <connections> |  | ||||||
|                                     <action selector="cut:" target="-1" id="YJe-68-I9s"/> |  | ||||||
|                                 </connections> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU"> |                             <menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="copy:" target="-1" id="G1f-GL-Joy"/> |                                     <action selector="copy:" target="-1" id="G1f-GL-Joy"/> | ||||||
| @@ -155,192 +150,6 @@ | |||||||
|                                     <action selector="paste:" target="-1" id="UvS-8e-Qdg"/> |                                     <action selector="paste:" target="-1" id="UvS-8e-Qdg"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Paste and Match Style" enabled="NO" keyEquivalent="V" id="WeT-3V-zwk"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |  | ||||||
|                                 <connections> |  | ||||||
|                                     <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> |  | ||||||
|                                 </connections> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Delete" enabled="NO" id="pa3-QI-u2k"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                 <connections> |  | ||||||
|                                     <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> |  | ||||||
|                                 </connections> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m"> |  | ||||||
|                                 <connections> |  | ||||||
|                                     <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> |  | ||||||
|                                 </connections> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/> |  | ||||||
|                             <menuItem title="Find" id="4EN-yA-p0u"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                 <menu key="submenu" title="Find" id="1b7-l0-nxx"> |  | ||||||
|                                     <items> |  | ||||||
|                                         <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                     </items> |  | ||||||
|                                 </menu> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                 <menu key="submenu" title="Spelling" id="3IN-sU-3Bg"> |  | ||||||
|                                     <items> |  | ||||||
|                                         <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7"> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/> |  | ||||||
|                                         <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                     </items> |  | ||||||
|                                 </menu> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Substitutions" id="9ic-FL-obx"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                 <menu key="submenu" title="Substitutions" id="FeM-D8-WVr"> |  | ||||||
|                                     <items> |  | ||||||
|                                         <menuItem title="Show Substitutions" id="z6F-FW-3nz"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/> |  | ||||||
|                                         <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Smart Quotes" id="hQb-2v-fYv"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Smart Dashes" id="rgM-f4-ycn"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Smart Links" id="cwL-P1-jid"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Data Detectors" id="tRr-pd-1PS"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Text Replacement" id="HFQ-gK-NFA"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                     </items> |  | ||||||
|                                 </menu> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Transformations" id="2oI-Rn-ZJC"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                 <menu key="submenu" title="Transformations" id="c8a-y6-VQd"> |  | ||||||
|                                     <items> |  | ||||||
|                                         <menuItem title="Make Upper Case" id="vmV-6d-7jI"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Make Lower Case" id="d9M-CD-aMd"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Capitalize" id="UEZ-Bs-lqG"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                     </items> |  | ||||||
|                                 </menu> |  | ||||||
|                             </menuItem> |  | ||||||
|                             <menuItem title="Speech" id="xrE-MZ-jX0"> |  | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                 <menu key="submenu" title="Speech" id="3rS-ZA-NoH"> |  | ||||||
|                                     <items> |  | ||||||
|                                         <menuItem title="Start Speaking" id="Ynk-f8-cLZ"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                         <menuItem title="Stop Speaking" id="Oyz-dy-DGm"> |  | ||||||
|                                             <modifierMask key="keyEquivalentModifierMask"/> |  | ||||||
|                                             <connections> |  | ||||||
|                                                 <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/> |  | ||||||
|                                             </connections> |  | ||||||
|                                         </menuItem> |  | ||||||
|                                     </items> |  | ||||||
|                                 </menu> |  | ||||||
|                             </menuItem> |  | ||||||
|                         </items> |                         </items> | ||||||
|                     </menu> |                     </menu> | ||||||
|                 </menuItem> |                 </menuItem> | ||||||
| @@ -348,10 +157,33 @@ | |||||||
|                     <modifierMask key="keyEquivalentModifierMask"/> |                     <modifierMask key="keyEquivalentModifierMask"/> | ||||||
|                     <menu key="submenu" title="View" id="HyV-fh-RgO"> |                     <menu key="submenu" title="View" id="HyV-fh-RgO"> | ||||||
|                         <items> |                         <items> | ||||||
|                             <menuItem title="Show Options" keyEquivalent="o" id="WCd-6R-baV"> |                             <menuItem title="Show Activity" keyEquivalent="a" id="WCd-6R-baV"> | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |                                 <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="showOptions:" target="-1" id="ge3-Qg-kb5"/> |                                     <action selector="showActivity:" target="-1" id="oeF-uJ-cOS"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                             <menuItem title="Show Options" keyEquivalent="o" id="GtG-CV-Uro"> | ||||||
|  |                                 <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="showOptions:" target="-1" id="M6T-DE-Duo"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                         </items> | ||||||
|  |                     </menu> | ||||||
|  |                 </menuItem> | ||||||
|  |                 <menuItem title="Input" id="5bL-VY-cxd"> | ||||||
|  |                     <modifierMask key="keyEquivalentModifierMask"/> | ||||||
|  |                     <menu key="submenu" title="Input" id="6yv-Cf-E9r"> | ||||||
|  |                         <items> | ||||||
|  |                             <menuItem title="Use Keyboard as Keyboard" state="on" tag="100" keyEquivalent="k" id="TfX-0B-j4U"> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="useKeyboardAsKeyboard:" target="-1" id="6fl-fS-Oe9"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                             <menuItem title="Use Keyboard as Joystick" tag="101" enabled="NO" keyEquivalent="j" id="5mn-ch-Xv6"> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="useKeyboardAsJoystick:" target="-1" id="Yz7-CL-f0y"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                         </items> |                         </items> | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -16,8 +16,8 @@ | |||||||
|         <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"> |         <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"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|             <rect key="contentRect" x="83" y="102" width="200" height="83"/> |             <rect key="contentRect" x="80" y="150" width="200" height="83"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <view key="contentView" id="tpZ-0B-QQu"> |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="200" height="83"/> |                 <rect key="frame" x="0.0" y="0.0" width="200" height="83"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -16,8 +16,8 @@ | |||||||
|         <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"> |         <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"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|             <rect key="contentRect" x="83" y="102" width="200" height="83"/> |             <rect key="contentRect" x="80" y="150" width="200" height="83"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <view key="contentView" id="tpZ-0B-QQu"> |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="200" height="83"/> |                 <rect key="frame" x="0.0" y="0.0" width="200" height="83"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -16,8 +16,8 @@ | |||||||
|         <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"> |         <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"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="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="contentRect" x="80" y="150" width="200" height="54"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <view key="contentView" id="tpZ-0B-QQu"> |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="200" height="54"/> |                 <rect key="frame" x="0.0" y="0.0" width="200" height="54"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?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"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/> |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -14,9 +15,9 @@ | |||||||
|         <customObject id="-3" userLabel="Application" customClass="NSObject"/> |         <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="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target"> |         <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |             <windowPositionMask key="initialPositionMask" leftStrut="YES" topStrut="YES"/> | ||||||
|             <rect key="contentRect" x="83" y="102" width="261" height="100"/> |             <rect key="contentRect" x="80" y="150" width="261" height="100"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|             <view key="contentView" id="7Pv-WL-2Rq"> |             <view key="contentView" id="7Pv-WL-2Rq"> | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="261" height="100"/> |                 <rect key="frame" x="0.0" y="0.0" width="261" height="100"/> | ||||||
|                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|   | |||||||
| @@ -4,6 +4,12 @@ | |||||||
| <dict> | <dict> | ||||||
| 	<key>com.apple.security.app-sandbox</key> | 	<key>com.apple.security.app-sandbox</key> | ||||||
| 	<true/> | 	<true/> | ||||||
|  | 	<key>com.apple.security.assets.pictures.read-write</key> | ||||||
|  | 	<true/> | ||||||
|  | 	<key>com.apple.security.device.bluetooth</key> | ||||||
|  | 	<true/> | ||||||
|  | 	<key>com.apple.security.device.usb</key> | ||||||
|  | 	<true/> | ||||||
| 	<key>com.apple.security.files.user-selected.read-write</key> | 	<key>com.apple.security.files.user-selected.read-write</key> | ||||||
| 	<true/> | 	<true/> | ||||||
| </dict> | </dict> | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ | |||||||
|  |  | ||||||
| #import	"CSOpenGLView.h" | #import	"CSOpenGLView.h" | ||||||
| #import "CSAudioQueue.h" | #import "CSAudioQueue.h" | ||||||
|  |  | ||||||
| #import "CSBestEffortUpdater.h" | #import "CSBestEffortUpdater.h" | ||||||
|  | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
| #include "KeyCodes.h" | #include "KeyCodes.h" | ||||||
|   | |||||||
| @@ -9,4 +9,5 @@ | |||||||
| import Cocoa | import Cocoa | ||||||
|  |  | ||||||
| class DocumentController: NSDocumentController { | class DocumentController: NSDocumentController { | ||||||
|  | 	let joystickManager = CSJoystickManager() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ | |||||||
| //  Copyright 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| import Cocoa |  | ||||||
| import AudioToolbox | import AudioToolbox | ||||||
|  | import Cocoa | ||||||
|  |  | ||||||
| class MachineDocument: | class MachineDocument: | ||||||
| 	NSDocument, | 	NSDocument, | ||||||
| @@ -40,6 +40,11 @@ class MachineDocument: | |||||||
| 		optionsPanel?.setIsVisible(true) | 		optionsPanel?.setIsVisible(true) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	@IBOutlet var activityPanel: NSPanel! | ||||||
|  | 	@IBAction func showActivity(_ sender: AnyObject!) { | ||||||
|  | 		activityPanel.setIsVisible(true) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	fileprivate var audioQueue: CSAudioQueue! = nil | 	fileprivate var audioQueue: CSAudioQueue! = nil | ||||||
| 	fileprivate var bestEffortUpdater: CSBestEffortUpdater? | 	fileprivate var bestEffortUpdater: CSBestEffortUpdater? | ||||||
|  |  | ||||||
| @@ -51,7 +56,6 @@ class MachineDocument: | |||||||
| 		super.windowControllerDidLoadNib(aController) | 		super.windowControllerDidLoadNib(aController) | ||||||
| 		aController.window?.contentAspectRatio = self.aspectRatio() | 		aController.window?.contentAspectRatio = self.aspectRatio() | ||||||
| 		setupMachineOutput() | 		setupMachineOutput() | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | ||||||
| @@ -102,7 +106,7 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) { | 	func machineSpeakerDidChangeInputClock(_ machine: CSMachine) { | ||||||
| 		setupAudioQueueClockRate() | 		setupAudioQueueClockRate() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -119,6 +123,9 @@ class MachineDocument: | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	override func close() { | 	override func close() { | ||||||
|  | 		activityPanel?.setIsVisible(false) | ||||||
|  | 		activityPanel = nil | ||||||
|  |  | ||||||
| 		optionsPanel?.setIsVisible(false) | 		optionsPanel?.setIsVisible(false) | ||||||
| 		optionsPanel = nil | 		optionsPanel = nil | ||||||
|  |  | ||||||
| @@ -147,6 +154,7 @@ class MachineDocument: | |||||||
| 			self.machine = machine | 			self.machine = machine | ||||||
| 			self.optionsPanelNibName = analysis.optionsPanelNibName | 			self.optionsPanelNibName = analysis.optionsPanelNibName | ||||||
| 			setupMachineOutput() | 			setupMachineOutput() | ||||||
|  | 			setupActivityDisplay() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -204,6 +212,7 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Runtime media insertion. | ||||||
| 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | ||||||
| 		let mediaSet = CSMediaSet(fileAt: URL) | 		let mediaSet = CSMediaSet(fileAt: URL) | ||||||
| 		if let mediaSet = mediaSet { | 		if let mediaSet = mediaSet { | ||||||
| @@ -211,6 +220,21 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	@IBAction final func insertMedia(_ sender: AnyObject!) { | ||||||
|  | 		let openPanel = NSOpenPanel() | ||||||
|  | 		openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window." | ||||||
|  | 		openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in | ||||||
|  | 			if response == .OK { | ||||||
|  | 				for url in openPanel.urls { | ||||||
|  | 					let mediaSet = CSMediaSet(fileAt: url) | ||||||
|  | 					if let mediaSet = mediaSet { | ||||||
|  | 						mediaSet.apply(to: self.machine) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// MARK: NSDocument overrides | 	// MARK: NSDocument overrides | ||||||
| 	override func data(ofType typeName: String) throws -> Data { | 	override func data(ofType typeName: String) throws -> Data { | ||||||
| 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | ||||||
| @@ -220,6 +244,13 @@ class MachineDocument: | |||||||
| 	func windowDidResignKey(_ notification: Notification) { | 	func windowDidResignKey(_ notification: Notification) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.clearAllKeys() | 			machine.clearAllKeys() | ||||||
|  | 			machine.joystickManager = nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func windowDidBecomeKey(_ notification: Notification) { | ||||||
|  | 		if let machine = self.machine { | ||||||
|  | 			machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -250,10 +281,165 @@ class MachineDocument: | |||||||
| 	@IBAction func createMachine(_ sender: NSButton?) { | 	@IBAction func createMachine(_ sender: NSButton?) { | ||||||
| 		self.configureAs(machinePicker!.selectedMachine()) | 		self.configureAs(machinePicker!.selectedMachine()) | ||||||
| 		machinePicker = nil | 		machinePicker = nil | ||||||
| 		sender?.window?.close() | 		self.windowControllers[0].window?.endSheet(self.machinePickerPanel!) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | ||||||
| 		close() | 		close() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Joystick-via-the-keyboard selection | ||||||
|  | 	@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) { | ||||||
|  | 		machine.inputMode = .keyboard | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@IBAction func useKeyboardAsJoystick(_ sender: NSMenuItem?) { | ||||||
|  | 		machine.inputMode = .joystick | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { | ||||||
|  | 		if let menuItem = item as? NSMenuItem { | ||||||
|  | 			switch item.action { | ||||||
|  | 				case #selector(self.useKeyboardAsKeyboard): | ||||||
|  | 					if machine == nil || !machine.hasKeyboard { | ||||||
|  | 						menuItem.state = .off | ||||||
|  | 						return false | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					menuItem.state = machine.inputMode == .keyboard ? .on : .off | ||||||
|  | 					return true | ||||||
|  |  | ||||||
|  | 				case #selector(self.useKeyboardAsJoystick): | ||||||
|  | 					if machine == nil || !machine.hasJoystick { | ||||||
|  | 						menuItem.state = .off | ||||||
|  | 						return false | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					menuItem.state = machine.inputMode == .joystick ? .on : .off | ||||||
|  | 					return true | ||||||
|  |  | ||||||
|  | 				case #selector(self.showActivity(_:)): | ||||||
|  | 					return self.activityPanel != nil | ||||||
|  |  | ||||||
|  | 				case #selector(self.insertMedia(_:)): | ||||||
|  | 					return self.machine != nil && self.machine.canInsertMedia | ||||||
|  |  | ||||||
|  | 				default: break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return super.validateUserInterfaceItem(item) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Screenshot capture. | ||||||
|  | 	@IBAction func saveScreenshot(_ sender: AnyObject!) { | ||||||
|  | 		// Grab a date formatter and form a file name. | ||||||
|  | 		let dateFormatter = DateFormatter() | ||||||
|  | 		dateFormatter.dateStyle = .short | ||||||
|  | 		dateFormatter.timeStyle = .long | ||||||
|  |  | ||||||
|  | 		let filename = ("Clock Signal Screen Shot " + dateFormatter.string(from: Date()) + ".png").replacingOccurrences(of: "/", with: "-") | ||||||
|  | 			.replacingOccurrences(of: ":", with: ".") | ||||||
|  | 		let pictursURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0] | ||||||
|  | 		let url = pictursURL.appendingPathComponent(filename) | ||||||
|  |  | ||||||
|  | 		// Obtain the machine's current display. | ||||||
|  | 		var imageRepresentation: NSBitmapImageRep? = nil | ||||||
|  | 		self.openGLView.perform { | ||||||
|  | 			imageRepresentation = self.machine.imageRepresentation | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Encode as a PNG and save. | ||||||
|  | 		let pngData = imageRepresentation!.representation(using: .png, properties: [:]) | ||||||
|  | 		try! pngData?.write(to: url) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Activity display. | ||||||
|  | 	class LED { | ||||||
|  | 		let levelIndicator: NSLevelIndicator | ||||||
|  | 		init(levelIndicator: NSLevelIndicator) { | ||||||
|  | 			self.levelIndicator = levelIndicator | ||||||
|  | 		} | ||||||
|  | 		var isLit = false | ||||||
|  | 		var isBlinking = false | ||||||
|  | 	} | ||||||
|  | 	fileprivate var leds: [String: LED] = [:] | ||||||
|  | 	func setupActivityDisplay() { | ||||||
|  | 		var leds = machine.leds | ||||||
|  | 		if leds.count > 0 { | ||||||
|  | 			Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil) | ||||||
|  | 			showActivity(nil) | ||||||
|  |  | ||||||
|  | 			// Inspect the activity panel for indicators. | ||||||
|  | 			var activityIndicators: [NSLevelIndicator] = [] | ||||||
|  | 			var textFields: [NSTextField] = [] | ||||||
|  | 			if let contentView = self.activityPanel.contentView { | ||||||
|  | 				for view in contentView.subviews { | ||||||
|  | 					if let levelIndicator = view as? NSLevelIndicator { | ||||||
|  | 						activityIndicators.append(levelIndicator) | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if let textField = view as? NSTextField { | ||||||
|  | 						textFields.append(textField) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// If there are fewer level indicators than LEDs, trim that list. | ||||||
|  | 			if activityIndicators.count < leds.count { | ||||||
|  | 				leds.removeSubrange(activityIndicators.count ..< leds.count) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Remove unused views. | ||||||
|  | 			for c in leds.count ..< activityIndicators.count { | ||||||
|  | 				textFields[c].removeFromSuperview() | ||||||
|  | 				activityIndicators[c].removeFromSuperview() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Apply labels and create leds entries. | ||||||
|  | 			for c in 0 ..< leds.count { | ||||||
|  | 				textFields[c].stringValue = leds[c] | ||||||
|  | 				self.leds[leds[c]] = LED(levelIndicator: activityIndicators[c]) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Add a constraints to minimise window height. | ||||||
|  | 			let heightConstraint = NSLayoutConstraint( | ||||||
|  | 				item: self.activityPanel.contentView!, | ||||||
|  | 				attribute: .bottom, | ||||||
|  | 				relatedBy: .equal, | ||||||
|  | 				toItem: activityIndicators[leds.count-1], | ||||||
|  | 				attribute: .bottom, | ||||||
|  | 				multiplier: 1.0, | ||||||
|  | 				constant: 20.0) | ||||||
|  | 			self.activityPanel.contentView?.addConstraint(heightConstraint) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func machine(_ machine: CSMachine, ledShouldBlink ledName: String) { | ||||||
|  | 		// If there is such an LED, switch it off for 0.03 of a second; if it's meant | ||||||
|  | 		// to be off at the end of that, leave it off. Don't allow the blinks to | ||||||
|  | 		// pile up — allow there to be only one in flight at a time. | ||||||
|  | 		if let led = leds[ledName] { | ||||||
|  | 			DispatchQueue.main.async { | ||||||
|  | 				if !led.isBlinking { | ||||||
|  | 					led.levelIndicator.floatValue = 0.0 | ||||||
|  | 					led.isBlinking = true | ||||||
|  |  | ||||||
|  | 					DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { | ||||||
|  | 						led.levelIndicator.floatValue = led.isLit ? 1.0 : 0.0 | ||||||
|  | 						led.isBlinking = false | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func machine(_ machine: CSMachine, led ledName: String, didChangeToLit isLit: Bool) { | ||||||
|  | 		// If there is such an LED, switch it appropriately. | ||||||
|  | 		if let led = leds[ledName] { | ||||||
|  | 			DispatchQueue.main.async { | ||||||
|  | 				led.levelIndicator.floatValue = isLit ? 1.0 : 0.0 | ||||||
|  | 				led.isLit = isLit | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,39 +13,43 @@ | |||||||
| 				<string>bin</string> | 				<string>bin</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge</string> | 			<string>cartridge.png</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Atari 2600 Cartridge</string> | 			<string>Atari 2600 Cartridge</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>rom</string> | 				<string>rom</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>chip</string> | 			<string>chip.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ROM Image</string> | 			<string>ROM Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -53,100 +57,110 @@ | |||||||
| 				<string>uef</string> | 				<string>uef</string> | ||||||
| 				<string>uef.gz</string> | 				<string>uef.gz</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC UEF Image</string> | 			<string>Electron/BBC UEF Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>prg</string> | 				<string>prg</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Program</string> | 			<string>Commodore Program</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tap</string> | 				<string>tap</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>g64</string> | 				<string>g64</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Disk</string> | 			<string>Commodore Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>d64</string> | 				<string>d64</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore 1540/1 Disk</string> | 			<string>Commodore 1540/1 Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -157,40 +171,44 @@ | |||||||
| 				<string>adl</string> | 				<string>adl</string> | ||||||
| 				<string>adm</string> | 				<string>adm</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC Disk Image</string> | 			<string>Electron/BBC Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>dsk</string> | 				<string>dsk</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -198,20 +216,22 @@ | |||||||
| 				<string>o</string> | 				<string>o</string> | ||||||
| 				<string>80</string> | 				<string>80</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ZX80 Tape Image</string> | 			<string>ZX80 Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -220,178 +240,196 @@ | |||||||
| 				<string>81</string> | 				<string>81</string> | ||||||
| 				<string>p81</string> | 				<string>p81</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ZX81 Tape Image</string> | 			<string>ZX81 Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>csw</string> | 				<string>csw</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tzx</string> | 				<string>tzx</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>cdt</string> | 				<string>cdt</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Amstrad CPC Tape Image</string> | 			<string>Amstrad CPC Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>hfe</string> | 				<string>hfe</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>HxC Disk Image</string> | 			<string>HxC Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>cas</string> | 				<string>cas</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>MSX Tape Image</string> | 			<string>MSX Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>dmk</string> | 				<string>dmk</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tsx</string> | 				<string>tsx</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>MSX Tape Image</string> | 			<string>MSX Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>col</string> | 				<string>col</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge</string> | 			<string>cartridge.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ColecoVision Cartridge</string> | 			<string>ColecoVision Cartridge</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -401,20 +439,22 @@ | |||||||
| 				<string>do</string> | 				<string>do</string> | ||||||
| 				<string>po</string> | 				<string>po</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Apple II Disk Image</string> | 			<string>Apple II Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 	</array> | 	</array> | ||||||
| 	<key>CFBundleExecutable</key> | 	<key>CFBundleExecutable</key> | ||||||
| @@ -436,7 +476,7 @@ | |||||||
| 	<key>CFBundleVersion</key> | 	<key>CFBundleVersion</key> | ||||||
| 	<string>1</string> | 	<string>1</string> | ||||||
| 	<key>LSApplicationCategoryType</key> | 	<key>LSApplicationCategoryType</key> | ||||||
| 	<string></string> | 	<string>public.app-category.entertainment</string> | ||||||
| 	<key>LSMinimumSystemVersion</key> | 	<key>LSMinimumSystemVersion</key> | ||||||
| 	<string>$(MACOSX_DEPLOYMENT_TARGET)</string> | 	<string>$(MACOSX_DEPLOYMENT_TARGET)</string> | ||||||
| 	<key>NSHumanReadableCopyright</key> | 	<key>NSHumanReadableCopyright</key> | ||||||
|   | |||||||
| @@ -0,0 +1,80 @@ | |||||||
|  | // | ||||||
|  | //  CSJoystickManager.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 19/07/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a single joystick button. | ||||||
|  | 	Buttons have an index and are either currently pressed, or not. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickButton: NSObject | ||||||
|  | /// The button index. By convention the USB spec defines the first button as number 1. | ||||||
|  | @property(nonatomic, readonly) NSInteger index; | ||||||
|  | @property(nonatomic, readonly) bool isPressed; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSJoystickAxisType) { | ||||||
|  | 	CSJoystickAxisTypeX, | ||||||
|  | 	CSJoystickAxisTypeY, | ||||||
|  | 	CSJoystickAxisTypeZ, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a joystick axis. | ||||||
|  | 	Axes have a nominated type and a continuous value between 0 and 1. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickAxis: NSObject | ||||||
|  | @property(nonatomic, readonly) CSJoystickAxisType type; | ||||||
|  | /// The current position of this axis in the range [0, 1]. | ||||||
|  | @property(nonatomic, readonly) float position; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | typedef NS_OPTIONS(NSInteger, CSJoystickHatDirection) { | ||||||
|  | 	CSJoystickHatDirectionUp = 1 << 0, | ||||||
|  | 	CSJoystickHatDirectionDown = 1 << 1, | ||||||
|  | 	CSJoystickHatDirectionLeft = 1 << 2, | ||||||
|  | 	CSJoystickHatDirectionRight = 1 << 3, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a joystick hat. | ||||||
|  | 	A hat is a digital directional input, so e.g. this is how thumbpads are represented. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickHat: NSObject | ||||||
|  | @property(nonatomic, readonly) CSJoystickHatDirection direction; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a joystick. | ||||||
|  |  | ||||||
|  | 	A joystick is a collection of buttons, axes and hats, each of which holds a current | ||||||
|  | 	state. The holder must use @c update to cause this joystick to read a fresh copy | ||||||
|  | 	of its state. | ||||||
|  | */ | ||||||
|  | @interface CSJoystick: NSObject | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystickButton *> *buttons; | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystickAxis *> *axes; | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystickHat *> *hats; | ||||||
|  |  | ||||||
|  | - (void)update; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	The joystick manager watches for joystick connections and disconnections and | ||||||
|  | 	offers a list of joysticks currently attached. | ||||||
|  |  | ||||||
|  | 	Be warned: this means using Apple's IOKit directly to watch for Bluetooth and | ||||||
|  | 	USB HID devices. So to use this code, make sure you have USB and Bluetooth | ||||||
|  | 	enabled for the app's sandbox. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickManager : NSObject | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystick *> *joysticks; | ||||||
|  |  | ||||||
|  | /// Updates all joysticks. | ||||||
|  | - (void)update; | ||||||
|  | @end | ||||||
							
								
								
									
										333
									
								
								OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | |||||||
|  | // | ||||||
|  | //  CSJoystickManager.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 19/07/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
|  | @import IOKit; | ||||||
|  | #include <IOKit/hid/IOHIDLib.h> | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickButton | ||||||
|  |  | ||||||
|  | @implementation CSJoystickButton { | ||||||
|  | 	IOHIDElementRef _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_index = index; | ||||||
|  | 		_element = (IOHIDElementRef)CFRetain(element); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_element); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDElementRef)element { | ||||||
|  | 	return _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setIsPressed:(bool)isPressed { | ||||||
|  | 	_isPressed = isPressed; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickAxis | ||||||
|  |  | ||||||
|  | @implementation CSJoystickAxis { | ||||||
|  | 	IOHIDElementRef _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_element = (IOHIDElementRef)CFRetain(element); | ||||||
|  | 		_type = type; | ||||||
|  | 		_position = 0.5f; | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_element); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDElementRef)element { | ||||||
|  | 	return _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setPosition:(float)position { | ||||||
|  | 	_position = position; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickHat | ||||||
|  |  | ||||||
|  | @implementation CSJoystickHat { | ||||||
|  | 	IOHIDElementRef _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElement:(IOHIDElementRef)element { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_element = (IOHIDElementRef)CFRetain(element); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_element); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDElementRef)element { | ||||||
|  | 	return _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setDirection:(CSJoystickHatDirection)direction { | ||||||
|  | 	_direction = direction; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystick | ||||||
|  |  | ||||||
|  | @implementation CSJoystick { | ||||||
|  | 	IOHIDDeviceRef _device; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons | ||||||
|  | 	axes:(NSArray<CSJoystickAxis *> *)axes | ||||||
|  | 	hats:(NSArray<CSJoystickHat *> *)hats | ||||||
|  | 	device:(IOHIDDeviceRef)device { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		// Sort buttons by index. | ||||||
|  | 		_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]]; | ||||||
|  |  | ||||||
|  | 		// Sort axes by enum value. | ||||||
|  | 		_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]]; | ||||||
|  |  | ||||||
|  | 		// Hats have no guaranteed ordering. | ||||||
|  | 		_hats = hats; | ||||||
|  |  | ||||||
|  | 		// Keep hold of the device. | ||||||
|  | 		_device = (IOHIDDeviceRef)CFRetain(device); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_device); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)update { | ||||||
|  | 	// Update buttons. | ||||||
|  | 	for(CSJoystickButton *button in _buttons) { | ||||||
|  | 		IOHIDValueRef value; | ||||||
|  | 		if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) { | ||||||
|  | 			// Some pressure-sensitive buttons return values greater than 1 for hard presses, | ||||||
|  | 			// but this class rationalised everything to Boolean. | ||||||
|  | 			button.isPressed = !!IOHIDValueGetIntegerValue(value); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Update hats. | ||||||
|  | 	for(CSJoystickHat *hat in _hats) { | ||||||
|  | 		IOHIDValueRef value; | ||||||
|  | 		if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) { | ||||||
|  | 			// Hats report a direction, which is either one of eight or one of four. | ||||||
|  | 			CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(hat.element); | ||||||
|  | 			const CFIndex range = 1 + IOHIDElementGetLogicalMax(hat.element) - IOHIDElementGetLogicalMin(hat.element); | ||||||
|  | 			integerValue *= 8 / range; | ||||||
|  |  | ||||||
|  | 			// Map from the HID direction to the bit field. | ||||||
|  | 			switch(integerValue) { | ||||||
|  | 				default:	hat.direction = 0;															break; | ||||||
|  | 				case 0:		hat.direction = CSJoystickHatDirectionUp;									break; | ||||||
|  | 				case 1:		hat.direction = CSJoystickHatDirectionUp | CSJoystickHatDirectionRight;		break; | ||||||
|  | 				case 2:		hat.direction = CSJoystickHatDirectionRight;								break; | ||||||
|  | 				case 3:		hat.direction = CSJoystickHatDirectionRight | CSJoystickHatDirectionDown;	break; | ||||||
|  | 				case 4:		hat.direction = CSJoystickHatDirectionDown;									break; | ||||||
|  | 				case 5:		hat.direction = CSJoystickHatDirectionDown | CSJoystickHatDirectionLeft;	break; | ||||||
|  | 				case 6:		hat.direction = CSJoystickHatDirectionLeft;									break; | ||||||
|  | 				case 7:		hat.direction = CSJoystickHatDirectionLeft | CSJoystickHatDirectionUp;		break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Update axes. | ||||||
|  | 	for(CSJoystickAxis *axis in _axes) { | ||||||
|  | 		IOHIDValueRef value; | ||||||
|  | 		if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) { | ||||||
|  | 			const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element); | ||||||
|  | 			const CFIndex range = 1 + IOHIDElementGetLogicalMax(axis.element) - IOHIDElementGetLogicalMin(axis.element); | ||||||
|  | 			axis.position = (float)integerValue / (float)range; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDDeviceRef)device { | ||||||
|  | 	return _device; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickManager | ||||||
|  |  | ||||||
|  | @interface CSJoystickManager () | ||||||
|  | - (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender; | ||||||
|  | - (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { | ||||||
|  | 	[(__bridge  CSJoystickManager *)context deviceMatched:device result:result sender:sender]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { | ||||||
|  | 	[(__bridge  CSJoystickManager *)context deviceRemoved:device result:result sender:sender]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @implementation CSJoystickManager { | ||||||
|  | 	IOHIDManagerRef _hidManager; | ||||||
|  | 	NSMutableArray<CSJoystick *> *_joysticks; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)init { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_joysticks = [[NSMutableArray alloc] init]; | ||||||
|  |  | ||||||
|  | 		_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); | ||||||
|  | 		if(!_hidManager) return nil; | ||||||
|  |  | ||||||
|  | 		NSArray<NSDictionary<NSString *, NSNumber *> *> *const multiple = @[ | ||||||
|  | 			@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick) }, | ||||||
|  | 			@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_GamePad) }, | ||||||
|  | 			@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_MultiAxisController) }, | ||||||
|  | 		]; | ||||||
|  |  | ||||||
|  | 		IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)multiple); | ||||||
|  | 		IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self); | ||||||
|  | 		IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self); | ||||||
|  | 		IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); | ||||||
|  |  | ||||||
|  | 		if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { | ||||||
|  | 			NSLog(@"Failed to open HID manager"); | ||||||
|  | 			// something | ||||||
|  | 			return nil; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); | ||||||
|  | 	IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); | ||||||
|  | 	CFRelease(_hidManager); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender { | ||||||
|  | 	// Double check this joystick isn't already known. | ||||||
|  | 	for(CSJoystick *joystick in _joysticks) { | ||||||
|  | 		if(joystick.device == device) return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Prepare to collate a list of buttons, axes and hats for the new device. | ||||||
|  | 	NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init]; | ||||||
|  | 	NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init]; | ||||||
|  | 	NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init]; | ||||||
|  |  | ||||||
|  | 	// Inspect all elements for those that are comprehensible to this code. | ||||||
|  | 	const CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); | ||||||
|  | 	for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) { | ||||||
|  | 		const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index); | ||||||
|  |  | ||||||
|  | 		// Check that this element is either on the generic desktop page or else is a button. | ||||||
|  | 		const uint32_t usagePage = IOHIDElementGetUsagePage(element); | ||||||
|  | 		if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue; | ||||||
|  |  | ||||||
|  | 		// Then inspect the type. | ||||||
|  | 		switch(IOHIDElementGetType(element)) { | ||||||
|  | 			default: break; | ||||||
|  |  | ||||||
|  | 			case kIOHIDElementTypeInput_Button: { | ||||||
|  | 				// Add a button; pretty easy stuff. 'Usage' provides a button index. | ||||||
|  | 				const uint32_t usage = IOHIDElementGetUsage(element); | ||||||
|  | 				[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]]; | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case kIOHIDElementTypeInput_Misc: | ||||||
|  | 			case kIOHIDElementTypeInput_Axis: { | ||||||
|  | 				CSJoystickAxisType axisType; | ||||||
|  | 				switch(IOHIDElementGetUsage(element)) { | ||||||
|  | 					default: continue; | ||||||
|  |  | ||||||
|  | 					// Three analogue axes are implemented here; there are another three sets | ||||||
|  | 					// of these that could be parsed in the future if interesting. | ||||||
|  | 					case kHIDUsage_GD_X:	axisType = CSJoystickAxisTypeX;		break; | ||||||
|  | 					case kHIDUsage_GD_Y:	axisType = CSJoystickAxisTypeY;		break; | ||||||
|  | 					case kHIDUsage_GD_Z:	axisType = CSJoystickAxisTypeZ;		break; | ||||||
|  |  | ||||||
|  | 					// A hatswitch is a multi-directional control all of its own. | ||||||
|  | 					case kHIDUsage_GD_Hatswitch: | ||||||
|  | 						[hats addObject:[[CSJoystickHat alloc] initWithElement:element]]; | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Add the axis; if it was a hat switch or unrecognised then the code doesn't | ||||||
|  | 				// reach here. | ||||||
|  | 				[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]]; | ||||||
|  | 			} break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	CFRelease(elements); | ||||||
|  |  | ||||||
|  | 	// Add this joystick to the list. | ||||||
|  | 	[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender { | ||||||
|  | 	// If this joystick was recorded, remove it. | ||||||
|  | 	for(CSJoystick *joystick in [_joysticks copy]) { | ||||||
|  | 		if(joystick.device == device) { | ||||||
|  | 			[_joysticks removeObject:joystick]; | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)update { | ||||||
|  | 	[self.joysticks makeObjectsPerformSelector:@selector(update)]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSArray<CSJoystick *> *)joysticks { | ||||||
|  | 	return [_joysticks copy]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -12,10 +12,13 @@ | |||||||
| #import "CSFastLoading.h" | #import "CSFastLoading.h" | ||||||
| #import "CSOpenGLView.h" | #import "CSOpenGLView.h" | ||||||
| #import "CSStaticAnalyser.h" | #import "CSStaticAnalyser.h" | ||||||
|  | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
| @class CSMachine; | @class CSMachine; | ||||||
| @protocol CSMachineDelegate | @protocol CSMachineDelegate | ||||||
| - (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine; | - (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine; | ||||||
|  | - (void)machine:(nonnull CSMachine *)machine led:(nonnull NSString *)led didChangeToLit:(BOOL)isLit; | ||||||
|  | - (void)machine:(nonnull CSMachine *)machine ledShouldBlink:(nonnull NSString *)led; | ||||||
| @end | @end | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { | typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { | ||||||
| @@ -24,47 +27,65 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { | |||||||
| 	CSMachineVideoSignalRGB | 	CSMachineVideoSignalRGB | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | ||||||
|  | 	CSMachineKeyboardInputModeKeyboard, | ||||||
|  | 	CSMachineKeyboardInputModeJoystick | ||||||
|  | }; | ||||||
|  |  | ||||||
| // Deliberately low; to ensure CSMachine has been declared as an @class already. | // Deliberately low; to ensure CSMachine has been declared as an @class already. | ||||||
| #import "CSAtari2600.h" | #import "CSAtari2600.h" | ||||||
| #import "CSZX8081.h" | #import "CSZX8081.h" | ||||||
|  |  | ||||||
| @interface CSMachine : NSObject | @interface CSMachine : NSObject | ||||||
|  |  | ||||||
| - (instancetype)init NS_UNAVAILABLE; | - (nonnull instancetype)init NS_UNAVAILABLE; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Initialises an instance of CSMachine. | 	Initialises an instance of CSMachine. | ||||||
|  |  | ||||||
| 	@param result The CSStaticAnalyser result that describes the machine needed. | 	@param result The CSStaticAnalyser result that describes the machine needed. | ||||||
| */ | */ | ||||||
| - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; | - (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; | ||||||
|  |  | ||||||
| - (void)runForInterval:(NSTimeInterval)interval; | - (void)runForInterval:(NSTimeInterval)interval; | ||||||
|  |  | ||||||
| - (float)idealSamplingRateFromRange:(NSRange)range; | - (float)idealSamplingRateFromRange:(NSRange)range; | ||||||
| - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize; | - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize; | ||||||
|  |  | ||||||
| - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; | - (void)setView:(nullable CSOpenGLView *)view aspectRatio:(float)aspectRatio; | ||||||
| - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; | - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; | ||||||
|  |  | ||||||
| - (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed; | - (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed; | ||||||
| - (void)clearAllKeys; | - (void)clearAllKeys; | ||||||
|  |  | ||||||
| @property (nonatomic, strong) CSAudioQueue *audioQueue; | @property (nonatomic, strong, nullable) CSAudioQueue *audioQueue; | ||||||
| @property (nonatomic, readonly) CSOpenGLView *view; | @property (nonatomic, readonly, nonnull) CSOpenGLView *view; | ||||||
| @property (nonatomic, weak) id<CSMachineDelegate> delegate; | @property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate; | ||||||
|  |  | ||||||
| @property (nonatomic, readonly) NSString *userDefaultsPrefix; | @property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix; | ||||||
|  |  | ||||||
| - (void)paste:(NSString *)string; | - (void)paste:(nonnull NSString *)string; | ||||||
|  | @property (nonatomic, readonly, nonnull) NSBitmapImageRep *imageRepresentation; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | ||||||
| @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | ||||||
|  |  | ||||||
|  | @property (nonatomic, readonly) BOOL canInsertMedia; | ||||||
|  |  | ||||||
| - (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal; | - (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal; | ||||||
|  |  | ||||||
|  | // Input control. | ||||||
|  | @property (nonatomic, readonly) BOOL hasKeyboard; | ||||||
|  | @property (nonatomic, readonly) BOOL hasJoystick; | ||||||
|  | @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | ||||||
|  | @property (nonatomic, nullable) CSJoystickManager *joystickManager; | ||||||
|  |  | ||||||
|  | // LED list. | ||||||
|  | @property (nonatomic, readonly, nonnull) NSArray<NSString *> *leds; | ||||||
|  |  | ||||||
| // Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type. | // Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type. | ||||||
| @property (nonatomic, readonly) CSAtari2600 *atari2600; | @property (nonatomic, readonly, nullable) CSAtari2600 *atari2600; | ||||||
| @property (nonatomic, readonly) CSZX8081 *zx8081; | @property (nonatomic, readonly, nullable) CSZX8081 *zx8081; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -11,13 +11,14 @@ | |||||||
|  |  | ||||||
| #include "CSROMFetcher.hpp" | #include "CSROMFetcher.hpp" | ||||||
|  |  | ||||||
| #include "ConfigurationTarget.hpp" | #include "MediaTarget.hpp" | ||||||
| #include "JoystickMachine.hpp" | #include "JoystickMachine.hpp" | ||||||
| #include "KeyboardMachine.hpp" | #include "KeyboardMachine.hpp" | ||||||
| #include "KeyCodes.h" | #include "KeyCodes.h" | ||||||
| #include "MachineForTarget.hpp" | #include "MachineForTarget.hpp" | ||||||
| #include "StandardOptions.hpp" | #include "StandardOptions.hpp" | ||||||
| #include "Typer.hpp" | #include "Typer.hpp" | ||||||
|  | #include "../../../../Activity/Observer.hpp" | ||||||
|  |  | ||||||
| #import "CSStaticAnalyser+TargetVector.h" | #import "CSStaticAnalyser+TargetVector.h" | ||||||
| #import "NSBundle+DataResource.h" | #import "NSBundle+DataResource.h" | ||||||
| @@ -28,6 +29,7 @@ | |||||||
| @interface CSMachine() <CSFastLoading> | @interface CSMachine() <CSFastLoading> | ||||||
| - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; | - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; | ||||||
| - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; | - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; | ||||||
|  | - (void)addLED:(NSString *)led; | ||||||
| @end | @end | ||||||
|  |  | ||||||
| struct LockProtectedDelegate { | struct LockProtectedDelegate { | ||||||
| @@ -50,14 +52,34 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct ActivityObserver: public Activity::Observer { | ||||||
|  | 	void register_led(const std::string &name) override { | ||||||
|  | 		[machine addLED:[NSString stringWithUTF8String:name.c_str()]]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void set_led_status(const std::string &name, bool lit) override { | ||||||
|  | 		[machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void announce_drive_event(const std::string &name, DriveEvent event) override { | ||||||
|  | 		[machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	__unsafe_unretained CSMachine *machine; | ||||||
|  | }; | ||||||
|  |  | ||||||
| @implementation CSMachine { | @implementation CSMachine { | ||||||
| 	SpeakerDelegate _speakerDelegate; | 	SpeakerDelegate _speakerDelegate; | ||||||
|  | 	ActivityObserver _activityObserver; | ||||||
| 	NSLock *_delegateMachineAccessLock; | 	NSLock *_delegateMachineAccessLock; | ||||||
|  |  | ||||||
| 	CSStaticAnalyser *_analyser; | 	CSStaticAnalyser *_analyser; | ||||||
| 	std::unique_ptr<Machine::DynamicMachine> _machine; | 	std::unique_ptr<Machine::DynamicMachine> _machine; | ||||||
|  | 	JoystickMachine::Machine *_joystickMachine; | ||||||
|  |  | ||||||
|  | 	CSJoystickManager *_joystickManager; | ||||||
| 	std::bitset<65536> _depressedKeys; | 	std::bitset<65536> _depressedKeys; | ||||||
|  | 	NSMutableArray<NSString *> *_leds; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result { | - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result { | ||||||
| @@ -69,10 +91,21 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
| 		_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error)); | 		_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error)); | ||||||
| 		if(!_machine) return nil; | 		if(!_machine) return nil; | ||||||
|  |  | ||||||
|  | 		_inputMode = _machine->keyboard_machine() ? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick; | ||||||
|  |  | ||||||
|  | 		_leds = [[NSMutableArray alloc] init]; | ||||||
|  | 		Activity::Source *const activity_source = _machine->activity_source(); | ||||||
|  | 		if(activity_source) { | ||||||
|  | 			_activityObserver.machine = self; | ||||||
|  | 			activity_source->set_activity_observer(&_activityObserver); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		_delegateMachineAccessLock = [[NSLock alloc] init]; | 		_delegateMachineAccessLock = [[NSLock alloc] init]; | ||||||
|  |  | ||||||
| 		_speakerDelegate.machine = self; | 		_speakerDelegate.machine = self; | ||||||
| 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; | 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; | ||||||
|  |  | ||||||
|  | 		_joystickMachine = _machine->joystick_machine(); | ||||||
| 	} | 	} | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
| @@ -133,6 +166,54 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
|  |  | ||||||
| - (void)runForInterval:(NSTimeInterval)interval { | - (void)runForInterval:(NSTimeInterval)interval { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
|  | 		if(_joystickMachine && _joystickManager) { | ||||||
|  | 			[_joystickManager update]; | ||||||
|  |  | ||||||
|  | 			// TODO: configurable mapping from physical joypad inputs to machine inputs. | ||||||
|  | 			// Until then, apply a default mapping. | ||||||
|  |  | ||||||
|  | 			size_t c = 0; | ||||||
|  | 			std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks(); | ||||||
|  | 			for(CSJoystick *joystick in _joystickManager.joysticks) { | ||||||
|  | 				size_t target = c % machine_joysticks.size(); | ||||||
|  | 				++++c; | ||||||
|  |  | ||||||
|  | 				// Post the first two analogue axes presented by the controller as horizontal and vertical inputs, | ||||||
|  | 				// unless the user seems to be using a hat. | ||||||
|  | 				// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0] | ||||||
|  | 				if(!joystick.hats.count || !joystick.hats[0].direction) { | ||||||
|  | 					if(joystick.axes.count > 0) { | ||||||
|  | 						const float x_axis = joystick.axes[0].position; | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis); | ||||||
|  | 					} | ||||||
|  | 					if(joystick.axes.count > 1) { | ||||||
|  | 						const float y_axis = joystick.axes[1].position; | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					// Forward hats as directions; hats always override analogue inputs. | ||||||
|  | 					for(CSJoystickHat *hat in joystick.hats) { | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp)); | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown)); | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft)); | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight)); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Forward all fire buttons, mapping as a function of index. | ||||||
|  | 				if(machine_joysticks[target]->get_number_of_fire_buttons()) { | ||||||
|  | 					std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons()); | ||||||
|  | 					for(CSJoystickButton *button in joystick.buttons) { | ||||||
|  | 						if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true; | ||||||
|  | 					} | ||||||
|  | 					for(size_t index = 0; index < button_states.size(); ++index) { | ||||||
|  | 						machine_joysticks[target]->set_input( | ||||||
|  | 							Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index), | ||||||
|  | 							button_states[index]); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		_machine->crt_machine()->run_for(interval); | 		_machine->crt_machine()->run_for(interval); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -162,16 +243,65 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
| 		keyboardMachine->type_string([paste UTF8String]); | 		keyboardMachine->type_string([paste UTF8String]); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (NSBitmapImageRep *)imageRepresentation { | ||||||
|  | 	// Get the current viewport to establish framebuffer size. Then determine how wide the | ||||||
|  | 	// centre 4/3 of that would be. | ||||||
|  | 	GLint dimensions[4]; | ||||||
|  | 	glGetIntegerv(GL_VIEWPORT, dimensions); | ||||||
|  | 	GLint proportionalWidth = (dimensions[3] * 4) / 3; | ||||||
|  |  | ||||||
|  | 	// Grab the framebuffer contents. | ||||||
|  | 	std::vector<uint8_t> temporaryData(static_cast<size_t>(proportionalWidth * dimensions[3] * 3)); | ||||||
|  | 	glReadPixels((dimensions[2] - proportionalWidth) >> 1, 0, proportionalWidth, dimensions[3], GL_RGB, GL_UNSIGNED_BYTE, temporaryData.data()); | ||||||
|  |  | ||||||
|  | 	// Generate an NSBitmapImageRep and populate it with a vertical flip | ||||||
|  | 	// of the original data. | ||||||
|  | 	NSBitmapImageRep *const result = | ||||||
|  | 		[[NSBitmapImageRep alloc] | ||||||
|  | 			initWithBitmapDataPlanes:NULL | ||||||
|  | 			pixelsWide:proportionalWidth | ||||||
|  | 			pixelsHigh:dimensions[3] | ||||||
|  | 			bitsPerSample:8 | ||||||
|  | 			samplesPerPixel:3 | ||||||
|  | 			hasAlpha:NO | ||||||
|  | 			isPlanar:NO | ||||||
|  | 			colorSpaceName:NSDeviceRGBColorSpace | ||||||
|  | 			bytesPerRow:3 * proportionalWidth | ||||||
|  | 			bitsPerPixel:0]; | ||||||
|  |  | ||||||
|  | 	const size_t line_size = static_cast<size_t>(proportionalWidth * 3); | ||||||
|  | 	for(GLint y = 0; y < dimensions[3]; ++y) { | ||||||
|  | 		memcpy( | ||||||
|  | 			&result.bitmapData[static_cast<size_t>(y) * line_size], | ||||||
|  | 			&temporaryData[static_cast<size_t>(dimensions[3] - y - 1) * line_size], | ||||||
|  | 			line_size); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
| - (void)applyMedia:(const Analyser::Static::Media &)media { | - (void)applyMedia:(const Analyser::Static::Media &)media { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		ConfigurationTarget::Machine *const configurationTarget = _machine->configuration_target(); | 		MediaTarget::Machine *const mediaTarget = _machine->media_target(); | ||||||
| 		if(configurationTarget) configurationTarget->insert_media(media); | 		if(mediaTarget) mediaTarget->insert_media(media); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setJoystickManager:(CSJoystickManager *)joystickManager { | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		_joystickManager = joystickManager; | ||||||
|  | 		if(_joystickMachine) { | ||||||
|  | 			std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks(); | ||||||
|  | 			for(const auto &joystick: machine_joysticks) { | ||||||
|  | 				joystick->reset_all_inputs(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed { | - (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed { | ||||||
| 	auto keyboard_machine = _machine->keyboard_machine(); | 	auto keyboard_machine = _machine->keyboard_machine(); | ||||||
| 	if(keyboard_machine) { | 	if(self.inputMode == CSMachineKeyboardInputModeKeyboard && keyboard_machine) { | ||||||
| 		// Don't pass anything on if this is not new information. | 		// Don't pass anything on if this is not new information. | ||||||
| 		if(_depressedKeys[key] == !!isPressed) return; | 		if(_depressedKeys[key] == !!isPressed) return; | ||||||
| 		_depressedKeys[key] = !!isPressed; | 		_depressedKeys[key] = !!isPressed; | ||||||
| @@ -248,23 +378,27 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	auto joystick_machine = _machine->joystick_machine(); | 	auto joystick_machine = _machine->joystick_machine(); | ||||||
| 	if(joystick_machine) { | 	if(self.inputMode == CSMachineKeyboardInputModeJoystick && joystick_machine) { | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks = joystick_machine->get_joysticks(); | 			std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks = joystick_machine->get_joysticks(); | ||||||
| 			if(!joysticks.empty()) { | 			if(!joysticks.empty()) { | ||||||
|  | 				// Convert to a C++ bool so that the following calls are resolved correctly even if overloaded. | ||||||
|  | 				bool is_pressed = !!isPressed; | ||||||
| 				switch(key) { | 				switch(key) { | ||||||
| 					case VK_LeftArrow:	joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Left, isPressed);	break; | 					case VK_LeftArrow:	joysticks[0]->set_input(Inputs::Joystick::Input::Left, is_pressed);		break; | ||||||
| 					case VK_RightArrow:	joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed);	break; | 					case VK_RightArrow:	joysticks[0]->set_input(Inputs::Joystick::Input::Right, is_pressed);	break; | ||||||
| 					case VK_UpArrow:	joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed);		break; | 					case VK_UpArrow:	joysticks[0]->set_input(Inputs::Joystick::Input::Up, is_pressed);		break; | ||||||
| 					case VK_DownArrow:	joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed);	break; | 					case VK_DownArrow:	joysticks[0]->set_input(Inputs::Joystick::Input::Down, is_pressed);		break; | ||||||
| 					case VK_Space:		joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed);	break; | 					case VK_Space:		joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed);		break; | ||||||
| 					case VK_ANSI_A:		joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 0), isPressed);	break; | 					case VK_ANSI_A:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 0), is_pressed);	break; | ||||||
| 					case VK_ANSI_S:		joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 1), isPressed);	break; | 					case VK_ANSI_S:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 1), is_pressed);	break; | ||||||
|  | 					case VK_ANSI_D:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed);	break; | ||||||
|  | 					case VK_ANSI_F:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed);	break; | ||||||
| 					default: | 					default: | ||||||
| 						if(characters) { | 						if(characters.length) { | ||||||
| 							joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput([characters characterAtIndex:0]), isPressed); | 							joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed); | ||||||
| 						} else { | 						} else { | ||||||
| 							joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); | 							joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); | ||||||
| 						} | 						} | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| @@ -382,6 +516,10 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
| 	return [[NSString stringWithUTF8String:name.c_str()] lowercaseString]; | 	return [[NSString stringWithUTF8String:name.c_str()] lowercaseString]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (BOOL)canInsertMedia { | ||||||
|  | 	return !!_machine->media_target(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #pragma mark - Special machines | #pragma mark - Special machines | ||||||
|  |  | ||||||
| - (CSAtari2600 *)atari2600 { | - (CSAtari2600 *)atari2600 { | ||||||
| @@ -392,4 +530,24 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP | |||||||
| 	return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self]; | 	return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #pragma mark - Input device queries | ||||||
|  |  | ||||||
|  | - (BOOL)hasJoystick { | ||||||
|  | 	return !!_machine->joystick_machine(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (BOOL)hasKeyboard { | ||||||
|  | 	return !!_machine->keyboard_machine(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #pragma mark - Activity observation | ||||||
|  |  | ||||||
|  | - (void)addLED:(NSString *)led { | ||||||
|  | 	[_leds addObject:led]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSArray<NSString *> *)leds { | ||||||
|  | 	return _leds; | ||||||
|  | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -12,7 +12,9 @@ | |||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { | typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { | ||||||
| 	CSMachineAppleIIModelAppleII, | 	CSMachineAppleIIModelAppleII, | ||||||
| 	CSMachineAppleIIModelAppleIIPlus | 	CSMachineAppleIIModelAppleIIPlus, | ||||||
|  | 	CSMachineAppleIIModelAppleIIe, | ||||||
|  | 	CSMachineAppleIIModelAppleEnhancedIIe | ||||||
| }; | }; | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { | typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { | ||||||
|   | |||||||
| @@ -168,7 +168,12 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
| 		using Target = Analyser::Static::AppleII::Target; | 		using Target = Analyser::Static::AppleII::Target; | ||||||
| 		std::unique_ptr<Target> target(new Target); | 		std::unique_ptr<Target> target(new Target); | ||||||
| 		target->machine = Analyser::Machine::AppleII; | 		target->machine = Analyser::Machine::AppleII; | ||||||
| 		target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus; | 		switch(model) { | ||||||
|  | 			default: 									target->model = Target::Model::II; 				break; | ||||||
|  | 			case CSMachineAppleIIModelAppleIIPlus:		target->model = Target::Model::IIplus;			break; | ||||||
|  | 			case CSMachineAppleIIModelAppleIIe:			target->model = Target::Model::IIe;				break; | ||||||
|  | 			case CSMachineAppleIIModelAppleEnhancedIIe:	target->model = Target::Model::EnhancedIIe;		break; | ||||||
|  | 		} | ||||||
| 		switch(diskController) { | 		switch(diskController) { | ||||||
| 			default: | 			default: | ||||||
| 			case CSMachineAppleIIDiskControllerNone:			target->disk_controller = Target::DiskController::None;				break; | 			case CSMachineAppleIIDiskControllerNone:			target->disk_controller = Target::DiskController::None;				break; | ||||||
| @@ -183,7 +188,8 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
|  |  | ||||||
| - (NSString *)optionsPanelNibName { | - (NSString *)optionsPanelNibName { | ||||||
| 	switch(_targets.front()->machine) { | 	switch(_targets.front()->machine) { | ||||||
| 		case Analyser::Machine::AmstradCPC:	return nil; | 		case Analyser::Machine::AmstradCPC:	return @"CompositeOptions"; | ||||||
|  | //		case Analyser::Machine::AppleII:	return @"AppleIIOptions"; | ||||||
| 		case Analyser::Machine::Atari2600:	return @"Atari2600Options"; | 		case Analyser::Machine::Atari2600:	return @"Atari2600Options"; | ||||||
| 		case Analyser::Machine::Electron:	return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::Electron:	return @"QuickLoadCompositeOptions"; | ||||||
| 		case Analyser::Machine::MSX:		return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::MSX:		return @"QuickLoadCompositeOptions"; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -64,11 +64,11 @@ Gw | |||||||
|                         <tabViewItems> |                         <tabViewItems> | ||||||
|                             <tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa"> |                             <tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa"> | ||||||
|                                 <view key="view" id="dHz-Yv-GNq"> |                                 <view key="view" id="dHz-Yv-GNq"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="93"/> |                                     <rect key="frame" x="10" y="33" width="554" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> | ||||||
|                                             <rect key="frame" x="15" y="71" width="46" height="17"/> |                                             <rect key="frame" x="15" y="72" width="46" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -76,7 +76,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6"> | ||||||
|                                             <rect key="frame" x="15" y="40" width="96" height="17"/> |                                             <rect key="frame" x="15" y="41" width="96" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -84,7 +84,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij"> | ||||||
|                                             <rect key="frame" x="65" y="66" width="91" height="26"/> |                                             <rect key="frame" x="65" y="67" width="115" height="26"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F"> |                                             <popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -92,12 +92,14 @@ Gw | |||||||
|                                                     <items> |                                                     <items> | ||||||
|                                                         <menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/> |                                                         <menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/> | ||||||
|                                                         <menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/> |                                                         <menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/> | ||||||
|  |                                                         <menuItem title="Apple IIe" tag="2" id="AMt-WU-a0H"/> | ||||||
|  |                                                         <menuItem title="Enhanced IIe" tag="3" id="kUz-FG-lqW"/> | ||||||
|                                                     </items> |                                                     </items> | ||||||
|                                                 </menu> |                                                 </menu> | ||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi"> | ||||||
|                                             <rect key="frame" x="115" y="35" width="132" height="26"/> |                                             <rect key="frame" x="115" y="36" width="132" height="26"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm"> |                                             <popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -128,11 +130,11 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM"> |                             <tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM"> | ||||||
|                                 <view key="view" id="5zS-Nj-Ynx"> |                                 <view key="view" id="5zS-Nj-Ynx"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="99"/> |                                     <rect key="frame" x="10" y="33" width="554" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh"> | ||||||
|                                             <rect key="frame" x="65" y="72" width="94" height="26"/> |                                             <rect key="frame" x="65" y="67" width="94" height="26"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea"> |                                             <popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -146,7 +148,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q"> | ||||||
|                                             <rect key="frame" x="15" y="77" width="46" height="17"/> |                                             <rect key="frame" x="15" y="72" width="46" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -130,6 +130,8 @@ class MachinePicker: NSObject { | |||||||
| 				var model: CSMachineAppleIIModel = .appleII | 				var model: CSMachineAppleIIModel = .appleII | ||||||
| 				switch appleIIModelButton!.selectedTag() { | 				switch appleIIModelButton!.selectedTag() { | ||||||
| 					case 1:		model = .appleIIPlus | 					case 1:		model = .appleIIPlus | ||||||
|  | 					case 2:		model = .appleIIe | ||||||
|  | 					case 3:		model = .appleEnhancedIIe | ||||||
| 					case 0:		fallthrough | 					case 0:		fallthrough | ||||||
| 					default:	model = .appleII | 					default:	model = .appleII | ||||||
| 				} | 				} | ||||||
| @@ -15,7 +15,7 @@ class MOS6502InterruptTests: XCTestCase { | |||||||
| 		super.setUp() | 		super.setUp() | ||||||
|  |  | ||||||
| 		// create a machine full of NOPs | 		// create a machine full of NOPs | ||||||
| 		machine = CSTestMachine6502() | 		machine = CSTestMachine6502(is65C02: false) | ||||||
| 		for c in 0...65535 { | 		for c in 0...65535 { | ||||||
| 			machine.setValue(0xea, forAddress: UInt16(c)) | 			machine.setValue(0xea, forAddress: UInt16(c)) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import XCTest | |||||||
| class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { | class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { | ||||||
|  |  | ||||||
| 	private var endTime: UInt32 = 0 | 	private var endTime: UInt32 = 0 | ||||||
| 	private let machine = CSTestMachine6502() | 	private let machine = CSTestMachine6502(is65C02: false) | ||||||
|  |  | ||||||
| 	func testImplied() { | 	func testImplied() { | ||||||
| 		let code: [UInt8] = [ | 		let code: [UInt8] = [ | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ class AllSuiteATests: XCTestCase { | |||||||
| 	func testAllSuiteA() { | 	func testAllSuiteA() { | ||||||
| 		if let filename = Bundle(for: type(of: self)).path(forResource: "AllSuiteA", ofType: "bin") { | 		if let filename = Bundle(for: type(of: self)).path(forResource: "AllSuiteA", ofType: "bin") { | ||||||
| 			if let allSuiteA = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | 			if let allSuiteA = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | ||||||
| 				let machine = CSTestMachine6502() | 				let machine = CSTestMachine6502(is65C02: false) | ||||||
|  |  | ||||||
| 				machine.setData(allSuiteA, atAddress: 0x4000) | 				machine.setData(allSuiteA, atAddress: 0x4000) | ||||||
| 				machine.setValue(CSTestMachine6502JamOpcode, forAddress:0x45c0);  // end | 				machine.setValue(CSTestMachine6502JamOpcode, forAddress:0x45c0);  // end | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user