mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			125 Commits
		
	
	
		
			2018-03-03
			...
			2018-04-07
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 506b4da6c3 | ||
|  | 10f637d2cf | ||
|  | 0bab7c88f0 | ||
|  | 78c612ca17 | ||
|  | e1c4035812 | ||
|  | eb6d6c8033 | ||
|  | 7bf88565ce | ||
|  | ee10155296 | ||
|  | cc49140f6f | ||
|  | 3e846f89a1 | ||
|  | 5782cab2a0 | ||
|  | 8c511e2b76 | ||
|  | ec72fb3baf | ||
|  | bab1440f5c | ||
|  | 60c1da6a66 | ||
|  | a849b3f2e4 | ||
|  | dbe3c5c3f8 | ||
|  | 60cf6b3cfd | ||
|  | 5044aac337 | ||
|  | 36e0cb29c0 | ||
|  | c0b4dd65da | ||
|  | d061ea232b | ||
|  | 49feca4ddf | ||
|  | 46b1c57bf4 | ||
|  | eaf1482182 | ||
|  | d3418550eb | ||
|  | 3ffa9e2751 | ||
|  | c697dd78f0 | ||
|  | 7dac791290 | ||
|  | cde2faeda6 | ||
|  | 69f520428d | ||
|  | 80c84ddd75 | ||
|  | fca8a58b36 | ||
|  | 33084899d0 | ||
|  | 7b381a8b6b | ||
|  | 9c75689a8d | ||
|  | 0ee40e8556 | ||
|  | 8b45377b89 | ||
|  | f6fb368d88 | ||
|  | 183a5379de | ||
|  | 912791d3d4 | ||
|  | 163a61dd63 | ||
|  | 207d462dbf | ||
|  | 33281b9d89 | ||
|  | 389979923e | ||
|  | 067174965e | ||
|  | 286259c83b | ||
|  | e1aa3e5a7f | ||
|  | 78e1c2851a | ||
|  | 0869213c55 | ||
|  | f3fe16215a | ||
|  | ec353ce663 | ||
|  | b7ff5ef9dd | ||
|  | 3b26e0a7c5 | ||
|  | 6d464557a0 | ||
|  | a776bec46a | ||
|  | a2da51c30b | ||
|  | 8067bf548a | ||
|  | 62b0645ed0 | ||
|  | 39a94874ae | ||
|  | e15d6717a1 | ||
|  | 37ef46e7bb | ||
|  | 70c09b3031 | ||
|  | 9378fbb0df | ||
|  | 2118b9c0cd | ||
|  | d0c53de250 | ||
|  | d98507eab0 | ||
|  | 760c75103e | ||
|  | 4407fd2f1f | ||
|  | 7fcd243be0 | ||
|  | 3165e9d82e | ||
|  | 6656a08c60 | ||
|  | 76661c0b51 | ||
|  | 3bb496f9ae | ||
|  | 45be1c19df | ||
|  | a301964bd0 | ||
|  | eea6858121 | ||
|  | 2a320fdf56 | ||
|  | 4695296ef2 | ||
|  | 0fdbbeca1d | ||
|  | 34cc39ad65 | ||
|  | 3d0c832a21 | ||
|  | 1acdab9448 | ||
|  | 93e85c5c4a | ||
|  | ab98189d25 | ||
|  | cd0fb7624b | ||
|  | bae38497bb | ||
|  | 29921bfa8d | ||
|  | 2712702461 | ||
|  | a3fa9440d1 | ||
|  | 6419b0e619 | ||
|  | 58e5b6e3f1 | ||
|  | 682c3d8079 | ||
|  | da3d65c18f | ||
|  | ece3a05504 | ||
|  | 927697b0f0 | ||
|  | 74dfc80b0f | ||
|  | a7f229bc4b | ||
|  | 89bec2919f | ||
|  | 78eaecb29e | ||
|  | d410aea856 | ||
|  | 6b1eef572b | ||
|  | 719f5d79c2 | ||
|  | 48737a32a7 | ||
|  | 53f05efb2d | ||
|  | 0e73ba4b3e | ||
|  | f0f9d5a6af | ||
|  | 03501df9e5 | ||
|  | dd6f85d4db | ||
|  | 1804ea6849 | ||
|  | c8657e08f4 | ||
|  | a942e1319b | ||
|  | 9e0a56b4f0 | ||
|  | 9abc020818 | ||
|  | 2dade8d353 | ||
|  | 1100dc6993 | ||
|  | f212b18511 | ||
|  | a6ca69550f | ||
|  | 2452641844 | ||
|  | c82af4b814 | ||
|  | fdef914137 | ||
|  | dfcc502a88 | ||
|  | 1c6faaae88 | ||
|  | 35c8a0dd8c | ||
|  | 38feedaf6a | 
							
								
								
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,5 +1,13 @@ | |||||||
| language: objective-c | # language: objective-c | ||||||
| osx_image: xcode8.2 | # osx_image: xcode8.2 | ||||||
| xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | # xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | ||||||
| xcode_scheme: Clock Signal | # xcode_scheme: Clock Signal | ||||||
| xcode_sdk: macosx10.12 | # xcode_sdk: macosx10.12 | ||||||
|  |  | ||||||
|  | language: cpp | ||||||
|  | before_install: | ||||||
|  | 	- sudo apt-get install libsdl2-dev | ||||||
|  | script: cd OSBindings/SDL && scons | ||||||
|  | compiler: | ||||||
|  | 	- clang | ||||||
|  | 	- gcc | ||||||
|   | |||||||
| @@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { | |||||||
| 	return speaker_; | 	return speaker_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiCRTMachine::run_for(const Cycles cycles) { | void MultiCRTMachine::run_for(Time::Seconds duration) { | ||||||
| 	perform_parallel([=](::CRTMachine::Machine *machine) { | 	perform_parallel([=](::CRTMachine::Machine *machine) { | ||||||
| 		if(machine->get_confidence() >= 0.01f) machine->run_for(cycles); | 		if(machine->get_confidence() >= 0.01f) machine->run_for(duration); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	if(delegate_) delegate_->multi_crt_did_run_machines(); | 	if(delegate_) delegate_->multi_crt_did_run_machines(); | ||||||
| } | } | ||||||
|  |  | ||||||
| double MultiCRTMachine::get_clock_rate() { |  | ||||||
| 	// TODO: something smarter than this? Not all clock rates will necessarily be the same. |  | ||||||
| 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); |  | ||||||
| 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); |  | ||||||
| 	return crt_machine ? crt_machine->get_clock_rate() : 0.0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MultiCRTMachine::get_clock_is_unlimited() { |  | ||||||
| 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); |  | ||||||
| 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); |  | ||||||
| 	return crt_machine ? crt_machine->get_clock_is_unlimited() : false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiCRTMachine::did_change_machine_order() { | void MultiCRTMachine::did_change_machine_order() { | ||||||
| 	if(speaker_) { | 	if(speaker_) { | ||||||
| 		speaker_->set_new_front_machine(machines_.front().get()); | 		speaker_->set_new_front_machine(machines_.front().get()); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) { |  | ||||||
| 	// TODO:  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) { |  | ||||||
| 	// TODO: consider passing along. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) { |  | ||||||
| 	// TODO: consider passing along. |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ namespace Dynamic { | |||||||
| 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | ||||||
| 	if the order of machines changes. | 	if the order of machines changes. | ||||||
| */ | */ | ||||||
| class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate { | class MultiCRTMachine: public CRTMachine::Machine { | ||||||
| 	public: | 	public: | ||||||
| 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); | 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); | ||||||
|  |  | ||||||
| @@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D | |||||||
| 		void close_output() override; | 		void close_output() override; | ||||||
| 		Outputs::CRT::CRT *get_crt() override; | 		Outputs::CRT::CRT *get_crt() override; | ||||||
| 		Outputs::Speaker::Speaker *get_speaker() override; | 		Outputs::Speaker::Speaker *get_speaker() override; | ||||||
| 		void run_for(const Cycles cycles) override; | 		void run_for(Time::Seconds duration) override; | ||||||
| 		double get_clock_rate() override; |  | ||||||
| 		bool get_clock_is_unlimited() override; |  | ||||||
| 		void set_delegate(::CRTMachine::Machine::Delegate *delegate) override; |  | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		// CRTMachine::Machine::Delegate | 		void run_for(const Cycles cycles) override {} | ||||||
| 		void machine_did_change_clock_rate(Machine *machine) override; |  | ||||||
| 		void machine_did_change_clock_is_unlimited(Machine *machine) override; |  | ||||||
|  |  | ||||||
| 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | ||||||
| 		std::mutex &machines_mutex_; | 		std::mutex &machines_mutex_; | ||||||
| 		std::vector<Concurrency::AsyncTaskQueue> queues_; | 		std::vector<Concurrency::AsyncTaskQueue> queues_; | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target &target) { | void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) { | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { | bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ struct MultiConfigurationTarget: public ConfigurationTarget::Machine { | |||||||
| 		MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | 		MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
| 		// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. | 		// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override; | 		void configure_as_target(const Analyser::Static::Target *target) override; | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override; | 		bool insert_media(const Analyser::Static::Media &media) override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -8,12 +8,68 @@ | |||||||
|  |  | ||||||
| #include "MultiJoystickMachine.hpp" | #include "MultiJoystickMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
| using namespace Analyser::Dynamic; | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | class MultiJoystick: public Inputs::Joystick { | ||||||
|  | 	public: | ||||||
|  | 		MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) { | ||||||
|  | 			for(const auto &machine: machines) { | ||||||
|  | 				const auto &joysticks = machine->get_joysticks(); | ||||||
|  | 				if(joysticks.size() >= index) { | ||||||
|  | 					joysticks_.push_back(joysticks[index].get()); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<DigitalInput> get_inputs() override { | ||||||
|  | 			std::vector<DigitalInput> inputs; | ||||||
|  |  | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				std::vector<DigitalInput> joystick_inputs = joystick->get_inputs(); | ||||||
|  | 				for(const auto &input: joystick_inputs) { | ||||||
|  | 					if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { | ||||||
|  | 						inputs.push_back(input); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return inputs; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->set_digital_input(digital_input, is_active); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		void reset_all_inputs() override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->reset_all_inputs(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<Inputs::Joystick *> joysticks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	std::size_t total_joysticks = 0; | ||||||
|  | 	std::vector<JoystickMachine::Machine *> joystick_machines; | ||||||
| 	for(const auto &machine: machines) { | 	for(const auto &machine: machines) { | ||||||
| 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | ||||||
| 		if(joystick_machine) machines_.push_back(joystick_machine); | 		if(joystick_machine) { | ||||||
|  | 			joystick_machines.push_back(joystick_machine); | ||||||
|  | 			total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size()); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | 	for(std::size_t index = 0; index < total_joysticks; ++index) { | ||||||
|  | 		joysticks_.emplace_back(new MultiJoystick(joystick_machines, index)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,7 +31,6 @@ class MultiJoystickMachine: public JoystickMachine::Machine { | |||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::vector<JoystickMachine::Machine *> machines_; |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -48,13 +48,29 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | ||||||
|  | 	if(!delegate_) return; | ||||||
|  | 	{ | ||||||
| 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
| 	if(delegate_ && speaker == front_speaker_) { | 		if(speaker != front_speaker_) return; | ||||||
|  | 	} | ||||||
| 	delegate_->speaker_did_complete_samples(this, buffer); | 	delegate_->speaker_did_complete_samples(this, buffer); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) { | ||||||
|  | 	if(!delegate_) return; | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		if(speaker != front_speaker_) return; | ||||||
|  | 	} | ||||||
|  | 	delegate_->speaker_did_change_input_clock(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { | void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { | ||||||
|  | 	{ | ||||||
| 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
| 		front_speaker_ = machine->crt_machine()->get_speaker(); | 		front_speaker_ = machine->crt_machine()->get_speaker(); | ||||||
| 	} | 	} | ||||||
|  | 	if(delegate_) { | ||||||
|  | 		delegate_->speaker_did_change_input_clock(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker: | |||||||
| 		void set_new_front_machine(::Machine::DynamicMachine *machine); | 		void set_new_front_machine(::Machine::DynamicMachine *machine); | ||||||
|  |  | ||||||
| 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | ||||||
| 		float get_ideal_clock_rate_in_range(float minimum, float maximum); | 		float get_ideal_clock_rate_in_range(float minimum, float maximum) override; | ||||||
| 		void set_output_rate(float cycles_per_second, int buffer_size); | 		void set_output_rate(float cycles_per_second, int buffer_size) override; | ||||||
| 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate); | 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer); | 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override; | ||||||
|  | 		void speaker_did_change_input_clock(Speaker *speaker) override; | ||||||
| 		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); | 		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); | ||||||
|  |  | ||||||
| 		std::vector<Outputs::Speaker::Speaker *> speakers_; | 		std::vector<Outputs::Speaker::Speaker *> speakers_; | ||||||
|   | |||||||
| @@ -62,8 +62,15 @@ Configurable::Device *MultiMachine::configurable_device() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) { | ||||||
|  | 	return | ||||||
|  | 		(machines.front()->crt_machine()->get_confidence() > 0.9f) || | ||||||
|  | 		(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence()); | ||||||
|  | } | ||||||
|  |  | ||||||
| void MultiMachine::multi_crt_did_run_machines() { | void MultiMachine::multi_crt_did_run_machines() { | ||||||
| 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | #ifdef DEBUG | ||||||
| 	for(const auto &machine: machines_) { | 	for(const auto &machine: machines_) { | ||||||
| 		CRTMachine::Machine *crt = machine->crt_machine(); | 		CRTMachine::Machine *crt = machine->crt_machine(); | ||||||
| 		printf("%0.2f ", crt->get_confidence()); | 		printf("%0.2f ", crt->get_confidence()); | ||||||
| @@ -71,6 +78,7 @@ void MultiMachine::multi_crt_did_run_machines() { | |||||||
| 		printf("; "); | 		printf("; "); | ||||||
| 	} | 	} | ||||||
| 	printf("\n"); | 	printf("\n"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
| 	DynamicMachine *front = machines_.front().get(); | 	DynamicMachine *front = machines_.front().get(); | ||||||
| 	std::stable_sort(machines_.begin(), machines_.end(), | 	std::stable_sort(machines_.begin(), machines_.end(), | ||||||
| @@ -84,10 +92,7 @@ void MultiMachine::multi_crt_did_run_machines() { | |||||||
| 		crt_machine_.did_change_machine_order(); | 		crt_machine_.did_change_machine_order(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if( | 	if(would_collapse(machines_)) { | ||||||
| 		(machines_.front()->crt_machine()->get_confidence() > 0.9f) || |  | ||||||
| 		(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence()) |  | ||||||
| 	) { |  | ||||||
| 		pick_first(); | 		pick_first(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,6 +40,14 @@ namespace Dynamic { | |||||||
| */ | */ | ||||||
| class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | ||||||
| 	public: | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in | ||||||
|  | 			requesting this class as a proxy. | ||||||
|  |  | ||||||
|  | 			@returns @c true if the multimachine would discard all but the first machine in this list; | ||||||
|  | 				@c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines); | ||||||
| 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | ||||||
|  |  | ||||||
| 		ConfigurationTarget::Machine *configuration_target() override; | 		ConfigurationTarget::Machine *configuration_target() override; | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
|  |  | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Acorn; | using namespace Analyser::Static::Acorn; | ||||||
|  |  | ||||||
| @@ -56,13 +57,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return acorn_cartridges; | 	return acorn_cartridges; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::Electron; | 	target->machine = Machine::Electron; | ||||||
| 	target->confidence = 1.0; // TODO: a proper estimation | 	target->confidence = 0.5; // TODO: a proper estimation | ||||||
| 	target->acorn.has_dfs = false; | 	target->has_dfs = false; | ||||||
| 	target->acorn.has_adfs = false; | 	target->has_adfs = false; | ||||||
| 	target->acorn.should_shift_restart = false; | 	target->should_shift_restart = false; | ||||||
|  |  | ||||||
| 	// strip out inappropriate cartridges | 	// strip out inappropriate cartridges | ||||||
| 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | ||||||
| @@ -109,17 +110,18 @@ void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::un | |||||||
| 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue || adfs_catalogue) { | 		if(dfs_catalogue || adfs_catalogue) { | ||||||
| 			target->media.disks = media.disks; | 			target->media.disks = media.disks; | ||||||
| 			target->acorn.has_dfs = !!dfs_catalogue; | 			target->has_dfs = !!dfs_catalogue; | ||||||
| 			target->acorn.has_adfs = !!adfs_catalogue; | 			target->has_adfs = !!adfs_catalogue; | ||||||
|  |  | ||||||
| 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | ||||||
| 			if(bootOption != Catalogue::BootOption::None) | 			if(bootOption != Catalogue::BootOption::None) | ||||||
| 				target->acorn.should_shift_restart = true; | 				target->should_shift_restart = true; | ||||||
| 			else | 			else | ||||||
| 				target->loading_command = "*CAT\n"; | 				target->loading_command = "*CAT\n"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) { | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Acorn_Target_h | ||||||
|  | #define Analyser_Static_Acorn_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Acorn { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool has_adfs = false; | ||||||
|  | 	bool has_dfs = false; | ||||||
|  | 	bool should_shift_restart = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Acorn_Target_h */ | ||||||
| @@ -11,6 +11,8 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstring> | #include <cstring> | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../../../Storage/Disk/Parsers/CPM.hpp" | #include "../../../Storage/Disk/Parsers/CPM.hpp" | ||||||
| #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  |  | ||||||
| @@ -58,7 +60,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { | |||||||
|  |  | ||||||
| static void InspectCatalogue( | static void InspectCatalogue( | ||||||
| 	const Storage::Disk::CPM::Catalogue &catalogue, | 	const Storage::Disk::CPM::Catalogue &catalogue, | ||||||
| 	const std::unique_ptr<Analyser::Static::Target> &target) { | 	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) { | ||||||
|  |  | ||||||
| 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | ||||||
| 	candidate_files.reserve(catalogue.files.size()); | 	candidate_files.reserve(catalogue.files.size()); | ||||||
| @@ -153,10 +155,10 @@ static void InspectCatalogue( | |||||||
| 	target->loading_command = "cat\n"; | 	target->loading_command = "cat\n"; | ||||||
| } | } | ||||||
|  |  | ||||||
| static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) { | static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) { | ||||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | ||||||
| 	if(boot_sector != nullptr && !boot_sector->samples.empty()) { | 	if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) { | ||||||
| 		// Check that the first 64 bytes of the sector aren't identical; if they are then probably | 		// Check that the first 64 bytes of the sector aren't identical; if they are then probably | ||||||
| 		// this disk was formatted and the filler byte never replaced. | 		// this disk was formatted and the filler byte never replaced. | ||||||
| 		bool matched = true; | 		bool matched = true; | ||||||
| @@ -177,24 +179,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::AmstradCPC; | 	target->machine = Machine::AmstradCPC; | ||||||
| 	target->confidence = 1.0; | 	target->confidence = 0.5; | ||||||
| 	target->media.disks = media.disks; |  | ||||||
|  | 	target->model = Target::Model::CPC6128; | ||||||
|  |  | ||||||
|  | 	if(!media.tapes.empty()) { | ||||||
|  | 		// TODO: which of these are actually potentially CPC tapes? | ||||||
| 		target->media.tapes = media.tapes; | 		target->media.tapes = media.tapes; | ||||||
| 	target->media.cartridges = media.cartridges; |  | ||||||
|  |  | ||||||
| 	target->amstradcpc.model = AmstradCPCModel::CPC6128; |  | ||||||
|  |  | ||||||
| 	if(!target->media.tapes.empty()) { |  | ||||||
| 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing | 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing | ||||||
| 		// enter and responding to the follow-on prompt to press a key, so just type for | 		// enter and responding to the follow-on prompt to press a key, so just type for | ||||||
| 		// a while. Yuck! | 		// a while. Yuck! | ||||||
| 		target->loading_command = "|tape\nrun\"\n1234567890"; | 		target->loading_command = "|tape\nrun\"\n1234567890"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!target->media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		Storage::Disk::CPM::ParameterBlock data_format; | 		Storage::Disk::CPM::ParameterBlock data_format; | ||||||
| 		data_format.sectors_per_track = 9; | 		data_format.sectors_per_track = 9; | ||||||
| 		data_format.tracks = 40; | 		data_format.tracks = 40; | ||||||
| @@ -203,11 +205,6 @@ void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<st | |||||||
| 		data_format.catalogue_allocation_bitmap = 0xc000; | 		data_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 		data_format.reserved_tracks = 0; | 		data_format.reserved_tracks = 0; | ||||||
|  |  | ||||||
| 		std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format); |  | ||||||
| 		if(data_catalogue) { |  | ||||||
| 			InspectCatalogue(*data_catalogue, target); |  | ||||||
| 		} else { |  | ||||||
| 			if(!CheckBootSector(target->media.disks.front(), target)) { |  | ||||||
| 		Storage::Disk::CPM::ParameterBlock system_format; | 		Storage::Disk::CPM::ParameterBlock system_format; | ||||||
| 		system_format.sectors_per_track = 9; | 		system_format.sectors_per_track = 9; | ||||||
| 		system_format.tracks = 40; | 		system_format.tracks = 40; | ||||||
| @@ -216,13 +213,32 @@ void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<st | |||||||
| 		system_format.catalogue_allocation_bitmap = 0xc000; | 		system_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 		system_format.reserved_tracks = 2; | 		system_format.reserved_tracks = 2; | ||||||
|  |  | ||||||
| 				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format); | 		for(const auto &disk: media.disks) { | ||||||
|  | 			// Check for an ordinary catalogue. | ||||||
|  | 			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format); | ||||||
|  | 			if(data_catalogue) { | ||||||
|  | 				InspectCatalogue(*data_catalogue, target); | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Failing that check for a boot sector. | ||||||
|  | 			if(CheckBootSector(disk, target)) { | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Failing that check for a system catalogue. | ||||||
|  | 			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format); | ||||||
| 			if(system_catalogue) { | 			if(system_catalogue) { | ||||||
| 				InspectCatalogue(*system_catalogue, target); | 				InspectCatalogue(*system_catalogue, target); | ||||||
| 				} | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// If any media survived, add the target. | ||||||
|  | 	if(!target->media.empty()) | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_AmstradCPC_Target_h | ||||||
|  | #define Analyser_Static_AmstradCPC_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class Model { | ||||||
|  | 		CPC464, | ||||||
|  | 		CPC664, | ||||||
|  | 		CPC6128 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Model model = Model::CPC464; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_AmstradCPC_Target_h */ | ||||||
| @@ -8,11 +8,13 @@ | |||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../Disassembler/6502.hpp" | #include "../Disassembler/6502.hpp" | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Atari; | using namespace Analyser::Static::Atari; | ||||||
|  |  | ||||||
| static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid | 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid | ||||||
| 	uint16_t entry_address, break_address; | 	uint16_t entry_address, break_address; | ||||||
|  |  | ||||||
| @@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, cons | |||||||
| 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses | 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses | ||||||
| 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it | 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it | ||||||
| 	// attempts to modify itself but it probably doesn't | 	// attempts to modify itself but it probably doesn't | ||||||
| 	if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid; | 	if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and | 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and | ||||||
| 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all | 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all | ||||||
| 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?) | 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?) | ||||||
| @@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons | |||||||
| 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | ||||||
| 		segment.data[0] == 0x78 | 		segment.data[0] == 0x78 | ||||||
| 	) { | 	) { | ||||||
| 		target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack; | 		target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack; | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// make an assumption that this is the Atari paging model | 	// make an assumption that this is the Atari paging model | ||||||
| 	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k; | 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k; | ||||||
|  |  | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons | |||||||
| 		tigervision_access_count += masked_address == 0x3f; | 		tigervision_access_count += masked_address == 0x3f; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros; | 	if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros; | ||||||
| 	else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; | 	else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is the Atari paging model | 	// make an assumption that this is the Atari paging model | ||||||
| 	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k; | 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k; | ||||||
|  |  | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, con | |||||||
| 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork; | 	if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is a Tigervision if there is a write to 3F | 	// make an assumption that this is a Tigervision if there is a write to 3F | ||||||
| 	target.atari.paging_model = | 	target.paging_model = | ||||||
| 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | ||||||
| 			Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy; | 			Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	if(segment.data.size() == 2048) { | 	if(segment.data.size() == 2048) { | ||||||
| 		DeterminePagingFor2kCartridge(target, segment); | 		DeterminePagingFor2kCartridge(target, segment); | ||||||
| 		return; | 		return; | ||||||
| @@ -138,16 +140,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const | |||||||
| 			DeterminePagingFor8kCartridge(target, segment, disassembly); | 			DeterminePagingFor8kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 10495: | 		case 10495: | ||||||
| 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2; | ||||||
| 		break; | 		break; | ||||||
| 		case 12288: | 		case 12288: | ||||||
| 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus; | ||||||
| 		break; | 		break; | ||||||
| 		case 16384: | 		case 16384: | ||||||
| 			DeterminePagingFor16kCartridge(target, segment, disassembly); | 			DeterminePagingFor16kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 32768: | 		case 32768: | ||||||
| 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k; | ||||||
| 		break; | 		break; | ||||||
| 		case 65536: | 		case 65536: | ||||||
| 			DeterminePagingFor64kCartridge(target, segment, disassembly); | 			DeterminePagingFor64kCartridge(target, segment, disassembly); | ||||||
| @@ -159,8 +161,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const | |||||||
| 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM | 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM | ||||||
| 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the | 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the | ||||||
| 	// next 128 bytes. So check for that. | 	// next 128 bytes. So check for that. | ||||||
| 	if(	target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus && | 	if(	target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus && | ||||||
| 		target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) { | 		target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) { | ||||||
| 		bool has_superchip = true; | 		bool has_superchip = true; | ||||||
| 		for(std::size_t address = 0; address < 128; address++) { | 		for(std::size_t address = 0; address < 128; address++) { | ||||||
| 			if(segment.data[address] != segment.data[address+128]) { | 			if(segment.data[address] != segment.data[address+128]) { | ||||||
| @@ -168,24 +170,24 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		target.atari.uses_superchip = has_superchip; | 		target.uses_superchip = has_superchip; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// check for a Tigervision or Tigervision-esque scheme | 	// check for a Tigervision or Tigervision-esque scheme | ||||||
| 	if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) { | 	if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) { | ||||||
| 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | ||||||
| 		if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; | 		if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) { | ||||||
| 	// TODO: sanity checking; is this image really for an Atari 2600. | 	// TODO: sanity checking; is this image really for an Atari 2600? | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target); | ||||||
| 	target->machine = Machine::Atari2600; | 	target->machine = Machine::Atari2600; | ||||||
| 	target->confidence = 1.0; | 	target->confidence = 0.5; | ||||||
| 	target->media.cartridges = media.cartridges; | 	target->media.cartridges = media.cartridges; | ||||||
| 	target->atari.paging_model = Atari2600PagingModel::None; | 	target->paging_model = Analyser::Static::Atari::Target::PagingModel::None; | ||||||
| 	target->atari.uses_superchip = false; | 	target->uses_superchip = false; | ||||||
|  |  | ||||||
| 	// try to figure out the paging scheme | 	// try to figure out the paging scheme | ||||||
| 	if(!media.cartridges.empty()) { | 	if(!media.cartridges.empty()) { | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								Analyser/Static/Atari/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Analyser/Static/Atari/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Atari_Target_h | ||||||
|  | #define Analyser_Static_Atari_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Atari { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class PagingModel { | ||||||
|  | 		None, | ||||||
|  | 		CommaVid, | ||||||
|  | 		Atari8k, | ||||||
|  | 		Atari16k, | ||||||
|  | 		Atari32k, | ||||||
|  | 		ActivisionStack, | ||||||
|  | 		ParkerBros, | ||||||
|  | 		Tigervision, | ||||||
|  | 		CBSRamPlus, | ||||||
|  | 		MNetwork, | ||||||
|  | 		MegaBoy, | ||||||
|  | 		Pitfall2 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// TODO: shouldn't these be properties of the cartridge? | ||||||
|  | 	PagingModel paging_model = PagingModel::None; | ||||||
|  | 	bool uses_superchip = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Atari_Target_h */ | ||||||
| @@ -55,7 +55,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::ColecoVision; | 	target->machine = Machine::ColecoVision; | ||||||
| 	target->confidence = 0.5; | 	target->confidence = 1.0f - 1.0f / 32768.0f; | ||||||
| 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | ||||||
| 	if(!target->media.empty()) | 	if(!target->media.empty()) | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
|   | |||||||
| @@ -71,19 +71,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 			bit_count_++; | 			bit_count_++; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		unsigned int proceed_to_next_block() { | 		unsigned int proceed_to_next_block(int max_index_count) { | ||||||
| 			// find GCR lead-in | 			// find GCR lead-in | ||||||
| 			proceed_to_shift_value(0x3ff); | 			proceed_to_shift_value(0x3ff); | ||||||
| 			if(shift_register_ != 0x3ff) return 0xff; | 			if(shift_register_ != 0x3ff) return 0xff; | ||||||
|  |  | ||||||
| 			// find end of lead-in | 			// find end of lead-in | ||||||
| 			while(shift_register_ == 0x3ff && index_count_ < 2) { | 			while(shift_register_ == 0x3ff && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// continue for a further nine bits | 			// continue for a further nine bits | ||||||
| 			bit_count_ = 0; | 			bit_count_ = 0; | ||||||
| 			while(bit_count_ < 9 && index_count_ < 2) { | 			while(bit_count_ < 9 && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -97,8 +97,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void proceed_to_shift_value(unsigned int shift_value) { | 		void proceed_to_shift_value(unsigned int shift_value) { | ||||||
| 			index_count_ = 0; | 			const int max_index_count = index_count_ + 2; | ||||||
| 			while(shift_register_ != shift_value && index_count_ < 2) { | 			while(shift_register_ != shift_value && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -124,13 +124,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
|  |  | ||||||
| 		std::shared_ptr<Sector> get_next_sector() { | 		std::shared_ptr<Sector> get_next_sector() { | ||||||
| 			std::shared_ptr<Sector> sector(new Sector); | 			std::shared_ptr<Sector> sector(new Sector); | ||||||
| 			index_count_ = 0; | 			const int max_index_count = index_count_ + 2; | ||||||
|  |  | ||||||
| 			while(index_count_ < 2) { | 			while(index_count_ < max_index_count) { | ||||||
| 				// look for a sector header | 				// look for a sector header | ||||||
| 				while(1) { | 				while(1) { | ||||||
| 					if(proceed_to_next_block() == 0x08) break; | 					if(proceed_to_next_block(max_index_count) == 0x08) break; | ||||||
| 					if(index_count_ >= 2) return nullptr; | 					if(index_count_ >= max_index_count) return nullptr; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// get sector details, skip if this looks malformed | 				// get sector details, skip if this looks malformed | ||||||
| @@ -144,8 +144,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
|  |  | ||||||
| 				// look for the following data | 				// look for the following data | ||||||
| 				while(1) { | 				while(1) { | ||||||
| 					if(proceed_to_next_block() == 0x07) break; | 					if(proceed_to_next_block(max_index_count) == 0x07) break; | ||||||
| 					if(index_count_ >= 2) return nullptr; | 					if(index_count_ >= max_index_count) return nullptr; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				checksum = 0; | 				checksum = 0; | ||||||
|   | |||||||
| @@ -11,8 +11,10 @@ | |||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
| #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
| #include <sstream> | #include <sstream> | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| @@ -38,10 +40,10 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return vic20_cartridges; | 	return vic20_cartridges; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::Vic20;	// TODO: machine estimation | 	target->machine = Machine::Vic20;	// TODO: machine estimation | ||||||
| 	target->confidence = 1.0; // TODO: a proper estimation | 	target->confidence = 0.5; // TODO: a proper estimation | ||||||
|  |  | ||||||
| 	int device = 0; | 	int device = 0; | ||||||
| 	std::vector<File> files; | 	std::vector<File> files; | ||||||
| @@ -73,7 +75,7 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!files.empty()) { | 	if(!files.empty()) { | ||||||
| 		target->vic20.memory_model = Vic20MemoryModel::Unexpanded; | 		target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
| 		std::ostringstream string_stream; | 		std::ostringstream string_stream; | ||||||
| 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | ||||||
|   		if(files.front().is_basic()) { |   		if(files.front().is_basic()) { | ||||||
| @@ -86,19 +88,22 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std | |||||||
|  |  | ||||||
| 		// make a first guess based on loading address | 		// make a first guess based on loading address | ||||||
| 		switch(files.front().starting_address) { | 		switch(files.front().starting_address) { | ||||||
|  | 			default: | ||||||
|  | 				printf("Starting address %04x?\n", files.front().starting_address); | ||||||
| 			case 0x1001: | 			case 0x1001: | ||||||
| 			default: break; | 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
|  | 			break; | ||||||
| 			case 0x1201: | 			case 0x1201: | ||||||
| 				target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 				target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
| 			break; | 			break; | ||||||
| 			case 0x0401: | 			case 0x0401: | ||||||
| 				target->vic20.memory_model = Vic20MemoryModel::EightKB; | 				target->memory_model = Target::MemoryModel::EightKB; | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// General approach: increase memory size conservatively such that the largest file found will fit. | 		// General approach: increase memory size conservatively such that the largest file found will fit. | ||||||
| 		for(File &file : files) { | //		for(File &file : files) { | ||||||
| 			std::size_t file_size = file.data.size(); | //			std::size_t file_size = file.data.size(); | ||||||
| //			bool is_basic = file.is_basic(); | //			bool is_basic = file.is_basic(); | ||||||
|  |  | ||||||
| 			/*if(is_basic) | 			/*if(is_basic) | ||||||
| @@ -124,18 +129,26 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std | |||||||
| 				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000. | 				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000. | ||||||
| 				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000. | 				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000. | ||||||
| 				// A 32kb expanded Vic has memory in the entire low 32kb. | 				// A 32kb expanded Vic has memory in the entire low 32kb. | ||||||
| 				uint16_t starting_address = file.starting_address; | //				uint16_t starting_address = file.starting_address; | ||||||
|  |  | ||||||
| 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the | 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the | ||||||
| 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb. | 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb. | ||||||
| 				if(starting_address + file_size > 0x2000) | //				if(starting_address + file_size > 0x2000) | ||||||
| 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | //					target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
| 				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | //				else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | ||||||
| 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | //					target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
|  | //			} | ||||||
| //		} | //		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if(!target->media.empty()) { | ||||||
|  | 		// Inspect filename for a region hint. | ||||||
|  | 		std::string lowercase_name = file_name; | ||||||
|  | 		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower); | ||||||
|  | 		if(lowercase_name.find("ntsc") != std::string::npos) { | ||||||
|  | 			target->region = Analyser::Static::Commodore::Target::Region::American; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	if(!target->media.empty()) |  | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -10,12 +10,13 @@ | |||||||
| #define StaticAnalyser_Commodore_StaticAnalyser_hpp | #define StaticAnalyser_Commodore_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Commodore_Target_h | ||||||
|  | #define Analyser_Static_Commodore_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Commodore { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class MemoryModel { | ||||||
|  | 		Unexpanded, | ||||||
|  | 		EightKB, | ||||||
|  | 		ThirtyTwoKB | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	enum class Region { | ||||||
|  | 		American, | ||||||
|  | 		Danish, | ||||||
|  | 		Japanese, | ||||||
|  | 		European, | ||||||
|  | 		Swedish | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||||
|  | 	Region region = Region::European; | ||||||
|  | 	bool has_c1540 = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Commodore_Target_h */ | ||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../Disassembler/Z80.hpp" | #include "../Disassembler/Z80.hpp" | ||||||
| #include "../Disassembler/AddressMapper.hpp" | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
| @@ -32,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | |||||||
| 		output_segments.emplace_back(start_address, segment.data); | 		output_segments.emplace_back(start_address, segment.data); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	std::unique_ptr<Analyser::Static::Target> target(new Analyser::Static::Target); | 	std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target); | ||||||
| 	target->machine = Analyser::Machine::MSX; | 	target->machine = Analyser::Machine::MSX; | ||||||
| 	target->confidence = confidence; | 	target->confidence = confidence; | ||||||
|  |  | ||||||
| @@ -259,9 +261,9 @@ static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFr | |||||||
| 	return targets; | 	return targets; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) { | ||||||
| 	// Append targets for any cartridges that look correct. | 	// Append targets for any cartridges that look correct. | ||||||
| 	std::vector<std::unique_ptr<Target>> cartridge_targets = CartridgeTargetsFrom(media.cartridges); | 	auto cartridge_targets = CartridgeTargetsFrom(media.cartridges); | ||||||
| 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | ||||||
|  |  | ||||||
| 	// Consider building a target for disks and/or tapes. | 	// Consider building a target for disks and/or tapes. | ||||||
| @@ -283,10 +285,11 @@ void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::uniq | |||||||
|  |  | ||||||
| 	// Blindly accept disks for now. | 	// Blindly accept disks for now. | ||||||
| 	target->media.disks = media.disks; | 	target->media.disks = media.disks; | ||||||
|  | 	target->has_disk_drive = !media.disks.empty(); | ||||||
|  |  | ||||||
| 	if(!target->media.empty()) { | 	if(!target->media.empty()) { | ||||||
| 		target->machine = Machine::MSX; | 		target->machine = Machine::MSX; | ||||||
| 		target->confidence = 1.0; | 		target->confidence = 0.5; | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 02/04/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_MSX_Target_h | ||||||
|  | #define Analyser_Static_MSX_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool has_disk_drive = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_MSX_Target_h */ | ||||||
| @@ -9,9 +9,15 @@ | |||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../Disassembler/6502.hpp" | #include "../Disassembler/6502.hpp" | ||||||
| #include "../Disassembler/AddressMapper.hpp" | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Oric; | using namespace Analyser::Static::Oric; | ||||||
|  |  | ||||||
| static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | ||||||
| @@ -73,10 +79,31 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl | |||||||
| 	return Score(disassembly, rom_functions, variable_locations); | 	return Score(disassembly, rom_functions, variable_locations); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) { | ||||||
|  | 	/* | ||||||
|  | 		The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature. | ||||||
|  | 	*/ | ||||||
|  | 	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2); | ||||||
|  | 	if(!sector) return false; | ||||||
|  | 	if(sector->samples.empty()) return false; | ||||||
|  |  | ||||||
|  | 	const std::vector<uint8_t> &first_sample = sector->samples[0]; | ||||||
|  | 	if(first_sample.size() != 256) return false; | ||||||
|  |  | ||||||
|  | 	const uint8_t signature[] = { | ||||||
|  | 		0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0, | ||||||
|  | 		0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00, | ||||||
|  | 		0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6, | ||||||
|  | 		0x12, 0x00 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	return !std::memcmp(signature, first_sample.data(), sizeof(signature)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::Oric; | 	target->machine = Machine::Oric; | ||||||
| 	target->confidence = 1.0; | 	target->confidence = 0.5; | ||||||
|  |  | ||||||
| 	int basic10_votes = 0; | 	int basic10_votes = 0; | ||||||
| 	int basic11_votes = 0; | 	int basic11_votes = 0; | ||||||
| @@ -102,17 +129,22 @@ void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::uni | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// trust that any disk supplied can be handled by the Microdisc. TODO: check. |  | ||||||
| 	if(!media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		target->oric.has_microdisc = true; | 		// Only the Microdisc is emulated right now, so accept only disks that it can boot from. | ||||||
| 		target->media.disks = media.disks; | 		for(const auto &disk: media.disks) { | ||||||
|  | 			Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
|  | 			if(IsMicrodisc(parser)) { | ||||||
|  | 				target->has_microdrive = true; | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		target->oric.has_microdisc = false; | 		target->has_microdrive = false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// TODO: really this should add two targets if not all votes agree | 	// TODO: really this should add two targets if not all votes agree | ||||||
| 	target->oric.use_atmos_rom = basic11_votes >= basic10_votes; | 	target->use_atmos_rom = basic11_votes >= basic10_votes; | ||||||
| 	if(target->oric.has_microdisc) target->oric.use_atmos_rom = true; | 	if(target->has_microdrive) target->use_atmos_rom = true; | ||||||
|  |  | ||||||
| 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Oric_Target_h | ||||||
|  | #define Analyser_Static_Oric_Target_h | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Oric { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool use_atmos_rom = false; | ||||||
|  | 	bool has_microdrive = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Oric_Target_h */ | ||||||
| @@ -52,21 +52,16 @@ | |||||||
|  |  | ||||||
| using namespace Analyser::Static; | using namespace Analyser::Static; | ||||||
|  |  | ||||||
| static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) { | static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) { | ||||||
|  | 	Media result; | ||||||
|  |  | ||||||
| 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase | 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase | ||||||
| 	// test as to file format. | 	// test as to file format. | ||||||
| 	const char *mixed_case_extension = strrchr(file_name, '.'); | 	std::string::size_type final_dot = file_name.find_last_of("."); | ||||||
| 	char *lowercase_extension = nullptr; | 	if(final_dot == std::string::npos) return result; | ||||||
| 	if(mixed_case_extension) { | 	std::string extension = file_name.substr(final_dot + 1); | ||||||
| 		lowercase_extension = strdup(mixed_case_extension+1); | 	std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); | ||||||
| 		char *parser = lowercase_extension; |  | ||||||
| 		while(*parser) { |  | ||||||
| 			*parser = (char)tolower(*parser); |  | ||||||
| 			parser++; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	Media result; |  | ||||||
| #define Insert(list, class, platforms) \ | #define Insert(list, class, platforms) \ | ||||||
| 	list.emplace_back(new Storage::class(file_name));\ | 	list.emplace_back(new Storage::class(file_name));\ | ||||||
| 	potential_platforms |= platforms;\ | 	potential_platforms |= platforms;\ | ||||||
| @@ -78,17 +73,16 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 		Insert(list, class, platforms) \ | 		Insert(list, class, platforms) \ | ||||||
| 	} catch(...) {} | 	} catch(...) {} | ||||||
|  |  | ||||||
| #define Format(extension, list, class, platforms) \ | #define Format(ext, list, class, platforms) \ | ||||||
| 	if(!std::strcmp(lowercase_extension, extension))	{	\ | 	if(extension == ext)	{		\ | ||||||
| 		TryInsert(list, class, platforms)	\ | 		TryInsert(list, class, platforms)	\ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(lowercase_extension) { |  | ||||||
| 	Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 80 | 	Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 80 | ||||||
| 	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 81 | 	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 81 | ||||||
| 	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// A26 | 	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// A26 | ||||||
| 	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF | 	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF | ||||||
| 		Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// BIN | 	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)					// BIN | ||||||
| 	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS | 	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS | ||||||
| 	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT | 	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT | ||||||
| 	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL | 	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL | ||||||
| @@ -100,13 +94,17 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)			// DSK (MSX) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)			// DSK (MSX) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)		// DSK (Oric) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)		// DSK (Oric) | ||||||
| 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)		// G64 | 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)		// G64 | ||||||
| 		Format("hfe", result.disks, Disk::DiskImageHolder<Storage::Disk::HFE>, TargetPlatform::AmstradCPC)		// HFE (TODO: plus other target platforms) | 	Format(	"hfe", | ||||||
|  | 			result.disks, | ||||||
|  | 			Disk::DiskImageHolder<Storage::Disk::HFE>, | ||||||
|  | 			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) | ||||||
|  | 			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy) | ||||||
| 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O | 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O | ||||||
| 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P | 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P | ||||||
| 	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P81 | 	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P81 | ||||||
|  |  | ||||||
| 	// PRG | 	// PRG | ||||||
| 		if(!std::strcmp(lowercase_extension, "prg")) { | 	if(extension == "prg") { | ||||||
| 		// try instantiating as a ROM; failing that accept as a tape | 		// try instantiating as a ROM; failing that accept as a tape | ||||||
| 		try { | 		try { | ||||||
| 			Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore) | 			Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore) | ||||||
| @@ -120,7 +118,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 	Format(	"rom", | 	Format(	"rom", | ||||||
| 			result.cartridges, | 			result.cartridges, | ||||||
| 			Cartridge::BinaryDump, | 			Cartridge::BinaryDump, | ||||||
| 				TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision)						// ROM | 			TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX)				// ROM | ||||||
| 	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD | 	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD | ||||||
| 	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore) | 	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore) | ||||||
| 	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric) | 	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric) | ||||||
| @@ -132,20 +130,15 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| #undef Insert | #undef Insert | ||||||
| #undef TryInsert | #undef TryInsert | ||||||
|  |  | ||||||
| 		// Filter potential platforms as per file preferences, if any. |  | ||||||
|  |  | ||||||
| 		free(lowercase_extension); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| Media Analyser::Static::GetMedia(const char *file_name) { | Media Analyser::Static::GetMedia(const std::string &file_name) { | ||||||
| 	TargetPlatform::IntType throwaway; | 	TargetPlatform::IntType throwaway; | ||||||
| 	return GetMediaAndPlatforms(file_name, throwaway); | 	return GetMediaAndPlatforms(file_name, throwaway); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *file_name) { | std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) { | ||||||
| 	std::vector<std::unique_ptr<Target>> targets; | 	std::vector<std::unique_ptr<Target>> targets; | ||||||
|  |  | ||||||
| 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | ||||||
| @@ -159,7 +152,7 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *fi | |||||||
| 	if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::ColecoVision)	Coleco::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::ColecoVision)	Coleco::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets, file_name); | ||||||
| 	if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms); | 	if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms); | ||||||
|   | |||||||
| @@ -21,39 +21,6 @@ | |||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
|  |  | ||||||
| enum class Vic20MemoryModel { |  | ||||||
| 	Unexpanded, |  | ||||||
| 	EightKB, |  | ||||||
| 	ThirtyTwoKB |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class Atari2600PagingModel { |  | ||||||
| 	None, |  | ||||||
| 	CommaVid, |  | ||||||
| 	Atari8k, |  | ||||||
| 	Atari16k, |  | ||||||
| 	Atari32k, |  | ||||||
| 	ActivisionStack, |  | ||||||
| 	ParkerBros, |  | ||||||
| 	Tigervision, |  | ||||||
| 	CBSRamPlus, |  | ||||||
| 	MNetwork, |  | ||||||
| 	MegaBoy, |  | ||||||
| 	Pitfall2 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class ZX8081MemoryModel { |  | ||||||
| 	Unexpanded, |  | ||||||
| 	SixteenKB, |  | ||||||
| 	SixtyFourKB |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class AmstradCPCModel { |  | ||||||
| 	CPC464, |  | ||||||
| 	CPC664, |  | ||||||
| 	CPC6128 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	A list of disks, tapes and cartridges. | 	A list of disks, tapes and cartridges. | ||||||
| */ | */ | ||||||
| @@ -72,45 +39,13 @@ struct Media { | |||||||
| 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | ||||||
| */ | */ | ||||||
| struct Target { | struct Target { | ||||||
|  | 	virtual ~Target() {} | ||||||
|  |  | ||||||
| 	Machine machine; | 	Machine machine; | ||||||
| 	Media media; | 	Media media; | ||||||
|  |  | ||||||
| 	float confidence; | 	float confidence; | ||||||
| 	std::string loading_command; | 	std::string loading_command; | ||||||
|  |  | ||||||
| 	// TODO: this is too C-like a solution; make Target a base class and |  | ||||||
| 	// turn the following into information held by more specific subclasses. |  | ||||||
| 	union { |  | ||||||
| 		struct { |  | ||||||
| 			bool has_adfs; |  | ||||||
| 			bool has_dfs; |  | ||||||
| 			bool should_shift_restart; |  | ||||||
| 		} acorn; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			Atari2600PagingModel paging_model; |  | ||||||
| 			bool uses_superchip; |  | ||||||
| 		} atari; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			bool use_atmos_rom; |  | ||||||
| 			bool has_microdisc; |  | ||||||
| 		} oric; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			Vic20MemoryModel memory_model; |  | ||||||
| 			bool has_c1540; |  | ||||||
| 		} vic20; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			ZX8081MemoryModel memory_model; |  | ||||||
| 			bool isZX81; |  | ||||||
| 		} zx8081; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			AmstradCPCModel model; |  | ||||||
| 		} amstradcpc; |  | ||||||
| 	}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -118,12 +53,12 @@ struct Target { | |||||||
|  |  | ||||||
| 	@returns The list of potential targets, sorted from most to least probable. | 	@returns The list of potential targets, sorted from most to least probable. | ||||||
| */ | */ | ||||||
| std::vector<std::unique_ptr<Target>> GetTargets(const char *file_name); | std::vector<std::unique_ptr<Target>> GetTargets(const std::string &file_name); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Inspects the supplied file and determines the media included. | 	Inspects the supplied file and determines the media included. | ||||||
| */ | */ | ||||||
| Media GetMedia(const char *file_name); | Media GetMedia(const std::string &file_name); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
| #include "../../../Storage/Tape/Parsers/ZX8081.hpp" | #include "../../../Storage/Tape/Parsers/ZX8081.hpp" | ||||||
|  |  | ||||||
| static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| @@ -27,41 +28,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S | |||||||
| 	return files; | 	return files; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) { | void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) { | ||||||
| 	if(!media.tapes.empty()) { | 	if(!media.tapes.empty()) { | ||||||
| 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | ||||||
| 		media.tapes.front()->reset(); | 		media.tapes.front()->reset(); | ||||||
| 		if(!files.empty()) { | 		if(!files.empty()) { | ||||||
| 			std::unique_ptr<Target> target(new Target); | 			Target *target = new Target; | ||||||
|  | 			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target)); | ||||||
| 			target->machine = Machine::ZX8081; | 			target->machine = Machine::ZX8081; | ||||||
|  |  | ||||||
| 			// Guess the machine type from the file only if it isn't already known. | 			// Guess the machine type from the file only if it isn't already known. | ||||||
| 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | ||||||
| 				default: | 				default: | ||||||
| 					target->zx8081.isZX81 = files.front().isZX81; | 					target->is_ZX81 = files.front().isZX81; | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case TargetPlatform::ZX80:	target->zx8081.isZX81 = false;	break; | 				case TargetPlatform::ZX80:	target->is_ZX81 = false;	break; | ||||||
| 				case TargetPlatform::ZX81:	target->zx8081.isZX81 = true;	break; | 				case TargetPlatform::ZX81:	target->is_ZX81 = true;		break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			/*if(files.front().data.size() > 16384) { | 			/*if(files.front().data.size() > 16384) { | ||||||
| 				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | 				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | ||||||
| 			} else*/ if(files.front().data.size() > 1024) { | 			} else*/ if(files.front().data.size() > 1024) { | ||||||
| 				target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB; | 				target->memory_model = Target::MemoryModel::SixteenKB; | ||||||
| 			} else { | 			} else { | ||||||
| 				target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded; | 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
| 			} | 			} | ||||||
| 			target->media.tapes = media.tapes; | 			target->media.tapes = media.tapes; | ||||||
|  |  | ||||||
| 			// TODO: how to run software once loaded? Might require a BASIC detokeniser. | 			// TODO: how to run software once loaded? Might require a BASIC detokeniser. | ||||||
| 			if(target->zx8081.isZX81) { | 			if(target->is_ZX81) { | ||||||
| 				target->loading_command = "J\"\"\n"; | 				target->loading_command = "J\"\"\n"; | ||||||
| 			} else { | 			} else { | ||||||
| 				target->loading_command = "W\n"; | 				target->loading_command = "W\n"; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			destination.push_back(std::move(target)); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_ZX8081_Target_h | ||||||
|  | #define Analyser_Static_ZX8081_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class MemoryModel { | ||||||
|  | 		Unexpanded, | ||||||
|  | 		SixteenKB, | ||||||
|  | 		SixtyFourKB | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||||
|  | 	bool is_ZX81 = false; | ||||||
|  | 	bool ZX80_uses_ZX81_ROM = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_ZX8081_Target_h */ | ||||||
							
								
								
									
										18
									
								
								ClockReceiver/TimeTypes.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ClockReceiver/TimeTypes.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | // | ||||||
|  | //  TimeTypes.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/03/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TimeTypes_h | ||||||
|  | #define TimeTypes_h | ||||||
|  |  | ||||||
|  | namespace Time { | ||||||
|  |  | ||||||
|  | typedef double Seconds; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* TimeTypes_h */ | ||||||
| @@ -18,7 +18,7 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue | |||||||
|  |  | ||||||
| void AudioGenerator::set_volume(uint8_t volume) { | void AudioGenerator::set_volume(uint8_t volume) { | ||||||
| 	audio_queue_.defer([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		volume_ = volume; | 		volume_ = static_cast<int16_t>(volume) * range_multiplier_; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -114,12 +114,12 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) | |||||||
|  |  | ||||||
| 		// this sums the output of all three sounds channels plus a DC offset for volume; | 		// this sums the output of all three sounds channels plus a DC offset for volume; | ||||||
| 		// TODO: what's the real ratio of this stuff? | 		// TODO: what's the real ratio of this stuff? | ||||||
| 		target[c] = ( | 		target[c] = static_cast<int16_t>( | ||||||
| 			(shift_registers_[0]&1) + | 			(shift_registers_[0]&1) + | ||||||
| 			(shift_registers_[1]&1) + | 			(shift_registers_[1]&1) + | ||||||
| 			(shift_registers_[2]&1) + | 			(shift_registers_[2]&1) + | ||||||
| 			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) | 			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) | ||||||
| 		) * volume_ * 700 + volume_ * 44; | 		) * volume_ + (volume_ >> 4); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -132,6 +132,10 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void AudioGenerator::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	range_multiplier_ = static_cast<int16_t>(range / 64); | ||||||
|  | } | ||||||
|  |  | ||||||
| #undef shift | #undef shift | ||||||
| #undef increment | #undef increment | ||||||
| #undef update | #undef update | ||||||
|   | |||||||
| @@ -25,8 +25,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | |||||||
| 		void set_volume(uint8_t volume); | 		void set_volume(uint8_t volume); | ||||||
| 		void set_control(int channel, uint8_t value); | 		void set_control(int channel, uint8_t value); | ||||||
|  |  | ||||||
|  | 		// For ::SampleSource. | ||||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
| 		void skip_samples(std::size_t number_of_samples); | 		void skip_samples(std::size_t number_of_samples); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| @@ -34,7 +36,8 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | |||||||
| 		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels | 		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels | ||||||
| 		unsigned int shift_registers_[4] = {0, 0, 0, 0}; | 		unsigned int shift_registers_[4] = {0, 0, 0, 0}; | ||||||
| 		uint8_t control_registers_[4] = {0, 0, 0, 0}; | 		uint8_t control_registers_[4] = {0, 0, 0, 0}; | ||||||
| 		uint8_t volume_ = 0; | 		int16_t volume_ = 0; | ||||||
|  | 		int16_t range_multiplier_ = 1; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -52,20 +55,28 @@ template <class T> class MOS6560 { | |||||||
| 				audio_generator_(audio_queue_), | 				audio_generator_(audio_queue_), | ||||||
| 				speaker_(audio_generator_) | 				speaker_(audio_generator_) | ||||||
| 		{ | 		{ | ||||||
| 			crt_->set_composite_sampling_function( | 			crt_->set_svideo_sampling_function( | ||||||
| 				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | 				"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | ||||||
| 				"{" | 				"{" | ||||||
| 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | ||||||
| 					"float phaseOffset = 6.283185308 * 2.0 * yc.y;" |  | ||||||
|  |  | ||||||
| 					"float chroma = cos(phase + phaseOffset);" | 					"float phaseOffset = 6.283185308 * 2.0 * yc.y;" | ||||||
| 					"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);" | 					"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);" | ||||||
|  |  | ||||||
|  | 					"return vec2(yc.x, chroma);" | ||||||
| 				"}"); | 				"}"); | ||||||
|  |  | ||||||
|  | 			// default to s-video output | ||||||
|  | 			crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo); | ||||||
|  |  | ||||||
| 			// default to NTSC | 			// default to NTSC | ||||||
| 			set_output_mode(OutputMode::NTSC); | 			set_output_mode(OutputMode::NTSC); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~MOS6560() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_clock_rate(double clock_rate) { | 		void set_clock_rate(double clock_rate) { | ||||||
| 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | ||||||
| 		} | 		} | ||||||
| @@ -87,28 +98,28 @@ template <class T> class MOS6560 { | |||||||
| 		void set_output_mode(OutputMode output_mode) { | 		void set_output_mode(OutputMode output_mode) { | ||||||
| 			output_mode_ = output_mode; | 			output_mode_ = output_mode; | ||||||
|  |  | ||||||
| 			// Lumunances are encoded trivially: on a 0–255 scale. | 			// Luminances are encoded trivially: on a 0–255 scale. | ||||||
| 			const uint8_t luminances[16] = { | 			const uint8_t luminances[16] = { | ||||||
| 				0,		255,	109,	189, | 				0,		255,	60,		189, | ||||||
| 				199,	144,	159,	161, | 				80,		144,	40,		227, | ||||||
| 				126,	227,	227,	207, | 				90,		161,	207,	227, | ||||||
| 				235,	173,	188,	196 | 				200,	196,	160,	196 | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			// Chrominances are encoded such that 0–128 is a complete revolution of phase; | 			// Chrominances are encoded such that 0–128 is a complete revolution of phase; | ||||||
| 			// anything above 191 disables the colour subcarrier. Phase is relative to the | 			// anything above 191 disables the colour subcarrier. Phase is relative to the | ||||||
| 			// colour burst, so 0 is green. | 			// colour burst, so 0 is green. | ||||||
| 			const uint8_t pal_chrominances[16] = { | 			const uint8_t pal_chrominances[16] = { | ||||||
| 				255,	255,	40,		112, | 				255,	255,	37,		101, | ||||||
| 				8,		88,		120,	56, | 				19,		86,		123,	59, | ||||||
| 				40,		48,		40,		112, | 				46,		53,		37,		101, | ||||||
| 				8,		88,		120,	56, | 				19,		86,		123,	59, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t ntsc_chrominances[16] = { | 			const uint8_t ntsc_chrominances[16] = { | ||||||
| 				255,	255,	8,		72, | 				255,	255,	7,		71, | ||||||
| 				32,		88,		48,		112, | 				25,		86,		48,		112, | ||||||
| 				0,		0,		8,		72, | 				0,		119,	7,		71, | ||||||
| 				32,		88,		48,		112, | 				25,		86,		48,		112, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t *chrominances; | 			const uint8_t *chrominances; | ||||||
| 			Outputs::CRT::DisplayType display_type; | 			Outputs::CRT::DisplayType display_type; | ||||||
| @@ -134,16 +145,15 @@ template <class T> class MOS6560 { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); |  | ||||||
|  |  | ||||||
| //			switch(output_mode) { | 			switch(output_mode) { | ||||||
| //				case OutputMode::PAL: | 				case OutputMode::PAL: | ||||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f)); | 					crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f)); | ||||||
| //				break; | 				break; | ||||||
| //				case OutputMode::NTSC: | 				case OutputMode::NTSC: | ||||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); | 					crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); | ||||||
| //				break; | 				break; | ||||||
| //			} | 			} | ||||||
|  |  | ||||||
| 			for(int c = 0; c < 16; c++) { | 			for(int c = 0; c < 16; c++) { | ||||||
| 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | ||||||
|   | |||||||
| @@ -96,7 +96,7 @@ TMS9918::TMS9918(Personality p) { | |||||||
| 		"{" | 		"{" | ||||||
| 			"return texture(sampler, coordinate).rgb / vec3(255.0);" | 			"return texture(sampler, coordinate).rgb / vec3(255.0);" | ||||||
| 		"}"); | 		"}"); | ||||||
| 	crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
| 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | ||||||
| 	crt_->set_input_gamma(2.8f); | 	crt_->set_input_gamma(2.8f); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,13 +56,18 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_ | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	set_sample_volume_range(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AY38910::set_sample_volume_range(std::int16_t range) { | ||||||
| 	// set up volume lookup table | 	// set up volume lookup table | ||||||
| 	float max_volume = 8192; | 	const float max_volume = static_cast<float>(range) / 3.0f;	// As there are three channels. | ||||||
| 	float root_two = sqrtf(2.0f); | 	const float root_two = sqrtf(2.0f); | ||||||
| 	for(int v = 0; v < 16; v++) { | 	for(int v = 0; v < 16; v++) { | ||||||
| 		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); | 		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); | ||||||
| 	} | 	} | ||||||
| 	volumes_[0] = 0; | 	volumes_[0] = 0; | ||||||
|  | 	evaluate_output_volume(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
|   | |||||||
| @@ -86,6 +86,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource { | |||||||
| 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | ||||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
| 		bool is_zero_level(); | 		bool is_zero_level(); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { | |||||||
|  |  | ||||||
| 	std::size_t c = 0; | 	std::size_t c = 0; | ||||||
| 	while((master_divider_&7) && c < number_of_samples) { | 	while((master_divider_&7) && c < number_of_samples) { | ||||||
| 		target[c] = output_volume_; | 		target[c] = transient_output_level_; | ||||||
| 		master_divider_++; | 		master_divider_++; | ||||||
| 		c++; | 		c++; | ||||||
| 	} | 	} | ||||||
| @@ -44,7 +44,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { | |||||||
| 		evaluate_output_volume(); | 		evaluate_output_volume(); | ||||||
|  |  | ||||||
| 		for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { | 		for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { | ||||||
| 			target[c] = output_volume_; | 			target[c] = transient_output_level_; | ||||||
| 			c++; | 			c++; | ||||||
| 			master_divider_++; | 			master_divider_++; | ||||||
| 		} | 		} | ||||||
| @@ -86,18 +86,24 @@ void SCC::write(uint16_t address, uint8_t value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void SCC::evaluate_output_volume() { | void SCC::evaluate_output_volume() { | ||||||
| 	output_volume_ = | 	transient_output_level_ = | ||||||
| 		static_cast<int16_t>( | 		static_cast<int16_t>( | ||||||
| 			( | 			(( | ||||||
| 				(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + | 				(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + | ||||||
| 				(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + | 				(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + | ||||||
| 				(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + | 				(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + | ||||||
| 				(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + | 				(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + | ||||||
| 				(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 | 				(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 | ||||||
| 			) | 			) * master_volume_) / (255*15*5) | ||||||
|  | 			// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5. | ||||||
| 		); | 		); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void SCC::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	master_volume_ = range; | ||||||
|  | 	evaluate_output_volume(); | ||||||
|  | } | ||||||
|  |  | ||||||
| uint8_t SCC::read(uint16_t address) { | uint8_t SCC::read(uint16_t address) { | ||||||
| 	address &= 0xff; | 	address &= 0xff; | ||||||
| 	if(address < 0x80) { | 	if(address < 0x80) { | ||||||
| @@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) { | |||||||
| 	} | 	} | ||||||
| 	return 0xff; | 	return 0xff; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource { | |||||||
|  |  | ||||||
| 		/// As per ::SampleSource; provides audio output. | 		/// As per ::SampleSource; provides audio output. | ||||||
| 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 		/// Writes to the SCC. | 		/// Writes to the SCC. | ||||||
| 		void write(uint16_t address, uint8_t value); | 		void write(uint16_t address, uint8_t value); | ||||||
| @@ -43,7 +44,8 @@ class SCC: public ::Outputs::Speaker::SampleSource { | |||||||
|  |  | ||||||
| 		// State from here on down is accessed ony from the audio thread. | 		// State from here on down is accessed ony from the audio thread. | ||||||
| 		int master_divider_ = 0; | 		int master_divider_ = 0; | ||||||
| 		int16_t output_volume_ = 0; | 		std::int16_t master_volume_ = 0; | ||||||
|  | 		int16_t transient_output_level_ = 0; | ||||||
|  |  | ||||||
| 		struct Channel { | 		struct Channel { | ||||||
| 			int period = 0; | 			int period = 0; | ||||||
|   | |||||||
| @@ -14,15 +14,7 @@ | |||||||
| using namespace TI; | using namespace TI; | ||||||
|  |  | ||||||
| SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { | SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { | ||||||
| 	// Build a volume table. | 	set_sample_volume_range(0); | ||||||
| 	double multiplier = pow(10.0, -0.1); |  | ||||||
| 	double volume = 8191.0f; |  | ||||||
| 	for(int c = 0; c < 16; ++c) { |  | ||||||
| 		volumes_[c] = (int)round(volume); |  | ||||||
| 		volume *= multiplier; |  | ||||||
| 	} |  | ||||||
| 	volumes_[15] = 0; |  | ||||||
| 	evaluate_output_volume(); |  | ||||||
|  |  | ||||||
| 	switch(personality) { | 	switch(personality) { | ||||||
| 		case Personality::SN76494: | 		case Personality::SN76494: | ||||||
| @@ -44,6 +36,18 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue & | |||||||
| 	master_divider_period_ /= additional_divider; | 	master_divider_period_ /= additional_divider; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void SN76489::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	// Build a volume table. | ||||||
|  | 	double multiplier = pow(10.0, -0.1); | ||||||
|  | 	double volume = static_cast<float>(range) / 4.0f;	// As there are four channels. | ||||||
|  | 	for(int c = 0; c < 16; ++c) { | ||||||
|  | 		volumes_[c] = (int)round(volume); | ||||||
|  | 		volume *= multiplier; | ||||||
|  | 	} | ||||||
|  | 	volumes_[15] = 0; | ||||||
|  | 	evaluate_output_volume(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void SN76489::set_register(uint8_t value) { | void SN76489::set_register(uint8_t value) { | ||||||
| 	task_queue_.defer([value, this] () { | 	task_queue_.defer([value, this] () { | ||||||
| 		if(value & 0x80) { | 		if(value & 0x80) { | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource { | |||||||
| 		// As per SampleSource. | 		// As per SampleSource. | ||||||
| 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
| 		bool is_zero_level(); | 		bool is_zero_level(); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		int master_divider_ = 0; | 		int master_divider_ = 0; | ||||||
|   | |||||||
| @@ -45,6 +45,7 @@ AsyncTaskQueue::AsyncTaskQueue() | |||||||
|  |  | ||||||
| AsyncTaskQueue::~AsyncTaskQueue() { | AsyncTaskQueue::~AsyncTaskQueue() { | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
|  | 	flush(); | ||||||
| 	dispatch_release(serial_dispatch_queue_); | 	dispatch_release(serial_dispatch_queue_); | ||||||
| 	serial_dispatch_queue_ = nullptr; | 	serial_dispatch_queue_ = nullptr; | ||||||
| #else | #else | ||||||
| @@ -82,6 +83,7 @@ void AsyncTaskQueue::flush() { | |||||||
|  |  | ||||||
| DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { | DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { | ||||||
| 	perform(); | 	perform(); | ||||||
|  | 	flush(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | ||||||
|   | |||||||
| @@ -36,13 +36,11 @@ void BestEffortUpdater::update() { | |||||||
| 				// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate | 				// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate | ||||||
| 				// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — | 				// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — | ||||||
| 				// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. | 				// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. | ||||||
| 				const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); | 				const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); | ||||||
| 				if(duration > 0) { | 				if(integer_duration > 0) { | ||||||
| 					double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_; |  | ||||||
| 					error_ = fmod(cycles, 1.0); |  | ||||||
|  |  | ||||||
| 					if(delegate_) { | 					if(delegate_) { | ||||||
| 						delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_); | 						const double duration = static_cast<double>(integer_duration) / 1e9; | ||||||
|  | 						delegate_->update(this, duration, has_skipped_); | ||||||
| 					} | 					} | ||||||
| 					has_skipped_ = false; | 					has_skipped_ = false; | ||||||
| 				} | 				} | ||||||
| @@ -70,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) { | |||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BestEffortUpdater::set_clock_rate(const double clock_rate) { |  | ||||||
| 	async_task_queue_.enqueue([this, clock_rate]() { |  | ||||||
| 		this->clock_rate_ = clock_rate; |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ | |||||||
| #include <chrono> | #include <chrono> | ||||||
|  |  | ||||||
| #include "AsyncTaskQueue.hpp" | #include "AsyncTaskQueue.hpp" | ||||||
|  | #include "../ClockReceiver/TimeTypes.hpp" | ||||||
|  |  | ||||||
| namespace Concurrency { | namespace Concurrency { | ||||||
|  |  | ||||||
| @@ -30,15 +31,12 @@ class BestEffortUpdater { | |||||||
|  |  | ||||||
| 		/// A delegate receives timing cues. | 		/// A delegate receives timing cues. | ||||||
| 		struct Delegate { | 		struct Delegate { | ||||||
| 			virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0; | 			virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0; | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		/// Sets the current delegate. | 		/// Sets the current delegate. | ||||||
| 		void set_delegate(Delegate *); | 		void set_delegate(Delegate *); | ||||||
|  |  | ||||||
| 		/// Sets the clock rate of the delegate. |  | ||||||
| 		void set_clock_rate(double clock_rate); |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. | 			If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. | ||||||
| 			The call is asynchronous; this method will return immediately. | 			The call is asynchronous; this method will return immediately. | ||||||
| @@ -54,11 +52,9 @@ class BestEffortUpdater { | |||||||
|  |  | ||||||
| 		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_; | 		std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_; | ||||||
| 		bool has_previous_time_point_ = false; | 		bool has_previous_time_point_ = false; | ||||||
| 		double error_ = 0.0; |  | ||||||
| 		bool has_skipped_ = false; | 		bool has_skipped_ = false; | ||||||
|  |  | ||||||
| 		Delegate *delegate_ = nullptr; | 		Delegate *delegate_ = nullptr; | ||||||
| 		double clock_rate_ = 1.0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -33,7 +33,13 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std: | |||||||
| std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) { | std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) { | ||||||
| 	std::vector<std::unique_ptr<Configurable::Option>> options; | 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||||
| 	if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload")); | 	if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload")); | ||||||
| 	if(mask & DisplayRGBComposite)			options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"})); | 	if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) { | ||||||
|  | 		std::vector<std::string> display_options; | ||||||
|  | 		if(mask & DisplayComposite)	display_options.emplace_back("composite"); | ||||||
|  | 		if(mask & DisplaySVideo) 	display_options.emplace_back("svideo"); | ||||||
|  | 		if(mask & DisplayRGB) 		display_options.emplace_back("rgb"); | ||||||
|  | 		options.emplace_back(new Configurable::ListOption("Display", "display", display_options)); | ||||||
|  | 	} | ||||||
| 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | ||||||
| 	return options; | 	return options; | ||||||
| } | } | ||||||
| @@ -48,7 +54,14 @@ void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &s | |||||||
| } | } | ||||||
|  |  | ||||||
| void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) { | void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) { | ||||||
| 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite")); | 	std::string string_selection; | ||||||
|  | 	switch(selection) { | ||||||
|  | 		default: | ||||||
|  | 		case Display::RGB:			string_selection = "rgb";		break; | ||||||
|  | 		case Display::SVideo:		string_selection = "svideo";	break; | ||||||
|  | 		case Display::Composite:	string_selection = "composite";	break; | ||||||
|  | 	} | ||||||
|  | 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection)); | ||||||
| } | } | ||||||
|  |  | ||||||
| // MARK: - Selection parsers | // MARK: - Selection parsers | ||||||
| @@ -67,6 +80,10 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o | |||||||
| 			result = Configurable::Display::RGB; | 			result = Configurable::Display::RGB; | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
|  | 		if(display->value == "svideo") { | ||||||
|  | 			result = Configurable::Display::SVideo; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
| 		if(display->value == "composite") { | 		if(display->value == "composite") { | ||||||
| 			result = Configurable::Display::Composite; | 			result = Configurable::Display::Composite; | ||||||
| 			return true; | 			return true; | ||||||
|   | |||||||
| @@ -14,13 +14,16 @@ | |||||||
| namespace Configurable { | namespace Configurable { | ||||||
|  |  | ||||||
| enum StandardOptions { | enum StandardOptions { | ||||||
| 	DisplayRGBComposite			= (1 << 0), | 	DisplayRGB					= (1 << 0), | ||||||
| 	QuickLoadTape				= (1 << 1), | 	DisplaySVideo				= (1 << 1), | ||||||
| 	AutomaticTapeMotorControl	= (1 << 2) | 	DisplayComposite			= (1 << 2), | ||||||
|  | 	QuickLoadTape				= (1 << 3), | ||||||
|  | 	AutomaticTapeMotorControl	= (1 << 4) | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class Display { | enum class Display { | ||||||
| 	RGB, | 	RGB, | ||||||
|  | 	SVideo, | ||||||
| 	Composite | 	Composite | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,11 +20,17 @@ | |||||||
| #include "../Utility/MemoryFuzzer.hpp" | #include "../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/AmstradCPC/Target.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| @@ -120,6 +126,10 @@ class AYDeferrer { | |||||||
| 			speaker_.set_input_rate(1000000); | 			speaker_.set_input_rate(1000000); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~AYDeferrer() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/// Adds @c half_cycles half cycles to the amount of time that has passed. | 		/// Adds @c half_cycles half cycles to the amount of time that has passed. | ||||||
| 		inline void run_for(HalfCycles half_cycles) { | 		inline void run_for(HalfCycles half_cycles) { | ||||||
| 			cycles_since_update_ += half_cycles; | 			cycles_since_update_ += half_cycles; | ||||||
| @@ -307,7 +317,7 @@ class CRTCBusHandler { | |||||||
| 					"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.075f, 0.05f, 0.9f, 0.9f)); | ||||||
| 			crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | 			crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Destructs the CRT. | 		/// Destructs the CRT. | ||||||
| @@ -674,6 +684,9 @@ 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: | class ConcreteMachine: | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public Sleeper::SleepObserver, | 	public Sleeper::SleepObserver, | ||||||
| @@ -867,19 +880,21 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override final  { | 		void configure_as_target(const Analyser::Static::Target *target) override final  { | ||||||
| 			switch(target.amstradcpc.model) { | 			auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target); | ||||||
| 				case Analyser::Static::AmstradCPCModel::CPC464: |  | ||||||
|  | 			switch(cpc_target->model) { | ||||||
|  | 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: | ||||||
| 					rom_model_ = ROMType::OS464; | 					rom_model_ = ROMType::OS464; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
| 					has_fdc_ = false; | 					has_fdc_ = false; | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::AmstradCPCModel::CPC664: | 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: | ||||||
| 					rom_model_ = ROMType::OS664; | 					rom_model_ = ROMType::OS664; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
| 					has_fdc_ = true; | 					has_fdc_ = true; | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::AmstradCPCModel::CPC6128: | 				case Analyser::Static::AmstradCPC::Target::Model::CPC6128: | ||||||
| 					rom_model_ = ROMType::OS6128; | 					rom_model_ = ROMType::OS6128; | ||||||
| 					has_128k_ = true; | 					has_128k_ = true; | ||||||
| 					has_fdc_ = true; | 					has_fdc_ = true; | ||||||
| @@ -901,11 +916,11 @@ class ConcreteMachine: | |||||||
| 			read_pointers_[3] = roms_[upper_rom_].data(); | 			read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
|  |  | ||||||
| 			// Type whatever is required. | 			// Type whatever is required. | ||||||
| 			if(target.loading_command.length()) { | 			if(target->loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
|   | |||||||
| @@ -9,19 +9,12 @@ | |||||||
| #ifndef AmstradCPC_hpp | #ifndef AmstradCPC_hpp | ||||||
| #define AmstradCPC_hpp | #define AmstradCPC_hpp | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "../KeyboardMachine.hpp" |  | ||||||
|  |  | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models an Amstrad CPC. | 	Models an Amstrad CPC. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine { | ||||||
| 	public CRTMachine::Machine, |  | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public KeyboardMachine::Machine { |  | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,12 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/Atari/Target.hpp" | ||||||
|  |  | ||||||
| #include "Cartridges/Atari8k.hpp" | #include "Cartridges/Atari8k.hpp" | ||||||
| #include "Cartridges/Atari16k.hpp" | #include "Cartridges/Atari16k.hpp" | ||||||
| #include "Cartridges/Atari32k.hpp" | #include "Cartridges/Atari32k.hpp" | ||||||
| @@ -72,11 +78,12 @@ class Joystick: public Inputs::Joystick { | |||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public JoystickMachine::Machine, | ||||||
| 	public Outputs::CRT::Delegate { | 	public Outputs::CRT::Delegate { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine() { | ||||||
| 			frame_record_pointer_(0), |  | ||||||
| 			is_ntsc_(true) { |  | ||||||
| 			set_clock_rate(NTSC_clock_rate); | 			set_clock_rate(NTSC_clock_rate); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -84,35 +91,38 @@ class ConcreteMachine: | |||||||
| 			close_output(); | 			close_output(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override { | 		void configure_as_target(const Analyser::Static::Target *target) override { | ||||||
| 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | 			auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target); | ||||||
| 			switch(target.atari.paging_model) { | 			const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data; | ||||||
| 				case Analyser::Static::Atari2600PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::CommaVid:		bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::MNetwork:		bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::None:			bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::Pitfall2:		bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; |  | ||||||
|  |  | ||||||
| 				case Analyser::Static::Atari2600PagingModel::Atari8k: | 			using PagingModel = Analyser::Static::Atari::Target::PagingModel; | ||||||
| 					if(target.atari.uses_superchip) { | 			switch(atari_target->paging_model) { | ||||||
|  | 				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::CommaVid:			bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||||
|  | 				case PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; | ||||||
|  | 				case PagingModel::MNetwork:			bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; | ||||||
|  | 				case PagingModel::None:				bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; | ||||||
|  | 				case PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; | ||||||
|  | 				case PagingModel::Pitfall2:			bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; | ||||||
|  | 				case PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||||
|  |  | ||||||
|  | 				case PagingModel::Atari8k: | ||||||
|  | 					if(atari_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 Analyser::Static::Atari2600PagingModel::Atari16k: | 				case PagingModel::Atari16k: | ||||||
| 					if(target.atari.uses_superchip) { | 					if(atari_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 Analyser::Static::Atari2600PagingModel::Atari32k: | 				case PagingModel::Atari32k: | ||||||
| 					if(target.atari.uses_superchip) { | 					if(atari_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)); | ||||||
| @@ -179,6 +189,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles) override { | 		void run_for(const Cycles cycles) override { | ||||||
| 			bus_->run_for(cycles); | 			bus_->run_for(cycles); | ||||||
|  | 			bus_->apply_confidence(confidence_counter_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy Outputs::CRT::Delegate | 		// to satisfy Outputs::CRT::Delegate | ||||||
| @@ -219,6 +230,10 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		float get_confidence() override { | ||||||
|  | 			return confidence_counter_.get_confidence(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		// the bus | 		// the bus | ||||||
| 		std::unique_ptr<Bus> bus_; | 		std::unique_ptr<Bus> bus_; | ||||||
| @@ -230,9 +245,12 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} | 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} | ||||||
| 		} frame_records_[4]; | 		} frame_records_[4]; | ||||||
| 		unsigned int frame_record_pointer_; | 		unsigned int frame_record_pointer_ = 0; | ||||||
| 		bool is_ntsc_; | 		bool is_ntsc_ = true; | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  |  | ||||||
|  | 		// a confidence counter | ||||||
|  | 		Analyser::Dynamic::ConfidenceCounter confidence_counter_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,10 +9,6 @@ | |||||||
| #ifndef Atari2600_cpp | #ifndef Atari2600_cpp | ||||||
| #define Atari2600_cpp | #define Atari2600_cpp | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "../JoystickMachine.hpp" |  | ||||||
|  |  | ||||||
| #include "Atari2600Inputs.h" | #include "Atari2600Inputs.h" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
| @@ -20,10 +16,7 @@ namespace Atari2600 { | |||||||
| /*! | /*! | ||||||
| 	Models an Atari 2600. | 	Models an Atari 2600. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine { | ||||||
| 	public CRTMachine::Machine, |  | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public JoystickMachine::Machine { |  | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| #include "TIA.hpp" | #include "TIA.hpp" | ||||||
| #include "TIASound.hpp" | #include "TIASound.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Dynamic/ConfidenceCounter.hpp" | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
| @@ -23,11 +24,14 @@ class Bus { | |||||||
| 	public: | 	public: | ||||||
| 		Bus() : | 		Bus() : | ||||||
| 			tia_sound_(audio_queue_), | 			tia_sound_(audio_queue_), | ||||||
| 			speaker_(tia_sound_), | 			speaker_(tia_sound_) {} | ||||||
| 			tia_input_value_{0xff, 0xff}, |  | ||||||
| 			cycles_since_speaker_update_(0) {} | 		virtual ~Bus() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		virtual void run_for(const Cycles cycles) = 0; | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
|  | 		virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0; | ||||||
| 		virtual void set_reset_line(bool state) = 0; | 		virtual void set_reset_line(bool state) = 0; | ||||||
|  |  | ||||||
| 		// the RIOT, TIA and speaker | 		// the RIOT, TIA and speaker | ||||||
| @@ -39,7 +43,7 @@ class Bus { | |||||||
| 		Outputs::Speaker::LowpassSpeaker<TIASound> speaker_; | 		Outputs::Speaker::LowpassSpeaker<TIASound> speaker_; | ||||||
|  |  | ||||||
| 		// joystick state | 		// joystick state | ||||||
| 		uint8_t tia_input_value_[2]; | 		uint8_t tia_input_value_[2] = {0xff, 0xff}; | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		// speaker backlog accumlation counter | 		// speaker backlog accumlation counter | ||||||
|   | |||||||
| @@ -39,7 +39,23 @@ template<class T> class Cartridge: | |||||||
| 			// consider doing something less fragile. | 			// consider doing something less fragile. | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles)	{ m6502_.run_for(cycles);		} | 		void run_for(const Cycles cycles)	{ | ||||||
|  | 			// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600 | ||||||
|  | 			// title. Random memory accesses are likely to trigger random counter resets. | ||||||
|  | 			horizontal_counter_resets_ = 0; | ||||||
|  | 			cycle_count_ = cycles; | ||||||
|  | 			m6502_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Adjusts @c confidence_counter according to the results of the most recent run_for. | ||||||
|  | 		*/ | ||||||
|  | 		void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) { | ||||||
|  | 			if(cycle_count_.as_int() < 200) return; | ||||||
|  | 			if(horizontal_counter_resets_ > 10) | ||||||
|  | 				confidence_counter.add_miss(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_reset_line(bool state)		{ m6502_.set_reset_line(state);	} | 		void set_reset_line(bool state)		{ m6502_.set_reset_line(state);	} | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| @@ -108,7 +124,11 @@ template<class T> class Cartridge: | |||||||
| 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | ||||||
|  |  | ||||||
| 							case 0x02:	m6502_.set_ready_line(true);						break; | 							case 0x02:	m6502_.set_ready_line(true);						break; | ||||||
| 							case 0x03:	update_video();	tia_->reset_horizontal_counter();	break; | 							case 0x03: | ||||||
|  | 								update_video(); | ||||||
|  | 								tia_->reset_horizontal_counter(); | ||||||
|  | 								horizontal_counter_resets_++; | ||||||
|  | 							break; | ||||||
| 								// TODO: audio will now be out of synchronisation — fix | 								// TODO: audio will now be out of synchronisation — fix | ||||||
|  |  | ||||||
| 							case 0x04: | 							case 0x04: | ||||||
| @@ -189,6 +209,9 @@ template<class T> class Cartridge: | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		T bus_extender_; | 		T bus_extender_; | ||||||
|  | 		int horizontal_counter_resets_ = 0; | ||||||
|  | 		Cycles cycle_count_; | ||||||
|  |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ namespace { | |||||||
| TIA::TIA(bool create_crt) { | TIA::TIA(bool create_crt) { | ||||||
| 	if(create_crt) { | 	if(create_crt) { | ||||||
| 		crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); | 		crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); | ||||||
| 		crt_->set_output_device(Outputs::CRT::OutputDevice::Television); | 		crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
| 		set_output_mode(OutputMode::NTSC); | 		set_output_mode(OutputMode::NTSC); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -123,20 +123,20 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { | |||||||
| 	Outputs::CRT::DisplayType display_type; | 	Outputs::CRT::DisplayType display_type; | ||||||
|  |  | ||||||
| 	if(output_mode == OutputMode::NTSC) { | 	if(output_mode == OutputMode::NTSC) { | ||||||
| 		crt_->set_composite_sampling_function( | 		crt_->set_svideo_sampling_function( | ||||||
| 			"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | 			"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | ||||||
| 			"{" | 			"{" | ||||||
| 				"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 mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" | 				"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_composite_sampling_function( | 		crt_->set_svideo_sampling_function( | ||||||
| 			"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | 			"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" | ||||||
| 			"{" | 			"{" | ||||||
| 				"uint c = texture(texID, coordinate).r;" | 				"uint c = texture(texID, coordinate).r;" | ||||||
| 				"uint y = c & 14u;" | 				"uint y = c & 14u;" | ||||||
| @@ -145,10 +145,12 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { | |||||||
| 				"uint direction = iPhase & 1u;" | 				"uint direction = iPhase & 1u;" | ||||||
| 				"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" | 				"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" | ||||||
| 				"phaseOffset *= 6.283185308 / 12.0;" | 				"phaseOffset *= 6.283185308 / 12.0;" | ||||||
| 				"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" | 				"return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));" | ||||||
| 			"}"); | 			"}"); | ||||||
| 		display_type = Outputs::CRT::DisplayType::PAL50; | 		display_type = Outputs::CRT::DisplayType::PAL50; | ||||||
| 	} | 	} | ||||||
|  | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
|  |  | ||||||
| 	// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari | 	// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari | ||||||
| 	// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled | 	// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled | ||||||
| 	// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply | 	// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply | ||||||
|   | |||||||
| @@ -119,7 +119,11 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			target[c] += volume_[channel] * 1024 * level; | 			target[c] += (volume_[channel] * per_channel_volume_ * level) >> 4; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	per_channel_volume_ = range / 2; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -26,7 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource { | |||||||
| 		void set_divider(int channel, uint8_t divider); | 		void set_divider(int channel, uint8_t divider); | ||||||
| 		void set_control(int channel, uint8_t control); | 		void set_control(int channel, uint8_t control); | ||||||
|  |  | ||||||
|  | 		// To satisfy ::SampleSource. | ||||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| @@ -41,6 +43,7 @@ class TIASound: public Outputs::Speaker::SampleSource { | |||||||
| 		int output_state_[2]; | 		int output_state_[2]; | ||||||
|  |  | ||||||
| 		int divider_counter_[2]; | 		int divider_counter_[2]; | ||||||
|  | 		int16_t per_channel_volume_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,8 +12,13 @@ | |||||||
| #include "../Outputs/CRT/CRT.hpp" | #include "../Outputs/CRT/CRT.hpp" | ||||||
| #include "../Outputs/Speaker/Speaker.hpp" | #include "../Outputs/Speaker/Speaker.hpp" | ||||||
| #include "../ClockReceiver/ClockReceiver.hpp" | #include "../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../ClockReceiver/TimeTypes.hpp" | ||||||
| #include "ROMMachine.hpp" | #include "ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "../Configurable/StandardOptions.hpp" | ||||||
|  |  | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
| namespace CRTMachine { | namespace CRTMachine { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -41,45 +46,58 @@ class Machine: public ROMMachine::Machine { | |||||||
| 		/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. | 		/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. | ||||||
| 		virtual Outputs::Speaker::Speaker *get_speaker() = 0; | 		virtual Outputs::Speaker::Speaker *get_speaker() = 0; | ||||||
|  |  | ||||||
| 		/// Runs the machine for @c cycles. |  | ||||||
| 		virtual void run_for(const Cycles cycles) = 0; |  | ||||||
|  |  | ||||||
| 		/// @returns The confidence that this machine is running content it understands. | 		/// @returns The confidence that this machine is running content it understands. | ||||||
| 		virtual float get_confidence() { return 0.5f; } | 		virtual float get_confidence() { return 0.5f; } | ||||||
| 		virtual void print_type() {} | 		virtual void print_type() {} | ||||||
|  |  | ||||||
| 		// TODO: sever the clock-rate stuff. | 		/// Runs the machine for @c duration seconds. | ||||||
| 		virtual double get_clock_rate() { | 		virtual void run_for(Time::Seconds duration) { | ||||||
| 			return clock_rate_; | 			const double cycles = (duration * clock_rate_) + clock_conversion_error_; | ||||||
|  | 			clock_conversion_error_ = std::fmod(cycles, 1.0); | ||||||
|  | 			run_for(Cycles(static_cast<int>(cycles))); | ||||||
| 		} | 		} | ||||||
| 		virtual bool get_clock_is_unlimited() { |  | ||||||
| 			return clock_is_unlimited_; |  | ||||||
| 		} |  | ||||||
| 		class Delegate { |  | ||||||
| 			public: |  | ||||||
| 				virtual void machine_did_change_clock_rate(Machine *machine) = 0; |  | ||||||
| 				virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0; |  | ||||||
| 		}; |  | ||||||
| 		virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; } |  | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
|  | 		/// Runs the machine for @c cycles. | ||||||
|  | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
| 		void set_clock_rate(double clock_rate) { | 		void set_clock_rate(double clock_rate) { | ||||||
| 			if(clock_rate_ != clock_rate) { |  | ||||||
| 			clock_rate_ = clock_rate; | 			clock_rate_ = clock_rate; | ||||||
| 				if(delegate_) delegate_->machine_did_change_clock_rate(this); |  | ||||||
| 		} | 		} | ||||||
|  | 		double get_clock_rate() { | ||||||
|  | 			return clock_rate_; | ||||||
| 		} | 		} | ||||||
| 		void set_clock_is_unlimited(bool clock_is_unlimited) { |  | ||||||
| 			if(clock_is_unlimited != clock_is_unlimited_) { | 		/*! | ||||||
| 				clock_is_unlimited_ = clock_is_unlimited; | 			Maps from Configurable::Display to Outputs::CRT::VideoSignal and calls | ||||||
| 				if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this); | 			@c set_video_signal with the result. | ||||||
|  | 		*/ | ||||||
|  | 		void set_video_signal_configurable(Configurable::Display type) { | ||||||
|  | 			Outputs::CRT::VideoSignal signal; | ||||||
|  | 			switch(type) { | ||||||
|  | 				default: | ||||||
|  | 				case Configurable::Display::RGB: | ||||||
|  | 					signal = Outputs::CRT::VideoSignal::RGB; | ||||||
|  | 				break; | ||||||
|  | 				case Configurable::Display::SVideo: | ||||||
|  | 					signal = Outputs::CRT::VideoSignal::SVideo; | ||||||
|  | 				break; | ||||||
|  | 				case Configurable::Display::Composite: | ||||||
|  | 					signal = Outputs::CRT::VideoSignal::Composite; | ||||||
|  | 				break; | ||||||
| 			} | 			} | ||||||
|  | 			set_video_signal(signal); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Forwards the video signal to the CRT returned by get_crt(). | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_video_signal(Outputs::CRT::VideoSignal video_signal) { | ||||||
|  | 			get_crt()->set_video_signal(video_signal); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Delegate *delegate_ = nullptr; |  | ||||||
| 		double clock_rate_ = 1.0; | 		double clock_rate_ = 1.0; | ||||||
| 		bool clock_is_unlimited_ = false; | 		double clock_conversion_error_ = 0.0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ | |||||||
| #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Dynamic/ConfidenceCounter.hpp" | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
| const int sn76489_divider = 2; | const int sn76489_divider = 2; | ||||||
| } | } | ||||||
| @@ -123,13 +125,17 @@ class ConcreteMachine: | |||||||
| 			joysticks_.emplace_back(new Joystick); | 			joysticks_.emplace_back(new Joystick); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
| 			return joysticks_; | 			return joysticks_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override { | 		void setup_output(float aspect_ratio) override { | ||||||
| 			vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); | 			vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); | ||||||
| 			get_crt()->set_output_device(Outputs::CRT::OutputDevice::Television); | 			get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void close_output() override { | 		void close_output() override { | ||||||
| @@ -148,9 +154,9 @@ class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override { | 		void configure_as_target(const Analyser::Static::Target *target) override { | ||||||
| 			// Insert the media. | 			// Insert the media. | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| @@ -192,9 +198,13 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		// 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) { | ||||||
|  | 			time_since_vdp_update_ += cycle.length; | ||||||
|  | 			time_since_sn76489_update_ += cycle.length; | ||||||
|  |  | ||||||
| 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | ||||||
| 			switch(cycle.operation) { | 			switch(cycle.operation) { | ||||||
| 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
|  | 					if(!address) pc_zero_accesses_++; | ||||||
| 				case CPU::Z80::PartialMachineCycle::Read: | 				case CPU::Z80::PartialMachineCycle::Read: | ||||||
| 					if(address < 0x2000) { | 					if(address < 0x2000) { | ||||||
| 						if(super_game_module_.replace_bios) { | 						if(super_game_module_.replace_bios) { | ||||||
| @@ -245,6 +255,11 @@ class ConcreteMachine: | |||||||
| 							} else { | 							} else { | ||||||
| 								*cycle.value = joystick->get_direction_input(); | 								*cycle.value = joystick->get_direction_input(); | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
|  | 							// Hitting exactly the recommended joypad input port is an indicator that | ||||||
|  | 							// this really is a ColecoVision game. The BIOS won't do this when just waiting | ||||||
|  | 							// to start a game (unlike accessing the VDP and SN). | ||||||
|  | 							if((address&0xfc) == 0xfc) confidence_counter_.add_hit(); | ||||||
| 						} break; | 						} break; | ||||||
|  |  | ||||||
| 						default: | 						default: | ||||||
| @@ -252,6 +267,7 @@ class ConcreteMachine: | |||||||
| 								default: *cycle.value = 0xff; break; | 								default: *cycle.value = 0xff; break; | ||||||
| 								case 0x52: | 								case 0x52: | ||||||
| 									// Read AY data. | 									// Read AY data. | ||||||
|  | 									update_audio(); | ||||||
| 									ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); | 									ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); | ||||||
| 									*cycle.value = ay_.get_data_output(); | 									*cycle.value = ay_.get_data_output(); | ||||||
| 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
| @@ -289,12 +305,14 @@ class ConcreteMachine: | |||||||
| 								break; | 								break; | ||||||
| 								case 0x50: | 								case 0x50: | ||||||
| 									// Set AY address. | 									// Set AY address. | ||||||
|  | 									update_audio(); | ||||||
| 									ay_.set_control_lines(GI::AY38910::BC1); | 									ay_.set_control_lines(GI::AY38910::BC1); | ||||||
| 									ay_.set_data_input(*cycle.value); | 									ay_.set_data_input(*cycle.value); | ||||||
| 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
| 								break; | 								break; | ||||||
| 								case 0x51: | 								case 0x51: | ||||||
| 									// Set AY data. | 									// Set AY data. | ||||||
|  | 									update_audio(); | ||||||
| 									ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); | 									ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); | ||||||
| 									ay_.set_data_input(*cycle.value); | 									ay_.set_data_input(*cycle.value); | ||||||
| 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
| @@ -310,9 +328,6 @@ class ConcreteMachine: | |||||||
| 				default: break; | 				default: break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			time_since_vdp_update_ += cycle.length; |  | ||||||
| 			time_since_sn76489_update_ += cycle.length; |  | ||||||
|  |  | ||||||
| 			if(time_until_interrupt_ > 0) { | 			if(time_until_interrupt_ > 0) { | ||||||
| 				time_until_interrupt_ -= cycle.length; | 				time_until_interrupt_ -= cycle.length; | ||||||
| 				if(time_until_interrupt_ <= HalfCycles(0)) { | 				if(time_until_interrupt_ <= HalfCycles(0)) { | ||||||
| @@ -329,6 +344,11 @@ class ConcreteMachine: | |||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		float get_confidence() override { | ||||||
|  | 			if(pc_zero_accesses_ > 1) return 0.0f; | ||||||
|  | 			return confidence_counter_.get_confidence(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		inline void page_megacart(uint16_t address) { | 		inline void page_megacart(uint16_t address) { | ||||||
| 			const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size(); | 			const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size(); | ||||||
| @@ -368,6 +388,9 @@ class ConcreteMachine: | |||||||
| 		HalfCycles time_since_vdp_update_; | 		HalfCycles time_since_vdp_update_; | ||||||
| 		HalfCycles time_since_sn76489_update_; | 		HalfCycles time_since_sn76489_update_; | ||||||
| 		HalfCycles time_until_interrupt_; | 		HalfCycles time_until_interrupt_; | ||||||
|  |  | ||||||
|  | 		Analyser::Dynamic::ConfidenceCounter confidence_counter_; | ||||||
|  | 		int pc_zero_accesses_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -165,14 +165,15 @@ void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint | |||||||
| 			attention_acknowledge_level_ = !(value&0x10); | 			attention_acknowledge_level_ = !(value&0x10); | ||||||
| 			data_level_output_ = (value&0x02); | 			data_level_output_ = (value&0x02); | ||||||
|  |  | ||||||
| 			serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!(value&0x08)); | //			printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Clock), value ? "high" : "low"); | ||||||
|  | 			serialPort->set_output(::Commodore::Serial::Line::Clock, static_cast<::Commodore::Serial::LineLevel>(!(value&0x08))); | ||||||
| 			update_data_line(); | 			update_data_line(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { | void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { | ||||||
| //	printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low"); | //	printf("[C1540] %s input is %s\n", StringForLine(line), value ? "high" : "low"); | ||||||
|  |  | ||||||
| 	switch(line) { | 	switch(line) { | ||||||
| 		default: break; | 		default: break; | ||||||
| @@ -194,9 +195,10 @@ void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::P | |||||||
| void SerialPortVIA::update_data_line() { | void SerialPortVIA::update_data_line() { | ||||||
| 	std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | 	std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||||
| 	if(serialPort) { | 	if(serialPort) { | ||||||
|  | //		printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Data), (!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)) ? "high" : "low"); | ||||||
| 		// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" | 		// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" | ||||||
| 		serialPort->set_output(::Commodore::Serial::Line::Data, | 		serialPort->set_output(::Commodore::Serial::Line::Data, | ||||||
| 			(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); | 			static_cast<::Commodore::Serial::LineLevel>(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,11 @@ | |||||||
|  |  | ||||||
| #include "Keyboard.hpp" | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
|  | #include "../../ConfigurationTarget.hpp" | ||||||
|  | #include "../../CRTMachine.hpp" | ||||||
|  | #include "../../KeyboardMachine.hpp" | ||||||
|  | #include "../../JoystickMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../../Processors/6502/6502.hpp" | #include "../../../Processors/6502/6502.hpp" | ||||||
| #include "../../../Components/6560/6560.hpp" | #include "../../../Components/6560/6560.hpp" | ||||||
| #include "../../../Components/6522/6522.hpp" | #include "../../../Components/6522/6522.hpp" | ||||||
| @@ -26,6 +31,8 @@ | |||||||
|  |  | ||||||
| #include "../../../Configurable/StandardOptions.hpp" | #include "../../../Configurable/StandardOptions.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Analyser/Static/Commodore/Target.hpp" | ||||||
|  |  | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
|  |  | ||||||
| @@ -40,7 +47,9 @@ enum ROMSlot { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 	return Configurable::standard_options(Configurable::QuickLoadTape); | 	return Configurable::standard_options( | ||||||
|  | 		static_cast<Configurable::StandardOptions>(Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape) | ||||||
|  | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| enum JoystickInput { | enum JoystickInput { | ||||||
| @@ -283,11 +292,17 @@ class Joystick: public Inputs::Joystick { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine, | ||||||
|  | 	public JoystickMachine::Machine, | ||||||
|  | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public MOS::MOS6522::IRQDelegatePortHandler::Delegate, | 	public MOS::MOS6522::IRQDelegatePortHandler::Delegate, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public Storage::Tape::BinaryTapePlayer::Delegate, | 	public Storage::Tape::BinaryTapePlayer::Delegate, | ||||||
| 	public Machine { | 	public Machine, | ||||||
|  | 	public Sleeper::SleepObserver { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine() : | ||||||
| 				m6502_(*this), | 				m6502_(*this), | ||||||
| @@ -313,17 +328,16 @@ 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); | ||||||
|  |  | ||||||
| 			// 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_)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { |  | ||||||
| 			delete[] rom_; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 		// Obtains the system ROMs. | ||||||
| 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
|  | 			rom_fetcher_ = roms_with_names; | ||||||
|  |  | ||||||
| 			auto roms = roms_with_names( | 			auto roms = roms_with_names( | ||||||
| 				"Vic20", | 				"Vic20", | ||||||
| 				{ | 				{ | ||||||
| @@ -353,24 +367,14 @@ class ConcreteMachine: | |||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override final { | 		void configure_as_target(const Analyser::Static::Target *target) override final { | ||||||
| 			if(target.loading_command.length()) { | 			commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target); | ||||||
| 				type_string(target.loading_command); |  | ||||||
|  | 			if(target->loading_command.length()) { | ||||||
|  | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			switch(target.vic20.memory_model) { | 			if(target->media.disks.size()) { | ||||||
| 				case Analyser::Static::Vic20MemoryModel::Unexpanded: |  | ||||||
| 					set_memory_size(Default); |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Vic20MemoryModel::EightKB: |  | ||||||
| 					set_memory_size(ThreeKB); |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Vic20MemoryModel::ThirtyTwoKB: |  | ||||||
| 					set_memory_size(ThirtyTwoKB); |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(target.media.disks.size()) { |  | ||||||
| 				// construct the 1540 | 				// construct the 1540 | ||||||
| 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); | 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); | ||||||
|  |  | ||||||
| @@ -379,9 +383,12 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 				// give it a means to obtain its ROM | 				// give it a means to obtain its ROM | ||||||
| 				c1540_->set_rom_fetcher(rom_fetcher_); | 				c1540_->set_rom_fetcher(rom_fetcher_); | ||||||
|  |  | ||||||
|  | 				// give it a little warm up | ||||||
|  | 				c1540_->run_for(Cycles(2000000)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| @@ -398,9 +405,8 @@ class ConcreteMachine: | |||||||
| 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; | 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; | ||||||
| 				rom_length_ = static_cast<uint16_t>(rom_image.size()); | 				rom_length_ = static_cast<uint16_t>(rom_image.size()); | ||||||
|  |  | ||||||
| 				rom_ = new uint8_t[0x2000]; | 				rom_ = rom_image; | ||||||
| 				std::memcpy(rom_, rom_image.data(), rom_image.size()); | 				rom_.resize(0x2000); | ||||||
| 				write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			set_use_fast_tape(); | 			set_use_fast_tape(); | ||||||
| @@ -423,16 +429,6 @@ class ConcreteMachine: | |||||||
| 			return joysticks_; | 			return joysticks_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_memory_size(MemorySize size) override final { |  | ||||||
| 			memory_size_ = size; |  | ||||||
| 			needs_configuration_ = true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_region(Region region) override final { |  | ||||||
| 			region_ = region; |  | ||||||
| 			needs_configuration_ = true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_ntsc_6560() { | 		void set_ntsc_6560() { | ||||||
| 			set_clock_rate(1022727); | 			set_clock_rate(1022727); | ||||||
| 			if(mos6560_) { | 			if(mos6560_) { | ||||||
| @@ -449,9 +445,9 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_memory() { | 		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_ == American || region_ == Japanese) { | 			if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) { | ||||||
| 				// NTSC | 				// NTSC | ||||||
| 				set_ntsc_6560(); | 				set_ntsc_6560(); | ||||||
| 			} else { | 			} else { | ||||||
| @@ -459,57 +455,73 @@ class ConcreteMachine: | |||||||
| 				set_pal_6560(); | 				set_pal_6560(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// Initialise the memory maps as all pointing to nothing | ||||||
| 			memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); | 			memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); | ||||||
| 			memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); | 			memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); | ||||||
| 			memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); | 			memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); | ||||||
|  |  | ||||||
| 			switch(memory_size_) { | #define set_ram(baseaddr, length)	\ | ||||||
| 				default: break; | 	write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length);	\ | ||||||
| 				case ThreeKB: | 	write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); | ||||||
| 					write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000); |  | ||||||
| 					write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000); | 			// Add 6502-visible RAM as requested | ||||||
|  | 			switch(memory_model) { | ||||||
|  | 				case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded: | ||||||
|  | 					// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000. | ||||||
|  | 					set_ram(0x0000, 0x0400); | ||||||
|  | 					set_ram(0x1000, 0x1000); | ||||||
| 				break; | 				break; | ||||||
| 				case ThirtyTwoKB: | 				case Analyser::Static::Commodore::Target::MemoryModel::EightKB: | ||||||
| 					write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000); | 					// An 8kb Vic-20 fills in the gap between the two blocks of RAM on an unexpanded machine. | ||||||
| 					write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000); | 					set_ram(0x0000, 0x2000); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Commodore::Target::MemoryModel::ThirtyTwoKB: | ||||||
|  | 					// A 32kb Vic-20 fills the entire lower 32kb with RAM. | ||||||
|  | 					set_ram(0x0000, 0x8000); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// install the system ROMs and VIC-visible memory | #undef set_ram | ||||||
| 			write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); |  | ||||||
| 			write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); |  | ||||||
| 			write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); |  | ||||||
|  |  | ||||||
| 			write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); | 			// all expansions also have colour RAM visible at 0x9400. | ||||||
| 			write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); | 			write_to_map(processor_read_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_)); | ||||||
| 			write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); | 			write_to_map(processor_write_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_)); | ||||||
|  |  | ||||||
| 			write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_)); | 			// also push memory resources into the 6560 video memory map; the 6560 has only a | ||||||
| 			write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_)); | 			// 14-bit address bus and the top bit is invested and used as bit 15 for the main | ||||||
| 			mos6560_->colour_memory = colour_memory_; | 			// memory bus. | ||||||
|  | 			for(int addr = 0; addr < 0x4000; addr += 0x400) { | ||||||
|  | 				int source_address = (addr & 0x1fff) | (((addr & 0x2000) << 2) ^ 0x8000); | ||||||
|  | 				if(processor_read_memory_map_[source_address >> 10]) { | ||||||
|  | 					write_to_map(mos6560_->video_memory_map, &ram_[source_address], static_cast<uint16_t>(addr), 0x400); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			mos6560_->colour_memory = colour_ram_; | ||||||
|  |  | ||||||
|  | 			// install the BASIC ROM | ||||||
| 			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 | ||||||
| 			ROM character_rom; | 			ROM character_rom; | ||||||
| 			ROM kernel_rom; | 			ROM kernel_rom; | ||||||
| 			switch(region_) { | 			switch(region) { | ||||||
| 				default: | 				default: | ||||||
| 					character_rom = CharactersEnglish; | 					character_rom = CharactersEnglish; | ||||||
| 					kernel_rom = KernelPAL; | 					kernel_rom = KernelPAL; | ||||||
| 				break; | 				break; | ||||||
| 				case American: | 				case Analyser::Static::Commodore::Target::Region::American: | ||||||
| 					character_rom = CharactersEnglish; | 					character_rom = CharactersEnglish; | ||||||
| 					kernel_rom = KernelNTSC; | 					kernel_rom = KernelNTSC; | ||||||
| 				break; | 				break; | ||||||
| 				case Danish: | 				case Analyser::Static::Commodore::Target::Region::Danish: | ||||||
| 					character_rom = CharactersDanish; | 					character_rom = CharactersDanish; | ||||||
| 					kernel_rom = KernelDanish; | 					kernel_rom = KernelDanish; | ||||||
| 				break; | 				break; | ||||||
| 				case Japanese: | 				case Analyser::Static::Commodore::Target::Region::Japanese: | ||||||
| 					character_rom = CharactersJapanese; | 					character_rom = CharactersJapanese; | ||||||
| 					kernel_rom = KernelJapanese; | 					kernel_rom = KernelJapanese; | ||||||
| 				break; | 				break; | ||||||
| 				case Swedish: | 				case Analyser::Static::Commodore::Target::Region::Swedish: | ||||||
| 					character_rom = CharactersSwedish; | 					character_rom = CharactersSwedish; | ||||||
| 					kernel_rom = KernelSwedish; | 					kernel_rom = KernelSwedish; | ||||||
| 				break; | 				break; | ||||||
| @@ -520,30 +532,30 @@ class ConcreteMachine: | |||||||
| 			write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size())); | 			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 | 			// install the inserted ROM if there is one | ||||||
| 			if(rom_) { | 			if(!rom_.empty()) { | ||||||
| 				write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); | 				write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			// run the phase-1 part of this cycle, in which the VIC accesses memory | 			// run the phase-1 part of this cycle, in which the VIC accesses memory | ||||||
| 			if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); | 			cycles_since_mos6560_update_++; | ||||||
|  |  | ||||||
| 			// run the phase-2 part of the cycle, which is whatever the 6502 said it should be | 			// run the phase-2 part of the cycle, which is whatever the 6502 said it should be | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; | 				uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; | ||||||
| 				if((address&0xfc00) == 0x9000) { | 				if((address&0xfc00) == 0x9000) { | ||||||
| 					if((address&0xff00) == 0x9000)	result &= mos6560_->get_register(address); | 					if((address&0xff00) == 0x9000) { | ||||||
|  | 						update_video(); | ||||||
|  | 						result &= mos6560_->get_register(address); | ||||||
|  | 					} | ||||||
| 					if((address&0xfc10) == 0x9010)	result &= user_port_via_.get_register(address); | 					if((address&0xfc10) == 0x9010)	result &= user_port_via_.get_register(address); | ||||||
| 					if((address&0xfc20) == 0x9020)	result &= keyboard_via_.get_register(address); | 					if((address&0xfc20) == 0x9020)	result &= keyboard_via_.get_register(address); | ||||||
| 				} | 				} | ||||||
| 				*value = result; | 				*value = result; | ||||||
|  |  | ||||||
| 				// This combined with the stuff below constitutes the fast tape hack. Performed here: if the | 				// Consider applying the fast tape hack. | ||||||
| 				// PC hits the start of the loop that just waits for an interesting tape interrupt to have |  | ||||||
| 				// occurred then skip both 6522s and the tape ahead to the next interrupt without any further |  | ||||||
| 				// CPU or 6560 costs. |  | ||||||
| 				if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { | 				if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { | ||||||
| 					if(address == 0xf7b2) { | 					if(address == 0xf7b2) { | ||||||
| 						// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. | 						// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. | ||||||
| @@ -551,28 +563,35 @@ class ConcreteMachine: | |||||||
| 						Storage::Tape::Commodore::Parser parser; | 						Storage::Tape::Commodore::Parser parser; | ||||||
| 						std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape()); | 						std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape()); | ||||||
|  |  | ||||||
| 						// serialise to wherever b2:b3 points | 						const uint64_t tape_position = tape_->get_tape()->get_offset(); | ||||||
| 						uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8); |  | ||||||
| 						if(header) { | 						if(header) { | ||||||
| 							header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); | 							// serialise to wherever b2:b3 points | ||||||
|  | 							const uint16_t tape_buffer_pointer = static_cast<uint16_t>(ram_[0xb2]) | static_cast<uint16_t>(ram_[0xb3] << 8); | ||||||
|  | 							header->serialise(&ram_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); | ||||||
|  | 							hold_tape_ = true; | ||||||
|  | 							printf("Found header\n"); | ||||||
| 						} else { | 						} else { | ||||||
| 							// no header found, so store end-of-tape | 							// no header found, so pretend this hack never interceded | ||||||
| 							user_basic_memory_[tape_buffer_pointer] = 0x05;	// i.e. end of tape | 							tape_->get_tape()->set_offset(tape_position); | ||||||
|  | 							hold_tape_ = false; | ||||||
|  | 							printf("Didn't find header\n"); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						// clear status and the verify flag | 						// clear status and the verify flag | ||||||
| 						user_basic_memory_[0x90] = 0; | 						ram_[0x90] = 0; | ||||||
| 						user_basic_memory_[0x93] = 0; | 						ram_[0x93] = 0; | ||||||
|  |  | ||||||
| 						*value = 0x0c;	// i.e. NOP abs | 						*value = 0x0c;	// i.e. NOP abs, to swallow the entire JSR | ||||||
| 					} else if(address == 0xf90b) { | 					} else if(address == 0xf90b) { | ||||||
| 						uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X)); | 						uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X)); | ||||||
| 						if(x == 0xe) { | 						if(x == 0xe) { | ||||||
| 							Storage::Tape::Commodore::Parser parser; | 							Storage::Tape::Commodore::Parser parser; | ||||||
| 							std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape()); | 							const uint64_t tape_position = tape_->get_tape()->get_offset(); | ||||||
|  | 							const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape()); | ||||||
|  | 							if(data) { | ||||||
| 								uint16_t start_address, end_address; | 								uint16_t start_address, end_address; | ||||||
| 							start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8)); | 								start_address = static_cast<uint16_t>(ram_[0xc1] | (ram_[0xc2] << 8)); | ||||||
| 							end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8)); | 								end_address = static_cast<uint16_t>(ram_[0xae] | (ram_[0xaf] << 8)); | ||||||
|  |  | ||||||
| 								// perform a via-processor_write_memory_map_ memcpy | 								// perform a via-processor_write_memory_map_ memcpy | ||||||
| 								uint8_t *data_ptr = data->data.data(); | 								uint8_t *data_ptr = data->data.data(); | ||||||
| @@ -586,7 +605,7 @@ class ConcreteMachine: | |||||||
| 								} | 								} | ||||||
|  |  | ||||||
| 								// set tape status, carry and flag | 								// set tape status, carry and flag | ||||||
| 							user_basic_memory_[0x90] |= 0x40; | 								ram_[0x90] |= 0x40; | ||||||
| 								uint8_t	flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags)); | 								uint8_t	flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags)); | ||||||
| 								flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt)); | 								flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt)); | ||||||
| 								m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags); | 								m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags); | ||||||
| @@ -595,14 +614,27 @@ class ConcreteMachine: | |||||||
| 								// ensure that the PC leaps to 0xfccf | 								// ensure that the PC leaps to 0xfccf | ||||||
| 								m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf); | 								m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf); | ||||||
| 								*value = 0xea;	// i.e. NOP implied | 								*value = 0xea;	// i.e. NOP implied | ||||||
|  | 								hold_tape_ = true; | ||||||
|  | 								printf("Found data\n"); | ||||||
|  | 							} else { | ||||||
|  | 								tape_->get_tape()->set_offset(tape_position); | ||||||
|  | 								hold_tape_ = false; | ||||||
|  | 								printf("Didn't find data\n"); | ||||||
|  | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				uint8_t *ram = processor_write_memory_map_[address >> 10]; | 				uint8_t *ram = processor_write_memory_map_[address >> 10]; | ||||||
| 				if(ram) ram[address & 0x3ff] = *value; | 				if(ram) { | ||||||
|  | 					update_video(); | ||||||
|  | 					ram[address & 0x3ff] = *value; | ||||||
|  | 				} | ||||||
| 				if((address&0xfc00) == 0x9000) { | 				if((address&0xfc00) == 0x9000) { | ||||||
| 					if((address&0xff00) == 0x9000)	mos6560_->set_register(address, *value); | 					if((address&0xff00) == 0x9000) { | ||||||
|  | 						update_video(); | ||||||
|  | 						mos6560_->set_register(address, *value); | ||||||
|  | 					} | ||||||
| 					if((address&0xfc10) == 0x9010)	user_port_via_.set_register(address, *value); | 					if((address&0xfc10) == 0x9010)	user_port_via_.set_register(address, *value); | ||||||
| 					if((address&0xfc20) == 0x9020)	keyboard_via_.set_register(address, *value); | 					if((address&0xfc20) == 0x9020)	keyboard_via_.set_register(address, *value); | ||||||
| 				} | 				} | ||||||
| @@ -616,21 +648,18 @@ class ConcreteMachine: | |||||||
| 					typer_.reset(); | 					typer_.reset(); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			tape_->run_for(Cycles(1)); | 			if(!tape_is_sleeping_ && !hold_tape_) tape_->run_for(Cycles(1)); | ||||||
| 			if(c1540_) c1540_->run_for(Cycles(1)); | 			if(c1540_) c1540_->run_for(Cycles(1)); | ||||||
|  |  | ||||||
| 			return Cycles(1); | 			return Cycles(1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		forceinline void flush() { | 		void flush() { | ||||||
|  | 			update_video(); | ||||||
| 			mos6560_->flush(); | 			mos6560_->flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles) override final { | 		void run_for(const Cycles cycles) override final { | ||||||
| 			if(needs_configuration_) { |  | ||||||
| 				needs_configuration_ = false; |  | ||||||
| 				configure_memory(); |  | ||||||
| 			} |  | ||||||
| 			m6502_.run_for(cycles); | 			m6502_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -638,7 +667,7 @@ class ConcreteMachine: | |||||||
| 			mos6560_.reset(new Vic6560()); | 			mos6560_.reset(new Vic6560()); | ||||||
| 			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. | 			// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set. | ||||||
| 			set_pal_6560(); | 			set_memory_map(commodore_target_.memory_model, commodore_target_.region); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void close_output() override final { | 		void close_output() override final { | ||||||
| @@ -682,21 +711,38 @@ class ConcreteMachine: | |||||||
| 				allow_fast_tape_hack_ = quickload; | 				allow_fast_tape_hack_ = quickload; | ||||||
| 				set_use_fast_tape(); | 				set_use_fast_tape(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			Configurable::Display display; | ||||||
|  | 			if(Configurable::get_display(selections_by_option, display)) { | ||||||
|  | 				set_video_signal_configurable(display); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Configurable::SelectionSet get_accurate_selections() override { | 		Configurable::SelectionSet get_accurate_selections() override { | ||||||
| 			Configurable::SelectionSet selection_set; | 			Configurable::SelectionSet selection_set; | ||||||
| 			Configurable::append_quick_load_tape_selection(selection_set, false); | 			Configurable::append_quick_load_tape_selection(selection_set, false); | ||||||
|  | 			Configurable::append_display_selection(selection_set, Configurable::Display::Composite); | ||||||
| 			return selection_set; | 			return selection_set; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Configurable::SelectionSet get_user_friendly_selections() override { | 		Configurable::SelectionSet get_user_friendly_selections() override { | ||||||
| 			Configurable::SelectionSet selection_set; | 			Configurable::SelectionSet selection_set; | ||||||
| 			Configurable::append_quick_load_tape_selection(selection_set, true); | 			Configurable::append_quick_load_tape_selection(selection_set, true); | ||||||
|  | 			Configurable::append_display_selection(selection_set, Configurable::Display::SVideo); | ||||||
| 			return selection_set; | 			return selection_set; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void set_component_is_sleeping(void *component, bool is_sleeping) override { | ||||||
|  | 			tape_is_sleeping_ = is_sleeping; | ||||||
|  | 			set_use_fast_tape(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		void update_video() { | ||||||
|  | 			mos6560_->run_for(cycles_since_mos6560_update_.flush()); | ||||||
|  | 		} | ||||||
|  | 		Analyser::Static::Commodore::Target commodore_target_; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		std::vector<uint8_t>  roms_[9]; | 		std::vector<uint8_t>  roms_[9]; | ||||||
| @@ -704,14 +750,11 @@ class ConcreteMachine: | |||||||
| 		std::vector<uint8_t>  character_rom_; | 		std::vector<uint8_t>  character_rom_; | ||||||
| 		std::vector<uint8_t>  basic_rom_; | 		std::vector<uint8_t>  basic_rom_; | ||||||
| 		std::vector<uint8_t>  kernel_rom_; | 		std::vector<uint8_t>  kernel_rom_; | ||||||
| 		uint8_t expansion_ram_[0x8000]; |  | ||||||
|  |  | ||||||
| 		uint8_t *rom_ = nullptr; | 		std::vector<uint8_t> rom_; | ||||||
| 		uint16_t rom_address_, rom_length_; | 		uint16_t rom_address_, rom_length_; | ||||||
|  | 		uint8_t ram_[0x8000]; | ||||||
| 		uint8_t user_basic_memory_[0x0400]; | 		uint8_t colour_ram_[0x0400]; | ||||||
| 		uint8_t screen_memory_[0x1000]; |  | ||||||
| 		uint8_t colour_memory_[0x0400]; |  | ||||||
|  |  | ||||||
| 		std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_; | 		std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_; | ||||||
|  |  | ||||||
| @@ -727,13 +770,10 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Region region_ = European; |  | ||||||
| 		MemorySize memory_size_ = MemorySize::Default; |  | ||||||
| 		bool needs_configuration_ = true; |  | ||||||
|  |  | ||||||
| 		Commodore::Vic20::KeyboardMapper keyboard_mapper_; | 		Commodore::Vic20::KeyboardMapper keyboard_mapper_; | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  |  | ||||||
|  | 		Cycles cycles_since_mos6560_update_; | ||||||
| 		std::unique_ptr<Vic6560> mos6560_; | 		std::unique_ptr<Vic6560> 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_; | ||||||
| @@ -746,10 +786,11 @@ class ConcreteMachine: | |||||||
| 		// Tape | 		// Tape | ||||||
| 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; | 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; | ||||||
| 		bool use_fast_tape_hack_ = false; | 		bool use_fast_tape_hack_ = false; | ||||||
|  | 		bool hold_tape_ = false; | ||||||
| 		bool allow_fast_tape_hack_ = false; | 		bool allow_fast_tape_hack_ = false; | ||||||
| 		bool is_running_at_zero_cost_ = false; | 		bool tape_is_sleeping_ = true; | ||||||
| 		void set_use_fast_tape() { | 		void set_use_fast_tape() { | ||||||
| 			use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_->has_tape(); | 			use_fast_tape_hack_ = !tape_is_sleeping_ && allow_fast_tape_hack_ && tape_->has_tape(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Disk | 		// Disk | ||||||
|   | |||||||
| @@ -10,48 +10,19 @@ | |||||||
| #define Vic20_hpp | #define Vic20_hpp | ||||||
|  |  | ||||||
| #include "../../../Configurable/Configurable.hpp" | #include "../../../Configurable/Configurable.hpp" | ||||||
| #include "../../ConfigurationTarget.hpp" |  | ||||||
| #include "../../CRTMachine.hpp" |  | ||||||
| #include "../../KeyboardMachine.hpp" |  | ||||||
| #include "../../JoystickMachine.hpp" |  | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace Vic20 { | namespace Vic20 { | ||||||
|  |  | ||||||
| enum MemorySize { |  | ||||||
| 	Default, |  | ||||||
| 	ThreeKB, |  | ||||||
| 	ThirtyTwoKB |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum Region { |  | ||||||
| 	American, |  | ||||||
| 	Danish, |  | ||||||
| 	Japanese, |  | ||||||
| 	European, |  | ||||||
| 	Swedish |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// @returns The options available for a Vic-20. | /// @returns The options available for a Vic-20. | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| class Machine: | class Machine { | ||||||
| 	public CRTMachine::Machine, |  | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public KeyboardMachine::Machine, |  | ||||||
| 	public JoystickMachine::Machine, |  | ||||||
| 	public Configurable::Device { |  | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns a Vic-20. | 		/// Creates and returns a Vic-20. | ||||||
| 		static Machine *Vic20(); | 		static Machine *Vic20(); | ||||||
|  |  | ||||||
| 		/// Sets the memory size of this Vic-20. |  | ||||||
| 		virtual void set_memory_size(MemorySize size) = 0; |  | ||||||
|  |  | ||||||
| 		/// Sets the region of this Vic-20. |  | ||||||
| 		virtual void set_region(Region region) = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ namespace ConfigurationTarget { | |||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		/// Instructs the machine to configure itself as described by @c target and insert the included media. | 		/// 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; | 		virtual void configure_as_target(const Analyser::Static::Target *target) = 0; | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Requests that the machine insert @c media as a modification to current state | 			Requests that the machine insert @c media as a modification to current state | ||||||
|   | |||||||
| @@ -8,6 +8,10 @@ | |||||||
|  |  | ||||||
| #include "Electron.hpp" | #include "Electron.hpp" | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../Configurable/StandardOptions.hpp" | ||||||
| @@ -16,6 +20,7 @@ | |||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  | #include "../../Analyser/Static/Acorn/Target.hpp" | ||||||
|  |  | ||||||
| #include "Interrupts.hpp" | #include "Interrupts.hpp" | ||||||
| #include "Keyboard.hpp" | #include "Keyboard.hpp" | ||||||
| @@ -28,12 +33,16 @@ namespace Electron { | |||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 	return Configurable::standard_options( | 	return Configurable::standard_options( | ||||||
| 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) | 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine, | ||||||
|  | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public Tape::Delegate, | 	public Tape::Delegate, | ||||||
| 	public Utility::TypeRecipient { | 	public Utility::TypeRecipient { | ||||||
| @@ -52,6 +61,10 @@ class ConcreteMachine: | |||||||
| 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final { | 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final { | ||||||
| 			uint8_t *target = nullptr; | 			uint8_t *target = nullptr; | ||||||
| 			switch(slot) { | 			switch(slot) { | ||||||
| @@ -115,28 +128,30 @@ 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 { | 		void configure_as_target(const Analyser::Static::Target *target) override final { | ||||||
| 			if(target.loading_command.length()) { | 			auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target); | ||||||
| 				type_string(target.loading_command); |  | ||||||
|  | 			if(target->loading_command.length()) { | ||||||
|  | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(target.acorn.should_shift_restart) { | 			if(acorn_target->should_shift_restart) { | ||||||
| 				shift_restart_counter_ = 1000000; | 				shift_restart_counter_ = 1000000; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(target.acorn.has_dfs || target.acorn.has_adfs) { | 			if(acorn_target->has_dfs || acorn_target->has_adfs) { | ||||||
| 				plus3_.reset(new Plus3); | 				plus3_.reset(new Plus3); | ||||||
|  |  | ||||||
| 				if(target.acorn.has_dfs) { | 				if(acorn_target->has_dfs) { | ||||||
| 					set_rom(ROMSlot0, dfs_, true); | 					set_rom(ROMSlot0, dfs_, true); | ||||||
| 				} | 				} | ||||||
| 				if(target.acorn.has_adfs) { | 				if(acorn_target->has_adfs) { | ||||||
| 					set_rom(ROMSlot4, adfs1_, true); | 					set_rom(ROMSlot4, adfs1_, true); | ||||||
| 					set_rom(ROMSlot5, adfs2_, true); | 					set_rom(ROMSlot5, adfs2_, true); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| @@ -439,7 +454,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			Configurable::Display display; | 			Configurable::Display display; | ||||||
| 			if(Configurable::get_display(selections_by_option, display)) { | 			if(Configurable::get_display(selections_by_option, display)) { | ||||||
| 				get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); | 				set_video_signal_configurable(display); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,9 +10,6 @@ | |||||||
| #define Electron_hpp | #define Electron_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "../KeyboardMachine.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -42,11 +39,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options(); | |||||||
| 	@discussion An instance of Electron::Machine represents the current state of an | 	@discussion An instance of Electron::Machine represents the current state of an | ||||||
| 	Acorn Electron. | 	Acorn Electron. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine { | ||||||
| 	public CRTMachine::Machine, |  | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public KeyboardMachine::Machine, |  | ||||||
| 	public Configurable::Device { |  | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,10 +15,14 @@ using namespace Electron; | |||||||
| SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | ||||||
| 	audio_queue_(audio_queue) {} | 	audio_queue_(audio_queue) {} | ||||||
|  |  | ||||||
|  | void SoundGenerator::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	volume_ = static_cast<unsigned int>(range / 2); | ||||||
|  | } | ||||||
|  |  | ||||||
| void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	if(is_enabled_) { | 	if(is_enabled_) { | ||||||
| 		while(number_of_samples--) { | 		while(number_of_samples--) { | ||||||
| 			*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192); | 			*target = static_cast<int16_t>((counter_ / (divider_+1)) * volume_); | ||||||
| 			target++; | 			target++; | ||||||
| 			counter_ = (counter_ + 1) % ((divider_+1) * 2); | 			counter_ = (counter_ + 1) % ((divider_+1) * 2); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -22,16 +22,19 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource { | |||||||
|  |  | ||||||
| 		void set_is_enabled(bool is_enabled); | 		void set_is_enabled(bool is_enabled); | ||||||
|  |  | ||||||
|  | 		static const unsigned int clock_rate_divider = 8; | ||||||
|  |  | ||||||
|  | 		// To satisfy ::SampleSource. | ||||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
| 		void skip_samples(std::size_t number_of_samples); | 		void skip_samples(std::size_t number_of_samples); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
| 		static const unsigned int clock_rate_divider = 8; |  | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| 		unsigned int counter_ = 0; | 		unsigned int counter_ = 0; | ||||||
| 		unsigned int divider_ = 0; | 		unsigned int divider_ = 0; | ||||||
| 		bool is_enabled_ = false; | 		bool is_enabled_ = false; | ||||||
|  | 		unsigned int volume_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,10 +16,10 @@ | |||||||
|  |  | ||||||
| namespace KeyboardMachine { | namespace KeyboardMachine { | ||||||
|  |  | ||||||
| class Machine: public Inputs::Keyboard::Delegate { | /*! | ||||||
| 	public: | 	Covers just the actions necessary to communicate keyboard state, as a purely abstract class. | ||||||
| 		Machine(); | */ | ||||||
|  | struct KeyActions { | ||||||
| 	/*! | 	/*! | ||||||
| 		Indicates that the key @c key has been either pressed or released, according to | 		Indicates that the key @c key has been either pressed or released, according to | ||||||
| 		the state of @c isPressed. | 		the state of @c isPressed. | ||||||
| @@ -30,6 +30,15 @@ class Machine: public Inputs::Keyboard::Delegate { | |||||||
| 		Instructs that all keys should now be treated as released. | 		Instructs that all keys should now be treated as released. | ||||||
| 	*/ | 	*/ | ||||||
| 	virtual void clear_all_keys() = 0; | 	virtual void clear_all_keys() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Describes the full functionality of being an emulated machine with a keyboard: not just being | ||||||
|  | 	able to receive key actions, but being able to vend a generic keyboard and a keyboard mapper. | ||||||
|  | */ | ||||||
|  | class Machine: public Inputs::Keyboard::Delegate, public KeyActions { | ||||||
|  | 	public: | ||||||
|  | 		Machine(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Causes the machine to attempt to type the supplied string. | 			Causes the machine to attempt to type the supplied string. | ||||||
|   | |||||||
| @@ -40,11 +40,13 @@ | |||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../Configurable/StandardOptions.hpp" | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/MSX/Target.hpp" | ||||||
|  |  | ||||||
| namespace MSX { | namespace MSX { | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 	return Configurable::standard_options( | 	return Configurable::standard_options( | ||||||
| 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) | 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -62,13 +64,17 @@ class AudioToggle: public Outputs::Speaker::SampleSource { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void set_sample_volume_range(std::int16_t range) { | ||||||
|  | 			volume_ = range; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void skip_samples(const std::size_t number_of_samples) {} | 		void skip_samples(const std::size_t number_of_samples) {} | ||||||
|  |  | ||||||
| 		void set_output(bool enabled) { | 		void set_output(bool enabled) { | ||||||
| 			if(is_enabled_ == enabled) return; | 			if(is_enabled_ == enabled) return; | ||||||
| 			is_enabled_ = enabled; | 			is_enabled_ = enabled; | ||||||
| 			audio_queue_.defer([=] { | 			audio_queue_.defer([=] { | ||||||
| 				level_ = enabled ? 4096 : 0; | 				level_ = enabled ? volume_ : 0; | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -78,7 +84,7 @@ class AudioToggle: public Outputs::Speaker::SampleSource { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		bool is_enabled_ = false; | 		bool is_enabled_ = false; | ||||||
| 		int16_t level_ = 0; | 		int16_t level_ = 0, volume_ = 0; | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -138,6 +144,13 @@ 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_sleep_observer(this); | ||||||
|  |  | ||||||
|  | 			// 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}); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override { | 		void setup_output(float aspect_ratio) override { | ||||||
| @@ -174,20 +187,22 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override { | 		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. | 			// Add a disk cartridge if any disks were supplied. | ||||||
| 			if(!target.media.disks.empty()) { | 			if(msx_target->has_disk_drive) { | ||||||
| 				map(2, 0, 0x4000, 0x2000); | 				map(2, 0, 0x4000, 0x2000); | ||||||
| 				unmap(2, 0x6000, 0x2000); | 				unmap(2, 0x6000, 0x2000); | ||||||
| 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); | 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Insert the media. | 			// Insert the media. | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
|  |  | ||||||
| 			// Type whatever has been requested. | 			// Type whatever has been requested. | ||||||
| 			if(target.loading_command.length()) { | 			if(target->loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -282,6 +297,17 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		// 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) { | ||||||
|  | 			// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, | ||||||
|  | 			// but otherwise runs without pause. | ||||||
|  | 			const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); | ||||||
|  | 			const HalfCycles total_length = addition + cycle.length; | ||||||
|  | 			time_since_vdp_update_ += total_length; | ||||||
|  | 			time_since_ay_update_ += total_length; | ||||||
|  | 			memory_slots_[0].cycles_since_update += total_length; | ||||||
|  | 			memory_slots_[1].cycles_since_update += total_length; | ||||||
|  | 			memory_slots_[2].cycles_since_update += total_length; | ||||||
|  | 			memory_slots_[3].cycles_since_update += total_length; | ||||||
|  |  | ||||||
| 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | ||||||
| 			switch(cycle.operation) { | 			switch(cycle.operation) { | ||||||
| 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
| @@ -459,23 +485,12 @@ class ConcreteMachine: | |||||||
| 			if(!tape_player_is_sleeping_) | 			if(!tape_player_is_sleeping_) | ||||||
| 				tape_player_.run_for(cycle.length.as_int()); | 				tape_player_.run_for(cycle.length.as_int()); | ||||||
|  |  | ||||||
| 			// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, |  | ||||||
| 			// but otherwise runs without pause. |  | ||||||
| 			const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); |  | ||||||
| 			const HalfCycles total_length = addition + cycle.length; |  | ||||||
|  |  | ||||||
| 			if(time_until_interrupt_ > 0) { | 			if(time_until_interrupt_ > 0) { | ||||||
| 				time_until_interrupt_ -= total_length; | 				time_until_interrupt_ -= total_length; | ||||||
| 				if(time_until_interrupt_ <= HalfCycles(0)) { | 				if(time_until_interrupt_ <= HalfCycles(0)) { | ||||||
| 					z80_.set_interrupt_line(true, time_until_interrupt_); | 					z80_.set_interrupt_line(true, time_until_interrupt_); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			time_since_vdp_update_ += total_length; |  | ||||||
| 			time_since_ay_update_ += total_length; |  | ||||||
| 			memory_slots_[0].cycles_since_update += total_length; |  | ||||||
| 			memory_slots_[1].cycles_since_update += total_length; |  | ||||||
| 			memory_slots_[2].cycles_since_update += total_length; |  | ||||||
| 			memory_slots_[3].cycles_since_update += total_length; |  | ||||||
| 			return addition; | 			return addition; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -554,7 +569,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			Configurable::Display display; | 			Configurable::Display display; | ||||||
| 			if(Configurable::get_display(selections_by_option, display)) { | 			if(Configurable::get_display(selections_by_option, display)) { | ||||||
| 				get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); | 				set_video_signal_configurable(display); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,10 @@ | |||||||
| #include "Microdisc.hpp" | #include "Microdisc.hpp" | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../Utility/MemoryFuzzer.hpp" | #include "../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
| @@ -26,6 +30,8 @@ | |||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../Configurable/StandardOptions.hpp" | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/Oric/Target.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -38,7 +44,7 @@ enum ROM { | |||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options() { | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
| 	return Configurable::standard_options( | 	return Configurable::standard_options( | ||||||
| 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) | 		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -184,6 +190,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine, | ||||||
|  | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public MOS::MOS6522::IRQDelegatePortHandler::Delegate, | 	public MOS::MOS6522::IRQDelegatePortHandler::Delegate, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| @@ -205,6 +215,10 @@ class ConcreteMachine: | |||||||
| 			Memory::Fuzz(ram_, sizeof(ram_)); | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 		// Obtains the system ROMs. | ||||||
| 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
| 			auto roms = roms_with_names( | 			auto roms = roms_with_names( | ||||||
| @@ -249,23 +263,21 @@ class ConcreteMachine: | |||||||
| 			use_fast_tape_hack_ = activate; | 			use_fast_tape_hack_ = activate; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_output_device(Outputs::CRT::OutputDevice output_device) { |  | ||||||
| 			video_output_->set_output_device(output_device); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// to satisfy ConfigurationTarget::Machine | 		// to satisfy ConfigurationTarget::Machine | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override final { | 		void configure_as_target(const Analyser::Static::Target *target) override final { | ||||||
| 			if(target.oric.has_microdisc) { | 			auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target); | ||||||
|  |  | ||||||
|  | 			if(oric_target->has_microdrive) { | ||||||
| 				microdisc_is_enabled_ = true; | 				microdisc_is_enabled_ = true; | ||||||
| 				microdisc_did_change_paging_flags(µdisc_); | 				microdisc_did_change_paging_flags(µdisc_); | ||||||
| 				microdisc_.set_delegate(this); | 				microdisc_.set_delegate(this); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(target.loading_command.length()) { | 			if(target->loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(target.oric.use_atmos_rom) { | 			if(oric_target->use_atmos_rom) { | ||||||
| 				std::memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_))); | 				std::memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_))); | ||||||
|  |  | ||||||
| 				is_using_basic11_ = true; | 				is_using_basic11_ = true; | ||||||
| @@ -281,7 +293,7 @@ class ConcreteMachine: | |||||||
| 				tape_speed_address_ = 0x67; | 				tape_speed_address_ = 0x67; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| @@ -376,7 +388,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			video_output_.reset(new VideoOutput(ram_)); | 			video_output_.reset(new VideoOutput(ram_)); | ||||||
| 			if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); | 			if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); | ||||||
| 			set_output_device(Outputs::CRT::OutputDevice::Monitor); | 			set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void close_output() override final { | 		void close_output() override final { | ||||||
| @@ -449,10 +461,14 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			Configurable::Display display; | 			Configurable::Display display; | ||||||
| 			if(Configurable::get_display(selections_by_option, display)) { | 			if(Configurable::get_display(selections_by_option, display)) { | ||||||
| 				set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); | 				set_video_signal_configurable(display); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void set_video_signal(Outputs::CRT::VideoSignal video_signal) override { | ||||||
|  | 			video_output_->set_video_signal(video_signal); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		Configurable::SelectionSet get_accurate_selections() override { | 		Configurable::SelectionSet get_accurate_selections() override { | ||||||
| 			Configurable::SelectionSet selection_set; | 			Configurable::SelectionSet selection_set; | ||||||
| 			Configurable::append_quick_load_tape_selection(selection_set, false); | 			Configurable::append_quick_load_tape_selection(selection_set, false); | ||||||
|   | |||||||
| @@ -10,9 +10,6 @@ | |||||||
| #define Oric_hpp | #define Oric_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "../KeyboardMachine.hpp" |  | ||||||
|  |  | ||||||
| namespace Oric { | namespace Oric { | ||||||
|  |  | ||||||
| @@ -22,11 +19,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options(); | |||||||
| /*! | /*! | ||||||
| 	Models an Oric 1/Atmos with or without a Microdisc. | 	Models an Oric 1/Atmos with or without a Microdisc. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine { | ||||||
| 	public CRTMachine::Machine, |  | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public KeyboardMachine::Machine, |  | ||||||
| 	public Configurable::Device { |  | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,13 +41,13 @@ 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_output_device(Outputs::CRT::OutputDevice::Television); | 	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(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) { | void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) { | ||||||
| 	output_device_ = output_device; | 	video_signal_ = video_signal; | ||||||
| 	crt_->set_output_device(output_device); | 	crt_->set_video_signal(video_signal); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) { | void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) { | ||||||
| @@ -129,7 +129,7 @@ void VideoOutput::run_for(const Cycles cycles) { | |||||||
| 				if(control_byte & 0x60) { | 				if(control_byte & 0x60) { | ||||||
| 					if(pixel_target_) { | 					if(pixel_target_) { | ||||||
| 						uint16_t colours[2]; | 						uint16_t colours[2]; | ||||||
| 						if(output_device_ == Outputs::CRT::OutputDevice::Monitor) { | 						if(video_signal_ == Outputs::CRT::VideoSignal::RGB) { | ||||||
| 							colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask); | 							colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask); | ||||||
| 							colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask); | 							colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask); | ||||||
| 						} else { | 						} else { | ||||||
| @@ -183,7 +183,7 @@ void VideoOutput::run_for(const Cycles cycles) { | |||||||
| 						pixel_target_[0] = pixel_target_[1] = | 						pixel_target_[0] = pixel_target_[1] = | ||||||
| 						pixel_target_[2] = pixel_target_[3] = | 						pixel_target_[2] = pixel_target_[3] = | ||||||
| 						pixel_target_[4] = pixel_target_[5] = | 						pixel_target_[4] = pixel_target_[5] = | ||||||
| 							(output_device_ == Outputs::CRT::OutputDevice::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask]; | 							(video_signal_ == Outputs::CRT::VideoSignal::RGB) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask]; | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				if(pixel_target_) pixel_target_ += 6; | 				if(pixel_target_) pixel_target_ += 6; | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class VideoOutput { | |||||||
| 		Outputs::CRT::CRT *get_crt(); | 		Outputs::CRT::CRT *get_crt(); | ||||||
| 		void run_for(const Cycles cycles); | 		void run_for(const Cycles cycles); | ||||||
| 		void set_colour_rom(const std::vector<uint8_t> &rom); | 		void set_colour_rom(const std::vector<uint8_t> &rom); | ||||||
| 		void set_output_device(Outputs::CRT::OutputDevice output_device); | 		void set_video_signal(Outputs::CRT::VideoSignal output_device); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		uint8_t *ram_; | 		uint8_t *ram_; | ||||||
| @@ -33,7 +33,7 @@ class VideoOutput { | |||||||
| 		// Output target and device | 		// Output target and device | ||||||
| 		uint16_t *pixel_target_; | 		uint16_t *pixel_target_; | ||||||
| 		uint16_t colour_forms_[8]; | 		uint16_t colour_forms_[8]; | ||||||
| 		Outputs::CRT::OutputDevice output_device_; | 		Outputs::CRT::VideoSignal video_signal_; | ||||||
|  |  | ||||||
| 		// Registers | 		// Registers | ||||||
| 		uint8_t ink_, paper_; | 		uint8_t ink_, paper_; | ||||||
|   | |||||||
| @@ -22,10 +22,10 @@ | |||||||
|  |  | ||||||
| namespace { | 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; | 	::Machine::DynamicMachine *machine = nullptr; | ||||||
| 	switch(target.machine) { | 	switch(target->machine) { | ||||||
| 		case Analyser::Machine::AmstradCPC:		machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());				break; | 		case Analyser::Machine::AmstradCPC:		machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());				break; | ||||||
| 		case Analyser::Machine::Atari2600:		machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());				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::ColecoVision:	machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision());	break; | ||||||
| @@ -71,7 +71,7 @@ namespace { | |||||||
| 	if(targets.size() > 1) { | 	if(targets.size() > 1) { | ||||||
| 		std::vector<std::unique_ptr<Machine::DynamicMachine>> machines; | 		std::vector<std::unique_ptr<Machine::DynamicMachine>> machines; | ||||||
| 		for(const auto &target: targets) { | 		for(const auto &target: targets) { | ||||||
| 			machines.emplace_back(MachineForTarget(*target, rom_fetcher, error)); | 			machines.emplace_back(MachineForTarget(target.get(), rom_fetcher, error)); | ||||||
|  |  | ||||||
| 			// Exit early if any errors have occurred. | 			// Exit early if any errors have occurred. | ||||||
| 			if(error != Error::None) { | 			if(error != Error::None) { | ||||||
| @@ -79,11 +79,17 @@ namespace { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// If a multimachine would just instantly collapse the list to a single machine, do | ||||||
|  | 		// so without the ongoing baggage of a multimachine. | ||||||
|  | 		if(Analyser::Dynamic::MultiMachine::would_collapse(machines)) { | ||||||
|  | 			return machines.front().release(); | ||||||
|  | 		} else { | ||||||
| 			return new Analyser::Dynamic::MultiMachine(std::move(machines)); | 			return new Analyser::Dynamic::MultiMachine(std::move(machines)); | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// There's definitely exactly one target. | 	// There's definitely exactly one target. | ||||||
| 	return MachineForTarget(*targets.front(), rom_fetcher, error); | 	return MachineForTarget(targets.front().get(), rom_fetcher, error); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) { | std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) { | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ class CharacterMapper { | |||||||
| */ | */ | ||||||
| class Typer { | class Typer { | ||||||
| 	public: | 	public: | ||||||
| 		class Delegate: public KeyboardMachine::Machine { | 		class Delegate: public KeyboardMachine::KeyActions { | ||||||
| 			public: | 			public: | ||||||
| 				virtual void typer_reset(Typer *typer) = 0; | 				virtual void typer_reset(Typer *typer) = 0; | ||||||
| 		}; | 		}; | ||||||
| @@ -56,7 +56,6 @@ class Typer { | |||||||
|  |  | ||||||
| 		void run_for(const HalfCycles duration); | 		void run_for(const HalfCycles duration); | ||||||
| 		bool type_next_character(); | 		bool type_next_character(); | ||||||
|  |  | ||||||
| 		bool is_completed(); | 		bool is_completed(); | ||||||
|  |  | ||||||
| 		const char BeginString = 0x02;	// i.e. ASCII start of text | 		const char BeginString = 0x02;	// i.e. ASCII start of text | ||||||
| @@ -81,7 +80,7 @@ class Typer { | |||||||
| 	which may or may not want to nominate an initial delay and typing frequency. | 	which may or may not want to nominate an initial delay and typing frequency. | ||||||
| */ | */ | ||||||
| class TypeRecipient: public Typer::Delegate { | class TypeRecipient: public Typer::Delegate { | ||||||
| 	public: | 	protected: | ||||||
| 		/// Attaches a typer to this class that will type @c string using @c character_mapper as a source. | 		/// Attaches a typer to this class that will type @c string using @c character_mapper as a source. | ||||||
| 		void add_typer(const std::string &string, std::unique_ptr<CharacterMapper> character_mapper) { | 		void add_typer(const std::string &string, std::unique_ptr<CharacterMapper> character_mapper) { | ||||||
| 			typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this)); | 			typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this)); | ||||||
| @@ -101,7 +100,6 @@ class TypeRecipient: public Typer::Delegate { | |||||||
| 			typer_ = nullptr; | 			typer_ = nullptr; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	protected: |  | ||||||
| 		virtual HalfCycles get_typer_delay() { return HalfCycles(0); } | 		virtual HalfCycles get_typer_delay() { return HalfCycles(0); } | ||||||
| 		virtual HalfCycles get_typer_frequency() { return HalfCycles(0); } | 		virtual HalfCycles get_typer_frequency() { return HalfCycles(0); } | ||||||
| 		std::unique_ptr<Typer> typer_; | 		std::unique_ptr<Typer> typer_; | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ Video::Video() : | |||||||
| 		"}"); | 		"}"); | ||||||
|  |  | ||||||
| 	// 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_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); | 	crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,11 @@ | |||||||
|  |  | ||||||
| #include "ZX8081.hpp" | #include "ZX8081.hpp" | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Components/AY38910/AY38910.hpp" | ||||||
| #include "../../Processors/Z80/Z80.hpp" | #include "../../Processors/Z80/Z80.hpp" | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
| #include "../../Storage/Tape/Parsers/ZX8081.hpp" | #include "../../Storage/Tape/Parsers/ZX8081.hpp" | ||||||
| @@ -18,6 +23,10 @@ | |||||||
| #include "../Utility/MemoryFuzzer.hpp" | #include "../Utility/MemoryFuzzer.hpp" | ||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/ZX8081/Target.hpp" | ||||||
|  |  | ||||||
| #include "Keyboard.hpp" | #include "Keyboard.hpp" | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
| @@ -31,6 +40,11 @@ namespace { | |||||||
| 	const unsigned int ZX8081ClockRate = 3250000; | 	const unsigned int ZX8081ClockRate = 3250000; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: | ||||||
|  | //	Quiksilva sound support: | ||||||
|  | //  7FFFh.W   PSG index | ||||||
|  | //  7FFEh.R/W PSG data | ||||||
|  |  | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
|  |  | ||||||
| enum ROMType: uint8_t { | enum ROMType: uint8_t { | ||||||
| @@ -44,20 +58,32 @@ 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 ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine, | ||||||
|  | 	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() : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			tape_player_(ZX8081ClockRate) { | 			tape_player_(ZX8081ClockRate), | ||||||
|  | 			ay_(audio_queue_), | ||||||
|  | 			speaker_(ay_) { | ||||||
| 			set_clock_rate(ZX8081ClockRate); | 			set_clock_rate(ZX8081ClockRate); | ||||||
|  | 			speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f); | ||||||
| 			clear_all_keys(); | 			clear_all_keys(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
| 			HalfCycles previous_counter = horizontal_counter_; | 			const HalfCycles previous_counter = horizontal_counter_; | ||||||
| 			horizontal_counter_ += cycle.length; | 			horizontal_counter_ += cycle.length; | ||||||
|  | 			time_since_ay_update_ += cycle.length; | ||||||
|  |  | ||||||
| 			if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) { | 			if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) { | ||||||
| 				video_->run_for(vsync_start_ - previous_counter); | 				video_->run_for(vsync_start_ - previous_counter); | ||||||
| @@ -94,7 +120,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 				return Cycles(0); | 				return Cycles(0); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			uint16_t address = cycle.address ? *cycle.address : 0; | 			const uint16_t address = cycle.address ? *cycle.address : 0; | ||||||
| 			bool is_opcode_read = false; | 			bool is_opcode_read = false; | ||||||
| 			switch(cycle.operation) { | 			switch(cycle.operation) { | ||||||
| 				case CPU::Z80::PartialMachineCycle::Output: | 				case CPU::Z80::PartialMachineCycle::Output: | ||||||
| @@ -106,6 +132,15 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 						if(vsync_) line_counter_ = 0; | 						if(vsync_) line_counter_ = 0; | ||||||
| 						set_vsync(false); | 						set_vsync(false); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					// The below emulates the ZonX AY expansion device. | ||||||
|  | 					if(is_zx81) { | ||||||
|  | 						if((address&0xef) == 0xcf) { | ||||||
|  | 							ay_set_register(*cycle.value); | ||||||
|  | 						} else if((address&0xef) == 0x0f) { | ||||||
|  | 							ay_set_data(*cycle.value); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case CPU::Z80::PartialMachineCycle::Input: { | 				case CPU::Z80::PartialMachineCycle::Input: { | ||||||
| @@ -121,6 +156,13 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
|  |  | ||||||
| 						value &= ~(tape_player_.get_input() ? 0x00 : 0x80); | 						value &= ~(tape_player_.get_input() ? 0x00 : 0x80); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					// The below emulates the ZonX AY expansion device. | ||||||
|  | 					if(is_zx81) { | ||||||
|  | 						if((address&0xef) == 0x0f) { | ||||||
|  | 							value &= ay_read_data(); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
| 					*cycle.value = value; | 					*cycle.value = value; | ||||||
| 				} break; | 				} break; | ||||||
|  |  | ||||||
| @@ -144,7 +186,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
| 					if(has_latched_video_byte_) { | 					if(has_latched_video_byte_) { | ||||||
| 						std::size_t char_address = static_cast<std::size_t>((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_); | 						std::size_t char_address = static_cast<std::size_t>((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_); | ||||||
| 						uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff; | 						const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff; | ||||||
| 						if(char_address < ram_base_) { | 						if(char_address < ram_base_) { | ||||||
| 							latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask; | 							latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask; | ||||||
| 						} else { | 						} else { | ||||||
| @@ -159,10 +201,10 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
| 					// Check for use of the fast tape hack. | 					// Check for use of the fast tape hack. | ||||||
| 					if(use_fast_tape_hack_ && address == tape_trap_address_) { | 					if(use_fast_tape_hack_ && address == tape_trap_address_) { | ||||||
| 						uint64_t prior_offset = tape_player_.get_tape()->get_offset(); | 						const uint64_t prior_offset = tape_player_.get_tape()->get_offset(); | ||||||
| 						int next_byte = parser_.get_next_byte(tape_player_.get_tape()); | 						const int next_byte = parser_.get_next_byte(tape_player_.get_tape()); | ||||||
| 						if(next_byte != -1) { | 						if(next_byte != -1) { | ||||||
| 							uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL); | 							const uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL); | ||||||
| 							ram_[hl & ram_mask_] = static_cast<uint8_t>(next_byte); | 							ram_[hl & ram_mask_] = static_cast<uint8_t>(next_byte); | ||||||
| 							*cycle.value = 0x00; | 							*cycle.value = 0x00; | ||||||
| 							z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1); | 							z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1); | ||||||
| @@ -187,7 +229,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 					if(address < ram_base_) { | 					if(address < ram_base_) { | ||||||
| 						*cycle.value = rom_[address & rom_mask_]; | 						*cycle.value = rom_[address & rom_mask_]; | ||||||
| 					} else { | 					} else { | ||||||
| 						uint8_t value = ram_[address & ram_mask_]; | 						const uint8_t value = ram_[address & ram_mask_]; | ||||||
|  |  | ||||||
| 						// If this is an M1 cycle reading from above the 32kb mark and HALT is not | 						// If this is an M1 cycle reading from above the 32kb mark and HALT is not | ||||||
| 						// currently active, latch for video output and return a NOP. Otherwise, | 						// currently active, latch for video output and return a NOP. Otherwise, | ||||||
| @@ -210,12 +252,15 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(typer_) typer_->run_for(cycle.length); | 			if(typer_) typer_->run_for(cycle.length); | ||||||
|  |  | ||||||
| 			return HalfCycles(0); | 			return HalfCycles(0); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		forceinline void flush() { | 		forceinline void flush() { | ||||||
| 			video_->flush(); | 			video_->flush(); | ||||||
|  | 			if(is_zx81) { | ||||||
|  | 				update_audio(); | ||||||
|  | 				audio_queue_.perform(); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override final { | 		void setup_output(float aspect_ratio) override final { | ||||||
| @@ -231,15 +276,16 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Outputs::Speaker::Speaker *get_speaker() override final { | 		Outputs::Speaker::Speaker *get_speaker() override final { | ||||||
| 			return nullptr; | 			return is_zx81 ? &speaker_ : nullptr; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles) override final { | 		void run_for(const Cycles cycles) override final { | ||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override final { | 		void configure_as_target(const Analyser::Static::Target *target) override final { | ||||||
| 			is_zx81_ = target.zx8081.isZX81; | 			auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target); | ||||||
|  | 			is_zx81_ = zx8081_target->is_ZX81; | ||||||
| 			if(is_zx81_) { | 			if(is_zx81_) { | ||||||
| 				rom_ = zx81_rom_; | 				rom_ = zx81_rom_; | ||||||
| 				tape_trap_address_ = 0x37c; | 				tape_trap_address_ = 0x37c; | ||||||
| @@ -249,7 +295,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 				automatic_tape_motor_start_address_ = 0x0340; | 				automatic_tape_motor_start_address_ = 0x0340; | ||||||
| 				automatic_tape_motor_end_address_ = 0x03c3; | 				automatic_tape_motor_end_address_ = 0x03c3; | ||||||
| 			} else { | 			} else { | ||||||
| 				rom_ = zx80_rom_; | 				rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_; | ||||||
| 				tape_trap_address_ = 0x220; | 				tape_trap_address_ = 0x220; | ||||||
| 				tape_return_address_ = 0x248; | 				tape_return_address_ = 0x248; | ||||||
| 				vsync_start_ = HalfCycles(26); | 				vsync_start_ = HalfCycles(26); | ||||||
| @@ -259,18 +305,18 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); | 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); | ||||||
|  |  | ||||||
| 			switch(target.zx8081.memory_model) { | 			switch(zx8081_target->memory_model) { | ||||||
| 				case Analyser::Static::ZX8081MemoryModel::Unexpanded: | 				case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded: | ||||||
| 					ram_.resize(1024); | 					ram_.resize(1024); | ||||||
| 					ram_base_ = 16384; | 					ram_base_ = 16384; | ||||||
| 					ram_mask_ = 1023; | 					ram_mask_ = 1023; | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::ZX8081MemoryModel::SixteenKB: | 				case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB: | ||||||
| 					ram_.resize(16384); | 					ram_.resize(16384); | ||||||
| 					ram_base_ = 16384; | 					ram_base_ = 16384; | ||||||
| 					ram_mask_ = 16383; | 					ram_mask_ = 16383; | ||||||
| 				break; | 				break; | ||||||
| 				case Analyser::Static::ZX8081MemoryModel::SixtyFourKB: | 				case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB: | ||||||
| 					ram_.resize(65536); | 					ram_.resize(65536); | ||||||
| 					ram_base_ = 8192; | 					ram_base_ = 8192; | ||||||
| 					ram_mask_ = 65535; | 					ram_mask_ = 65535; | ||||||
| @@ -278,11 +324,11 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 			Memory::Fuzz(ram_); | 			Memory::Fuzz(ram_); | ||||||
|  |  | ||||||
| 			if(target.loading_command.length()) { | 			if(target->loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target->loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target.media); | 			insert_media(target->media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| @@ -301,7 +347,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 		// Obtains the system ROMs. | ||||||
| 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
| 			auto roms = roms_with_names( | 			const auto roms = roms_with_names( | ||||||
| 				"ZX8081", | 				"ZX8081", | ||||||
| 				{ | 				{ | ||||||
| 					"zx80.rom",	"zx81.rom", | 					"zx80.rom",	"zx81.rom", | ||||||
| @@ -320,8 +366,8 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Keyboard | 		// MARK: - Keyboard | ||||||
| 		void set_key_state(uint16_t key, bool isPressed) override final { | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
| 			if(isPressed) | 			if(is_pressed) | ||||||
| 				key_states_[key >> 8] &= static_cast<uint8_t>(~key); | 				key_states_[key >> 8] &= static_cast<uint8_t>(~key); | ||||||
| 			else | 			else | ||||||
| 				key_states_[key >> 8] |= static_cast<uint8_t>(key); | 				key_states_[key >> 8] |= static_cast<uint8_t>(key); | ||||||
| @@ -443,6 +489,34 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		inline void update_sync() { | 		inline void update_sync() { | ||||||
| 			video_->set_sync(vsync_ || hsync_); | 			video_->set_sync(vsync_ || hsync_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Audio | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		GI::AY38910::AY38910 ay_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_; | ||||||
|  | 		HalfCycles time_since_ay_update_; | ||||||
|  | 		inline void ay_set_register(uint8_t value) { | ||||||
|  | 			update_audio(); | ||||||
|  | 			ay_.set_control_lines(GI::AY38910::BC1); | ||||||
|  | 			ay_.set_data_input(value); | ||||||
|  | 			ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
|  | 		} | ||||||
|  | 		inline void ay_set_data(uint8_t value) { | ||||||
|  | 			update_audio(); | ||||||
|  | 			ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); | ||||||
|  | 			ay_.set_data_input(value); | ||||||
|  | 			ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
|  | 		} | ||||||
|  | 		inline uint8_t ay_read_data() { | ||||||
|  | 			update_audio(); | ||||||
|  | 			ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); | ||||||
|  | 			const uint8_t value = ay_.get_data_output(); | ||||||
|  | 			ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
|  | 			return value; | ||||||
|  | 		} | ||||||
|  | 		inline void update_audio() { | ||||||
|  | 			speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); | ||||||
|  | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -450,9 +524,11 @@ 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_hint) { | ||||||
|  | 	const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint); | ||||||
|  |  | ||||||
| 	// Instantiate the correct type of machine. | 	// Instantiate the correct type of machine. | ||||||
| 	if(target_hint.zx8081.isZX81) | 	if(hint->is_ZX81) | ||||||
| 		return new ZX8081::ConcreteMachine<true>(); | 		return new ZX8081::ConcreteMachine<true>(); | ||||||
| 	else | 	else | ||||||
| 		return new ZX8081::ConcreteMachine<false>(); | 		return new ZX8081::ConcreteMachine<false>(); | ||||||
|   | |||||||
| @@ -10,24 +10,18 @@ | |||||||
| #define ZX8081_hpp | #define ZX8081_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "../KeyboardMachine.hpp" |  | ||||||
|  |  | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
|  |  | ||||||
| /// @returns The options available for a ZX80 or ZX81. | /// @returns The options available for a ZX80 or ZX81. | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| class Machine: | class Machine { | ||||||
| 	public CRTMachine::Machine, |  | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public KeyboardMachine::Machine, |  | ||||||
| 	public Configurable::Device { |  | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		static Machine *ZX8081(const Analyser::Static::Target &target_hint); | 		static Machine *ZX8081(const Analyser::Static::Target *target_hint); | ||||||
|  |  | ||||||
| 		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; | ||||||
|   | |||||||
| @@ -157,7 +157,6 @@ | |||||||
| 		4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; }; | 		4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; }; | ||||||
| 		4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; }; | 		4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; }; | ||||||
| 		4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; }; | 		4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; }; | ||||||
| 		4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */; }; |  | ||||||
| 		4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; }; | 		4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; }; | ||||||
| 		4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; }; | 		4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; }; | ||||||
| 		4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; }; | 		4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; }; | ||||||
| @@ -280,7 +279,6 @@ | |||||||
| 		4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; | 		4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; | ||||||
| 		4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; | 		4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; | ||||||
| 		4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; }; | 		4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; }; | ||||||
| 		4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */; }; |  | ||||||
| 		4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; }; | 		4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; }; | ||||||
| 		4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; }; | 		4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; }; | ||||||
| 		4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; }; | 		4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; }; | ||||||
| @@ -293,6 +291,8 @@ | |||||||
| 		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 */; }; | ||||||
| @@ -605,6 +605,7 @@ | |||||||
| 		4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; | 		4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; | ||||||
| 		4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; }; | 		4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; }; | ||||||
| 		4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; }; | 		4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; }; | ||||||
|  | 		4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; }; | ||||||
| 		4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; | 		4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; | ||||||
| 		4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; | 		4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; | ||||||
| 		4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; | 		4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; | ||||||
| @@ -723,8 +724,6 @@ | |||||||
| 		4B2A53971D117D36003C6002 /* KeyCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; }; | 		4B2A53971D117D36003C6002 /* KeyCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; }; | ||||||
| 		4B2A53991D117D36003C6002 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; }; | 		4B2A53991D117D36003C6002 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; }; | ||||||
| 		4B2A539A1D117D36003C6002 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = "<group>"; }; | 		4B2A539A1D117D36003C6002 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = "<group>"; }; | ||||||
| 		4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; }; |  | ||||||
| 		4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; }; |  | ||||||
| 		4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; }; | 		4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; }; | ||||||
| 		4B2B3A471F9B8FA70062DABF /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; }; | 		4B2B3A471F9B8FA70062DABF /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; }; | 		4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -757,7 +756,6 @@ | |||||||
| 		4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; }; | 		4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; }; | 		4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; }; | 		4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B38F34E1F2EC6BA00D9235D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AmstradCPCOptions.xib"; sourceTree = SOURCE_ROOT; }; |  | ||||||
| 		4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; }; | 		4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; }; | 		4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; }; | 		4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; }; | ||||||
| @@ -780,6 +778,7 @@ | |||||||
| 		4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; | 		4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; | 		4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; }; | 		4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B449C942063389900A095C8 /* TimeTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimeTypes.hpp; sourceTree = "<group>"; }; | ||||||
| 		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>"; }; | ||||||
| @@ -962,7 +961,6 @@ | |||||||
| 		4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; | 		4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; }; | 		4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadCompositeOptions.xib"; sourceTree = SOURCE_ROOT; }; | 		4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadCompositeOptions.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4B8FE21A1DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Options.xib"; sourceTree = SOURCE_ROOT; }; |  | ||||||
| 		4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600OptionsPanel.swift; sourceTree = "<group>"; }; | 		4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600OptionsPanel.swift; sourceTree = "<group>"; }; | ||||||
| 		4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePanel.swift; sourceTree = "<group>"; }; | 		4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePanel.swift; sourceTree = "<group>"; }; | ||||||
| 		4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; }; | 		4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; }; | ||||||
| @@ -977,9 +975,11 @@ | |||||||
| 		4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; }; | 		4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; }; | ||||||
| 		4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; }; | 		4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; }; | ||||||
| 		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>"; }; | ||||||
| 		4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20OptionsPanel.swift; 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>"; }; | ||||||
| 		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 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1327,9 +1327,16 @@ | |||||||
| 		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>"; }; | ||||||
|  | 		4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; }; | 		4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; }; | 		4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; }; | 		4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; }; | ||||||
|  | 		4BE3231220532443006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BE32313205327D7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BE32314205328FF006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; }; | 		4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; }; | 		4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; }; | 		4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1368,7 +1375,6 @@ | |||||||
| 		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>"; }; | ||||||
| 		4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; }; | 		4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSZX8081+Instantiation.h"; sourceTree = "<group>"; }; |  | ||||||
| 		4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; }; | 		4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; }; | 		4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = "<group>"; }; | 		4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1574,11 +1580,8 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B2A53991D117D36003C6002 /* CSAtari2600.h */, | 				4B2A53991D117D36003C6002 /* CSAtari2600.h */, | ||||||
| 				4B2A539D1D117D36003C6002 /* CSVic20.h */, |  | ||||||
| 				4B14978D1EE4B4D200CE2596 /* CSZX8081.h */, | 				4B14978D1EE4B4D200CE2596 /* CSZX8081.h */, | ||||||
| 				4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */, |  | ||||||
| 				4B2A539A1D117D36003C6002 /* CSAtari2600.mm */, | 				4B2A539A1D117D36003C6002 /* CSAtari2600.mm */, | ||||||
| 				4B2A539E1D117D36003C6002 /* CSVic20.mm */, |  | ||||||
| 				4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */, | 				4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */, | ||||||
| 			); | 			); | ||||||
| 			path = Wrappers; | 			path = Wrappers; | ||||||
| @@ -1874,14 +1877,12 @@ | |||||||
| 				4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */, | 				4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */, | ||||||
| 				4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, | 				4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, | ||||||
| 				4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, | 				4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, | ||||||
| 				4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */, |  | ||||||
| 				4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */, | 				4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */, | ||||||
| 				4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */, |  | ||||||
| 				4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, | 				4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, | ||||||
| 				4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */, |  | ||||||
| 				4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, | 				4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, | ||||||
| 				4B2A332B1DB86821002876E3 /* OricOptions.xib */, | 				4B2A332B1DB86821002876E3 /* OricOptions.xib */, | ||||||
| 				4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */, | 				4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */, | ||||||
|  | 				4BD61662206B2AC700236112 /* QuickLoadOptions.xib */, | ||||||
| 				4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */, | 				4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */, | ||||||
| 			); | 			); | ||||||
| 			path = Documents; | 			path = Documents; | ||||||
| @@ -2164,6 +2165,7 @@ | |||||||
| 				4B8944EE201967B4007DE474 /* File.hpp */, | 				4B8944EE201967B4007DE474 /* File.hpp */, | ||||||
| 				4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
| 				4B8944EF201967B4007DE474 /* Tape.hpp */, | 				4B8944EF201967B4007DE474 /* Tape.hpp */, | ||||||
|  | 				4BE32313205327D7006EF799 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Acorn; | 			path = Acorn; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2173,6 +2175,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */, | 				4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */, | ||||||
| 				4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
|  | 				4BE3231720532CC0006EF799 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Atari; | 			path = Atari; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2184,6 +2187,7 @@ | |||||||
| 				4B8944F9201967B4007DE474 /* Tape.cpp */, | 				4B8944F9201967B4007DE474 /* Tape.cpp */, | ||||||
| 				4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
| 				4B8944F8201967B4007DE474 /* Tape.hpp */, | 				4B8944F8201967B4007DE474 /* Tape.hpp */, | ||||||
|  | 				4BE3231620532BED006EF799 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Oric; | 			path = Oric; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2199,6 +2203,7 @@ | |||||||
| 				4B8944FE201967B4007DE474 /* File.hpp */, | 				4B8944FE201967B4007DE474 /* File.hpp */, | ||||||
| 				4B8944FD201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B8944FD201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
| 				4B8944FF201967B4007DE474 /* Tape.hpp */, | 				4B8944FF201967B4007DE474 /* Tape.hpp */, | ||||||
|  | 				4BE3231520532AA7006EF799 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Commodore; | 			path = Commodore; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2208,6 +2213,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B894506201967B4007DE474 /* StaticAnalyser.cpp */, | 				4B894506201967B4007DE474 /* StaticAnalyser.cpp */, | ||||||
| 				4B894505201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B894505201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
|  | 				4BE3231220532443006EF799 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = ZX8081; | 			path = ZX8081; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2234,6 +2240,7 @@ | |||||||
| 				4B047075201ABC180047AB0D /* Cartridge.hpp */, | 				4B047075201ABC180047AB0D /* Cartridge.hpp */, | ||||||
| 				4B894510201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B894510201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
| 				4B894511201967B4007DE474 /* Tape.hpp */, | 				4B894511201967B4007DE474 /* Tape.hpp */, | ||||||
|  | 				4BA141C12073100800A31EC9 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = MSX; | 			path = MSX; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2243,6 +2250,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B894516201967B4007DE474 /* StaticAnalyser.cpp */, | 				4B894516201967B4007DE474 /* StaticAnalyser.cpp */, | ||||||
| 				4B894515201967B4007DE474 /* StaticAnalyser.hpp */, | 				4B894515201967B4007DE474 /* StaticAnalyser.hpp */, | ||||||
|  | 				4BE32314205328FF006EF799 /* Target.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = AmstradCPC; | 			path = AmstradCPC; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2257,6 +2265,16 @@ | |||||||
| 			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 = ( | ||||||
| @@ -2620,11 +2638,12 @@ | |||||||
| 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | ||||||
| 				4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, | 				4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, | ||||||
| 				4BB73EA81B587A5100552FC2 /* Assets.xcassets */, | 				4BB73EA81B587A5100552FC2 /* Assets.xcassets */, | ||||||
| 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, |  | ||||||
| 				4B2A538F1D117D36003C6002 /* Audio */, | 				4B2A538F1D117D36003C6002 /* Audio */, | ||||||
| 				4B643F3D1D77B88000D431D6 /* Document Controller */, | 				4B643F3D1D77B88000D431D6 /* Document Controller */, | ||||||
| 				4B55CE551C3B7D360093A61B /* Documents */, | 				4B55CE551C3B7D360093A61B /* Documents */, | ||||||
| 				4B2A53921D117D36003C6002 /* Machine */, | 				4B2A53921D117D36003C6002 /* Machine */, | ||||||
|  | 				4BA141C02072E8B300A31EC9 /* MachinePicker */, | ||||||
|  | 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, | ||||||
| 				4BE5F85A1C3E1C2500C43F01 /* Resources */, | 				4BE5F85A1C3E1C2500C43F01 /* Resources */, | ||||||
| 				4BD5F1961D1352A000631CD1 /* Updater */, | 				4BD5F1961D1352A000631CD1 /* Updater */, | ||||||
| 				4B55CE5A1C3B7D6F0093A61B /* Views */, | 				4B55CE5A1C3B7D6F0093A61B /* Views */, | ||||||
| @@ -2960,6 +2979,7 @@ | |||||||
| 				4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, | 				4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, | ||||||
| 				4BB06B211F316A3F00600C7A /* ForceInline.hpp */, | 				4BB06B211F316A3F00600C7A /* ForceInline.hpp */, | ||||||
| 				4BB146C61F49D7D700253439 /* Sleeper.hpp */, | 				4BB146C61F49D7D700253439 /* Sleeper.hpp */, | ||||||
|  | 				4B449C942063389900A095C8 /* TimeTypes.hpp */, | ||||||
| 			); | 			); | ||||||
| 			name = ClockReceiver; | 			name = ClockReceiver; | ||||||
| 			path = ../../ClockReceiver; | 			path = ../../ClockReceiver; | ||||||
| @@ -3054,7 +3074,7 @@ | |||||||
| 			isa = PBXProject; | 			isa = PBXProject; | ||||||
| 			attributes = { | 			attributes = { | ||||||
| 				LastSwiftUpdateCheck = 0700; | 				LastSwiftUpdateCheck = 0700; | ||||||
| 				LastUpgradeCheck = 0900; | 				LastUpgradeCheck = 0930; | ||||||
| 				ORGANIZATIONNAME = "Thomas Harte"; | 				ORGANIZATIONNAME = "Thomas Harte"; | ||||||
| 				TargetAttributes = { | 				TargetAttributes = { | ||||||
| 					4B055A691FAE763F0060FFFF = { | 					4B055A691FAE763F0060FFFF = { | ||||||
| @@ -3111,16 +3131,16 @@ | |||||||
| 				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 */, | ||||||
| 				4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, | 				4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, | ||||||
| 				4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, | 				4B2A332D1DB86821002876E3 /* OricOptions.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 */, | ||||||
| 				4B79E4441E3AF38600141F11 /* cassette.png in Resources */, | 				4B79E4441E3AF38600141F11 /* cassette.png in Resources */, | ||||||
| 				4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */, |  | ||||||
| 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | ||||||
| 				4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, | 				4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, | ||||||
| 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | ||||||
| 				4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */, |  | ||||||
| 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | ||||||
| 				4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */, | 				4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */, | ||||||
| 			); | 			); | ||||||
| @@ -3648,6 +3668,7 @@ | |||||||
| 				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 */, | ||||||
| @@ -3803,14 +3824,6 @@ | |||||||
| 			name = OricOptions.xib; | 			name = OricOptions.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| 		4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */ = { |  | ||||||
| 			isa = PBXVariantGroup; |  | ||||||
| 			children = ( |  | ||||||
| 				4B38F34E1F2EC6BA00D9235D /* Base */, |  | ||||||
| 			); |  | ||||||
| 			name = AmstradCPCOptions.xib; |  | ||||||
| 			sourceTree = "<group>"; |  | ||||||
| 		}; |  | ||||||
| 		4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { | 		4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -3835,12 +3848,12 @@ | |||||||
| 			name = QuickLoadCompositeOptions.xib; | 			name = QuickLoadCompositeOptions.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| 		4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */ = { | 		4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B8FE21A1DA19D5F0090D3CE /* Base */, | 				4BA141BC2072E8A400A31EC9 /* Base */, | ||||||
| 			); | 			); | ||||||
| 			name = Vic20Options.xib; | 			name = MachinePicker.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| 		4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = { | 		4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = { | ||||||
| @@ -3851,6 +3864,14 @@ | |||||||
| 			name = MainMenu.xib; | 			name = MainMenu.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BD61663206B2AC700236112 /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = QuickLoadOptions.xib; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| /* End PBXVariantGroup section */ | /* End PBXVariantGroup section */ | ||||||
|  |  | ||||||
| /* Begin XCBuildConfiguration section */ | /* Begin XCBuildConfiguration section */ | ||||||
| @@ -3904,12 +3925,14 @@ | |||||||
| 				CLANG_WARN_BOOL_CONVERSION = YES; | 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||||
| 				CLANG_WARN_COMMA = YES; | 				CLANG_WARN_COMMA = YES; | ||||||
| 				CLANG_WARN_CONSTANT_CONVERSION = YES; | 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||||
| 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||||
| 				CLANG_WARN_EMPTY_BODY = YES; | 				CLANG_WARN_EMPTY_BODY = YES; | ||||||
| 				CLANG_WARN_ENUM_CONVERSION = YES; | 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||||
| 				CLANG_WARN_INFINITE_RECURSION = YES; | 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||||
| 				CLANG_WARN_INT_CONVERSION = YES; | 				CLANG_WARN_INT_CONVERSION = YES; | ||||||
| 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||||
| 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||||
| 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||||
| 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||||
| @@ -3957,12 +3980,14 @@ | |||||||
| 				CLANG_WARN_BOOL_CONVERSION = YES; | 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||||
| 				CLANG_WARN_COMMA = YES; | 				CLANG_WARN_COMMA = YES; | ||||||
| 				CLANG_WARN_CONSTANT_CONVERSION = YES; | 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||||
| 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||||
| 				CLANG_WARN_EMPTY_BODY = YES; | 				CLANG_WARN_EMPTY_BODY = YES; | ||||||
| 				CLANG_WARN_ENUM_CONVERSION = YES; | 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||||
| 				CLANG_WARN_INFINITE_RECURSION = YES; | 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||||
| 				CLANG_WARN_INT_CONVERSION = YES; | 				CLANG_WARN_INT_CONVERSION = YES; | ||||||
| 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||||
| 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||||
| 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||||
| 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <Scheme | <Scheme | ||||||
|    LastUpgradeVersion = "0900" |    LastUpgradeVersion = "0930" | ||||||
|    version = "1.3"> |    version = "1.3"> | ||||||
|    <BuildAction |    <BuildAction | ||||||
|       parallelizeBuildables = "YES" |       parallelizeBuildables = "YES" | ||||||
| @@ -26,9 +26,8 @@ | |||||||
|       buildConfiguration = "Debug" |       buildConfiguration = "Debug" | ||||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|       language = "" |       codeCoverageEnabled = "YES" | ||||||
|       shouldUseLaunchSchemeArgsEnv = "YES" |       shouldUseLaunchSchemeArgsEnv = "YES"> | ||||||
|       codeCoverageEnabled = "YES"> |  | ||||||
|       <Testables> |       <Testables> | ||||||
|          <TestableReference |          <TestableReference | ||||||
|             skipped = "NO"> |             skipped = "NO"> | ||||||
| @@ -74,7 +73,6 @@ | |||||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|       enableASanStackUseAfterReturn = "YES" |       enableASanStackUseAfterReturn = "YES" | ||||||
|       disableMainThreadChecker = "YES" |       disableMainThreadChecker = "YES" | ||||||
|       language = "" |  | ||||||
|       launchStyle = "0" |       launchStyle = "0" | ||||||
|       useCustomWorkingDirectory = "NO" |       useCustomWorkingDirectory = "NO" | ||||||
|       ignoresPersistentStateOnLaunch = "NO" |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|   | |||||||
| @@ -3,46 +3,55 @@ | |||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "16x16", |       "size" : "16x16", | ||||||
|  |       "filename" : "Icon16.png", | ||||||
|       "scale" : "1x" |       "scale" : "1x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "16x16", |       "size" : "16x16", | ||||||
|  |       "filename" : "Icon32.png", | ||||||
|       "scale" : "2x" |       "scale" : "2x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "32x32", |       "size" : "32x32", | ||||||
|  |       "filename" : "Icon32.png", | ||||||
|       "scale" : "1x" |       "scale" : "1x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "32x32", |       "size" : "32x32", | ||||||
|  |       "filename" : "Icon64.png", | ||||||
|       "scale" : "2x" |       "scale" : "2x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "128x128", |       "size" : "128x128", | ||||||
|  |       "filename" : "Icon128.png", | ||||||
|       "scale" : "1x" |       "scale" : "1x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "128x128", |       "size" : "128x128", | ||||||
|  |       "filename" : "Icon256.png", | ||||||
|       "scale" : "2x" |       "scale" : "2x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "256x256", |       "size" : "256x256", | ||||||
|  |       "filename" : "Icon256.png", | ||||||
|       "scale" : "1x" |       "scale" : "1x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "256x256", |       "size" : "256x256", | ||||||
|  |       "filename" : "Icon512.png", | ||||||
|       "scale" : "2x" |       "scale" : "2x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "idiom" : "mac", |       "idiom" : "mac", | ||||||
|       "size" : "512x512", |       "size" : "512x512", | ||||||
|  |       "filename" : "Icon512.png", | ||||||
|       "scale" : "1x" |       "scale" : "1x" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 11 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 505 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 39 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.2 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 149 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.5 KiB | 
| @@ -1,10 +1,12 @@ | |||||||
| <?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="14109" 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="14109"/> | ||||||
|  |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
|         <customObject id="-2" userLabel="File's Owner" customClass="Vic20Document" customModule="Clock_Signal" customModuleProvider="target"> |         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|             <connections> |             <connections> | ||||||
|                 <outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/> |                 <outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/> | ||||||
|                 <outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/> |                 <outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/> | ||||||
| @@ -16,15 +18,15 @@ | |||||||
|             <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="400" height="300"/> |             <rect key="contentRect" x="133" y="235" 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="1366" height="768"/> | ||||||
|             <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="400" height="300"/> |                 <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|                 <subviews> |                 <subviews> | ||||||
|                     <openGLView useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView"> |                     <openGLView hidden="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView"> | ||||||
|                         <rect key="frame" x="0.0" y="0.0" width="400" height="300"/> |                         <rect key="frame" x="0.0" y="0.0" width="600" height="450"/> | ||||||
|                     </openGLView> |                     </openGLView> | ||||||
|                 </subviews> |                 </subviews> | ||||||
|                 <constraints> |                 <constraints> | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/> |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
|         <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> |         <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> | ||||||
| @@ -93,30 +94,30 @@ | |||||||
|                                     <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/> |                                     <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV"> |                             <menuItem title="Save…" enabled="NO" keyEquivalent="s" id="pxx-59-PXV"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/> |                                     <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A"> |                             <menuItem title="Save As…" enabled="NO" keyEquivalent="S" id="Bw7-FT-i3A"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/> |                                     <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Revert to Saved" id="KaW-ft-85H"> |                             <menuItem title="Revert to Saved" enabled="NO" id="KaW-ft-85H"> | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |                                 <modifierMask key="keyEquivalentModifierMask"/> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/> |                                     <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> |                             <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> | ||||||
|                             <menuItem title="Page Setup…" 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> | ||||||
|                                     <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/> |                                     <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS"> |                             <menuItem title="Print…" enabled="NO" keyEquivalent="p" id="aTl-1u-JFS"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/> |                                     <action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
| @@ -128,23 +129,23 @@ | |||||||
|                     <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" keyEquivalent="z" id="dRJ-4n-Yzg"> |                             <menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="undo:" target="-1" id="M6e-cu-g7V"/> |                                     <action selector="undo:" target="-1" id="M6e-cu-g7V"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam"> |                             <menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="redo:" target="-1" id="oIA-Rs-6OD"/> |                                     <action selector="redo:" target="-1" id="oIA-Rs-6OD"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> |                             <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> | ||||||
|                             <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG"> |                             <menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="cut:" target="-1" id="YJe-68-I9s"/> |                                     <action selector="cut:" target="-1" id="YJe-68-I9s"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Copy" 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"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
| @@ -154,19 +155,19 @@ | |||||||
|                                     <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" keyEquivalent="V" id="WeT-3V-zwk"> |                             <menuItem title="Paste and Match Style" enabled="NO" keyEquivalent="V" id="WeT-3V-zwk"> | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |                                 <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> |                                     <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Delete" id="pa3-QI-u2k"> |                             <menuItem title="Delete" enabled="NO" id="pa3-QI-u2k"> | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask"/> |                                 <modifierMask key="keyEquivalentModifierMask"/> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> |                                     <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m"> |                             <menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m"> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|                                     <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> |                                     <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|   | |||||||
| @@ -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="14109" 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="14109"/> | ||||||
|         <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> | ||||||
| @@ -17,7 +17,7 @@ | |||||||
|             <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="83" y="102" width="200" height="83"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> | ||||||
|             <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"/> | ||||||
| @@ -40,7 +40,7 @@ | |||||||
|                             <menu key="menu" id="L06-TO-EF0"> |                             <menu key="menu" id="L06-TO-EF0"> | ||||||
|                                 <items> |                                 <items> | ||||||
|                                     <menuItem title="SCART" state="on" id="tJM-kX-gaK"/> |                                     <menuItem title="SCART" state="on" id="tJM-kX-gaK"/> | ||||||
|                                     <menuItem title="Composite" id="fFm-fS-rWG"/> |                                     <menuItem title="Composite" tag="1" id="fFm-fS-rWG"/> | ||||||
|                                 </items> |                                 </items> | ||||||
|                             </menu> |                             </menu> | ||||||
|                         </popUpButtonCell> |                         </popUpButtonCell> | ||||||
|   | |||||||
| @@ -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="14109" 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="14109"/> | ||||||
|         <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> | ||||||
| @@ -17,7 +17,7 @@ | |||||||
|             <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="83" y="102" width="200" height="83"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> | ||||||
|             <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"/> | ||||||
| @@ -34,13 +34,14 @@ | |||||||
|                     </button> |                     </button> | ||||||
|                     <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n"> |                     <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n"> | ||||||
|                         <rect key="frame" x="18" y="17" width="165" height="26"/> |                         <rect key="frame" x="18" y="17" width="165" height="26"/> | ||||||
|                         <popUpButtonCell key="cell" type="push" title="Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1"> |                         <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"/> |                             <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                             <font key="font" metaFont="menu"/> |                             <font key="font" metaFont="menu"/> | ||||||
|                             <menu key="menu" id="L06-TO-EF0"> |                             <menu key="menu" id="L06-TO-EF0"> | ||||||
|                                 <items> |                                 <items> | ||||||
|                                     <menuItem title="Monitor" state="on" id="tJM-kX-gaK"/> |                                     <menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/> | ||||||
|                                     <menuItem title="Television" id="fFm-fS-rWG"/> |                                     <menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/> | ||||||
|  |                                     <menuItem title="Television" tag="1" id="fFm-fS-rWG"/> | ||||||
|                                 </items> |                                 </items> | ||||||
|                             </menu> |                             </menu> | ||||||
|                         </popUpButtonCell> |                         </popUpButtonCell> | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ | |||||||
|             <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="83" y="102" width="200" height="54"/> | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> |             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> | ||||||
|             <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"/> | ||||||
| @@ -34,9 +34,9 @@ | |||||||
|                     </button> |                     </button> | ||||||
|                 </subviews> |                 </subviews> | ||||||
|                 <constraints> |                 <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 firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/> | ||||||
|                     <constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/> |                     <constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/> | ||||||
|                     <constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/> |  | ||||||
|                     <constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/> |                     <constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/> | ||||||
|                 </constraints> |                 </constraints> | ||||||
|             </view> |             </view> | ||||||
| @@ -1,92 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> |  | ||||||
|     <dependencies> |  | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/> |  | ||||||
|         <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="ota-g7-hOL" id="zeO-di-9i3"/> |  | ||||||
|             </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="ota-g7-hOL" customClass="Vic20OptionsPanel" customModule="Clock_Signal" customModuleProvider="target"> |  | ||||||
|             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> |  | ||||||
|             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> |  | ||||||
|             <rect key="contentRect" x="83" y="102" width="200" height="112"/> |  | ||||||
|             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> |  | ||||||
|             <view key="contentView" id="7Pv-WL-2Rq"> |  | ||||||
|                 <rect key="frame" x="0.0" y="0.0" width="200" height="112"/> |  | ||||||
|                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |  | ||||||
|                 <subviews> |  | ||||||
|                     <button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s"> |  | ||||||
|                         <rect key="frame" x="18" y="76" width="164" height="18"/> |  | ||||||
|                         <buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm"> |  | ||||||
|                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |  | ||||||
|                             <font key="font" metaFont="system"/> |  | ||||||
|                         </buttonCell> |  | ||||||
|                         <connections> |  | ||||||
|                             <action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/> |  | ||||||
|                         </connections> |  | ||||||
|                     </button> |  | ||||||
|                     <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MlB-rE-TXV" userLabel="Country Selector"> |  | ||||||
|                         <rect key="frame" x="18" y="46" width="165" height="26"/> |  | ||||||
|                         <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="UIu-uz-pTu"> |  | ||||||
|                             <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |  | ||||||
|                             <font key="font" metaFont="menu"/> |  | ||||||
|                             <menu key="menu" id="ajo-u0-WGk"> |  | ||||||
|                                 <items> |  | ||||||
|                                     <menuItem title="Danish Machine" id="38Y-Wm-1uo"/> |  | ||||||
|                                     <menuItem title="European Machine" id="5ju-Z0-BDa"/> |  | ||||||
|                                     <menuItem title="Japanese Machine" id="YlT-9e-azY"/> |  | ||||||
|                                     <menuItem title="Swedish Machine" id="joU-Bt-XFb"/> |  | ||||||
|                                     <menuItem title="US Machine" id="FXe-ca-cTY"/> |  | ||||||
|                                 </items> |  | ||||||
|                             </menu> |  | ||||||
|                         </popUpButtonCell> |  | ||||||
|                         <connections> |  | ||||||
|                             <action selector="setCountry:" target="ota-g7-hOL" id="YIc-QB-R1S"/> |  | ||||||
|                         </connections> |  | ||||||
|                     </popUpButton> |  | ||||||
|                     <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0NP-x1-qH2"> |  | ||||||
|                         <rect key="frame" x="18" y="17" width="165" height="26"/> |  | ||||||
|                         <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="K81-0X-C4f"> |  | ||||||
|                             <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |  | ||||||
|                             <font key="font" metaFont="menu"/> |  | ||||||
|                             <menu key="menu" id="diI-80-lCf"> |  | ||||||
|                                 <items> |  | ||||||
|                                     <menuItem title="5 kb" id="ze7-6B-ois"/> |  | ||||||
|                                     <menuItem title="8 kb" id="6C7-Iv-Wvl"/> |  | ||||||
|                                     <menuItem title="32 kb" id="DOo-f6-OeZ"/> |  | ||||||
|                                 </items> |  | ||||||
|                             </menu> |  | ||||||
|                         </popUpButtonCell> |  | ||||||
|                         <connections> |  | ||||||
|                             <action selector="setMemorySize:" target="ota-g7-hOL" id="lep-Qi-00V"/> |  | ||||||
|                         </connections> |  | ||||||
|                     </popUpButton> |  | ||||||
|                 </subviews> |  | ||||||
|                 <constraints> |  | ||||||
|                     <constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="8" id="0kc-u0-05p"/> |  | ||||||
|                     <constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/> |  | ||||||
|                     <constraint firstItem="0NP-x1-qH2" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="7EF-L9-lIu"/> |  | ||||||
|                     <constraint firstAttribute="bottom" secondItem="0NP-x1-qH2" secondAttribute="bottom" constant="20" id="Dtd-kf-4oU"/> |  | ||||||
|                     <constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/> |  | ||||||
|                     <constraint firstItem="0NP-x1-qH2" firstAttribute="top" secondItem="MlB-rE-TXV" secondAttribute="bottom" constant="8" id="NbW-5e-wGB"/> |  | ||||||
|                     <constraint firstAttribute="trailing" secondItem="0NP-x1-qH2" secondAttribute="trailing" constant="20" id="ero-D6-tJj"/> |  | ||||||
|                     <constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/> |  | ||||||
|                     <constraint firstItem="MlB-rE-TXV" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="qb4-Lp-ZMc"/> |  | ||||||
|                     <constraint firstAttribute="trailing" secondItem="MlB-rE-TXV" secondAttribute="trailing" constant="20" id="v18-62-uee"/> |  | ||||||
|                 </constraints> |  | ||||||
|             </view> |  | ||||||
|             <connections> |  | ||||||
|                 <outlet property="countryButton" destination="MlB-rE-TXV" id="Duc-AC-ZRO"/> |  | ||||||
|                 <outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/> |  | ||||||
|                 <outlet property="memorySizeButton" destination="0NP-x1-qH2" id="qYy-3f-o94"/> |  | ||||||
|             </connections> |  | ||||||
|             <point key="canvasLocation" x="-2" y="21"/> |  | ||||||
|         </window> |  | ||||||
|     </objects> |  | ||||||
| </document> |  | ||||||
| @@ -6,7 +6,6 @@ | |||||||
| #import "CSFastLoading.h" | #import "CSFastLoading.h" | ||||||
|  |  | ||||||
| #import "CSAtari2600.h" | #import "CSAtari2600.h" | ||||||
| #import "CSVic20.h" |  | ||||||
| #import "CSZX8081.h" | #import "CSZX8081.h" | ||||||
|  |  | ||||||
| #import "CSStaticAnalyser.h" | #import "CSStaticAnalyser.h" | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class MachineDocument: | |||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	var optionsPanelNibName: String? | ||||||
|  |  | ||||||
| 	func aspectRatio() -> NSSize { | 	func aspectRatio() -> NSSize { | ||||||
| 		return NSSize(width: 4.0, height: 3.0) | 		return NSSize(width: 4.0, height: 3.0) | ||||||
| @@ -48,43 +49,64 @@ class MachineDocument: | |||||||
|  |  | ||||||
| 	override func windowControllerDidLoadNib(_ aController: NSWindowController) { | 	override func windowControllerDidLoadNib(_ aController: NSWindowController) { | ||||||
| 		super.windowControllerDidLoadNib(aController) | 		super.windowControllerDidLoadNib(aController) | ||||||
|  | 		aController.window?.contentAspectRatio = self.aspectRatio() | ||||||
|  | 		setupMachineOutput() | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | ||||||
|  | 	// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window | ||||||
|  | 	// is visible, though it's a little premature. | ||||||
|  | 	func windowDidUpdate(_ notification: Notification) { | ||||||
|  | 		if self.shouldShowNewMachinePanel { | ||||||
|  | 			self.shouldShowNewMachinePanel = false | ||||||
|  | 			Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil) | ||||||
|  | 			self.machinePicker?.establishStoredOptions() | ||||||
|  | 			self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fileprivate func setupMachineOutput() { | ||||||
|  | 		if let machine = self.machine, let openGLView = self.openGLView { | ||||||
| 			// establish the output aspect ratio and audio | 			// establish the output aspect ratio and audio | ||||||
| 		let displayAspectRatio = self.aspectRatio() | 			let aspectRatio = self.aspectRatio() | ||||||
| 		aController.window?.contentAspectRatio = displayAspectRatio |  | ||||||
| 			openGLView.perform(glContext: { | 			openGLView.perform(glContext: { | ||||||
| 			self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height)) | 				machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height)) | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 		self.machine.delegate = self | 			// attach an options panel if one is available | ||||||
|  | 			if let optionsPanelNibName = self.optionsPanelNibName { | ||||||
|  | 				Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil) | ||||||
|  | 				self.optionsPanel.machine = machine | ||||||
|  | 				self.optionsPanel?.establishStoredOptions() | ||||||
|  | 				showOptions(self) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			machine.delegate = self | ||||||
| 			self.bestEffortUpdater = CSBestEffortUpdater() | 			self.bestEffortUpdater = CSBestEffortUpdater() | ||||||
|  |  | ||||||
| 			// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set; | 			// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set; | ||||||
| 			// hence the full setup of the best-effort updater prior to setting self as a delegate | 			// hence the full setup of the best-effort updater prior to setting self as a delegate | ||||||
| 		self.openGLView.delegate = self | 			openGLView.delegate = self | ||||||
| 		self.openGLView.responderDelegate = self | 			openGLView.responderDelegate = self | ||||||
|  |  | ||||||
| 		setupClockRate() | 			setupAudioQueueClockRate() | ||||||
| 		self.optionsPanel?.establishStoredOptions() |  | ||||||
|  |  | ||||||
| 		// bring OpenGL view-holding window on top of the options panel | 			// bring OpenGL view-holding window on top of the options panel and show the content | ||||||
| 		self.openGLView.window!.makeKeyAndOrderFront(self) | 			openGLView.isHidden = false | ||||||
|  | 			openGLView.window!.makeKeyAndOrderFront(self) | ||||||
|  | 			openGLView.window!.makeFirstResponder(openGLView) | ||||||
|  |  | ||||||
| 			// start accepting best effort updates | 			// start accepting best effort updates | ||||||
| 			self.bestEffortUpdater!.delegate = self | 			self.bestEffortUpdater!.delegate = self | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	func machineDidChangeClockRate(_ machine: CSMachine!) { |  | ||||||
| 		setupClockRate() |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) { | 	func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) { | ||||||
| 		bestEffortLock.lock() | 		setupAudioQueueClockRate() | ||||||
| 		self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited |  | ||||||
| 		bestEffortLock.unlock() |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fileprivate func setupClockRate() { | 	fileprivate func setupAudioQueueClockRate() { | ||||||
| 		// establish and provide the audio queue, taking advice as to an appropriate sampling rate | 		// establish and provide the audio queue, taking advice as to an appropriate sampling rate | ||||||
| 		let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() | 		let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() | ||||||
| 		let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))) | 		let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))) | ||||||
| @@ -94,10 +116,6 @@ class MachineDocument: | |||||||
| 			self.machine.audioQueue = self.audioQueue | 			self.machine.audioQueue = self.audioQueue | ||||||
| 			self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) | 			self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bestEffortLock.lock() |  | ||||||
| 		self.bestEffortUpdater?.clockRate = self.machine.clockRate |  | ||||||
| 		bestEffortLock.unlock() |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	override func close() { | 	override func close() { | ||||||
| @@ -105,9 +123,11 @@ class MachineDocument: | |||||||
| 		optionsPanel = nil | 		optionsPanel = nil | ||||||
|  |  | ||||||
| 		bestEffortLock.lock() | 		bestEffortLock.lock() | ||||||
| 		bestEffortUpdater!.delegate = nil | 		if let bestEffortUpdater = bestEffortUpdater { | ||||||
| 		bestEffortUpdater!.flush() | 			bestEffortUpdater.delegate = nil | ||||||
| 		bestEffortUpdater = nil | 			bestEffortUpdater.flush() | ||||||
|  | 			self.bestEffortUpdater = nil | ||||||
|  | 		} | ||||||
| 		bestEffortLock.unlock() | 		bestEffortLock.unlock() | ||||||
|  |  | ||||||
| 		actionLock.lock() | 		actionLock.lock() | ||||||
| @@ -125,15 +145,12 @@ class MachineDocument: | |||||||
| 	func configureAs(_ analysis: CSStaticAnalyser) { | 	func configureAs(_ analysis: CSStaticAnalyser) { | ||||||
| 		if let machine = CSMachine(analyser: analysis) { | 		if let machine = CSMachine(analyser: analysis) { | ||||||
| 			self.machine = machine | 			self.machine = machine | ||||||
| 		} | 			self.optionsPanelNibName = analysis.optionsPanelNibName | ||||||
|  | 			setupMachineOutput() | ||||||
| 		if let optionsPanelNibName = analysis.optionsPanelNibName { |  | ||||||
| 			Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil) |  | ||||||
| 			self.optionsPanel.machine = self.machine |  | ||||||
| 			showOptions(self) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	fileprivate var shouldShowNewMachinePanel = false | ||||||
| 	override func read(from url: URL, ofType typeName: String) throws { | 	override func read(from url: URL, ofType typeName: String) throws { | ||||||
| 		if let analyser = CSStaticAnalyser(fileAt: url) { | 		if let analyser = CSStaticAnalyser(fileAt: url) { | ||||||
| 			self.displayName = analyser.displayName | 			self.displayName = analyser.displayName | ||||||
| @@ -143,6 +160,12 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	convenience init(type typeName: String) throws { | ||||||
|  | 		self.init() | ||||||
|  | 		self.fileType = typeName | ||||||
|  | 		self.shouldShowNewMachinePanel = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// MARK: the pasteboard | 	// MARK: the pasteboard | ||||||
| 	func paste(_ sender: AnyObject!) { | 	func paste(_ sender: AnyObject!) { | ||||||
| 		let pasteboard = NSPasteboard.general | 		let pasteboard = NSPasteboard.general | ||||||
| @@ -152,22 +175,11 @@ class MachineDocument: | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: CSBestEffortUpdaterDelegate | 	// MARK: CSBestEffortUpdaterDelegate | ||||||
| 	final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) { | 	final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) { | ||||||
| 		runForNumberOfCycles(Int32(cycles)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	func runForNumberOfCycles(_ numberOfCycles: Int32) { |  | ||||||
| 		bestEffortLock.lock() |  | ||||||
| 		if let bestEffortUpdater = bestEffortUpdater { |  | ||||||
| 			bestEffortLock.unlock() |  | ||||||
| 			let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10)) |  | ||||||
| 		if actionLock.try() { | 		if actionLock.try() { | ||||||
| 				self.machine.runForNumber(ofCycles: cyclesToRunFor) | 			self.machine.run(forInterval: duration) | ||||||
| 			actionLock.unlock() | 			actionLock.unlock() | ||||||
| 		} | 		} | ||||||
| 		} else { |  | ||||||
| 			bestEffortLock.unlock() |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: CSAudioQueueDelegate | 	// MARK: CSAudioQueueDelegate | ||||||
| @@ -206,21 +218,42 @@ class MachineDocument: | |||||||
|  |  | ||||||
| 	// MARK: Input management | 	// MARK: Input management | ||||||
| 	func windowDidResignKey(_ notification: Notification) { | 	func windowDidResignKey(_ notification: Notification) { | ||||||
| 		self.machine.clearAllKeys() | 		if let machine = self.machine { | ||||||
|  | 			machine.clearAllKeys() | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func keyDown(_ event: NSEvent) { | 	func keyDown(_ event: NSEvent) { | ||||||
| 		self.machine.setKey(event.keyCode, characters: event.characters, isPressed: true) | 		if let machine = self.machine { | ||||||
|  | 			machine.setKey(event.keyCode, characters: event.characters, isPressed: true) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func keyUp(_ event: NSEvent) { | 	func keyUp(_ event: NSEvent) { | ||||||
| 		self.machine.setKey(event.keyCode, characters: event.characters, isPressed: false) | 		if let machine = self.machine { | ||||||
|  | 			machine.setKey(event.keyCode, characters: event.characters, isPressed: false) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func flagsChanged(_ newModifiers: NSEvent) { | 	func flagsChanged(_ newModifiers: NSEvent) { | ||||||
| 		self.machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift)) | 		if let machine = self.machine { | ||||||
| 		self.machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control)) | 			machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift)) | ||||||
| 		self.machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command)) | 			machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control)) | ||||||
| 		self.machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option)) | 			machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command)) | ||||||
|  | 			machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: New machine creation | ||||||
|  | 	@IBOutlet var machinePicker: MachinePicker? | ||||||
|  | 	@IBOutlet var machinePickerPanel: NSWindow? | ||||||
|  | 	@IBAction func createMachine(_ sender: NSButton?) { | ||||||
|  | 		self.configureAs(machinePicker!.selectedMachine()) | ||||||
|  | 		machinePicker = nil | ||||||
|  | 		sender?.window?.close() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | ||||||
|  | 		close() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,13 +28,24 @@ class MachinePanel: NSPanel { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	fileprivate func signalForTag(tag: Int) -> CSMachineVideoSignal { | ||||||
|  | 		switch tag { | ||||||
|  | 			case 1: return .composite | ||||||
|  | 			case 2: return .sVideo | ||||||
|  | 			default: break | ||||||
|  | 		} | ||||||
|  | 		return .RGB | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	var displayTypeUserDefaultsKey: String { | 	var displayTypeUserDefaultsKey: String { | ||||||
| 		return prefixedUserDefaultsKey("displayType") | 		return prefixedUserDefaultsKey("displayType") | ||||||
| 	} | 	} | ||||||
| 	@IBOutlet var displayTypeButton: NSPopUpButton? | 	@IBOutlet var displayTypeButton: NSPopUpButton? | ||||||
| 	@IBAction func setDisplayType(_ sender: NSPopUpButton!) { | 	@IBAction func setDisplayType(_ sender: NSPopUpButton!) { | ||||||
| 		machine.useCompositeOutput = (sender.indexOfSelectedItem == 1) | 		if let selectedItem = sender.selectedItem { | ||||||
| 		UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) | 			machine.videoSignal = signalForTag(tag: selectedItem.tag) | ||||||
|  | 			UserDefaults.standard.set(selectedItem.tag, forKey: self.displayTypeUserDefaultsKey) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func establishStoredOptions() { | 	func establishStoredOptions() { | ||||||
| @@ -50,8 +61,21 @@ class MachinePanel: NSPanel { | |||||||
| 			self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off | 			self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if let displayTypeButton = self.displayTypeButton { | ||||||
|  | 			// Enable or disable options as per machine support. | ||||||
|  | 			var titlesToRemove: [String] = [] | ||||||
|  | 			for item in displayTypeButton.itemArray { | ||||||
|  | 				if !machine.supportsVideoSignal(signalForTag(tag: item.tag)) { | ||||||
|  | 					titlesToRemove.append(item.title) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			for title in titlesToRemove { | ||||||
|  | 				displayTypeButton.removeItem(withTitle: title) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey) | 			let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey) | ||||||
| 		machine.useCompositeOutput = (displayType == 1) | 			displayTypeButton.selectItem(withTag: displayType) | ||||||
| 		self.displayTypeButton?.selectItem(at: displayType) | 			setDisplayType(displayTypeButton) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,97 +0,0 @@ | |||||||
| // |  | ||||||
| //  Vic20OptionsPanel.swift |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 03/10/2016. |  | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| class Vic20OptionsPanel: MachinePanel { |  | ||||||
| 	var vic20: CSVic20! { |  | ||||||
| 		get { |  | ||||||
| 			return self.machine as! CSVic20 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// MARK: country selector |  | ||||||
| 	@IBOutlet var countryButton: NSPopUpButton? |  | ||||||
| 	var countryUserDefaultsKey: String { |  | ||||||
| 		get { return prefixedUserDefaultsKey("country") } |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	@IBAction func setCountry(_ sender: NSPopUpButton!) { |  | ||||||
| 		UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.countryUserDefaultsKey) |  | ||||||
| 		setCountry(sender.indexOfSelectedItem) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fileprivate func setCountry(_ countryID: Int) { |  | ||||||
| 		switch countryID { |  | ||||||
| 			case 0:	// Danish |  | ||||||
| 				vic20.country = .danish |  | ||||||
| 			case 1: // European |  | ||||||
| 				vic20.country = .european |  | ||||||
| 			case 2: // Japanese |  | ||||||
| 				vic20.country = .japanese |  | ||||||
| 			case 3: // Swedish |  | ||||||
| 				vic20.country = .swedish |  | ||||||
| 			case 4: // US |  | ||||||
| 				vic20.country = .american |  | ||||||
| 			default: break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// MARK: memory model selector |  | ||||||
| 	@IBOutlet var memorySizeButton: NSPopUpButton? |  | ||||||
| 	var memorySizeUserDefaultsKey: String { |  | ||||||
| 		get { return prefixedUserDefaultsKey("memorySize") } |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	@IBAction func setMemorySize(_ sender: NSPopUpButton!) { |  | ||||||
| 		var selectedSize: Int? |  | ||||||
| 		switch sender.indexOfSelectedItem { |  | ||||||
| 			case 0: selectedSize = 5 |  | ||||||
| 			case 1: selectedSize = 8 |  | ||||||
| 			case 2: selectedSize = 32 |  | ||||||
| 			default: break |  | ||||||
| 		} |  | ||||||
| 		if let selectedSize = selectedSize { |  | ||||||
| 			UserDefaults.standard.set(selectedSize, forKey: self.memorySizeUserDefaultsKey) |  | ||||||
| 			setMemorySize(sender.indexOfSelectedItem) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	fileprivate func setMemorySize(_ sizeIndex: Int) { |  | ||||||
| 		switch sizeIndex { |  | ||||||
| 			case 2:		vic20.memorySize = .size32Kb |  | ||||||
| 			case 1:		vic20.memorySize = .size8Kb |  | ||||||
| 			default:	vic20.memorySize = .size5Kb |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// MARK: option restoration |  | ||||||
| 	override func establishStoredOptions() { |  | ||||||
| 		super.establishStoredOptions() |  | ||||||
|  |  | ||||||
| 		let standardUserDefaults = UserDefaults.standard |  | ||||||
| 		standardUserDefaults.register(defaults: [ |  | ||||||
| 			self.memorySizeUserDefaultsKey: 5, |  | ||||||
| 			self.countryUserDefaultsKey: 1 |  | ||||||
| 		]) |  | ||||||
|  |  | ||||||
| //			let memorySize = standardUserDefaults.integer(forKey: self.memorySizeUserDefaultsKey) |  | ||||||
| //			var indexToSelect: Int? |  | ||||||
| //			switch memorySize { |  | ||||||
| //				case 32:	indexToSelect = 2 |  | ||||||
| //				case 8:		indexToSelect = 1 |  | ||||||
| //				default:	indexToSelect = 0 |  | ||||||
| //			} |  | ||||||
| //			if let indexToSelect = indexToSelect { |  | ||||||
| //				self.memorySizeButton?.selectItem(at: indexToSelect) |  | ||||||
| //				setMemorySize(indexToSelect) |  | ||||||
| //			} |  | ||||||
|  |  | ||||||
| 		// TODO: this should be part of the configuration |  | ||||||
| 		let country = standardUserDefaults.integer(forKey: self.countryUserDefaultsKey) |  | ||||||
| 		setCountry(country) |  | ||||||
| 		self.countryButton?.selectItem(at: country) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -15,10 +15,15 @@ | |||||||
|  |  | ||||||
| @class CSMachine; | @class CSMachine; | ||||||
| @protocol CSMachineDelegate | @protocol CSMachineDelegate | ||||||
| - (void)machineDidChangeClockRate:(CSMachine *)machine; | - (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine; | ||||||
| - (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine; |  | ||||||
| @end | @end | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { | ||||||
|  | 	CSMachineVideoSignalComposite, | ||||||
|  | 	CSMachineVideoSignalSVideo, | ||||||
|  | 	CSMachineVideoSignalRGB | ||||||
|  | }; | ||||||
|  |  | ||||||
| // 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" | ||||||
| @@ -33,7 +38,7 @@ | |||||||
| */ | */ | ||||||
| - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; | - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; | ||||||
|  |  | ||||||
| - (void)runForNumberOfCycles:(int)numberOfCycles; | - (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; | ||||||
| @@ -48,17 +53,16 @@ | |||||||
| @property (nonatomic, readonly) CSOpenGLView *view; | @property (nonatomic, readonly) CSOpenGLView *view; | ||||||
| @property (nonatomic, weak) id<CSMachineDelegate> delegate; | @property (nonatomic, weak) id<CSMachineDelegate> delegate; | ||||||
|  |  | ||||||
| @property (nonatomic, readonly) double clockRate; |  | ||||||
| @property (nonatomic, readonly) BOOL clockIsUnlimited; |  | ||||||
|  |  | ||||||
| @property (nonatomic, readonly) NSString *userDefaultsPrefix; | @property (nonatomic, readonly) NSString *userDefaultsPrefix; | ||||||
|  |  | ||||||
| - (void)paste:(NSString *)string; | - (void)paste:(NSString *)string; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) BOOL useCompositeOutput; | @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | ||||||
| @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | ||||||
|  |  | ||||||
|  | - (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal; | ||||||
|  |  | ||||||
| // 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) CSAtari2600 *atari2600; | ||||||
| @property (nonatomic, readonly) CSZX8081 *zx8081; | @property (nonatomic, readonly) CSZX8081 *zx8081; | ||||||
|   | |||||||
| @@ -25,8 +25,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)machineDidChangeClockRate; | - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; | ||||||
| - (void)machineDidChangeClockIsUnlimited; |  | ||||||
| @end | @end | ||||||
|  |  | ||||||
| struct LockProtectedDelegate { | struct LockProtectedDelegate { | ||||||
| @@ -37,29 +36,20 @@ struct LockProtectedDelegate { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate { | struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate { | ||||||
| 	void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) { | 	void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override { | ||||||
| 		[machineAccessLock lock]; | 		[machineAccessLock lock]; | ||||||
| 		[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; | 		[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; | ||||||
| 		[machineAccessLock unlock]; | 		[machineAccessLock unlock]; | ||||||
| 	} | 	} | ||||||
| }; | 	void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override { | ||||||
|  |  | ||||||
| struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate { |  | ||||||
| 	void machine_did_change_clock_rate(CRTMachine::Machine *sender) { |  | ||||||
| 		[machineAccessLock lock]; | 		[machineAccessLock lock]; | ||||||
| 		[machine machineDidChangeClockRate]; | 		[machine speakerDidChangeInputClock:speaker]; | ||||||
| 		[machineAccessLock unlock]; |  | ||||||
| 	} |  | ||||||
| 	void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) { |  | ||||||
| 		[machineAccessLock lock]; |  | ||||||
| 		[machine machineDidChangeClockIsUnlimited]; |  | ||||||
| 		[machineAccessLock unlock]; | 		[machineAccessLock unlock]; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @implementation CSMachine { | @implementation CSMachine { | ||||||
| 	SpeakerDelegate _speakerDelegate; | 	SpeakerDelegate _speakerDelegate; | ||||||
| 	MachineDelegate _machineDelegate; |  | ||||||
| 	NSLock *_delegateMachineAccessLock; | 	NSLock *_delegateMachineAccessLock; | ||||||
|  |  | ||||||
| 	CSStaticAnalyser *_analyser; | 	CSStaticAnalyser *_analyser; | ||||||
| @@ -77,12 +67,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg | |||||||
|  |  | ||||||
| 		_delegateMachineAccessLock = [[NSLock alloc] init]; | 		_delegateMachineAccessLock = [[NSLock alloc] init]; | ||||||
|  |  | ||||||
| 		_machineDelegate.machine = self; |  | ||||||
| 		_speakerDelegate.machine = self; | 		_speakerDelegate.machine = self; | ||||||
| 		_machineDelegate.machineAccessLock = _delegateMachineAccessLock; |  | ||||||
| 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; | 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; | ||||||
|  |  | ||||||
| 		_machine->crt_machine()->set_delegate(&_machineDelegate); |  | ||||||
| 	} | 	} | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
| @@ -91,12 +77,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg | |||||||
| 	[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; | 	[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)machineDidChangeClockRate { | - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker { | ||||||
| 	[self.delegate machineDidChangeClockRate:self]; | 	[self.delegate machineSpeakerDidChangeInputClock:self]; | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)machineDidChangeClockIsUnlimited { |  | ||||||
| 	[self.delegate machineDidChangeClockIsUnlimited:self]; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)dealloc { | - (void)dealloc { | ||||||
| @@ -107,13 +89,12 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg | |||||||
| 	// They are nilled inside an explicit lock because that allows the delegates to protect their entire | 	// They are nilled inside an explicit lock because that allows the delegates to protect their entire | ||||||
| 	// call into the machine, not just the pointer access. | 	// call into the machine, not just the pointer access. | ||||||
| 	[_delegateMachineAccessLock lock]; | 	[_delegateMachineAccessLock lock]; | ||||||
| 	_machineDelegate.machine = nil; |  | ||||||
| 	_speakerDelegate.machine = nil; | 	_speakerDelegate.machine = nil; | ||||||
| 	[_delegateMachineAccessLock unlock]; | 	[_delegateMachineAccessLock unlock]; | ||||||
|  |  | ||||||
| 	[_view performWithGLContext:^{ | 	[_view performWithGLContext:^{ | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			_machine->crt_machine()->close_output(); | 			self->_machine->crt_machine()->close_output(); | ||||||
| 		} | 		} | ||||||
| 	}]; | 	}]; | ||||||
| } | } | ||||||
| @@ -146,9 +127,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)runForNumberOfCycles:(int)numberOfCycles { | - (void)runForInterval:(NSTimeInterval)interval { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_machine->crt_machine()->run_for(Cycles(numberOfCycles)); | 		_machine->crt_machine()->run_for(interval); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -171,14 +152,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg | |||||||
| 	_machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); | 	_machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); | ||||||
| } | } | ||||||
|  |  | ||||||
| - (double)clockRate { |  | ||||||
| 	return _machine->crt_machine()->get_clock_rate(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (BOOL)clockIsUnlimited { |  | ||||||
| 	return _machine->crt_machine()->get_clock_is_unlimited() ? YES : NO; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| - (void)paste:(NSString *)paste { | - (void)paste:(NSString *)paste { | ||||||
| 	KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine(); | 	KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine(); | ||||||
| 	if(keyboardMachine) | 	if(keyboardMachine) | ||||||
| @@ -316,19 +289,63 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setUseCompositeOutput:(BOOL)useCompositeOutput { | - (void)setVideoSignal:(CSMachineVideoSignal)videoSignal { | ||||||
| 	Configurable::Device *configurable_device = _machine->configurable_device(); | 	Configurable::Device *configurable_device = _machine->configurable_device(); | ||||||
| 	if(!configurable_device) return; | 	if(!configurable_device) return; | ||||||
|  |  | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_useCompositeOutput = useCompositeOutput; | 		_videoSignal = videoSignal; | ||||||
|  |  | ||||||
| 		Configurable::SelectionSet selection_set; | 		Configurable::SelectionSet selection_set; | ||||||
| 		append_display_selection(selection_set, useCompositeOutput ? Configurable::Display::Composite : Configurable::Display::RGB); | 		Configurable::Display display; | ||||||
|  | 		switch(videoSignal) { | ||||||
|  | 			case CSMachineVideoSignalRGB:		display = Configurable::Display::RGB;		break; | ||||||
|  | 			case CSMachineVideoSignalSVideo:	display = Configurable::Display::SVideo;	break; | ||||||
|  | 			case CSMachineVideoSignalComposite:	display = Configurable::Display::Composite;	break; | ||||||
|  | 		} | ||||||
|  | 		append_display_selection(selection_set, display); | ||||||
| 		configurable_device->set_selections(selection_set); | 		configurable_device->set_selections(selection_set); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal { | ||||||
|  | 	Configurable::Device *configurable_device = _machine->configurable_device(); | ||||||
|  | 	if(!configurable_device) return NO; | ||||||
|  |  | ||||||
|  | 	// Get the options this machine provides. | ||||||
|  | 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		options = configurable_device->get_options(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get the standard option for this video signal. | ||||||
|  | 	Configurable::StandardOptions option; | ||||||
|  | 	switch(videoSignal) { | ||||||
|  | 		case CSMachineVideoSignalRGB:		option = Configurable::DisplayRGB;			break; | ||||||
|  | 		case CSMachineVideoSignalSVideo:	option = Configurable::DisplaySVideo;		break; | ||||||
|  | 		case CSMachineVideoSignalComposite:	option = Configurable::DisplayComposite;	break; | ||||||
|  | 	} | ||||||
|  | 	std::unique_ptr<Configurable::Option> display_option = std::move(standard_options(option).front()); | ||||||
|  | 	Configurable::ListOption *display_list_option = dynamic_cast<Configurable::ListOption *>(display_option.get()); | ||||||
|  | 	NSAssert(display_list_option, @"Expected display option to be a list"); | ||||||
|  |  | ||||||
|  | 	// See whether the video signal is included in the machine options. | ||||||
|  | 	for(auto &candidate: options) { | ||||||
|  | 		Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(candidate.get()); | ||||||
|  |  | ||||||
|  | 		// Both should be list options | ||||||
|  | 		if(!list_option) continue; | ||||||
|  |  | ||||||
|  | 		// Check for same name of option. | ||||||
|  | 		if(candidate->short_name != display_option->short_name) continue; | ||||||
|  |  | ||||||
|  | 		// Check that the video signal option is included. | ||||||
|  | 		return std::find(list_option->options.begin(), list_option->options.end(), display_list_option->options.front()) != list_option->options.end(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return NO; | ||||||
|  | } | ||||||
|  |  | ||||||
| - (void)setUseAutomaticTapeMotorControl:(BOOL)useAutomaticTapeMotorControl { | - (void)setUseAutomaticTapeMotorControl:(BOOL)useAutomaticTapeMotorControl { | ||||||
| 	Configurable::Device *configurable_device = _machine->configurable_device(); | 	Configurable::Device *configurable_device = _machine->configurable_device(); | ||||||
| 	if(!configurable_device) return; | 	if(!configurable_device) return; | ||||||
|   | |||||||
| @@ -10,10 +10,39 @@ | |||||||
|  |  | ||||||
| @class CSMachine; | @class CSMachine; | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSMachineCPCModel) { | ||||||
|  | 	CSMachineCPCModel464, | ||||||
|  | 	CSMachineCPCModel664, | ||||||
|  | 	CSMachineCPCModel6128 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSMachineOricModel) { | ||||||
|  | 	CSMachineOricModelOric1, | ||||||
|  | 	CSMachineOricModelOricAtmos | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSMachineVic20Region) { | ||||||
|  | 	CSMachineVic20RegionAmerican, | ||||||
|  | 	CSMachineVic20RegionEuropean, | ||||||
|  | 	CSMachineVic20RegionDanish, | ||||||
|  | 	CSMachineVic20RegionSwedish, | ||||||
|  | 	CSMachineVic20RegionJapanese, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | typedef int Kilobytes; | ||||||
|  |  | ||||||
| @interface CSStaticAnalyser : NSObject | @interface CSStaticAnalyser : NSObject | ||||||
|  |  | ||||||
| - (instancetype)initWithFileAtURL:(NSURL *)url; | - (instancetype)initWithFileAtURL:(NSURL *)url; | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs; | ||||||
|  | - (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model; | ||||||
|  | - (instancetype)initWithMSXHasDiskDrive:(BOOL)hasDiskDrive; | ||||||
|  | - (instancetype)initWithOricModel:(CSMachineOricModel)model hasMicrodrive:(BOOL)hasMicrodrive; | ||||||
|  | - (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540; | ||||||
|  | - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM; | ||||||
|  | - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize; | ||||||
|  |  | ||||||
| @property(nonatomic, readonly) NSString *optionsPanelNibName; | @property(nonatomic, readonly) NSString *optionsPanelNibName; | ||||||
| @property(nonatomic, readonly) NSString *displayName; | @property(nonatomic, readonly) NSString *displayName; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,13 @@ | |||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../../../Analyser/Static/Acorn/Target.hpp" | ||||||
|  | #include "../../../../../Analyser/Static/AmstradCPC/Target.hpp" | ||||||
|  | #include "../../../../../Analyser/Static/Commodore/Target.hpp" | ||||||
|  | #include "../../../../../Analyser/Static/MSX/Target.hpp" | ||||||
|  | #include "../../../../../Analyser/Static/Oric/Target.hpp" | ||||||
|  | #include "../../../../../Analyser/Static/ZX8081/Target.hpp" | ||||||
|  |  | ||||||
| #import "Clock_Signal-Swift.h" | #import "Clock_Signal-Swift.h" | ||||||
|  |  | ||||||
| @implementation CSStaticAnalyser { | @implementation CSStaticAnalyser { | ||||||
| @@ -32,6 +39,120 @@ | |||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::Acorn::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::Electron; | ||||||
|  | 		target->has_dfs = !!dfs; | ||||||
|  | 		target->has_adfs = !!adfs; | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::AmstradCPC::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::AmstradCPC; | ||||||
|  | 		switch(model) { | ||||||
|  | 			case CSMachineCPCModel464: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC464;		break; | ||||||
|  | 			case CSMachineCPCModel664: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC664;		break; | ||||||
|  | 			case CSMachineCPCModel6128: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC6128;	break; | ||||||
|  | 		} | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithMSXHasDiskDrive:(BOOL)hasDiskDrive { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::MSX::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::MSX; | ||||||
|  | 		target->has_disk_drive = !!hasDiskDrive; | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithOricModel:(CSMachineOricModel)model hasMicrodrive:(BOOL)hasMicrodrive { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::Oric::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::Oric; | ||||||
|  | 		target->use_atmos_rom = (model == CSMachineOricModelOricAtmos); | ||||||
|  | 		target->has_microdrive = !!hasMicrodrive; | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540 { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::Commodore::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::Vic20; | ||||||
|  | 		switch(region) { | ||||||
|  | 			case CSMachineVic20RegionDanish:	target->region = Target::Region::Danish;	break; | ||||||
|  | 			case CSMachineVic20RegionSwedish:	target->region = Target::Region::Swedish;	break; | ||||||
|  | 			case CSMachineVic20RegionAmerican:	target->region = Target::Region::American;	break; | ||||||
|  | 			case CSMachineVic20RegionEuropean:	target->region = Target::Region::European;	break; | ||||||
|  | 			case CSMachineVic20RegionJapanese:	target->region = Target::Region::Japanese;	break; | ||||||
|  | 		} | ||||||
|  | 		switch(memorySize) { | ||||||
|  | 			default:	target->memory_model = Target::MemoryModel::Unexpanded;		break; | ||||||
|  | 			case 8:		target->memory_model = Target::MemoryModel::EightKB;		break; | ||||||
|  | 			case 32:	target->memory_model = Target::MemoryModel::ThirtyTwoKB;	break; | ||||||
|  | 		} | ||||||
|  | 		target->has_c1540 = !!hasC1540; | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(Kilobytes size) { | ||||||
|  | 	using MemoryModel = Analyser::Static::ZX8081::Target::MemoryModel; | ||||||
|  | 	switch(size) { | ||||||
|  | 		default:	return MemoryModel::Unexpanded; | ||||||
|  | 		case 16:	return MemoryModel::SixteenKB; | ||||||
|  | 		case 64:	return MemoryModel::SixtyFourKB; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::ZX8081::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::ZX8081; | ||||||
|  | 		target->is_ZX81 = false; | ||||||
|  | 		target->ZX80_uses_ZX81_ROM = !!useZX81ROM; | ||||||
|  | 		target->memory_model = ZX8081MemoryModelFromSize(memorySize); | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		using Target = Analyser::Static::ZX8081::Target; | ||||||
|  | 		std::unique_ptr<Target> target(new Target); | ||||||
|  | 		target->machine = Analyser::Machine::ZX8081; | ||||||
|  | 		target->is_ZX81 = true; | ||||||
|  | 		target->memory_model = ZX8081MemoryModelFromSize(memorySize); | ||||||
|  | 		_targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
| - (NSString *)optionsPanelNibName { | - (NSString *)optionsPanelNibName { | ||||||
| 	switch(_targets.front()->machine) { | 	switch(_targets.front()->machine) { | ||||||
| 		case Analyser::Machine::AmstradCPC:	return nil; | 		case Analyser::Machine::AmstradCPC:	return nil; | ||||||
| @@ -39,7 +160,7 @@ | |||||||
| 		case Analyser::Machine::Electron:	return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::Electron:	return @"QuickLoadCompositeOptions"; | ||||||
| 		case Analyser::Machine::MSX:		return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::MSX:		return @"QuickLoadCompositeOptions"; | ||||||
| 		case Analyser::Machine::Oric:		return @"OricOptions"; | 		case Analyser::Machine::Oric:		return @"OricOptions"; | ||||||
| 		case Analyser::Machine::Vic20:		nil; //return @"Vic20Options"; | 		case Analyser::Machine::Vic20:		return @"QuickLoadCompositeOptions"; | ||||||
| 		case Analyser::Machine::ZX8081:		return @"ZX8081Options"; | 		case Analyser::Machine::ZX8081:		return @"ZX8081Options"; | ||||||
| 		default: return nil; | 		default: return nil; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -66,8 +66,8 @@ | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | 	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | ||||||
| 		@synchronized(_machine) { | 		@synchronized(self->_machine) { | ||||||
| 			_atari2600->set_switch_is_enabled(toggleSwitch, false); | 			self->_atari2600->set_switch_is_enabled(toggleSwitch, false); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,35 +0,0 @@ | |||||||
| // |  | ||||||
| //  CSVic20.h |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 04/06/2016. |  | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #import "CSMachine.h" |  | ||||||
| #import "CSFastLoading.h" |  | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSVic20Country) |  | ||||||
| { |  | ||||||
| 	CSVic20CountryAmerican, |  | ||||||
| 	CSVic20CountryDanish, |  | ||||||
| 	CSVic20CountryEuropean, |  | ||||||
| 	CSVic20CountryJapanese, |  | ||||||
| 	CSVic20CountrySwedish |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSVic20MemorySize) |  | ||||||
| { |  | ||||||
| 	CSVic20MemorySize5Kb, |  | ||||||
| 	CSVic20MemorySize8Kb, |  | ||||||
| 	CSVic20MemorySize32Kb, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| @interface CSVic20 : CSMachine <CSFastLoading> |  | ||||||
|  |  | ||||||
| - (instancetype)init; |  | ||||||
|  |  | ||||||
| @property (nonatomic, assign) CSVic20Country country; |  | ||||||
| @property (nonatomic, assign) CSVic20MemorySize memorySize; |  | ||||||
|  |  | ||||||
| @end |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user