Compare commits
	
		
			269 Commits
		
	
	
		
			2017-01-01
			...
			2017-03-26
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | defec2c9b0 | ||
|  | 04921e64de | ||
|  | bdd432fe1d | ||
|  | 6e9ab9f330 | ||
|  | 814c0ada13 | ||
|  | dfc468f220 | ||
|  | 6817b38322 | ||
|  | e01f3f06c8 | ||
|  | 3229502fa1 | ||
|  | a4c5eebd1e | ||
|  | a3c22d5abb | ||
|  | a26b87f348 | ||
|  | 4c3cc42c91 | ||
|  | f3f4e1a541 | ||
|  | 4722f6b5c4 | ||
|  | 7d8d1c7828 | ||
|  | 4bb70e7d31 | ||
|  | 321030bb44 | ||
|  | 6c161b1150 | ||
|  | d5c37c8619 | ||
|  | c445eaec3e | ||
|  | 7c66c36d3f | ||
|  | 031a68000a | ||
|  | 7d7b665be8 | ||
|  | c3d82f88a5 | ||
|  | c033bad0b9 | ||
|  | c31d85f820 | ||
|  | 217fbf257e | ||
|  | 0b611a14b9 | ||
|  | df6861c9dc | ||
|  | a4cd12394e | ||
|  | e0bca1e37b | ||
|  | 55ce851bb2 | ||
|  | e8d34f2eb4 | ||
|  | bb3daaa99b | ||
|  | 36b58d03b7 | ||
|  | 7958459db9 | ||
|  | 14a76af0d3 | ||
|  | a04a58e01f | ||
|  | afbd9fd41b | ||
|  | 3d53d4e55e | ||
|  | 7302703039 | ||
|  | 97a8a96593 | ||
|  | be2e99077e | ||
|  | 3b29276228 | ||
|  | 4a528b9ecb | ||
|  | b3632a4e86 | ||
|  | 8a659e3117 | ||
|  | a6897ebde0 | ||
|  | 582da14a14 | ||
|  | b81bf6b547 | ||
|  | 8e147444d5 | ||
|  | a9964ee0c8 | ||
|  | b671df9906 | ||
|  | 0bcf9c30de | ||
|  | 2c07cce282 | ||
|  | 8c5e39c0c5 | ||
|  | cae48aaa95 | ||
|  | 37f4f6ba14 | ||
|  | 597bd97b01 | ||
|  | 38de5300e5 | ||
|  | 62b3c9dda8 | ||
|  | 146f3ea0f5 | ||
|  | af9b7fbc30 | ||
|  | 78213f1e95 | ||
|  | de347ad7c8 | ||
|  | a4bba8a92e | ||
|  | fcacfc2726 | ||
|  | bab464e765 | ||
|  | 2879763c34 | ||
|  | ea2ea30193 | ||
|  | 608569cc48 | ||
|  | c7e973aab4 | ||
|  | 443d57bc32 | ||
|  | 57ec756f5b | ||
|  | 9286a5ba73 | ||
|  | 1c9dffe41f | ||
|  | 8c7f724ce4 | ||
|  | b193248056 | ||
|  | f0d944847b | ||
|  | a72d70e707 | ||
|  | add14fb43a | ||
|  | 38ce4dc56c | ||
|  | bce5abd33b | ||
|  | 3f36eeb071 | ||
|  | 33bda2d40c | ||
|  | 2b5e3a600e | ||
|  | 8dbf9fd302 | ||
|  | 9c72ce5bd2 | ||
|  | ec2762509b | ||
|  | e63229a5e5 | ||
|  | ad73379d1c | ||
|  | abd4d2c42a | ||
|  | 79784a8e57 | ||
|  | 61b8fc1e2f | ||
|  | 4751615623 | ||
|  | cccdc558e7 | ||
|  | d3257c345a | ||
|  | e09b76bf32 | ||
|  | 837cccdf83 | ||
|  | a3fcd15980 | ||
|  | 93d1573481 | ||
|  | 893a5dd007 | ||
|  | 026b418b4a | ||
|  | 06dd98b23c | ||
|  | 2a81ae1dec | ||
|  | 1625b9c7f9 | ||
|  | 184c8ae707 | ||
|  | 8f8b103224 | ||
|  | 1af415a88e | ||
|  | fe07cd0248 | ||
|  | a3d339092e | ||
|  | 837216ee9a | ||
|  | dcd0c90283 | ||
|  | b24cd00a39 | ||
|  | 0273860018 | ||
|  | 82c089cde4 | ||
|  | 997707a45b | ||
|  | 9d7985c1e1 | ||
|  | 8b1ec827e0 | ||
|  | 153525f23d | ||
|  | 3101dc94a7 | ||
|  | e6a84fd26b | ||
|  | 440467ea3e | ||
|  | 98376de9ad | ||
|  | e61e355251 | ||
|  | c898c8a99e | ||
|  | 8c9062857c | ||
|  | 77ed4ddc05 | ||
|  | 82f392fada | ||
|  | 2f0c923c29 | ||
|  | 8291a63d5f | ||
|  | 4c947ad553 | ||
|  | 6120dae61a | ||
|  | 1d03793f22 | ||
|  | 4f5f191cd6 | ||
|  | 21abf4e9fc | ||
|  | 144d6b70d9 | ||
|  | b769f22ca0 | ||
|  | 7019d396d0 | ||
|  | f4447fd9cd | ||
|  | 36396b3d62 | ||
|  | d1dbf8c21f | ||
|  | 1bde0fed6f | ||
|  | 7ab2358bba | ||
|  | 99547181f1 | ||
|  | 2bf784535c | ||
|  | 57f434c199 | ||
|  | 87afa9140e | ||
|  | d19f26887d | ||
|  | 6cb95b4fc5 | ||
|  | d979a822ac | ||
|  | fccdce65b9 | ||
|  | 99a35266e1 | ||
|  | 51bcaea60c | ||
|  | e00339ef0a | ||
|  | 53cd125712 | ||
|  | 04693b067c | ||
|  | cd7876a746 | ||
|  | ed5ff49ef5 | ||
|  | 8d502a0b03 | ||
|  | 5ea232310f | ||
|  | 09309aa74f | ||
|  | b5357860b9 | ||
|  | dd17459687 | ||
|  | cd90118a0f | ||
|  | 25776de59d | ||
|  | 600bdc9af7 | ||
|  | 0c9be2b09e | ||
|  | df8a5cbe6d | ||
|  | 9ce68c38ae | ||
|  | 40954d6a2a | ||
|  | ac444a3f34 | ||
|  | b8abeced6d | ||
|  | aeff59addc | ||
|  | 7ab6023a0c | ||
|  | 97cdfea9e9 | ||
|  | aff69dbc34 | ||
|  | 6381e4e1b0 | ||
|  | c8e595d9aa | ||
|  | 8c88fd4261 | ||
|  | a86a6367b5 | ||
|  | 905ed1f87b | ||
|  | 8de6caf6ff | ||
|  | 327c19a222 | ||
|  | 40d3f5f7f6 | ||
|  | 64d5712d1d | ||
|  | 3b20d862f0 | ||
|  | 2e9ef2b0ef | ||
|  | 70745286a5 | ||
|  | dcb7584060 | ||
|  | a477499724 | ||
|  | 944d835eea | ||
|  | 8f5039130c | ||
|  | ba165bb70a | ||
|  | 474e2e8d2c | ||
|  | 8b8eb787df | ||
|  | 66bcdd36f3 | ||
|  | fcf8cafb5d | ||
|  | 6bcf95042c | ||
|  | 23f3ccd77a | ||
|  | f2437cb257 | ||
|  | abe04334c2 | ||
|  | 8545707b54 | ||
|  | 2b08758b2b | ||
|  | 764b528891 | ||
|  | 92754ace7a | ||
|  | 1cc13b2799 | ||
|  | 38f944bc34 | ||
|  | 427175b9c0 | ||
|  | ebde955356 | ||
|  | 7fd02e7f4c | ||
|  | d51f185dc7 | ||
|  | 2390358c24 | ||
|  | 2432a3b4d7 | ||
|  | 9c3597c7e3 | ||
|  | fba6baaa9c | ||
|  | a246530953 | ||
|  | 0ffded72a6 | ||
|  | acadfbabec | ||
|  | 9001cc3fc2 | ||
|  | 015b2b49f9 | ||
|  | 92f928ca42 | ||
|  | 6d087ca054 | ||
|  | c2d7e36c8f | ||
|  | 4d6e78e641 | ||
|  | 5761c8267b | ||
|  | a66a8c31b2 | ||
|  | 19e4ee12e1 | ||
|  | 4871572a33 | ||
|  | 2e744a95e4 | ||
|  | ff87f1390d | ||
|  | 76ca30c26d | ||
|  | 7c2685cb34 | ||
|  | 8cf25a2d70 | ||
|  | 8d69dd30f3 | ||
|  | ae8068b86f | ||
|  | baeb0ee89f | ||
|  | c07993bb0a | ||
|  | 7680cbf9c3 | ||
|  | 4920fe6701 | ||
|  | 55fe0176bd | ||
|  | 99fcbb55d1 | ||
|  | 6f78ecd12b | ||
|  | ced644b103 | ||
|  | be1cb2a551 | ||
|  | b4159295f6 | ||
|  | d0a93409e6 | ||
|  | 4c3669f210 | ||
|  | eeb646868b | ||
|  | 3d789732a2 | ||
|  | d2a7d39749 | ||
|  | 9521718120 | ||
|  | 28909e33ca | ||
|  | 79632b1d34 | ||
|  | cf6d03e35c | ||
|  | 4a4b31a15c | ||
|  | f3d9aec8fc | ||
|  | 7ad64ff16b | ||
|  | 6153ada33b | ||
|  | be48c950b4 | ||
|  | 0487b8c178 | ||
|  | 5740015f56 | ||
|  | c84004bfa3 | ||
|  | c746a3711f | ||
|  | aa7774a9a6 | ||
|  | a836120945 | ||
|  | 7d60df9075 | ||
|  | f2b8b26bc4 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -20,6 +20,7 @@ DerivedData | ||||
|  | ||||
| # Exclude system ROMs | ||||
| ROMImages/* | ||||
| OSBindings/Mac/Clock SignalTests/Atari\ ROMs | ||||
|  | ||||
| # CocoaPods | ||||
| # | ||||
|   | ||||
| @@ -22,8 +22,7 @@ WD1770::Status::Status() : | ||||
| 		lost_data(false), | ||||
| 		data_request(false), | ||||
| 		interrupt_request(false), | ||||
| 	busy(false) | ||||
| {} | ||||
| 		busy(false) {} | ||||
|  | ||||
| WD1770::WD1770(Personality p) : | ||||
| 		Storage::Disk::Controller(8000000, 16, 300), | ||||
| @@ -36,14 +35,12 @@ WD1770::WD1770(Personality p) : | ||||
| 		data_mode_(DataMode::Scanning), | ||||
| 		delegate_(nullptr), | ||||
| 		personality_(p), | ||||
| 	head_is_loaded_(false) | ||||
| { | ||||
| 		head_is_loaded_(false) { | ||||
| 	set_is_double_density(false); | ||||
| 	posit_event(Event::Command); | ||||
| } | ||||
|  | ||||
| void WD1770::set_is_double_density(bool is_double_density) | ||||
| { | ||||
| void WD1770::set_is_double_density(bool is_double_density) { | ||||
| 	is_double_density_ = is_double_density; | ||||
| 	Storage::Time bit_length; | ||||
| 	bit_length.length = 1; | ||||
| @@ -53,21 +50,15 @@ void WD1770::set_is_double_density(bool is_double_density) | ||||
| 	if(!is_double_density) is_awaiting_marker_value_ = false; | ||||
| } | ||||
|  | ||||
| void WD1770::set_register(int address, uint8_t value) | ||||
| { | ||||
| 	switch(address&3) | ||||
| 	{ | ||||
| 		case 0: | ||||
| 		{ | ||||
| 			if((value&0xf0) == 0xd0) | ||||
| 			{ | ||||
| void WD1770::set_register(int address, uint8_t value) { | ||||
| 	switch(address&3) { | ||||
| 		case 0: { | ||||
| 			if((value&0xf0) == 0xd0) { | ||||
| 				printf("!!!TODO: force interrupt!!!\n"); | ||||
| 				update_status([] (Status &status) { | ||||
| 					status.type = Status::One; | ||||
| 				}); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				command_ = value; | ||||
| 				posit_event(Event::Command); | ||||
| 			} | ||||
| @@ -84,12 +75,9 @@ void WD1770::set_register(int address, uint8_t value) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t WD1770::get_register(int address) | ||||
| { | ||||
| 	switch(address&3) | ||||
| 	{ | ||||
| 		default: | ||||
| 		{ | ||||
| uint8_t WD1770::get_register(int address) { | ||||
| 	switch(address&3) { | ||||
| 		default: { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.interrupt_request = false; | ||||
| 			}); | ||||
| @@ -97,8 +85,7 @@ uint8_t WD1770::get_register(int address) | ||||
| 					(status_.write_protect ? Flag::WriteProtect : 0) | | ||||
| 					(status_.crc_error ? Flag::CRCError : 0) | | ||||
| 					(status_.busy ? Flag::Busy : 0); | ||||
| 			switch(status_.type) | ||||
| 			{ | ||||
| 			switch(status_.type) { | ||||
| 				case Status::One: | ||||
| 					status |= | ||||
| 						(get_is_track_zero() ? Flag::TrackZero : 0) | | ||||
| @@ -116,14 +103,11 @@ uint8_t WD1770::get_register(int address) | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			if(!has_motor_on_line()) | ||||
| 			{ | ||||
| 			if(!has_motor_on_line()) { | ||||
| 				status |= get_drive_is_ready() ? 0 : Flag::NotReady; | ||||
| 				if(status_.type == Status::One) | ||||
| 					status |= (head_is_loaded_ ? Flag::HeadLoaded : 0); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				status |= (get_motor_on() ? Flag::MotorOn : 0); | ||||
| 				if(status_.type == Status::One) | ||||
| 					status |= (status_.spin_up ? Flag::SpinUp : 0); | ||||
| @@ -140,38 +124,29 @@ uint8_t WD1770::get_register(int address) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void WD1770::run_for_cycles(unsigned int number_of_cycles) | ||||
| { | ||||
| void WD1770::run_for_cycles(unsigned int number_of_cycles) { | ||||
| 	Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); | ||||
|  | ||||
| 	if(delay_time_) | ||||
| 	{ | ||||
| 		if(delay_time_ <= number_of_cycles) | ||||
| 		{ | ||||
| 	if(delay_time_) { | ||||
| 		if(delay_time_ <= number_of_cycles) { | ||||
| 			delay_time_ = 0; | ||||
| 			posit_event(Event::Timer); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			delay_time_ -= number_of_cycles; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| { | ||||
| void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) { | ||||
| 	if(data_mode_ == DataMode::Writing) return; | ||||
|  | ||||
| 	shift_register_ = (shift_register_ << 1) | value; | ||||
| 	bits_since_token_++; | ||||
|  | ||||
| 	if(data_mode_ == DataMode::Scanning) | ||||
| 	{ | ||||
| 	if(data_mode_ == DataMode::Scanning) { | ||||
| 		Token::Type token_type = Token::Byte; | ||||
| 		if(!is_double_density_) | ||||
| 		{ | ||||
| 			switch(shift_register_ & 0xffff) | ||||
| 			{ | ||||
| 		if(!is_double_density_) { | ||||
| 			switch(shift_register_ & 0xffff) { | ||||
| 				case Storage::Encodings::MFM::FMIndexAddressMark: | ||||
| 					token_type = Token::Index; | ||||
| 					crc_generator_.reset(); | ||||
| @@ -195,11 +170,8 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| 				default: | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			switch(shift_register_ & 0xffff) | ||||
| 			{ | ||||
| 		} else { | ||||
| 			switch(shift_register_ & 0xffff) { | ||||
| 				case Storage::Encodings::MFM::MFMIndexSync: | ||||
| 					bits_since_token_ = 0; | ||||
| 					is_awaiting_marker_value_ = true; | ||||
| @@ -220,8 +192,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(token_type != Token::Byte) | ||||
| 		{ | ||||
| 		if(token_type != Token::Byte) { | ||||
| 			latest_token_.type = token_type; | ||||
| 			bits_since_token_ = 0; | ||||
| 			posit_event(Event::Token); | ||||
| @@ -229,8 +200,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(bits_since_token_ == 16) | ||||
| 	{ | ||||
| 	if(bits_since_token_ == 16) { | ||||
| 		latest_token_.type = Token::Byte; | ||||
| 		latest_token_.byte_value = (uint8_t)( | ||||
| 			((shift_register_ & 0x0001) >> 0) | | ||||
| @@ -243,11 +213,9 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| 			((shift_register_ & 0x4000) >> 7)); | ||||
| 		bits_since_token_ = 0; | ||||
|  | ||||
| 		if(is_awaiting_marker_value_ && is_double_density_) | ||||
| 		{ | ||||
| 		if(is_awaiting_marker_value_ && is_double_density_) { | ||||
| 			is_awaiting_marker_value_ = false; | ||||
| 			switch(latest_token_.byte_value) | ||||
| 			{ | ||||
| 			switch(latest_token_.byte_value) { | ||||
| 				case Storage::Encodings::MFM::IndexAddressByte: | ||||
| 					latest_token_.type = Token::Index; | ||||
| 				break; | ||||
| @@ -270,31 +238,26 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void WD1770::process_index_hole() | ||||
| { | ||||
| void WD1770::process_index_hole() { | ||||
| 	index_hole_count_++; | ||||
| 	posit_event(Event::IndexHole); | ||||
| 	if(index_hole_count_target_ == index_hole_count_) | ||||
| 	{ | ||||
| 	if(index_hole_count_target_ == index_hole_count_) { | ||||
| 		posit_event(Event::IndexHoleTarget); | ||||
| 		index_hole_count_target_ = -1; | ||||
| 	} | ||||
|  | ||||
| 	// motor power-down | ||||
| 	if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) | ||||
| 	{ | ||||
| 	if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) { | ||||
| 		set_motor_on(false); | ||||
| 	} | ||||
|  | ||||
| 	// head unload | ||||
| 	if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) | ||||
| 	{ | ||||
| 	if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) { | ||||
| 		set_head_load_request(false); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void WD1770::process_write_completed() | ||||
| { | ||||
| void WD1770::process_write_completed() { | ||||
| 	posit_event(Event::DataWritten); | ||||
| } | ||||
|  | ||||
| @@ -305,11 +268,9 @@ void WD1770::process_write_completed() | ||||
| #define END_SECTION()	0; } | ||||
|  | ||||
| #define READ_ID()	\ | ||||
| 		if(new_event_type == Event::Token)	\ | ||||
| 		{	\ | ||||
| 		if(new_event_type == Event::Token) {	\ | ||||
| 			if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }	\ | ||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte)	\ | ||||
| 			{	\ | ||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) {	\ | ||||
| 				header_[distance_into_section_ - 1] = latest_token_.byte_value;	\ | ||||
| 				distance_into_section_++;	\ | ||||
| 			}	\ | ||||
| @@ -343,8 +304,7 @@ void WD1770::process_write_completed() | ||||
| //     !	 4  ! Forc int !  1  1	0  1 i3 i2 i1 i0 ! | ||||
| //     +--------+----------+-------------------------+ | ||||
|  | ||||
| void WD1770::posit_event(Event new_event_type) | ||||
| { | ||||
| void WD1770::posit_event(Event new_event_type) { | ||||
| 	if(!(interesting_event_mask_ & (int)new_event_type)) return; | ||||
| 	interesting_event_mask_ &= ~new_event_type; | ||||
|  | ||||
| @@ -405,8 +365,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		goto begin_type1_load_head; | ||||
|  | ||||
| 	begin_type1_load_head: | ||||
| 		if(!(command_&0x08)) | ||||
| 		{ | ||||
| 		if(!(command_&0x08)) { | ||||
| 			set_head_load_request(false); | ||||
| 			goto test_type1_type; | ||||
| 		} | ||||
| @@ -426,8 +385,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		if((command_ >> 5) != 0) goto perform_step_command; | ||||
|  | ||||
| 		// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00. | ||||
| 		if(!(command_ & 0x10)) | ||||
| 		{ | ||||
| 		if(!(command_ & 0x10)) { | ||||
| 			track_ = 0xff; | ||||
| 			data_ = 0; | ||||
| 		} | ||||
| @@ -440,15 +398,13 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		if(step_direction_) track_++; else track_--; | ||||
|  | ||||
| 	perform_step: | ||||
| 		if(!step_direction_ && get_is_track_zero()) | ||||
| 		{ | ||||
| 		if(!step_direction_ && get_is_track_zero()) { | ||||
| 			track_ = 0; | ||||
| 			goto verify; | ||||
| 		} | ||||
| 		step(step_direction_ ? 1 : -1); | ||||
| 		int time_to_wait; | ||||
| 		switch(command_ & 3) | ||||
| 		{ | ||||
| 		switch(command_ & 3) { | ||||
| 			default: | ||||
| 			case 0: time_to_wait = 6;	break; | ||||
| 			case 1: time_to_wait = 12;	break; | ||||
| @@ -464,8 +420,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		goto perform_step; | ||||
|  | ||||
| 	verify: | ||||
| 		if(!(command_ & 0x04)) | ||||
| 		{ | ||||
| 		if(!(command_ & 0x04)) { | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
|  | ||||
| @@ -476,26 +431,22 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		WAIT_FOR_EVENT(Event::IndexHole | Event::Token); | ||||
| 		READ_ID(); | ||||
|  | ||||
| 		if(index_hole_count_ == 6) | ||||
| 		{ | ||||
| 		if(index_hole_count_ == 6) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.seek_error = true; | ||||
| 			}); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		if(distance_into_section_ == 7) | ||||
| 		{ | ||||
| 		if(distance_into_section_ == 7) { | ||||
| 			data_mode_ = DataMode::Scanning; | ||||
| 			if(crc_generator_.get_value()) | ||||
| 			{ | ||||
| 			if(crc_generator_.get_value()) { | ||||
| 				update_status([] (Status &status) { | ||||
| 					status.crc_error = true; | ||||
| 				}); | ||||
| 				goto verify_read_data; | ||||
| 			} | ||||
|  | ||||
| 			if(header_[0] == track_) | ||||
| 			{ | ||||
| 			if(header_[0] == track_) { | ||||
| 				printf("Reached track %d\n", track_); | ||||
| 				update_status([] (Status &status) { | ||||
| 					status.crc_error = false; | ||||
| @@ -553,8 +504,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		WAIT_FOR_TIME(30); | ||||
|  | ||||
| 	test_type2_write_protection: | ||||
| 		if(command_&0x20 && get_drive_is_read_only()) | ||||
| 		{ | ||||
| 		if(command_&0x20 && get_drive_is_read_only()) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.write_protect = true; | ||||
| 			}); | ||||
| @@ -565,24 +515,20 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		WAIT_FOR_EVENT(Event::IndexHole | Event::Token); | ||||
| 		READ_ID(); | ||||
|  | ||||
| 		if(index_hole_count_ == 5) | ||||
| 		{ | ||||
| 		if(index_hole_count_ == 5) { | ||||
| 			printf("Failed to find sector %d\n", sector_); | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.record_not_found = true; | ||||
| 			}); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		if(distance_into_section_ == 7) | ||||
| 		{ | ||||
| 		if(distance_into_section_ == 7) { | ||||
| 			printf("Considering %d/%d\n", header_[0], header_[2]); | ||||
| 			data_mode_ = DataMode::Scanning; | ||||
| 			if(		header_[0] == track_ && header_[2] == sector_ && | ||||
| 				(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) | ||||
| 			{ | ||||
| 					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { | ||||
| 				printf("Found %d/%d\n", header_[0], header_[2]); | ||||
| 				if(crc_generator_.get_value()) | ||||
| 				{ | ||||
| 				if(crc_generator_.get_value()) { | ||||
| 					printf("CRC error; back to searching\n"); | ||||
| 					update_status([] (Status &status) { | ||||
| 						status.crc_error = true; | ||||
| @@ -607,8 +553,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 	type2_read_data: | ||||
| 		WAIT_FOR_EVENT(Event::Token); | ||||
| 		// TODO: timeout | ||||
| 		if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) | ||||
| 		{ | ||||
| 		if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) { | ||||
| 			update_status([this] (Status &status) { | ||||
| 				status.record_type = (latest_token_.type == Token::DeletedData); | ||||
| 			}); | ||||
| @@ -627,8 +572,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 			status.data_request = true; | ||||
| 		}); | ||||
| 		distance_into_section_++; | ||||
| 		if(distance_into_section_ == 128 << header_[3]) | ||||
| 		{ | ||||
| 		if(distance_into_section_ == 128 << header_[3]) { | ||||
| 			distance_into_section_ = 0; | ||||
| 			goto type2_check_crc; | ||||
| 		} | ||||
| @@ -639,10 +583,8 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		if(latest_token_.type != Token::Byte) goto type2_read_byte; | ||||
| 		header_[distance_into_section_] = latest_token_.byte_value; | ||||
| 		distance_into_section_++; | ||||
| 		if(distance_into_section_ == 2) | ||||
| 		{ | ||||
| 			if(crc_generator_.get_value()) | ||||
| 			{ | ||||
| 		if(distance_into_section_ == 2) { | ||||
| 			if(crc_generator_.get_value()) { | ||||
| 				printf("CRC error; terminating\n"); | ||||
| 				update_status([this] (Status &status) { | ||||
| 					status.crc_error = true; | ||||
| @@ -650,8 +592,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 				goto wait_for_command; | ||||
| 			} | ||||
|  | ||||
| 			if(command_ & 0x10) | ||||
| 			{ | ||||
| 			if(command_ & 0x10) { | ||||
| 				sector_++; | ||||
| 				goto test_type2_write_protection; | ||||
| 			} | ||||
| @@ -667,35 +608,29 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 			status.data_request = true; | ||||
| 		}); | ||||
| 		WAIT_FOR_BYTES(9); | ||||
| 		if(status_.data_request) | ||||
| 		{ | ||||
| 		if(status_.data_request) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.lost_data = true; | ||||
| 			}); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		WAIT_FOR_BYTES(1); | ||||
| 		if(is_double_density_) | ||||
| 		{ | ||||
| 		if(is_double_density_) { | ||||
| 			WAIT_FOR_BYTES(11); | ||||
| 		} | ||||
|  | ||||
| 		data_mode_ = DataMode::Writing; | ||||
| 		begin_writing(); | ||||
| 		for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) | ||||
| 		{ | ||||
| 		for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) { | ||||
| 			write_byte(0); | ||||
| 		} | ||||
| 		WAIT_FOR_EVENT(Event::DataWritten); | ||||
|  | ||||
| 		if(is_double_density_) | ||||
| 		{ | ||||
| 		if(is_double_density_) { | ||||
| 			crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); | ||||
| 			for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync); | ||||
| 			write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			crc_generator_.reset(); | ||||
| 			crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); | ||||
| 			write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark); | ||||
| @@ -715,8 +650,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		*/ | ||||
| 		write_byte(data_); | ||||
| 		distance_into_section_++; | ||||
| 		if(distance_into_section_ == 128 << header_[3]) | ||||
| 		{ | ||||
| 		if(distance_into_section_ == 128 << header_[3]) { | ||||
| 			goto type2_write_crc; | ||||
| 		} | ||||
|  | ||||
| @@ -724,8 +658,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 			status.data_request = true; | ||||
| 		}); | ||||
| 		WAIT_FOR_EVENT(Event::DataWritten); | ||||
| 		if(status_.data_request) | ||||
| 		{ | ||||
| 		if(status_.data_request) { | ||||
| 			end_writing(); | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.lost_data = true; | ||||
| @@ -735,8 +668,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
|  | ||||
| 		goto type2_write_loop; | ||||
|  | ||||
| 	type2_write_crc: | ||||
| 		{ | ||||
| 	type2_write_crc: { | ||||
| 			uint16_t crc = crc_generator_.get_value(); | ||||
| 			write_byte(crc >> 8); | ||||
| 			write_byte(crc & 0xff); | ||||
| @@ -745,8 +677,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		WAIT_FOR_EVENT(Event::DataWritten); | ||||
| 		end_writing(); | ||||
|  | ||||
| 		if(command_ & 0x10) | ||||
| 		{ | ||||
| 		if(command_ & 0x10) { | ||||
| 			sector_++; | ||||
| 			goto test_type2_write_protection; | ||||
| 		} | ||||
| @@ -802,13 +733,10 @@ void WD1770::posit_event(Event new_event_type) | ||||
|  | ||||
| 	read_address_get_header: | ||||
| 		WAIT_FOR_EVENT(Event::IndexHole | Event::Token); | ||||
| 		if(new_event_type == Event::Token) | ||||
| 		{ | ||||
| 		if(new_event_type == Event::Token) { | ||||
| 			if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } | ||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) | ||||
| 			{ | ||||
| 				if(status_.data_request) | ||||
| 				{ | ||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { | ||||
| 				if(status_.data_request) { | ||||
| 					update_status([] (Status &status) { | ||||
| 						status.lost_data = true; | ||||
| 					}); | ||||
| @@ -821,10 +749,8 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 				}); | ||||
| 				distance_into_section_++; | ||||
|  | ||||
| 				if(distance_into_section_ == 7) | ||||
| 				{ | ||||
| 					if(crc_generator_.get_value()) | ||||
| 					{ | ||||
| 				if(distance_into_section_ == 7) { | ||||
| 					if(crc_generator_.get_value()) { | ||||
| 						update_status([] (Status &status) { | ||||
| 							status.crc_error = true; | ||||
| 						}); | ||||
| @@ -834,8 +760,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(index_hole_count_ == 6) | ||||
| 		{ | ||||
| 		if(index_hole_count_ == 6) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.record_not_found = true; | ||||
| 			}); | ||||
| @@ -849,12 +774,10 @@ void WD1770::posit_event(Event new_event_type) | ||||
|  | ||||
| 	read_track_read_byte: | ||||
| 		WAIT_FOR_EVENT(Event::Token | Event::IndexHole); | ||||
| 		if(index_hole_count_) | ||||
| 		{ | ||||
| 		if(index_hole_count_) { | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		if(status_.data_request) | ||||
| 		{ | ||||
| 		if(status_.data_request) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.lost_data = true; | ||||
| 			}); | ||||
| @@ -873,8 +796,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		}); | ||||
|  | ||||
| 	write_track_test_write_protect: | ||||
| 		if(get_drive_is_read_only()) | ||||
| 		{ | ||||
| 		if(get_drive_is_read_only()) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.write_protect = true; | ||||
| 			}); | ||||
| @@ -885,8 +807,7 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 			status.data_request = true; | ||||
| 		}); | ||||
| 		WAIT_FOR_BYTES(3); | ||||
| 		if(status_.data_request) | ||||
| 		{ | ||||
| 		if(status_.data_request) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.lost_data = true; | ||||
| 			}); | ||||
| @@ -898,10 +819,8 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 		index_hole_count_ = 0; | ||||
|  | ||||
| 	write_track_write_loop: | ||||
| 		if(is_double_density_) | ||||
| 		{ | ||||
| 			switch(data_) | ||||
| 			{ | ||||
| 		if(is_double_density_) { | ||||
| 			switch(data_) { | ||||
| 				case 0xf5: | ||||
| 					write_raw_short(Storage::Encodings::MFM::MFMSync); | ||||
| 					crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); | ||||
| @@ -918,11 +837,8 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 					write_byte(data_); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			switch(data_) | ||||
| 			{ | ||||
| 		} else { | ||||
| 			switch(data_) { | ||||
| 				case 0xf8: case 0xf9: case 0xfa: case 0xfb: | ||||
| 				case 0xfd: case 0xfe: | ||||
| 					// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022 | ||||
| @@ -960,16 +876,14 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 			status.data_request = true; | ||||
| 		}); | ||||
| 		WAIT_FOR_EVENT(Event::DataWritten); | ||||
| 		if(status_.data_request) | ||||
| 		{ | ||||
| 		if(status_.data_request) { | ||||
| 			update_status([] (Status &status) { | ||||
| 				status.lost_data = true; | ||||
| 			}); | ||||
| 			end_writing(); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| 		if(index_hole_count_) | ||||
| 		{ | ||||
| 		if(index_hole_count_) { | ||||
| 			end_writing(); | ||||
| 			goto wait_for_command; | ||||
| 		} | ||||
| @@ -979,10 +893,8 @@ void WD1770::posit_event(Event new_event_type) | ||||
| 	END_SECTION() | ||||
| } | ||||
|  | ||||
| void WD1770::update_status(std::function<void(Status &)> updater) | ||||
| { | ||||
| 	if(delegate_) | ||||
| 	{ | ||||
| void WD1770::update_status(std::function<void(Status &)> updater) { | ||||
| 	if(delegate_) { | ||||
| 		Status old_status = status_; | ||||
| 		updater(status_); | ||||
| 		bool did_change = | ||||
| @@ -995,37 +907,29 @@ void WD1770::update_status(std::function<void(Status &)> updater) | ||||
|  | ||||
| void WD1770::set_head_load_request(bool head_load) {} | ||||
|  | ||||
| void WD1770::set_head_loaded(bool head_loaded) | ||||
| { | ||||
| void WD1770::set_head_loaded(bool head_loaded) { | ||||
| 	head_is_loaded_ = head_loaded; | ||||
| 	if(head_loaded) posit_event(Event::HeadLoad); | ||||
| } | ||||
|  | ||||
| void WD1770::write_bit(int bit) | ||||
| { | ||||
| 	if(is_double_density_) | ||||
| 	{ | ||||
| void WD1770::write_bit(int bit) { | ||||
| 	if(is_double_density_) { | ||||
| 		Controller::write_bit(!bit && !last_bit_); | ||||
| 		Controller::write_bit(!!bit); | ||||
| 		last_bit_ = bit; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		Controller::write_bit(true); | ||||
| 		Controller::write_bit(!!bit); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void WD1770::write_byte(uint8_t byte) | ||||
| { | ||||
| void WD1770::write_byte(uint8_t byte) { | ||||
| 	for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80); | ||||
| 	crc_generator_.add(byte); | ||||
| } | ||||
|  | ||||
| void WD1770::write_raw_short(uint16_t value) | ||||
| { | ||||
| 	for(int c = 0; c < 16; c++) | ||||
| 	{ | ||||
| void WD1770::write_raw_short(uint16_t value) { | ||||
| 	for(int c = 0; c < 16; c++) { | ||||
| 		Controller::write_bit(!!((value << c)&0x8000)); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -50,12 +50,10 @@ template <class T> class MOS6522 { | ||||
| 		}; | ||||
|  | ||||
| 		/*! Sets a register value. */ | ||||
| 		inline void set_register(int address, uint8_t value) | ||||
| 		{ | ||||
| 		inline void set_register(int address, uint8_t value) { | ||||
| 			address &= 0xf; | ||||
| //			printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value); | ||||
| 			switch(address) | ||||
| 			{ | ||||
| 			switch(address) { | ||||
| 				case 0x0: | ||||
| 					registers_.output[1] = value; | ||||
| 					static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake | ||||
| @@ -88,8 +86,7 @@ template <class T> class MOS6522 { | ||||
| 				case 0x5:	case 0x7: | ||||
| 					registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8); | ||||
| 					registers_.interrupt_flags &= ~InterruptFlag::Timer1; | ||||
| 					if(address == 0x05) | ||||
| 					{ | ||||
| 					if(address == 0x05) { | ||||
| 						registers_.next_timer[0] = registers_.timer_latch[0]; | ||||
| 						timer_is_running_[0] = true; | ||||
| 					} | ||||
| @@ -117,19 +114,15 @@ template <class T> class MOS6522 { | ||||
| 					registers_.peripheral_control = value; | ||||
|  | ||||
| 					// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode | ||||
| 					if(value & 0x08) | ||||
| 					{ | ||||
| 						switch(value & 0x0e) | ||||
| 						{ | ||||
| 					if(value & 0x08) { | ||||
| 						switch(value & 0x0e) { | ||||
| 							default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break; | ||||
| 							case 0x0c:	static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false);		break; | ||||
| 							case 0x0e:	static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true);		break; | ||||
| 						} | ||||
| 					} | ||||
| 					if(value & 0x80) | ||||
| 					{ | ||||
| 						switch(value & 0xe0) | ||||
| 						{ | ||||
| 					if(value & 0x80) { | ||||
| 						switch(value & 0xe0) { | ||||
| 							default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break; | ||||
| 							case 0xc0:	static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false);		break; | ||||
| 							case 0xe0:	static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true);		break; | ||||
| @@ -153,12 +146,10 @@ template <class T> class MOS6522 { | ||||
| 		} | ||||
|  | ||||
| 		/*! Gets a register value. */ | ||||
| 		inline uint8_t get_register(int address) | ||||
| 		{ | ||||
| 		inline uint8_t get_register(int address) { | ||||
| 			address &= 0xf; | ||||
| //			printf("6522 %p: %d\n", this, address); | ||||
| 			switch(address) | ||||
| 			{ | ||||
| 			switch(address) { | ||||
| 				case 0x0: | ||||
| 					registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | ||||
| 					reevaluate_interrupts(); | ||||
| @@ -200,15 +191,12 @@ template <class T> class MOS6522 { | ||||
| 			return 0xff; | ||||
| 		} | ||||
|  | ||||
| 		inline void set_control_line_input(Port port, Line line, bool value) | ||||
| 		{ | ||||
| 			switch(line) | ||||
| 			{ | ||||
| 		inline void set_control_line_input(Port port, Line line, bool value) { | ||||
| 			switch(line) { | ||||
| 				case Line::One: | ||||
| 					if(	value != control_inputs_[port].line_one && | ||||
| 						value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01)) | ||||
| 					) | ||||
| 					{ | ||||
| 					) { | ||||
| 						registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; | ||||
| 						reevaluate_interrupts(); | ||||
| 					} | ||||
| @@ -220,8 +208,7 @@ template <class T> class MOS6522 { | ||||
| 					if(	value != control_inputs_[port].line_two &&							// i.e. value has changed ... | ||||
| 						!(registers_.peripheral_control & (port ? 0x80 : 0x08)) &&			// ... and line is input ... | ||||
| 						value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04))	// ... and it's either high or low, as required | ||||
| 					) | ||||
| 					{ | ||||
| 					) { | ||||
| 						registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; | ||||
| 						reevaluate_interrupts(); | ||||
| 					} | ||||
| @@ -234,8 +221,7 @@ template <class T> class MOS6522 { | ||||
| 	registers_.last_timer[0] = registers_.timer[0];\ | ||||
| 	registers_.last_timer[1] = registers_.timer[1];\ | ||||
| \ | ||||
| 	if(registers_.timer_needs_reload)\ | ||||
| 	{\ | ||||
| 	if(registers_.timer_needs_reload) {\ | ||||
| 		registers_.timer_needs_reload = false;\ | ||||
| 		registers_.timer[0] = registers_.timer_latch[0];\ | ||||
| 	}\ | ||||
| @@ -248,15 +234,13 @@ template <class T> class MOS6522 { | ||||
|  | ||||
| 	// IRQ is raised on the half cycle after overflow | ||||
| #define phase1()	\ | ||||
| 	if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1])\ | ||||
| 	{\ | ||||
| 	if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\ | ||||
| 		timer_is_running_[1] = false;\ | ||||
| 		registers_.interrupt_flags |= InterruptFlag::Timer2;\ | ||||
| 		reevaluate_interrupts();\ | ||||
| 	}\ | ||||
| \ | ||||
| 	if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0])\ | ||||
| 	{\ | ||||
| 	if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\ | ||||
| 		registers_.interrupt_flags |= InterruptFlag::Timer1;\ | ||||
| 		reevaluate_interrupts();\ | ||||
| \ | ||||
| @@ -279,28 +263,22 @@ template <class T> class MOS6522 { | ||||
| 			Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not | ||||
| 			intermingle usage. | ||||
| 		*/ | ||||
| 		inline void run_for_half_cycles(unsigned int number_of_cycles) | ||||
| 		{ | ||||
| 			if(is_phase2_) | ||||
| 			{ | ||||
| 		inline void run_for_half_cycles(unsigned int number_of_cycles) { | ||||
| 			if(is_phase2_) { | ||||
| 				phase2(); | ||||
| 				number_of_cycles--; | ||||
| 			} | ||||
|  | ||||
| 			while(number_of_cycles >= 2) | ||||
| 			{ | ||||
| 			while(number_of_cycles >= 2) { | ||||
| 				phase1(); | ||||
| 				phase2(); | ||||
| 				number_of_cycles -= 2; | ||||
| 			} | ||||
|  | ||||
| 			if(number_of_cycles) | ||||
| 			{ | ||||
| 			if(number_of_cycles) { | ||||
| 				phase1(); | ||||
| 				is_phase2_ = true; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				is_phase2_ = false; | ||||
| 			} | ||||
| 		} | ||||
| @@ -311,10 +289,8 @@ template <class T> class MOS6522 { | ||||
| 			Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not | ||||
| 			intermingle usage. | ||||
| 		*/ | ||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) | ||||
| 		{ | ||||
| 			while(number_of_cycles--) | ||||
| 			{ | ||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) { | ||||
| 			while(number_of_cycles--) { | ||||
| 				phase1(); | ||||
| 				phase2(); | ||||
| 			} | ||||
| @@ -324,8 +300,7 @@ template <class T> class MOS6522 { | ||||
| #undef phase2 | ||||
|  | ||||
| 		/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ | ||||
| 		inline bool get_interrupt_line() | ||||
| 		{ | ||||
| 		inline bool get_interrupt_line() { | ||||
| 			uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; | ||||
| 			return !!interrupt_status; | ||||
| 		} | ||||
| @@ -333,8 +308,7 @@ template <class T> class MOS6522 { | ||||
| 		MOS6522() : | ||||
| 			timer_is_running_{false, false}, | ||||
| 			last_posted_interrupt_status_(false), | ||||
| 			is_phase2_(false) | ||||
| 		{} | ||||
| 			is_phase2_(false) {} | ||||
|  | ||||
| 	private: | ||||
| 		// Expected to be overridden | ||||
| @@ -344,8 +318,7 @@ template <class T> class MOS6522 { | ||||
| 		void set_interrupt_status(bool status)									{} | ||||
|  | ||||
| 		// Input/output multiplexer | ||||
| 		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) | ||||
| 		{ | ||||
| 		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) { | ||||
| 			uint8_t input = static_cast<T *>(this)->get_port_input(port); | ||||
| 			return (input & ~output_mask) | (output & output_mask); | ||||
| 		} | ||||
| @@ -355,11 +328,9 @@ template <class T> class MOS6522 { | ||||
|  | ||||
| 		// Delegate and communications | ||||
| 		bool last_posted_interrupt_status_; | ||||
| 		inline void reevaluate_interrupts() | ||||
| 		{ | ||||
| 		inline void reevaluate_interrupts() { | ||||
| 			bool new_interrupt_status = get_interrupt_line(); | ||||
| 			if(new_interrupt_status != last_posted_interrupt_status_) | ||||
| 			{ | ||||
| 			if(new_interrupt_status != last_posted_interrupt_status_) { | ||||
| 				last_posted_interrupt_status_ = new_interrupt_status; | ||||
| 				static_cast<T *>(this)->set_interrupt_status(new_interrupt_status); | ||||
| 			} | ||||
| @@ -404,13 +375,11 @@ class MOS6522IRQDelegate { | ||||
| 				virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; | ||||
| 		}; | ||||
|  | ||||
| 		inline void set_interrupt_delegate(Delegate *delegate) | ||||
| 		{ | ||||
| 		inline void set_interrupt_delegate(Delegate *delegate) { | ||||
| 			delegate_ = delegate; | ||||
| 		} | ||||
|  | ||||
| 		inline void set_interrupt_status(bool new_status) | ||||
| 		{ | ||||
| 		inline void set_interrupt_status(bool new_status) { | ||||
| 			if(delegate_) delegate_->mos6522_did_change_interrupt_status(this); | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -30,8 +30,7 @@ template <class T> class MOS6532 { | ||||
| 		inline void set_ram(uint16_t address, uint8_t value)	{	ram_[address&0x7f] = value;		} | ||||
| 		inline uint8_t get_ram(uint16_t address)				{	return ram_[address & 0x7f];	} | ||||
|  | ||||
| 		inline void set_register(int address, uint8_t value) | ||||
| 		{ | ||||
| 		inline void set_register(int address, uint8_t value) { | ||||
| 			const uint8_t decodedAddress = address & 0x07; | ||||
| 			switch(decodedAddress) { | ||||
| 				// Port output | ||||
| @@ -48,16 +47,13 @@ template <class T> class MOS6532 { | ||||
|  | ||||
| 				// The timer and edge detect control | ||||
| 				case 0x04: case 0x05: case 0x06: case 0x07: | ||||
| 					if(address & 0x10) | ||||
| 					{ | ||||
| 					if(address & 0x10) { | ||||
| 						timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10 | ||||
| 						timer_.value = ((unsigned int)(value) << timer_.activeShift) | ((1 << timer_.activeShift)-1); | ||||
| 						timer_.value = ((unsigned int)value << timer_.activeShift) ; | ||||
| 						timer_.interrupt_enabled = !!(address&0x08); | ||||
| 						interrupt_status_ &= ~InterruptFlag::Timer; | ||||
| 						evaluate_interrupts(); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 					} else { | ||||
| 						a7_interrupt_.enabled = !!(address&0x2); | ||||
| 						a7_interrupt_.active_on_positive = !!(address & 0x01); | ||||
| 					} | ||||
| @@ -65,13 +61,11 @@ template <class T> class MOS6532 { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		inline uint8_t get_register(int address) | ||||
| 		{ | ||||
| 		inline uint8_t get_register(int address) { | ||||
| 			const uint8_t decodedAddress = address & 0x7; | ||||
| 			switch(decodedAddress) { | ||||
| 				// Port input | ||||
| 				case 0x00: case 0x02: | ||||
| 				{ | ||||
| 				case 0x00: case 0x02: { | ||||
| 					const int port = decodedAddress / 2; | ||||
| 					uint8_t input = static_cast<T *>(this)->get_port_input(port); | ||||
| 					return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask); | ||||
| @@ -82,8 +76,7 @@ template <class T> class MOS6532 { | ||||
| 				break; | ||||
|  | ||||
| 				// Timer and interrupt control | ||||
| 				case 0x04: case 0x06: | ||||
| 				{ | ||||
| 				case 0x04: case 0x06: { | ||||
| 					uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift); | ||||
| 					timer_.interrupt_enabled = !!(address&0x08); | ||||
| 					interrupt_status_ &= ~InterruptFlag::Timer; | ||||
| @@ -99,8 +92,7 @@ template <class T> class MOS6532 { | ||||
| 				} | ||||
| 				break; | ||||
|  | ||||
| 				case 0x05: case 0x07: | ||||
| 				{ | ||||
| 				case 0x05: case 0x07: { | ||||
| 					uint8_t value = interrupt_status_; | ||||
| 					interrupt_status_ &= ~InterruptFlag::PA7; | ||||
| 					evaluate_interrupts(); | ||||
| @@ -112,14 +104,13 @@ template <class T> class MOS6532 { | ||||
| 			return 0xff; | ||||
| 		} | ||||
|  | ||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) | ||||
| 		{ | ||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) { | ||||
| 			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour | ||||
| 			if(timer_.value >= number_of_cycles) { | ||||
| 				timer_.value -= number_of_cycles; | ||||
| 			} else { | ||||
| 				number_of_cycles -= timer_.value; | ||||
| 				timer_.value = 0x100 - number_of_cycles; | ||||
| 				timer_.value = (0x100 - number_of_cycles) & 0xff; | ||||
| 				timer_.activeShift = 0; | ||||
| 				interrupt_status_ |= InterruptFlag::Timer; | ||||
| 				evaluate_interrupts(); | ||||
| @@ -130,23 +121,19 @@ template <class T> class MOS6532 { | ||||
| 			interrupt_status_(0), | ||||
| 			port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}}, | ||||
| 			a7_interrupt_({.last_port_value = 0, .enabled = false}), | ||||
| 			interrupt_line_(false) | ||||
| 		{} | ||||
| 			interrupt_line_(false), | ||||
| 			timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {} | ||||
|  | ||||
| 		inline void set_port_did_change(int port) | ||||
| 		{ | ||||
| 			if(!port) | ||||
| 			{ | ||||
| 		inline void set_port_did_change(int port) { | ||||
| 			if(!port) { | ||||
| 				uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask); | ||||
| 				uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value; | ||||
| 				a7_interrupt_.last_port_value = new_port_a_value; | ||||
| 				if(difference&0x80) | ||||
| 				{ | ||||
| 				if(difference&0x80) { | ||||
| 					if( | ||||
| 						((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) || | ||||
| 						(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive) | ||||
| 					) | ||||
| 					{ | ||||
| 					) { | ||||
| 						interrupt_status_ |= InterruptFlag::PA7; | ||||
| 						evaluate_interrupts(); | ||||
| 					} | ||||
| @@ -154,8 +141,7 @@ template <class T> class MOS6532 { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		inline bool get_inerrupt_line() | ||||
| 		{ | ||||
| 		inline bool get_inerrupt_line() { | ||||
| 			return interrupt_line_; | ||||
| 		} | ||||
|  | ||||
| @@ -190,8 +176,7 @@ template <class T> class MOS6532 { | ||||
| 		void set_port_output(int port, uint8_t value, uint8_t output_mask)		{} | ||||
| 		void set_irq_line(bool new_value)										{} | ||||
|  | ||||
| 		inline void evaluate_interrupts() | ||||
| 		{ | ||||
| 		inline void evaluate_interrupts() { | ||||
| 			interrupt_line_ = | ||||
| 				((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) || | ||||
| 				((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled); | ||||
|   | ||||
| @@ -14,18 +14,15 @@ Speaker::Speaker() : | ||||
| 	volume_(0), | ||||
| 	control_registers_{0, 0, 0, 0}, | ||||
| 	shift_registers_{0, 0, 0, 0}, | ||||
| 	counters_{2, 1, 0, 0}	// create a slight phase offset for the three channels | ||||
| {} | ||||
| 	counters_{2, 1, 0, 0} {}	// create a slight phase offset for the three channels | ||||
|  | ||||
| void Speaker::set_volume(uint8_t volume) | ||||
| { | ||||
| void Speaker::set_volume(uint8_t volume) { | ||||
| 	enqueue([=]() { | ||||
| 		volume_ = volume; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Speaker::set_control(int channel, uint8_t value) | ||||
| { | ||||
| void Speaker::set_control(int channel, uint8_t value) { | ||||
| 	enqueue([=]() { | ||||
| 		control_registers_[channel] = value; | ||||
| 	}); | ||||
| @@ -108,10 +105,8 @@ static uint8_t noise_pattern[] = { | ||||
| // testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e | ||||
| // means every second cycle, etc. | ||||
|  | ||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| { | ||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) | ||||
| 	{ | ||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | ||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||
| 		update(0, 2, shift); | ||||
| 		update(1, 1, shift); | ||||
| 		update(2, 0, shift); | ||||
| @@ -128,10 +123,8 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Speaker::skip_samples(unsigned int number_of_samples) | ||||
| { | ||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) | ||||
| 	{ | ||||
| void Speaker::skip_samples(unsigned int number_of_samples) { | ||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||
| 		update(0, 2, shift); | ||||
| 		update(1, 1, shift); | ||||
| 		update(2, 0, shift); | ||||
|   | ||||
| @@ -49,8 +49,7 @@ template <class T> class MOS6560 { | ||||
| 				vertical_counter_(0), | ||||
| 				cycles_since_speaker_update_(0), | ||||
| 				is_odd_frame_(false), | ||||
| 			is_odd_line_(false) | ||||
| 		{ | ||||
| 				is_odd_line_(false) { | ||||
| 			crt_->set_composite_sampling_function( | ||||
| 				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||
| 				"{" | ||||
| @@ -67,8 +66,7 @@ template <class T> class MOS6560 { | ||||
| 			set_output_mode(OutputMode::NTSC); | ||||
| 		} | ||||
|  | ||||
| 		void set_clock_rate(double clock_rate) | ||||
| 		{ | ||||
| 		void set_clock_rate(double clock_rate) { | ||||
| 			speaker_->set_input_rate((float)(clock_rate / 4.0)); | ||||
| 		} | ||||
|  | ||||
| @@ -82,8 +80,7 @@ template <class T> class MOS6560 { | ||||
| 		/*! | ||||
| 			Sets the output mode to either PAL or NTSC. | ||||
| 		*/ | ||||
| 		void set_output_mode(OutputMode output_mode) | ||||
| 		{ | ||||
| 		void set_output_mode(OutputMode output_mode) { | ||||
| 			output_mode_ = output_mode; | ||||
| 			uint8_t luminances[16] = {		// range is 0–4 | ||||
| 				0, 4, 1, 3, 2, 2, 1, 3, | ||||
| @@ -100,8 +97,7 @@ template <class T> class MOS6560 { | ||||
| 			uint8_t *chrominances; | ||||
| 			Outputs::CRT::DisplayType display_type; | ||||
|  | ||||
| 			switch(output_mode) | ||||
| 			{ | ||||
| 			switch(output_mode) { | ||||
| 				case OutputMode::PAL: | ||||
| 					chrominances = pal_chrominances; | ||||
| 					display_type = Outputs::CRT::PAL50; | ||||
| @@ -124,8 +120,7 @@ template <class T> class MOS6560 { | ||||
| 			crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type); | ||||
| //			crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); | ||||
|  | ||||
| //			switch(output_mode) | ||||
| //			{ | ||||
| //			switch(output_mode) { | ||||
| //				case OutputMode::PAL: | ||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f)); | ||||
| //				break; | ||||
| @@ -134,8 +129,7 @@ template <class T> class MOS6560 { | ||||
| //				break; | ||||
| //			} | ||||
|  | ||||
| 			for(int c = 0; c < 16; c++) | ||||
| 			{ | ||||
| 			for(int c = 0; c < 16; c++) { | ||||
| 				colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]); | ||||
| 			} | ||||
| 		} | ||||
| @@ -143,23 +137,19 @@ template <class T> class MOS6560 { | ||||
| 		/*! | ||||
| 			Runs for cycles. Derr. | ||||
| 		*/ | ||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) | ||||
| 		{ | ||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) { | ||||
| 			// keep track of the amount of time since the speaker was updated; lazy updates are applied | ||||
| 			cycles_since_speaker_update_ += number_of_cycles; | ||||
|  | ||||
| 			while(number_of_cycles--) | ||||
| 			{ | ||||
| 			while(number_of_cycles--) { | ||||
| 				// keep an old copy of the vertical count because that test is a cycle later than the actual changes | ||||
| 				int previous_vertical_counter = vertical_counter_; | ||||
|  | ||||
| 				// keep track of internal time relative to this scanline | ||||
| 				horizontal_counter_++; | ||||
| 				full_frame_counter_++; | ||||
| 				if(horizontal_counter_ == timing_.cycles_per_line) | ||||
| 				{ | ||||
| 					if(horizontal_drawing_latch_) | ||||
| 					{ | ||||
| 				if(horizontal_counter_ == timing_.cycles_per_line) { | ||||
| 					if(horizontal_drawing_latch_) { | ||||
| 						current_character_row_++; | ||||
| 						if( | ||||
| 							(current_character_row_ == 16) || | ||||
| @@ -179,8 +169,7 @@ template <class T> class MOS6560 { | ||||
| 					horizontal_drawing_latch_ = false; | ||||
|  | ||||
| 					vertical_counter_ ++; | ||||
| 					if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) | ||||
| 					{ | ||||
| 					if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) { | ||||
| 						vertical_counter_ = 0; | ||||
| 						full_frame_counter_ = 0; | ||||
|  | ||||
| @@ -198,11 +187,9 @@ template <class T> class MOS6560 { | ||||
| 				horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location); | ||||
|  | ||||
| 				if(pixel_line_cycle_ >= 0) pixel_line_cycle_++; | ||||
| 				switch(pixel_line_cycle_) | ||||
| 				{ | ||||
| 				switch(pixel_line_cycle_) { | ||||
| 					case -1: | ||||
| 						if(horizontal_drawing_latch_) | ||||
| 						{ | ||||
| 						if(horizontal_drawing_latch_) { | ||||
| 							pixel_line_cycle_ = 0; | ||||
| 							video_matrix_address_counter_ = base_video_matrix_address_counter_; | ||||
| 						} | ||||
| @@ -213,14 +200,10 @@ template <class T> class MOS6560 { | ||||
| 				} | ||||
|  | ||||
| 				uint16_t fetch_address = 0x1c; | ||||
| 				if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) | ||||
| 				{ | ||||
| 					if(column_counter_&1) | ||||
| 					{ | ||||
| 				if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) { | ||||
| 					if(column_counter_&1) { | ||||
| 						fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_; | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 					} else { | ||||
| 						fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_); | ||||
| 						video_matrix_address_counter_++; | ||||
| 						if( | ||||
| @@ -244,8 +227,7 @@ template <class T> class MOS6560 { | ||||
| 				// determine output state; colour burst and sync timing are currently a guess | ||||
| 				if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst; | ||||
| 				else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync; | ||||
| 				else | ||||
| 				{ | ||||
| 				else { | ||||
| 					this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border; | ||||
| 				} | ||||
|  | ||||
| @@ -262,10 +244,8 @@ template <class T> class MOS6560 { | ||||
| 					this_state_ = State::Sync; | ||||
|  | ||||
| 				// update the CRT | ||||
| 				if(this_state_ != output_state_) | ||||
| 				{ | ||||
| 					switch(output_state_) | ||||
| 					{ | ||||
| 				if(this_state_ != output_state_) { | ||||
| 					switch(output_state_) { | ||||
| 						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break; | ||||
| 						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0);	break; | ||||
| 						case State::Border:			output_border(cycles_in_state_ * 4);															break; | ||||
| @@ -275,32 +255,24 @@ template <class T> class MOS6560 { | ||||
| 					cycles_in_state_ = 0; | ||||
|  | ||||
| 					pixel_pointer = nullptr; | ||||
| 					if(output_state_ == State::Pixels) | ||||
| 					{ | ||||
| 					if(output_state_ == State::Pixels) { | ||||
| 						pixel_pointer = crt_->allocate_write_area(260); | ||||
| 					} | ||||
| 				} | ||||
| 				cycles_in_state_++; | ||||
|  | ||||
| 				if(this_state_ == State::Pixels) | ||||
| 				{ | ||||
| 					if(column_counter_&1) | ||||
| 					{ | ||||
| 				if(this_state_ == State::Pixels) { | ||||
| 					if(column_counter_&1) { | ||||
| 						character_value_ = pixel_data; | ||||
|  | ||||
| 						if(pixel_pointer) | ||||
| 						{ | ||||
| 						if(pixel_pointer) { | ||||
| 							uint8_t cell_colour = colours_[character_colour_ & 0x7]; | ||||
| 							if(!(character_colour_&0x8)) | ||||
| 							{ | ||||
| 							if(!(character_colour_&0x8)) { | ||||
| 								uint8_t colours[2]; | ||||
| 								if(registers_.invertedCells) | ||||
| 								{ | ||||
| 								if(registers_.invertedCells) { | ||||
| 									colours[0] = cell_colour; | ||||
| 									colours[1] = registers_.backgroundColour; | ||||
| 								} | ||||
| 								else | ||||
| 								{ | ||||
| 								} else { | ||||
| 									colours[0] = registers_.backgroundColour; | ||||
| 									colours[1] = cell_colour; | ||||
| 								} | ||||
| @@ -312,9 +284,7 @@ template <class T> class MOS6560 { | ||||
| 								pixel_pointer[5] = colours[(character_value_ >> 2)&1]; | ||||
| 								pixel_pointer[6] = colours[(character_value_ >> 1)&1]; | ||||
| 								pixel_pointer[7] = colours[(character_value_ >> 0)&1]; | ||||
| 							} | ||||
| 							else | ||||
| 							{ | ||||
| 							} else { | ||||
| 								uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour}; | ||||
| 								pixel_pointer[0] = | ||||
| 								pixel_pointer[1] = colours[(character_value_ >> 6)&3]; | ||||
| @@ -327,9 +297,7 @@ template <class T> class MOS6560 { | ||||
| 							} | ||||
| 							pixel_pointer += 8; | ||||
| 						} | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 					} else { | ||||
| 						character_code_ = pixel_data; | ||||
| 						character_colour_ = colour_data; | ||||
| 					} | ||||
| @@ -347,12 +315,10 @@ template <class T> class MOS6560 { | ||||
| 		/*! | ||||
| 			Writes to a 6560 register. | ||||
| 		*/ | ||||
| 		void set_register(int address, uint8_t value) | ||||
| 		{ | ||||
| 		void set_register(int address, uint8_t value) { | ||||
| 			address &= 0xf; | ||||
| 			registers_.direct_values[address] = value; | ||||
| 			switch(address) | ||||
| 			{ | ||||
| 			switch(address) { | ||||
| 				case 0x0: | ||||
| 					registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing; | ||||
| 					registers_.first_column_location = value & 0x7f; | ||||
| @@ -391,11 +357,9 @@ template <class T> class MOS6560 { | ||||
| 					speaker_->set_volume(value & 0xf); | ||||
| 				break; | ||||
|  | ||||
| 				case 0xf: | ||||
| 				{ | ||||
| 				case 0xf: { | ||||
| 					uint8_t new_border_colour = colours_[value & 0x07]; | ||||
| 					if(this_state_ == State::Border && new_border_colour != registers_.borderColour) | ||||
| 					{ | ||||
| 					if(this_state_ == State::Border && new_border_colour != registers_.borderColour) { | ||||
| 						output_border(cycles_in_state_ * 4); | ||||
| 						cycles_in_state_ = 0; | ||||
| 					} | ||||
| @@ -415,12 +379,10 @@ template <class T> class MOS6560 { | ||||
| 		/* | ||||
| 			Reads from a 6560 register. | ||||
| 		*/ | ||||
| 		uint8_t get_register(int address) | ||||
| 		{ | ||||
| 		uint8_t get_register(int address) { | ||||
| 			address &= 0xf; | ||||
| 			int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line; | ||||
| 			switch(address) | ||||
| 			{ | ||||
| 			switch(address) { | ||||
| 				default: return registers_.direct_values[address]; | ||||
| 				case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f); | ||||
| 				case 0x04: return (current_line >> 1) & 0xff; | ||||
| @@ -432,8 +394,7 @@ template <class T> class MOS6560 { | ||||
|  | ||||
| 		std::shared_ptr<Speaker> speaker_; | ||||
| 		unsigned int cycles_since_speaker_update_; | ||||
| 		void update_audio() | ||||
| 		{ | ||||
| 		void update_audio() { | ||||
| 			speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2); | ||||
| 			cycles_since_speaker_update_ &= 3; | ||||
| 		} | ||||
| @@ -478,8 +439,7 @@ template <class T> class MOS6560 { | ||||
| 		uint8_t colours_[16]; | ||||
|  | ||||
| 		uint8_t *pixel_pointer; | ||||
| 		void output_border(unsigned int number_of_cycles) | ||||
| 		{ | ||||
| 		void output_border(unsigned int number_of_cycles) { | ||||
| 			uint8_t *colour_pointer = crt_->allocate_write_area(1); | ||||
| 			if(colour_pointer) *colour_pointer = registers_.borderColour; | ||||
| 			crt_->output_level(number_of_cycles); | ||||
|   | ||||
| @@ -16,17 +16,13 @@ AY38910::AY38910() : | ||||
| 		noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0), | ||||
| 		envelope_divider_(0), envelope_period_(0), envelope_position_(0), | ||||
| 		master_divider_(0), | ||||
| 	output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||||
| { | ||||
| 		output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} { | ||||
| 	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0; | ||||
|  | ||||
| 	// set up envelope lookup tables | ||||
| 	for(int c = 0; c < 16; c++) | ||||
| 	{ | ||||
| 		for(int p = 0; p < 32; p++) | ||||
| 		{ | ||||
| 			switch(c) | ||||
| 			{ | ||||
| 	for(int c = 0; c < 16; c++) { | ||||
| 		for(int p = 0; p < 32; p++) { | ||||
| 			switch(c) { | ||||
| 				case 0: case 1: case 2: case 3: case 9: | ||||
| 					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0; | ||||
| 					envelope_overflow_masks_[c] = 0x1f; | ||||
| @@ -69,34 +65,28 @@ AY38910::AY38910() : | ||||
| 	// set up volume lookup table | ||||
| 	float max_volume = 8192; | ||||
| 	float root_two = sqrtf(2.0f); | ||||
| 	for(int v = 0; v < 16; v++) | ||||
| 	{ | ||||
| 	for(int v = 0; v < 16; v++) { | ||||
| 		volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf))); | ||||
| 	} | ||||
| 	volumes_[0] = 0; | ||||
| } | ||||
|  | ||||
| void AY38910::set_clock_rate(double clock_rate) | ||||
| { | ||||
| void AY38910::set_clock_rate(double clock_rate) { | ||||
| 	set_input_rate((float)clock_rate); | ||||
| } | ||||
|  | ||||
| void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| { | ||||
| void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { | ||||
| 	int c = 0; | ||||
| 	while((master_divider_&15) && c < number_of_samples) | ||||
| 	{ | ||||
| 	while((master_divider_&7) && c < number_of_samples) { | ||||
| 		target[c] = output_volume_; | ||||
| 		master_divider_++; | ||||
| 		c++; | ||||
| 	} | ||||
|  | ||||
| 	while(c < number_of_samples) | ||||
| 	{ | ||||
| 	while(c < number_of_samples) { | ||||
| #define step_channel(c) \ | ||||
| 	if(tone_counters_[c]) tone_counters_[c]--;\ | ||||
| 	else\ | ||||
| 	{\ | ||||
| 	else {\ | ||||
| 		tone_outputs_[c] ^= 1;\ | ||||
| 		tone_counters_[c] = tone_periods_[c];\ | ||||
| 	} | ||||
| @@ -111,8 +101,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| 		// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting | ||||
| 		// it into the official 17 upon divider underflow. | ||||
| 		if(noise_counter_) noise_counter_--; | ||||
| 		else | ||||
| 		{ | ||||
| 		else { | ||||
| 			noise_counter_ = noise_period_; | ||||
| 			noise_output_ ^= noise_shift_register_&1; | ||||
| 			noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17; | ||||
| @@ -122,8 +111,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| 		// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of | ||||
| 		// implementing non-repeating patterns by locking them to table position 0x1f. | ||||
| 		if(envelope_divider_) envelope_divider_--; | ||||
| 		else | ||||
| 		{ | ||||
| 		else { | ||||
| 			envelope_divider_ = envelope_period_; | ||||
| 			envelope_position_ ++; | ||||
| 			if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]]; | ||||
| @@ -131,19 +119,17 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
|  | ||||
| 		evaluate_output_volume(); | ||||
|  | ||||
| 		for(int ic = 0; ic < 16 && c < number_of_samples; ic++) | ||||
| 		{ | ||||
| 		for(int ic = 0; ic < 8 && c < number_of_samples; ic++) { | ||||
| 			target[c] = output_volume_; | ||||
| 			c++; | ||||
| 			master_divider_++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	master_divider_ &= 15; | ||||
| 	master_divider_ &= 7; | ||||
| } | ||||
|  | ||||
| void AY38910::evaluate_output_volume() | ||||
| { | ||||
| void AY38910::evaluate_output_volume() { | ||||
| 	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_]; | ||||
|  | ||||
| 	// The output level for a channel is: | ||||
| @@ -180,24 +166,19 @@ void AY38910::evaluate_output_volume() | ||||
| 	); | ||||
| } | ||||
|  | ||||
| void AY38910::select_register(uint8_t r) | ||||
| { | ||||
| void AY38910::select_register(uint8_t r) { | ||||
| 	selected_register_ = r & 0xf; | ||||
| } | ||||
|  | ||||
| void AY38910::set_register_value(uint8_t value) | ||||
| { | ||||
| void AY38910::set_register_value(uint8_t value) { | ||||
| 	registers_[selected_register_] = value; | ||||
| 	if(selected_register_ < 14) | ||||
| 	{ | ||||
| 	if(selected_register_ < 14) { | ||||
| 		int selected_register = selected_register_; | ||||
| 		enqueue([=] () { | ||||
| 			uint8_t masked_value = value; | ||||
| 			switch(selected_register) | ||||
| 			{ | ||||
| 			switch(selected_register) { | ||||
| 				case 0: case 2: case 4: | ||||
| 				case 1: case 3: case 5: | ||||
| 				{ | ||||
| 				case 1: case 3: case 5: { | ||||
| 					int channel = selected_register >> 1; | ||||
|  | ||||
| 					if(selected_register & 1) | ||||
| @@ -234,8 +215,7 @@ void AY38910::set_register_value(uint8_t value) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t AY38910::get_register_value() | ||||
| { | ||||
| uint8_t AY38910::get_register_value() { | ||||
| 	// This table ensures that bits that aren't defined within the AY are returned as 1s | ||||
| 	// when read. I can't find documentation on this and don't have a machine to test, so | ||||
| 	// this is provisionally a guess. TODO: investigate. | ||||
| @@ -247,26 +227,21 @@ uint8_t AY38910::get_register_value() | ||||
| 	return registers_[selected_register_] | register_masks[selected_register_]; | ||||
| } | ||||
|  | ||||
| uint8_t AY38910::get_port_output(bool port_b) | ||||
| { | ||||
| uint8_t AY38910::get_port_output(bool port_b) { | ||||
| 	return registers_[port_b ? 15 : 14]; | ||||
| } | ||||
|  | ||||
| void AY38910::set_data_input(uint8_t r) | ||||
| { | ||||
| void AY38910::set_data_input(uint8_t r) { | ||||
| 	data_input_ = r; | ||||
| } | ||||
|  | ||||
| uint8_t AY38910::get_data_output() | ||||
| { | ||||
| uint8_t AY38910::get_data_output() { | ||||
| 	return data_output_; | ||||
| } | ||||
|  | ||||
| void AY38910::set_control_lines(ControlLines control_lines) | ||||
| { | ||||
| void AY38910::set_control_lines(ControlLines control_lines) { | ||||
| 	ControlState new_state; | ||||
| 	switch((int)control_lines) | ||||
| 	{ | ||||
| 	switch((int)control_lines) { | ||||
| 		default:					new_state = Inactive;		break; | ||||
|  | ||||
| 		case (int)(BCDIR | BC2 | BC1): | ||||
| @@ -277,11 +252,9 @@ void AY38910::set_control_lines(ControlLines control_lines) | ||||
| 		case (int)(BCDIR | BC2):	new_state = Write;			break; | ||||
| 	} | ||||
|  | ||||
| 	if(new_state != control_state_) | ||||
| 	{ | ||||
| 	if(new_state != control_state_) { | ||||
| 		control_state_ = new_state; | ||||
| 		switch(new_state) | ||||
| 		{ | ||||
| 		switch(new_state) { | ||||
| 			default: break; | ||||
| 			case LatchAddress:	select_register(data_input_);			break; | ||||
| 			case Write:			set_register_value(data_input_);		break; | ||||
|   | ||||
| @@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue() | ||||
| 	serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL); | ||||
| #else | ||||
| 	thread_.reset(new std::thread([this]() { | ||||
| 		while(!should_destruct_) | ||||
| 		{ | ||||
| 		while(!should_destruct_) { | ||||
| 			std::function<void(void)> next_function; | ||||
|  | ||||
| 			// Take lock, check for a new task | ||||
| 			std::unique_lock<std::mutex> lock(queue_mutex_); | ||||
| 			if(!pending_tasks_.empty()) | ||||
| 			{ | ||||
| 			if(!pending_tasks_.empty()) { | ||||
| 				next_function = pending_tasks_.front(); | ||||
| 				pending_tasks_.pop_front(); | ||||
| 			} | ||||
|  | ||||
| 			if(next_function) | ||||
| 			{ | ||||
| 			if(next_function) { | ||||
| 				// If there is a task, release lock and perform it | ||||
| 				lock.unlock(); | ||||
| 				next_function(); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				// If there isn't a task, atomically block on the processing condition and release the lock | ||||
| 				// until there's something pending (and then release it again via scope) | ||||
| 				processing_condition_.wait(lock); | ||||
| @@ -48,8 +43,7 @@ AsyncTaskQueue::AsyncTaskQueue() | ||||
| #endif | ||||
| } | ||||
|  | ||||
| AsyncTaskQueue::~AsyncTaskQueue() | ||||
| { | ||||
| AsyncTaskQueue::~AsyncTaskQueue() { | ||||
| #ifdef __APPLE__ | ||||
| 	dispatch_release(serial_dispatch_queue_); | ||||
| #else | ||||
| @@ -60,8 +54,7 @@ AsyncTaskQueue::~AsyncTaskQueue() | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void AsyncTaskQueue::enqueue(std::function<void(void)> function) | ||||
| { | ||||
| void AsyncTaskQueue::enqueue(std::function<void(void)> function) { | ||||
| #ifdef __APPLE__ | ||||
| 	dispatch_async(serial_dispatch_queue_, ^{function();}); | ||||
| #else | ||||
| @@ -71,8 +64,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function) | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void AsyncTaskQueue::flush() | ||||
| { | ||||
| void AsyncTaskQueue::flush() { | ||||
| #ifdef __APPLE__ | ||||
| 	dispatch_sync(serial_dispatch_queue_, ^{}); | ||||
| #else | ||||
|   | ||||
| @@ -10,772 +10,148 @@ | ||||
| #include <algorithm> | ||||
| #include <stdio.h> | ||||
|  | ||||
| #include "Cartridges/CartridgeAtari8k.hpp" | ||||
| #include "Cartridges/CartridgeAtari16k.hpp" | ||||
| #include "Cartridges/CartridgeAtari32k.hpp" | ||||
| #include "Cartridges/CartridgeActivisionStack.hpp" | ||||
| #include "Cartridges/CartridgeCBSRAMPlus.hpp" | ||||
| #include "Cartridges/CartridgeCommaVid.hpp" | ||||
| #include "Cartridges/CartridgeMegaBoy.hpp" | ||||
| #include "Cartridges/CartridgeMNetwork.hpp" | ||||
| #include "Cartridges/CartridgeParkerBros.hpp" | ||||
| #include "Cartridges/CartridgePitfall2.hpp" | ||||
| #include "Cartridges/CartridgeTigervision.hpp" | ||||
| #include "Cartridges/CartridgeUnpaged.hpp" | ||||
|  | ||||
| using namespace Atari2600; | ||||
| namespace { | ||||
| 	static const unsigned int horizontalTimerPeriod = 228; | ||||
| 	static const double NTSC_clock_rate = 1194720; | ||||
| 	static const double PAL_clock_rate = 1182298; | ||||
| } | ||||
|  | ||||
| Machine::Machine() : | ||||
| 	horizontal_timer_(0), | ||||
| 	last_output_state_duration_(0), | ||||
| 	last_output_state_(OutputState::Sync), | ||||
| 	rom_(nullptr), | ||||
| 	tia_input_value_{0xff, 0xff}, | ||||
| 	upcoming_events_pointer_(0), | ||||
| 	object_counter_pointer_(0), | ||||
| 	state_by_time_(state_by_extend_time_[0]), | ||||
| 	cycles_since_speaker_update_(0), | ||||
| 	is_pal_region_(false) | ||||
| { | ||||
| 	memset(collisions_, 0xff, sizeof(collisions_)); | ||||
| 	setup_reported_collisions(); | ||||
|  | ||||
| 	for(int vbextend = 0; vbextend < 2; vbextend++) | ||||
| 	{ | ||||
| 		for(int c = 0; c < 57; c++) | ||||
| 		{ | ||||
| 			OutputState state; | ||||
|  | ||||
| 			// determine which output state will be active in four cycles from now | ||||
| 			switch(c) | ||||
| 			{ | ||||
| 				case 0: case 1: case 2: case 3:					state = OutputState::Blank;									break; | ||||
| 				case 4: case 5: case 6: case 7:					state = OutputState::Sync;									break; | ||||
| 				case 8: case 9: case 10: case 11:				state = OutputState::ColourBurst;							break; | ||||
| 				case 12: case 13: case 14: | ||||
| 				case 15: case 16:								state = OutputState::Blank;									break; | ||||
|  | ||||
| 				case 17: case 18:								state = vbextend ? OutputState::Blank : OutputState::Pixel;	break; | ||||
| 				default:										state = OutputState::Pixel;									break; | ||||
| 			} | ||||
|  | ||||
| 			state_by_extend_time_[vbextend][c] = state; | ||||
| 		} | ||||
| 	} | ||||
| 	frame_record_pointer_(0), | ||||
| 	is_ntsc_(true) { | ||||
| 	set_clock_rate(NTSC_clock_rate); | ||||
| } | ||||
|  | ||||
| void Machine::setup_output(float aspect_ratio) | ||||
| { | ||||
| 	speaker_.reset(new Speaker); | ||||
| 	crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, false, 1)); | ||||
| 	crt_->set_output_device(Outputs::CRT::Television); | ||||
|  | ||||
| 	// this is the NTSC phase offset function; see below for PAL | ||||
| 	crt_->set_composite_sampling_function( | ||||
| 		"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||
| 		"{" | ||||
| 			"uint c = texture(texID, coordinate).r;" | ||||
| 			"uint y = c & 14u;" | ||||
| 			"uint iPhase = (c >> 4);" | ||||
|  | ||||
| 			"float phaseOffset = 6.283185308 * float(iPhase - 1u) / 13.0;" | ||||
| 			"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" | ||||
| 		"}"); | ||||
| 	speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); | ||||
| void Machine::setup_output(float aspect_ratio) { | ||||
| 	bus_->tia_.reset(new TIA); | ||||
| 	bus_->speaker_.reset(new Speaker); | ||||
| 	bus_->speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); | ||||
| 	bus_->tia_->get_crt()->set_delegate(this); | ||||
| } | ||||
|  | ||||
| void Machine::switch_region() | ||||
| { | ||||
| 	// the PAL function | ||||
| 	crt_->set_composite_sampling_function( | ||||
| 		"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||
| 		"{" | ||||
| 			"uint c = texture(texID, coordinate).r;" | ||||
| 			"uint y = c & 14u;" | ||||
| 			"uint iPhase = (c >> 4);" | ||||
|  | ||||
| 			"uint direction = iPhase & 1u;" | ||||
| 			"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" | ||||
| 			"phaseOffset *= 6.283185308 / 12.0;" | ||||
| 			"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" | ||||
| 		"}"); | ||||
|  | ||||
| 	crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1, true); | ||||
|  | ||||
| 	is_pal_region_ = true; | ||||
| 	speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); | ||||
| 	set_clock_rate(PAL_clock_rate); | ||||
| void Machine::close_output() { | ||||
| 	bus_.reset(); | ||||
| } | ||||
|  | ||||
| void Machine::close_output() | ||||
| { | ||||
| 	crt_ = nullptr; | ||||
| } | ||||
|  | ||||
| Machine::~Machine() | ||||
| { | ||||
| 	delete[] rom_; | ||||
| Machine::~Machine() { | ||||
| 	close_output(); | ||||
| } | ||||
|  | ||||
| void Machine::update_timers(int mask) | ||||
| { | ||||
| 	unsigned int upcoming_pointer_plus_4 = (upcoming_events_pointer_ + 4)%number_of_upcoming_events; | ||||
|  | ||||
| 	object_counter_pointer_ = (object_counter_pointer_ + 1)%number_of_recorded_counters; | ||||
| 	ObjectCounter *oneClockAgo = object_counter_[(object_counter_pointer_ - 1 + number_of_recorded_counters)%number_of_recorded_counters]; | ||||
| 	ObjectCounter *twoClocksAgo = object_counter_[(object_counter_pointer_ - 2 + number_of_recorded_counters)%number_of_recorded_counters]; | ||||
| 	ObjectCounter *now = object_counter_[object_counter_pointer_]; | ||||
|  | ||||
| 	// grab the background now, for application in four clocks | ||||
| 	if(mask & (1 << 5) && !(horizontal_timer_&3)) | ||||
| 	{ | ||||
| 		unsigned int offset = 4 + horizontal_timer_ - (horizontalTimerPeriod - 160); | ||||
| 		upcoming_events_[upcoming_pointer_plus_4].updates |= Event::Action::Playfield; | ||||
| 		upcoming_events_[upcoming_pointer_plus_4].playfield_pixel = playfield_[(offset >> 2)%40]; | ||||
| 	} | ||||
|  | ||||
| 	if(mask & (1 << 4)) | ||||
| 	{ | ||||
| 		// the ball becomes visible whenever it hits zero, regardless of whether its status | ||||
| 		// is the result of a counter rollover or a programmatic reset, and there's a four | ||||
| 		// clock delay on that triggering the start signal | ||||
| 		now[4].count = (oneClockAgo[4].count + 1)%160; | ||||
| 		now[4].pixel = oneClockAgo[4].pixel + 1; | ||||
| 		if(!now[4].count) now[4].pixel = 0; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		now[4] = oneClockAgo[4]; | ||||
| 	} | ||||
|  | ||||
| 	// check for player and missle triggers | ||||
| 	for(int c = 0; c < 4; c++) | ||||
| 	{ | ||||
| 		if(mask & (1 << c)) | ||||
| 		{ | ||||
| 			// update the count | ||||
| 			now[c].count = (oneClockAgo[c].count + 1)%160; | ||||
|  | ||||
| 			uint8_t repeatMask = player_and_missile_size_[c&1] & 7; | ||||
| 			ObjectCounter *rollover; | ||||
| 			ObjectCounter *equality; | ||||
|  | ||||
| 			if(c < 2) | ||||
| 			{ | ||||
| 				// update the pixel | ||||
| 				now[c].broad_pixel = oneClockAgo[c].broad_pixel + 1; | ||||
| 				switch(repeatMask) | ||||
| 				{ | ||||
| 					default:	now[c].pixel = oneClockAgo[c].pixel + 1;	break; | ||||
| 					case 5:		now[c].pixel = oneClockAgo[c].pixel + (now[c].broad_pixel&1);	break; | ||||
| 					case 7:		now[c].pixel = oneClockAgo[c].pixel + (((now[c].broad_pixel | (now[c].broad_pixel >> 1))^1)&1);	break; | ||||
| 				} | ||||
|  | ||||
| 				// check for a rollover six clocks ago or equality five clocks ago | ||||
| 				rollover = twoClocksAgo; | ||||
| 				equality = oneClockAgo; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// update the pixel | ||||
| 				now[c].pixel = oneClockAgo[c].pixel + 1; | ||||
|  | ||||
| 				// check for a rollover five clocks ago or equality four clocks ago | ||||
| 				rollover = oneClockAgo; | ||||
| 				equality = now; | ||||
| 			} | ||||
|  | ||||
| 			if( | ||||
| 				(rollover[c].count == 159) || | ||||
| 				(has_second_copy_[c&1] && equality[c].count == 16) || | ||||
| 				(has_third_copy_[c&1] && equality[c].count == 32) || | ||||
| 				(has_fourth_copy_[c&1] && equality[c].count == 64) | ||||
| 			) | ||||
| 			{ | ||||
| 				now[c].pixel = 0; | ||||
| 				now[c].broad_pixel = 0; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			now[c] = oneClockAgo[c]; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t Machine::get_output_pixel() | ||||
| { | ||||
| 	ObjectCounter *now = object_counter_[object_counter_pointer_]; | ||||
|  | ||||
| 	// get the playfield pixel | ||||
| 	unsigned int offset = horizontal_timer_ - (horizontalTimerPeriod - 160); | ||||
| 	uint8_t playfieldColour = ((playfield_control_&6) == 2) ? player_colour_[offset / 80] : playfield_colour_; | ||||
|  | ||||
| 	// ball pixel | ||||
| 	uint8_t ballPixel = 0; | ||||
| 	if(now[4].pixel < ball_size_) { | ||||
| 		ballPixel = ball_graphics_enable_[ball_graphics_selector_]; | ||||
| 	} | ||||
|  | ||||
| 	// determine the player and missile pixels | ||||
| 	uint8_t playerPixels[2] = { 0, 0 }; | ||||
| 	uint8_t missilePixels[2] = { 0, 0 }; | ||||
| 	for(int c = 0; c < 2; c++) | ||||
| 	{ | ||||
| 		if(player_graphics_[c] && now[c].pixel < 8) { | ||||
| 			playerPixels[c] = (player_graphics_[player_graphics_selector_[c]][c] >> (now[c].pixel ^ player_reflection_mask_[c])) & 1; | ||||
| 		} | ||||
|  | ||||
| 		if(!missile_graphics_reset_[c] && now[c+2].pixel < missile_size_[c]) { | ||||
| 			missilePixels[c] = missile_graphics_enable_[c]; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// accumulate collisions | ||||
| 	int pixel_mask = playerPixels[0] | (playerPixels[1] << 1) | (missilePixels[0] << 2) | (missilePixels[1] << 3) | (ballPixel << 4) | (playfield_output_ << 5); | ||||
| 	collisions_[0] |= reported_collisions_[pixel_mask][0]; | ||||
| 	collisions_[1] |= reported_collisions_[pixel_mask][1]; | ||||
| 	collisions_[2] |= reported_collisions_[pixel_mask][2]; | ||||
| 	collisions_[3] |= reported_collisions_[pixel_mask][3]; | ||||
| 	collisions_[4] |= reported_collisions_[pixel_mask][4]; | ||||
| 	collisions_[5] |= reported_collisions_[pixel_mask][5]; | ||||
| 	collisions_[6] |= reported_collisions_[pixel_mask][6]; | ||||
| 	collisions_[7] |= reported_collisions_[pixel_mask][7]; | ||||
|  | ||||
| 	// apply appropriate priority to pick a colour | ||||
| 	uint8_t playfield_pixel = playfield_output_ | ballPixel; | ||||
| 	uint8_t outputColour = playfield_pixel ? playfieldColour : background_colour_; | ||||
|  | ||||
| 	if(!(playfield_control_&0x04) || !playfield_pixel) { | ||||
| 		if(playerPixels[1] || missilePixels[1]) outputColour = player_colour_[1]; | ||||
| 		if(playerPixels[0] || missilePixels[0]) outputColour = player_colour_[0]; | ||||
| 	} | ||||
|  | ||||
| 	// return colour | ||||
| 	return outputColour; | ||||
| } | ||||
|  | ||||
| void Machine::setup_reported_collisions() | ||||
| { | ||||
| 	for(int c = 0; c < 64; c++) | ||||
| 	{ | ||||
| 		memset(reported_collisions_[c], 0, 8); | ||||
|  | ||||
| 		int playerPixels[2] = { c&1, (c >> 1)&1 }; | ||||
| 		int missilePixels[2] = { (c >> 2)&1, (c >> 3)&1 }; | ||||
| 		int ballPixel = (c >> 4)&1; | ||||
| 		int playfield_pixel = (c >> 5)&1; | ||||
|  | ||||
| 		if(playerPixels[0] | playerPixels[1]) { | ||||
| 			reported_collisions_[c][0] |= ((missilePixels[0] & playerPixels[1]) << 7)	| ((missilePixels[0] & playerPixels[0]) << 6); | ||||
| 			reported_collisions_[c][1] |= ((missilePixels[1] & playerPixels[0]) << 7)	| ((missilePixels[1] & playerPixels[1]) << 6); | ||||
|  | ||||
| 			reported_collisions_[c][2] |= ((playfield_pixel & playerPixels[0]) << 7)	| ((ballPixel & playerPixels[0]) << 6); | ||||
| 			reported_collisions_[c][3] |= ((playfield_pixel & playerPixels[1]) << 7)	| ((ballPixel & playerPixels[1]) << 6); | ||||
|  | ||||
| 			reported_collisions_[c][7] |= ((playerPixels[0] & playerPixels[1]) << 7); | ||||
| 		} | ||||
|  | ||||
| 		if(playfield_pixel | ballPixel) { | ||||
| 			reported_collisions_[c][4] |= ((playfield_pixel & missilePixels[0]) << 7)	| ((ballPixel & missilePixels[0]) << 6); | ||||
| 			reported_collisions_[c][5] |= ((playfield_pixel & missilePixels[1]) << 7)	| ((ballPixel & missilePixels[1]) << 6); | ||||
|  | ||||
| 			reported_collisions_[c][6] |= ((playfield_pixel & ballPixel) << 7); | ||||
| 		} | ||||
|  | ||||
| 		if(missilePixels[0] & missilePixels[1]) | ||||
| 			reported_collisions_[c][7] |= (1 << 6); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::output_pixels(unsigned int count) | ||||
| { | ||||
| 	while(count--) | ||||
| 	{ | ||||
| 		if(upcoming_events_[upcoming_events_pointer_].updates) | ||||
| 		{ | ||||
| 			// apply any queued changes and flush the record | ||||
| 			if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveSetup) | ||||
| 			{ | ||||
| 				// schedule an extended left border | ||||
| 				state_by_time_ = state_by_extend_time_[1]; | ||||
|  | ||||
| 				// clear any ongoing moves | ||||
| 				if(hmove_flags_) | ||||
| 				{ | ||||
| 					for(int c = 0; c < number_of_upcoming_events; c++) | ||||
| 					{ | ||||
| 						upcoming_events_[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// schedule new moves | ||||
| 				hmove_flags_ = 0x1f; | ||||
| 				hmove_counter_ = 15; | ||||
|  | ||||
| 				// follow-through into a compare immediately | ||||
| 				upcoming_events_[upcoming_events_pointer_].updates |= Event::Action::HMoveCompare; | ||||
| 			} | ||||
|  | ||||
| 			if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveCompare) | ||||
| 			{ | ||||
| 				for(int c = 0; c < 5; c++) | ||||
| 				{ | ||||
| 					if(((object_motions_[c] >> 4)^hmove_counter_) == 7) | ||||
| 					{ | ||||
| 						hmove_flags_ &= ~(1 << c); | ||||
| 					} | ||||
| 				} | ||||
| 				if(hmove_flags_) | ||||
| 				{ | ||||
| 					if(hmove_counter_) hmove_counter_--; | ||||
| 					upcoming_events_[(upcoming_events_pointer_+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; | ||||
| 					upcoming_events_[(upcoming_events_pointer_+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveDecrement) | ||||
| 			{ | ||||
| 				update_timers(hmove_flags_); | ||||
| 			} | ||||
|  | ||||
| 			if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::ResetCounter) | ||||
| 			{ | ||||
| 				object_counter_[object_counter_pointer_][upcoming_events_[upcoming_events_pointer_].counter].count = 0; | ||||
| 			} | ||||
|  | ||||
| 			// zero out current update event | ||||
| 			upcoming_events_[upcoming_events_pointer_].updates = 0; | ||||
| 		} | ||||
|  | ||||
| 		//  progress to next event | ||||
| 		upcoming_events_pointer_ = (upcoming_events_pointer_ + 1)%number_of_upcoming_events; | ||||
|  | ||||
| 		// determine which output state is currently active | ||||
| 		OutputState primary_state = state_by_time_[horizontal_timer_ >> 2]; | ||||
| 		OutputState effective_state = primary_state; | ||||
|  | ||||
| 		// update pixel timers | ||||
| 		if(primary_state == OutputState::Pixel) update_timers(~0); | ||||
|  | ||||
| 		// update the background chain | ||||
| 		if(horizontal_timer_ >= 64 && horizontal_timer_ <= 160+64 && !(horizontal_timer_&3)) | ||||
| 		{ | ||||
| 			playfield_output_ = next_playfield_output_; | ||||
| 			next_playfield_output_ = playfield_[(horizontal_timer_ - 64) >> 2]; | ||||
| 		} | ||||
|  | ||||
| 		// if vsync is enabled, output the opposite of the automatic hsync output; | ||||
| 		// also	honour the vertical blank flag | ||||
| 		if(vsync_enabled_) { | ||||
| 			effective_state = (effective_state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; | ||||
| 		} else if(vblank_enabled_ && effective_state == OutputState::Pixel) { | ||||
| 			effective_state = OutputState::Blank; | ||||
| 		} | ||||
|  | ||||
| 		// decide what that means needs to be communicated to the CRT | ||||
| 		last_output_state_duration_++; | ||||
| 		if(effective_state != last_output_state_) { | ||||
| 			switch(last_output_state_) { | ||||
| 				case OutputState::Blank:		crt_->output_blank(last_output_state_duration_);				break; | ||||
| 				case OutputState::Sync:			crt_->output_sync(last_output_state_duration_);					break; | ||||
| 				case OutputState::ColourBurst:	crt_->output_colour_burst(last_output_state_duration_, 96, 0);	break; | ||||
| 				case OutputState::Pixel:		crt_->output_data(last_output_state_duration_,	1);				break; | ||||
| 			} | ||||
| 			last_output_state_duration_ = 0; | ||||
| 			last_output_state_ = effective_state; | ||||
|  | ||||
| 			if(effective_state == OutputState::Pixel) { | ||||
| 				output_buffer_ = crt_->allocate_write_area(160); | ||||
| 			} else { | ||||
| 				output_buffer_ = nullptr; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// decide on a pixel colour if that's what's happening | ||||
| 		if(effective_state == OutputState::Pixel) | ||||
| 		{ | ||||
| 			uint8_t colour = get_output_pixel(); | ||||
| 			if(output_buffer_) | ||||
| 			{ | ||||
| 				*output_buffer_ = colour; | ||||
| 				output_buffer_++; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// advance horizontal timer, perform reset actions if desired | ||||
| 		horizontal_timer_ = (horizontal_timer_ + 1) % horizontalTimerPeriod; | ||||
| 		if(!horizontal_timer_) | ||||
| 		{ | ||||
| 			// switch back to a normal length left border | ||||
| 			state_by_time_ = state_by_extend_time_[0]; | ||||
| 			set_ready_line(false); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) | ||||
| { | ||||
| 	uint8_t returnValue = 0xff; | ||||
| 	unsigned int cycles_run_for = 3; | ||||
|  | ||||
| 	// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for | ||||
| 	// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take | ||||
| 	// effect until the next read; therefore it isn't safe to assume that signalling ready immediately | ||||
| 	// skips to the end of the line. | ||||
| 	if(operation == CPU6502::BusOperation::Ready) { | ||||
| 		unsigned int distance_to_end_of_ready = horizontalTimerPeriod - horizontal_timer_; | ||||
| 		cycles_run_for = distance_to_end_of_ready; | ||||
| 	} | ||||
|  | ||||
| 	output_pixels(cycles_run_for); | ||||
| 	cycles_since_speaker_update_ += cycles_run_for; | ||||
|  | ||||
| 	if(operation != CPU6502::BusOperation::Ready) { | ||||
|  | ||||
| 		// check for a paging access | ||||
| 		if(rom_size_ > 4096 && ((address & 0x1f00) == 0x1f00)) { | ||||
| 			uint8_t *base_ptr = rom_pages_[0]; | ||||
| 			uint8_t first_paging_register = (uint8_t)(0xf8 - (rom_size_ >> 14)*2); | ||||
|  | ||||
| 			const uint8_t paging_register = address&0xff; | ||||
| 			if(paging_register >= first_paging_register) { | ||||
| 				const uint16_t selected_page = paging_register - first_paging_register; | ||||
| 				if(selected_page * 4096 < rom_size_) { | ||||
| 					base_ptr = &rom_[selected_page * 4096]; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(base_ptr != rom_pages_[0]) { | ||||
| 				rom_pages_[0] = base_ptr; | ||||
| 				rom_pages_[1] = base_ptr + 1024; | ||||
| 				rom_pages_[2] = base_ptr + 2048; | ||||
| 				rom_pages_[3] = base_ptr + 3072; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// check for a ROM read | ||||
| 		if((address&0x1000) && isReadOperation(operation)) { | ||||
| 			returnValue &= rom_pages_[(address >> 10)&3][address&1023]; | ||||
| 		} | ||||
|  | ||||
| 		// check for a RAM access | ||||
| 		if((address&0x1280) == 0x80) { | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				returnValue &= mos6532_.get_ram(address); | ||||
| 			} else { | ||||
| 				mos6532_.set_ram(address, *value); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// check for a TIA access | ||||
| 		if(!(address&0x1080)) { | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				const uint16_t decodedAddress = address & 0xf; | ||||
| 				switch(decodedAddress) { | ||||
| 					case 0x00:		// missile 0 / player collisions | ||||
| 					case 0x01:		// missile 1 / player collisions | ||||
| 					case 0x02:		// player 0 / playfield / ball collisions | ||||
| 					case 0x03:		// player 1 / playfield / ball collisions | ||||
| 					case 0x04:		// missile 0 / playfield / ball collisions | ||||
| 					case 0x05:		// missile 1 / playfield / ball collisions | ||||
| 					case 0x06:		// ball / playfield collisions | ||||
| 					case 0x07:		// player / player, missile / missile collisions | ||||
| 						returnValue &= collisions_[decodedAddress]; | ||||
| 					break; | ||||
|  | ||||
| 					case 0x08: | ||||
| 					case 0x09: | ||||
| 					case 0x0a: | ||||
| 					case 0x0b: | ||||
| 						// TODO: pot ports | ||||
| 					break; | ||||
|  | ||||
| 					case 0x0c: | ||||
| 					case 0x0d: | ||||
| 						returnValue &= tia_input_value_[decodedAddress - 0x0c]; | ||||
| 					break; | ||||
| 				} | ||||
| 			} else { | ||||
| 				const uint16_t decodedAddress = address & 0x3f; | ||||
| 				switch(decodedAddress) { | ||||
| 					case 0x00: | ||||
| 						vsync_enabled_ = !!(*value & 0x02); | ||||
| 					break; | ||||
| 					case 0x01:	vblank_enabled_ = !!(*value & 0x02);	break; | ||||
|  | ||||
| 					case 0x02: | ||||
| 						if(horizontal_timer_) set_ready_line(true); | ||||
| 					break; | ||||
| 					case 0x03: | ||||
| 						// Reset is delayed by four cycles. | ||||
| 						horizontal_timer_ = horizontalTimerPeriod - 4; | ||||
|  | ||||
| 						// TODO: audio will now be out of synchronisation — fix | ||||
| 					break; | ||||
|  | ||||
| 					case 0x04: | ||||
| 					case 0x05: { | ||||
| 						int entry = decodedAddress - 0x04; | ||||
| 						player_and_missile_size_[entry] = *value; | ||||
| 						missile_size_[entry] = 1 << ((*value >> 4)&3); | ||||
|  | ||||
| 						uint8_t repeatMask = (*value)&7; | ||||
| 						has_second_copy_[entry] = (repeatMask == 1) || (repeatMask == 3); | ||||
| 						has_third_copy_[entry] = (repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6); | ||||
| 						has_fourth_copy_[entry] = (repeatMask == 4) || (repeatMask == 6); | ||||
| 					} break; | ||||
|  | ||||
| 					case 0x06: | ||||
| 					case 0x07: player_colour_[decodedAddress - 0x06] = *value;	break; | ||||
| 					case 0x08: playfield_colour_ = *value;	break; | ||||
| 					case 0x09: background_colour_ = *value;	break; | ||||
|  | ||||
| 					case 0x0a: { | ||||
| 						uint8_t old_playfield_control = playfield_control_; | ||||
| 						playfield_control_ = *value; | ||||
| 						ball_size_ = 1 << ((playfield_control_ >> 4)&3); | ||||
|  | ||||
| 						// did the mirroring bit change? | ||||
| 						if((playfield_control_^old_playfield_control)&1) { | ||||
| 							if(playfield_control_&1) { | ||||
| 								for(int c = 0; c < 20; c++) playfield_[c+20] = playfield_[19-c]; | ||||
| 							} else { | ||||
| 								memcpy(&playfield_[20], playfield_, 20); | ||||
| 							} | ||||
| 						} | ||||
| 					} break; | ||||
| 					case 0x0b: | ||||
| 					case 0x0c: player_reflection_mask_[decodedAddress - 0x0b] = (*value)&8 ? 0 : 7;	break; | ||||
|  | ||||
| 					case 0x0d: | ||||
| 						playfield_[0] = ((*value) >> 4)&1; | ||||
| 						playfield_[1] = ((*value) >> 5)&1; | ||||
| 						playfield_[2] = ((*value) >> 6)&1; | ||||
| 						playfield_[3] = (*value) >> 7; | ||||
|  | ||||
| 						if(playfield_control_&1) { | ||||
| 							for(int c = 0; c < 4; c++) playfield_[39-c] = playfield_[c]; | ||||
| 						} else { | ||||
| 							memcpy(&playfield_[20], playfield_, 4); | ||||
| 						} | ||||
| 					break; | ||||
| 					case 0x0e: | ||||
| 						playfield_[4] = (*value) >> 7; | ||||
| 						playfield_[5] = ((*value) >> 6)&1; | ||||
| 						playfield_[6] = ((*value) >> 5)&1; | ||||
| 						playfield_[7] = ((*value) >> 4)&1; | ||||
| 						playfield_[8] = ((*value) >> 3)&1; | ||||
| 						playfield_[9] = ((*value) >> 2)&1; | ||||
| 						playfield_[10] = ((*value) >> 1)&1; | ||||
| 						playfield_[11] = (*value)&1; | ||||
|  | ||||
| 						if(playfield_control_&1) { | ||||
| 							for(int c = 0; c < 8; c++) playfield_[35-c] = playfield_[c+4]; | ||||
| 						} else { | ||||
| 							memcpy(&playfield_[24], &playfield_[4], 8); | ||||
| 						} | ||||
| 					break; | ||||
| 					case 0x0f: | ||||
| 						playfield_[19] = (*value) >> 7; | ||||
| 						playfield_[18] = ((*value) >> 6)&1; | ||||
| 						playfield_[17] = ((*value) >> 5)&1; | ||||
| 						playfield_[16] = ((*value) >> 4)&1; | ||||
| 						playfield_[15] = ((*value) >> 3)&1; | ||||
| 						playfield_[14] = ((*value) >> 2)&1; | ||||
| 						playfield_[13] = ((*value) >> 1)&1; | ||||
| 						playfield_[12] = (*value)&1; | ||||
|  | ||||
| 						if(playfield_control_&1) { | ||||
| 							for(int c = 0; c < 8; c++) playfield_[27-c] = playfield_[c+12]; | ||||
| 						} else { | ||||
| 							memcpy(&playfield_[32], &playfield_[12], 8); | ||||
| 						} | ||||
| 					break; | ||||
|  | ||||
| 					case 0x10:	case 0x11:	case 0x12:	case 0x13: | ||||
| 					case 0x14: | ||||
| 						upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].updates |= Event::Action::ResetCounter; | ||||
| 						upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].counter = decodedAddress - 0x10; | ||||
| 					break; | ||||
|  | ||||
| 					case 0x15: case 0x16: | ||||
| 						update_audio(); | ||||
| 						speaker_->set_control(decodedAddress - 0x15, *value); | ||||
| 					break; | ||||
|  | ||||
| 					case 0x17: case 0x18: | ||||
| 						update_audio(); | ||||
| 						speaker_->set_divider(decodedAddress - 0x17, *value); | ||||
| 					break; | ||||
|  | ||||
| 					case 0x19: case 0x1a: | ||||
| 						update_audio(); | ||||
| 						speaker_->set_volume(decodedAddress - 0x19, *value); | ||||
| 					break; | ||||
|  | ||||
| 					case 0x1c: | ||||
| 						ball_graphics_enable_[1] = ball_graphics_enable_[0]; | ||||
| 					case 0x1b: { | ||||
| 						int index = decodedAddress - 0x1b; | ||||
| 						player_graphics_[0][index] = *value; | ||||
| 						player_graphics_[1][index^1] = player_graphics_[0][index^1]; | ||||
| 					} break; | ||||
| 					case 0x1d: | ||||
| 					case 0x1e: | ||||
| 						missile_graphics_enable_[decodedAddress - 0x1d] = ((*value) >> 1)&1; | ||||
| //						printf("e:%02x <- %c\n", decodedAddress - 0x1d, ((*value)&1) ? 'E' : '-'); | ||||
| 					break; | ||||
| 					case 0x1f: | ||||
| 						ball_graphics_enable_[0] = ((*value) >> 1)&1; | ||||
| 					break; | ||||
|  | ||||
| 					case 0x20: | ||||
| 					case 0x21: | ||||
| 					case 0x22: | ||||
| 					case 0x23: | ||||
| 					case 0x24: | ||||
| 						object_motions_[decodedAddress - 0x20] = *value; | ||||
| 					break; | ||||
|  | ||||
| 					case 0x25: player_graphics_selector_[0]	= (*value)&1;	break; | ||||
| 					case 0x26: player_graphics_selector_[1]	= (*value)&1;	break; | ||||
| 					case 0x27: ball_graphics_selector_		= (*value)&1;	break; | ||||
|  | ||||
| 					case 0x28: | ||||
| 					case 0x29: | ||||
| 					{ | ||||
| 						// TODO: this should properly mean setting a flag and propagating later, I think? | ||||
| 						int index = decodedAddress - 0x28; | ||||
| 						if(!(*value&0x02) && missile_graphics_reset_[index]) | ||||
| 						{ | ||||
| 							object_counter_[object_counter_pointer_][index + 2].count = object_counter_[object_counter_pointer_][index].count; | ||||
|  | ||||
| 							uint8_t repeatMask = player_and_missile_size_[index] & 7; | ||||
| 							int extra_offset; | ||||
| 							switch(repeatMask) | ||||
| 							{ | ||||
| 								default:	extra_offset = 3;	break; | ||||
| 								case 5:		extra_offset = 6;	break; | ||||
| 								case 7:		extra_offset = 10;	break; | ||||
| 							} | ||||
|  | ||||
| 							object_counter_[object_counter_pointer_][index + 2].count = (object_counter_[object_counter_pointer_][index + 2].count + extra_offset)%160; | ||||
| 						} | ||||
| 						missile_graphics_reset_[index] = !!((*value) & 0x02); | ||||
| //						printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-'); | ||||
| 					} | ||||
| 					break; | ||||
|  | ||||
| 					case 0x2a: { | ||||
| 						// justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete"; | ||||
| 						// which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be | ||||
| 						// in five cycles from now | ||||
| //						int start_pause = ((horizontal_timer_ + 3)&3) + 4; | ||||
| 						upcoming_events_[(upcoming_events_pointer_ + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; | ||||
| 					} break; | ||||
| 					case 0x2b: | ||||
| 						object_motions_[0] = | ||||
| 						object_motions_[1] = | ||||
| 						object_motions_[2] = | ||||
| 						object_motions_[3] = | ||||
| 						object_motions_[4] = 0; | ||||
| 					break; | ||||
| 					case 0x2c: | ||||
| 						collisions_[0] = collisions_[1] = collisions_[2] =  | ||||
| 						collisions_[3] = collisions_[4] = collisions_[5] = 0x3f; | ||||
| 						collisions_[6] = 0x7f; | ||||
| 						collisions_[7] = 0x3f; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// check for a PIA access | ||||
| 		if((address&0x1280) == 0x280) { | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				returnValue &= mos6532_.get_register(address); | ||||
| 			} else { | ||||
| 				mos6532_.set_register(address, *value); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(isReadOperation(operation)) { | ||||
| 			*value = returnValue; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	mos6532_.run_for_cycles(cycles_run_for / 3); | ||||
|  | ||||
| 	return cycles_run_for / 3; | ||||
| } | ||||
|  | ||||
| void Machine::set_digital_input(Atari2600DigitalInput input, bool state) | ||||
| { | ||||
| void Machine::set_digital_input(Atari2600DigitalInput input, bool state) { | ||||
| 	switch (input) { | ||||
| 		case Atari2600DigitalInputJoy1Up:		mos6532_.update_port_input(0, 0x10, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Down:		mos6532_.update_port_input(0, 0x20, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Left:		mos6532_.update_port_input(0, 0x40, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Right:	mos6532_.update_port_input(0, 0x80, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Up:		bus_->mos6532_.update_port_input(0, 0x10, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Down:		bus_->mos6532_.update_port_input(0, 0x20, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Left:		bus_->mos6532_.update_port_input(0, 0x40, state);	break; | ||||
| 		case Atari2600DigitalInputJoy1Right:	bus_->mos6532_.update_port_input(0, 0x80, state);	break; | ||||
|  | ||||
| 		case Atari2600DigitalInputJoy2Up:		mos6532_.update_port_input(0, 0x01, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Down:		mos6532_.update_port_input(0, 0x02, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Left:		mos6532_.update_port_input(0, 0x04, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Right:	mos6532_.update_port_input(0, 0x08, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Up:		bus_->mos6532_.update_port_input(0, 0x01, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Down:		bus_->mos6532_.update_port_input(0, 0x02, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Left:		bus_->mos6532_.update_port_input(0, 0x04, state);	break; | ||||
| 		case Atari2600DigitalInputJoy2Right:	bus_->mos6532_.update_port_input(0, 0x08, state);	break; | ||||
|  | ||||
| 		// TODO: latching | ||||
| 		case Atari2600DigitalInputJoy1Fire:		if(state) tia_input_value_[0] &= ~0x80; else tia_input_value_[0] |= 0x80; break; | ||||
| 		case Atari2600DigitalInputJoy2Fire:		if(state) tia_input_value_[1] &= ~0x80; else tia_input_value_[1] |= 0x80; break; | ||||
| 		case Atari2600DigitalInputJoy1Fire:		if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break; | ||||
| 		case Atari2600DigitalInputJoy2Fire:		if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break; | ||||
|  | ||||
| 		default: break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) | ||||
| { | ||||
| void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) { | ||||
| 	switch(input) { | ||||
| 		case Atari2600SwitchReset:					mos6532_.update_port_input(1, 0x01, state);	break; | ||||
| 		case Atari2600SwitchSelect:					mos6532_.update_port_input(1, 0x02, state);	break; | ||||
| 		case Atari2600SwitchColour:					mos6532_.update_port_input(1, 0x08, state);	break; | ||||
| 		case Atari2600SwitchLeftPlayerDifficulty:	mos6532_.update_port_input(1, 0x40, state);	break; | ||||
| 		case Atari2600SwitchRightPlayerDifficulty:	mos6532_.update_port_input(1, 0x80, state);	break; | ||||
| 		case Atari2600SwitchReset:					bus_->mos6532_.update_port_input(1, 0x01, state);	break; | ||||
| 		case Atari2600SwitchSelect:					bus_->mos6532_.update_port_input(1, 0x02, state);	break; | ||||
| 		case Atari2600SwitchColour:					bus_->mos6532_.update_port_input(1, 0x08, state);	break; | ||||
| 		case Atari2600SwitchLeftPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x40, state);	break; | ||||
| 		case Atari2600SwitchRightPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x80, state);	break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| { | ||||
| 	if(!target.cartridges.front()->get_segments().size()) return; | ||||
| 	Storage::Cartridge::Cartridge::Segment segment = target.cartridges.front()->get_segments().front(); | ||||
| 	size_t length = segment.data.size(); | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { | ||||
| 	const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data; | ||||
| 	switch(target.atari.paging_model) { | ||||
| 		case StaticAnalyser::Atari2600PagingModel::ActivisionStack:	bus_.reset(new CartridgeActivisionStack(rom));	break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new CartridgeCBSRAMPlus(rom));		break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::CommaVid:		bus_.reset(new CartridgeCommaVid(rom));			break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::MegaBoy:			bus_.reset(new CartridgeMegaBoy(rom));			break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::MNetwork:		bus_.reset(new CartridgeMNetwork(rom));			break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::None:			bus_.reset(new CartridgeUnpaged(rom));			break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::ParkerBros:		bus_.reset(new CartridgeParkerBros(rom));		break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::Pitfall2:		bus_.reset(new CartridgePitfall2(rom));			break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::Tigervision:		bus_.reset(new CartridgeTigervision(rom));		break; | ||||
|  | ||||
| 	rom_size_ = 1024; | ||||
| 	while(rom_size_ < length && rom_size_ < 32768) rom_size_ <<= 1; | ||||
|  | ||||
| 	delete[] rom_; | ||||
| 	rom_ = new uint8_t[rom_size_]; | ||||
|  | ||||
| 	size_t offset = 0; | ||||
| 	const size_t copy_step = std::min(rom_size_, length); | ||||
| 	while(offset < rom_size_) | ||||
| 	{ | ||||
| 		size_t copy_length = std::min(copy_step, rom_size_ - offset); | ||||
| 		memcpy(&rom_[offset], &segment.data[0], copy_length); | ||||
| 		offset += copy_length; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari8k: | ||||
| 			if(target.atari.uses_superchip) { | ||||
| 				bus_.reset(new CartridgeAtari8kSuperChip(rom)); | ||||
| 			} else { | ||||
| 				bus_.reset(new CartridgeAtari8k(rom)); | ||||
| 			} | ||||
| 		break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari16k: | ||||
| 			if(target.atari.uses_superchip) { | ||||
| 				bus_.reset(new CartridgeAtari16kSuperChip(rom)); | ||||
| 			} else { | ||||
| 				bus_.reset(new CartridgeAtari16k(rom)); | ||||
| 			} | ||||
| 		break; | ||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari32k: | ||||
| 			if(target.atari.uses_superchip) { | ||||
| 				bus_.reset(new CartridgeAtari32kSuperChip(rom)); | ||||
| 			} else { | ||||
| 				bus_.reset(new CartridgeAtari32k(rom)); | ||||
| 			} | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 	size_t romMask = rom_size_ - 1; | ||||
| 	rom_pages_[0] = rom_; | ||||
| 	rom_pages_[1] = &rom_[1024 & romMask]; | ||||
| 	rom_pages_[2] = &rom_[2048 & romMask]; | ||||
| 	rom_pages_[3] = &rom_[3072 & romMask]; | ||||
| #pragma mark - CRT delegate | ||||
|  | ||||
| void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { | ||||
| 	const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); | ||||
| 	frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames; | ||||
| 	frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; | ||||
| 	frame_record_pointer_ ++; | ||||
|  | ||||
| 	if(frame_record_pointer_ >= 6) { | ||||
| 		unsigned int total_number_of_frames = 0; | ||||
| 		unsigned int total_number_of_unexpected_vertical_syncs = 0; | ||||
| 		for(size_t c = 0; c < number_of_frame_records; c++) { | ||||
| 			total_number_of_frames += frame_records_[c].number_of_frames; | ||||
| 			total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; | ||||
| 		} | ||||
|  | ||||
| #pragma mark - Audio | ||||
| 		if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { | ||||
| 			for(size_t c = 0; c < number_of_frame_records; c++) { | ||||
| 				frame_records_[c].number_of_frames = 0; | ||||
| 				frame_records_[c].number_of_unexpected_vertical_syncs = 0; | ||||
| 			} | ||||
| 			is_ntsc_ ^= true; | ||||
|  | ||||
| void Machine::update_audio() | ||||
| { | ||||
| 	unsigned int audio_cycles = cycles_since_speaker_update_ / 114; | ||||
|  | ||||
| 	speaker_->run_for_cycles(audio_cycles); | ||||
| 	cycles_since_speaker_update_ %= 114; | ||||
| 			double clock_rate; | ||||
| 			if(is_ntsc_) { | ||||
| 				clock_rate = NTSC_clock_rate; | ||||
| 				bus_->tia_->set_output_mode(TIA::OutputMode::NTSC); | ||||
| 			} else { | ||||
| 				clock_rate = PAL_clock_rate; | ||||
| 				bus_->tia_->set_output_mode(TIA::OutputMode::PAL); | ||||
| 			} | ||||
|  | ||||
| void Machine::synchronise() | ||||
| { | ||||
| 	update_audio(); | ||||
| 			bus_->speaker_->set_input_rate((float)(clock_rate / 38.0)); | ||||
| 			bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / (38.0 * 2.0))); | ||||
| 			set_clock_rate(clock_rate); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,8 +13,10 @@ | ||||
|  | ||||
| #include "../../Processors/6502/CPU6502.hpp" | ||||
| #include "../CRTMachine.hpp" | ||||
| #include "Bus.hpp" | ||||
| #include "PIA.hpp" | ||||
| #include "Speaker.hpp" | ||||
| #include "TIA.hpp" | ||||
|  | ||||
| #include "../ConfigurationTarget.hpp" | ||||
| #include "Atari2600Inputs.h" | ||||
| @@ -25,9 +27,9 @@ const unsigned int number_of_upcoming_events = 6; | ||||
| const unsigned int number_of_recorded_counters = 7; | ||||
|  | ||||
| class Machine: | ||||
| 	public CPU6502::Processor<Machine>, | ||||
| 	public CRTMachine::Machine, | ||||
| 	public ConfigurationTarget::Machine { | ||||
| 	public ConfigurationTarget::Machine, | ||||
| 	public Outputs::CRT::Delegate { | ||||
|  | ||||
| 	public: | ||||
| 		Machine(); | ||||
| @@ -38,137 +40,31 @@ class Machine: | ||||
|  | ||||
| 		void set_digital_input(Atari2600DigitalInput input, bool state); | ||||
| 		void set_switch_is_enabled(Atari2600Switch input, bool state); | ||||
|  | ||||
| 		// to satisfy CPU6502::Processor | ||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); | ||||
| 		void synchronise(); | ||||
| 		void set_reset_line(bool state) { bus_->set_reset_line(state); } | ||||
|  | ||||
| 		// to satisfy CRTMachine::Machine | ||||
| 		virtual void setup_output(float aspect_ratio); | ||||
| 		virtual void close_output(); | ||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; } | ||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; } | ||||
| 		virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); } | ||||
| 		// TODO: different rate for PAL | ||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); } | ||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; } | ||||
| 		virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); } | ||||
|  | ||||
| 		// to satisfy Outputs::CRT::Delegate | ||||
| 		virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_, *rom_pages_[4]; | ||||
| 		size_t rom_size_; | ||||
| 		// the bus | ||||
| 		std::unique_ptr<Bus> bus_; | ||||
|  | ||||
| 		// the RIOT | ||||
| 		PIA mos6532_; | ||||
| 		// output frame rate tracker | ||||
| 		struct FrameRecord { | ||||
| 			unsigned int number_of_frames; | ||||
| 			unsigned int number_of_unexpected_vertical_syncs; | ||||
|  | ||||
| 		// playfield registers | ||||
| 		uint8_t playfield_control_; | ||||
| 		uint8_t playfield_colour_; | ||||
| 		uint8_t background_colour_; | ||||
| 		uint8_t playfield_[41]; | ||||
|  | ||||
| 		// ... and derivatives | ||||
| 		int ball_size_, missile_size_[2]; | ||||
|  | ||||
| 		// delayed clock events | ||||
| 		enum OutputState { | ||||
| 			Sync, | ||||
| 			Blank, | ||||
| 			ColourBurst, | ||||
| 			Pixel | ||||
| 		}; | ||||
|  | ||||
| 		struct Event { | ||||
| 			enum Action { | ||||
| 				Playfield			= 1 << 0, | ||||
| 				ResetCounter		= 1 << 1, | ||||
|  | ||||
| 				HMoveSetup			= 1 << 2, | ||||
| 				HMoveCompare		= 1 << 3, | ||||
| 				HMoveDecrement		= 1 << 4, | ||||
| 			}; | ||||
| 			int updates; | ||||
|  | ||||
| 			OutputState state; | ||||
| 			uint8_t playfield_pixel; | ||||
| 			int counter; | ||||
|  | ||||
| 			Event() : updates(0), playfield_pixel(0) {} | ||||
| 		} upcoming_events_[number_of_upcoming_events]; | ||||
| 		unsigned int upcoming_events_pointer_; | ||||
|  | ||||
| 		// object counters | ||||
| 		struct ObjectCounter { | ||||
| 			int count;			// the counter value, multiplied by four, counting phase | ||||
| 			int pixel;			// for non-sprite objects, a count of cycles since the last counter reset; for sprite objects a count of pixels so far elapsed | ||||
| 			int broad_pixel;	// for sprite objects, a count of cycles since the last counter reset; otherwise unused | ||||
|  | ||||
| 			ObjectCounter() : count(0), pixel(0), broad_pixel(0) {} | ||||
| 		} object_counter_[number_of_recorded_counters][5]; | ||||
| 		unsigned int object_counter_pointer_; | ||||
|  | ||||
| 		// the latched playfield output | ||||
| 		uint8_t playfield_output_, next_playfield_output_; | ||||
|  | ||||
| 		// player registers | ||||
| 		uint8_t player_colour_[2]; | ||||
| 		uint8_t player_reflection_mask_[2]; | ||||
| 		uint8_t player_graphics_[2][2]; | ||||
| 		uint8_t player_graphics_selector_[2]; | ||||
|  | ||||
| 		// object flags | ||||
| 		bool has_second_copy_[2]; | ||||
| 		bool has_third_copy_[2]; | ||||
| 		bool has_fourth_copy_[2]; | ||||
| 		uint8_t object_motions_[5];		// the value stored to this counter's motion register | ||||
|  | ||||
| 		// player + missile registers | ||||
| 		uint8_t player_and_missile_size_[2]; | ||||
|  | ||||
| 		// missile registers | ||||
| 		uint8_t missile_graphics_enable_[2]; | ||||
| 		bool missile_graphics_reset_[2]; | ||||
|  | ||||
| 		// ball registers | ||||
| 		uint8_t ball_graphics_enable_[2]; | ||||
| 		uint8_t ball_graphics_selector_; | ||||
|  | ||||
| 		// graphics output | ||||
| 		unsigned int horizontal_timer_; | ||||
| 		bool vsync_enabled_, vblank_enabled_; | ||||
|  | ||||
| 		// horizontal motion control | ||||
| 		uint8_t hmove_counter_; | ||||
| 		uint8_t hmove_flags_; | ||||
|  | ||||
| 		// joystick state | ||||
| 		uint8_t tia_input_value_[2]; | ||||
|  | ||||
| 		// collisions | ||||
| 		uint8_t collisions_[8]; | ||||
|  | ||||
| 		void output_pixels(unsigned int count); | ||||
| 		uint8_t get_output_pixel(); | ||||
| 		void update_timers(int mask); | ||||
|  | ||||
| 		// outputs | ||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | ||||
| 		std::shared_ptr<Speaker> speaker_; | ||||
|  | ||||
| 		// current mode | ||||
| 		bool is_pal_region_; | ||||
|  | ||||
| 		// speaker backlog accumlation counter | ||||
| 		unsigned int cycles_since_speaker_update_; | ||||
| 		void update_audio(); | ||||
|  | ||||
| 		// latched output state | ||||
| 		unsigned int last_output_state_duration_; | ||||
| 		OutputState state_by_extend_time_[2][57]; | ||||
| 		OutputState *state_by_time_; | ||||
| 		OutputState last_output_state_; | ||||
| 		uint8_t *output_buffer_; | ||||
|  | ||||
| 		// lookup table for collision reporting | ||||
| 		uint8_t reported_collisions_[64][8]; | ||||
| 		void setup_reported_collisions(); | ||||
| 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} | ||||
| 		} frame_records_[4]; | ||||
| 		unsigned int frame_record_pointer_; | ||||
| 		bool is_ntsc_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										63
									
								
								Machines/Atari2600/Bus.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | ||||
| // | ||||
| //  Bus.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_Bus_hpp | ||||
| #define Atari2600_Bus_hpp | ||||
|  | ||||
| #include "PIA.hpp" | ||||
| #include "TIA.hpp" | ||||
| #include "Speaker.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class Bus { | ||||
| 	public: | ||||
| 		Bus() : | ||||
| 			tia_input_value_{0xff, 0xff}, | ||||
| 			cycles_since_speaker_update_(0), | ||||
| 			cycles_since_video_update_(0), | ||||
| 			cycles_since_6532_update_(0) {} | ||||
|  | ||||
| 		virtual void run_for_cycles(int number_of_cycles) = 0; | ||||
| 		virtual void set_reset_line(bool state) = 0; | ||||
|  | ||||
| 		// the RIOT, TIA and speaker | ||||
| 		PIA mos6532_; | ||||
| 		std::shared_ptr<TIA> tia_; | ||||
| 		std::shared_ptr<Speaker> speaker_; | ||||
|  | ||||
| 		// joystick state | ||||
| 		uint8_t tia_input_value_[2]; | ||||
|  | ||||
| 	protected: | ||||
| 		// speaker backlog accumlation counter | ||||
| 		unsigned int cycles_since_speaker_update_; | ||||
| 		inline void update_audio() { | ||||
| 			unsigned int audio_cycles = cycles_since_speaker_update_ / 114; | ||||
| 			cycles_since_speaker_update_ %= 114; | ||||
| 			speaker_->run_for_cycles(audio_cycles); | ||||
| 		} | ||||
|  | ||||
| 		// video backlog accumulation counter | ||||
| 		unsigned int cycles_since_video_update_; | ||||
| 		inline void update_video() { | ||||
| 			tia_->run_for_cycles((int)cycles_since_video_update_); | ||||
| 			cycles_since_video_update_ = 0; | ||||
| 		} | ||||
|  | ||||
| 		// RIOT backlog accumulation counter | ||||
| 		unsigned int cycles_since_6532_update_; | ||||
| 		inline void update_6532() { | ||||
| 			mos6532_.run_for_cycles(cycles_since_6532_update_); | ||||
| 			cycles_since_6532_update_ = 0; | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_Bus_hpp */ | ||||
							
								
								
									
										176
									
								
								Machines/Atari2600/Cartridges/Cartridge.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,176 @@ | ||||
| // | ||||
| //  Cartridge.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 17/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_Cartridge_hpp | ||||
| #define Atari2600_Cartridge_hpp | ||||
|  | ||||
| #include "../../../Processors/6502/CPU6502.hpp" | ||||
| #include "../Bus.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| template<class T> class Cartridge: | ||||
| 	public CPU6502::Processor<Cartridge<T>>, | ||||
| 	public Bus { | ||||
|  | ||||
| 	public: | ||||
| 		Cartridge(const std::vector<uint8_t> &rom) : | ||||
| 			rom_(rom) {} | ||||
|  | ||||
| 		void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); } | ||||
| 		void set_reset_line(bool state) { CPU6502::Processor<Cartridge<T>>::set_reset_line(state); } | ||||
| 		void advance_cycles(unsigned int cycles) {} | ||||
|  | ||||
| 		// to satisfy CPU6502::Processor | ||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			uint8_t returnValue = 0xff; | ||||
| 			unsigned int cycles_run_for = 3; | ||||
|  | ||||
| 			// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for | ||||
| 			// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take | ||||
| 			// effect until the next read; therefore it isn't safe to assume that signalling ready immediately | ||||
| 			// skips to the end of the line. | ||||
| 			if(operation == CPU6502::BusOperation::Ready) | ||||
| 				cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); | ||||
|  | ||||
| 			cycles_since_speaker_update_ += cycles_run_for; | ||||
| 			cycles_since_video_update_ += cycles_run_for; | ||||
| 			cycles_since_6532_update_ += (cycles_run_for / 3); | ||||
| 			static_cast<T *>(this)->advance_cycles(cycles_run_for / 3); | ||||
|  | ||||
| 			if(operation != CPU6502::BusOperation::Ready) { | ||||
| 				// give the cartridge a chance to respond to the bus access | ||||
| 				static_cast<T *>(this)->perform_bus_operation(operation, address, value); | ||||
|  | ||||
| 				// check for a RIOT RAM access | ||||
| 				if((address&0x1280) == 0x80) { | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						returnValue &= mos6532_.get_ram(address); | ||||
| 					} else { | ||||
| 						mos6532_.set_ram(address, *value); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// check for a TIA access | ||||
| 				if(!(address&0x1080)) { | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						const uint16_t decodedAddress = address & 0xf; | ||||
| 						switch(decodedAddress) { | ||||
| 							case 0x00:		// missile 0 / player collisions | ||||
| 							case 0x01:		// missile 1 / player collisions | ||||
| 							case 0x02:		// player 0 / playfield / ball collisions | ||||
| 							case 0x03:		// player 1 / playfield / ball collisions | ||||
| 							case 0x04:		// missile 0 / playfield / ball collisions | ||||
| 							case 0x05:		// missile 1 / playfield / ball collisions | ||||
| 							case 0x06:		// ball / playfield collisions | ||||
| 							case 0x07:		// player / player, missile / missile collisions | ||||
| 								returnValue &= tia_->get_collision_flags(decodedAddress); | ||||
| 							break; | ||||
|  | ||||
| 							case 0x08: | ||||
| 							case 0x09: | ||||
| 							case 0x0a: | ||||
| 							case 0x0b: | ||||
| 								// TODO: pot ports | ||||
| 								returnValue &= 0; | ||||
| 							break; | ||||
|  | ||||
| 							case 0x0c: | ||||
| 							case 0x0d: | ||||
| 								returnValue &= tia_input_value_[decodedAddress - 0x0c]; | ||||
| 							break; | ||||
| 						} | ||||
| 					} else { | ||||
| 						const uint16_t decodedAddress = address & 0x3f; | ||||
| 						switch(decodedAddress) { | ||||
| 							case 0x00:	update_video(); tia_->set_sync(*value & 0x02);		break; | ||||
| 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | ||||
|  | ||||
| 							case 0x02:	CPU6502::Processor<Cartridge<T>>::set_ready_line(true);								break; | ||||
| 							case 0x03:	update_video();	tia_->reset_horizontal_counter();	break; | ||||
| 								// TODO: audio will now be out of synchronisation — fix | ||||
|  | ||||
| 							case 0x04: | ||||
| 							case 0x05:	update_video();	tia_->set_player_number_and_size(decodedAddress - 0x04, *value);	break; | ||||
| 							case 0x06: | ||||
| 							case 0x07:	update_video();	tia_->set_player_missile_colour(decodedAddress - 0x06, *value);		break; | ||||
| 							case 0x08:	update_video();	tia_->set_playfield_ball_colour(*value);							break; | ||||
| 							case 0x09:	update_video();	tia_->set_background_colour(*value);								break; | ||||
| 							case 0x0a:	update_video();	tia_->set_playfield_control_and_ball_size(*value);					break; | ||||
| 							case 0x0b: | ||||
| 							case 0x0c:	update_video();	tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8));	break; | ||||
| 							case 0x0d: | ||||
| 							case 0x0e: | ||||
| 							case 0x0f:	update_video();	tia_->set_playfield(decodedAddress - 0x0d, *value);					break; | ||||
| 							case 0x10: | ||||
| 							case 0x11:	update_video(); tia_->set_player_position(decodedAddress - 0x10);					break; | ||||
| 							case 0x12: | ||||
| 							case 0x13:	update_video(); tia_->set_missile_position(decodedAddress - 0x12);					break; | ||||
| 							case 0x14:	update_video();	tia_->set_ball_position();											break; | ||||
| 							case 0x1b: | ||||
| 							case 0x1c:	update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value);			break; | ||||
| 							case 0x1d: | ||||
| 							case 0x1e:	update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2);		break; | ||||
| 							case 0x1f:	update_video(); tia_->set_ball_enable((*value)&2);									break; | ||||
| 							case 0x20: | ||||
| 							case 0x21:	update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value);				break; | ||||
| 							case 0x22: | ||||
| 							case 0x23:	update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value);			break; | ||||
| 							case 0x24:	update_video(); tia_->set_ball_motion(*value);										break; | ||||
| 							case 0x25: | ||||
| 							case 0x26:	tia_->set_player_delay(decodedAddress - 0x25, (*value)&1);							break; | ||||
| 							case 0x27:	tia_->set_ball_delay((*value)&1);													break; | ||||
| 							case 0x28: | ||||
| 							case 0x29:	update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2);		break; | ||||
| 							case 0x2a:	update_video(); tia_->move();														break; | ||||
| 							case 0x2b:	update_video(); tia_->clear_motion();												break; | ||||
| 							case 0x2c:	update_video(); tia_->clear_collision_flags();										break; | ||||
|  | ||||
| 							case 0x15: | ||||
| 							case 0x16:	update_audio(); speaker_->set_control(decodedAddress - 0x15, *value);				break; | ||||
| 							case 0x17: | ||||
| 							case 0x18:	update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value);				break; | ||||
| 							case 0x19: | ||||
| 							case 0x1a:	update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value);				break; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// check for a PIA access | ||||
| 				if((address&0x1280) == 0x280) { | ||||
| 					update_6532(); | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						returnValue &= mos6532_.get_register(address); | ||||
| 					} else { | ||||
| 						mos6532_.set_register(address, *value); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if(isReadOperation(operation)) { | ||||
| 					*value &= returnValue; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU6502::Processor<Cartridge<T>>::set_ready_line(false); | ||||
|  | ||||
| 			return cycles_run_for / 3; | ||||
| 		} | ||||
|  | ||||
| 		void synchronise() { | ||||
| 			update_audio(); | ||||
| 			update_video(); | ||||
| 			speaker_->flush(); | ||||
| 		} | ||||
|  | ||||
| 	protected: | ||||
| 		std::vector<uint8_t> rom_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_Cartridge_hpp */ | ||||
							
								
								
									
										50
									
								
								Machines/Atari2600/Cartridges/CartridgeActivisionStack.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,50 @@ | ||||
| // | ||||
| //  CartridgeActivisionStack.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeActivisionStack_hpp | ||||
| #define Atari2600_CartridgeActivisionStack_hpp | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | ||||
| 	public: | ||||
| 		CartridgeActivisionStack(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom), | ||||
| 			last_opcode_(0x00) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see | ||||
| 			// address line 13. Instead it looks for a pattern in recent address accesses that would imply an | ||||
| 			// RST or JSR. | ||||
| 			if(operation == CPU6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { | ||||
| 				if(address & 0x2000) { | ||||
| 					rom_ptr_ = rom_.data(); | ||||
| 				} else { | ||||
| 					rom_ptr_ = rom_.data() + 4096; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
|  | ||||
| 			if(operation == CPU6502::BusOperation::ReadOpcode) last_opcode_ = *value; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t last_opcode_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeActivisionStack_hpp */ | ||||
							
								
								
									
										66
									
								
								Machines/Atari2600/Cartridges/CartridgeAtari16k.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | ||||
| // | ||||
| //  CartridgeAtari8k.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeAtari16k_hpp | ||||
| #define Atari2600_CartridgeAtari16k_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> { | ||||
| 	public: | ||||
| 		CartridgeAtari16k(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| }; | ||||
|  | ||||
| class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> { | ||||
| 	public: | ||||
| 		CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
|  | ||||
| 			if(address < 0x1080) ram_[address & 0x7f] = *value; | ||||
| 			else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t ram_[128]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeAtari16k_hpp */ | ||||
							
								
								
									
										66
									
								
								Machines/Atari2600/Cartridges/CartridgeAtari32k.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | ||||
| // | ||||
| //  CartridgeAtari8k.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeAtari32k_hpp | ||||
| #define Atari2600_CartridgeAtari32k_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> { | ||||
| 	public: | ||||
| 		CartridgeAtari32k(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| }; | ||||
|  | ||||
| class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> { | ||||
| 	public: | ||||
| 		CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
|  | ||||
| 			if(address < 0x1080) ram_[address & 0x7f] = *value; | ||||
| 			else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t ram_[128]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeAtari32k_hpp */ | ||||
							
								
								
									
										68
									
								
								Machines/Atari2600/Cartridges/CartridgeAtari8k.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| // | ||||
| //  CartridgeAtari8k.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeAtari8k_hpp | ||||
| #define Atari2600_CartridgeAtari8k_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> { | ||||
| 	public: | ||||
| 		CartridgeAtari8k(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address == 0x1ff8) rom_ptr_ = rom_.data(); | ||||
| 			else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| }; | ||||
|  | ||||
| class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> { | ||||
| 	public: | ||||
| 		CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address == 0x1ff8) rom_ptr_ = rom_.data(); | ||||
| 			if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
|  | ||||
| 			if(address < 0x1080) ram_[address & 0x7f] = *value; | ||||
| 			else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t ram_[128]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeAtari8k_hpp */ | ||||
							
								
								
									
										44
									
								
								Machines/Atari2600/Cartridges/CartridgeCBSRAMPlus.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | ||||
| // | ||||
| //  CartridgeCBSRAMPlus.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeCBSRAMPlus_hpp | ||||
| #define Atari2600_CartridgeCBSRAMPlus_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> { | ||||
| 	public: | ||||
| 		CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096; | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
|  | ||||
| 			if(address < 0x1100) ram_[address & 0xff] = *value; | ||||
| 			else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff]; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t ram_[256]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeCBSRAMPlus_hpp */ | ||||
							
								
								
									
										42
									
								
								Machines/Atari2600/Cartridges/CartridgeCommaVid.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | ||||
| // | ||||
| //  CartridgeCommaVid.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeCommaVid_hpp | ||||
| #define Atari2600_CartridgeCommaVid_hpp | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> { | ||||
| 	public: | ||||
| 		CartridgeCommaVid(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) {} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			if(!(address & 0x1000)) return; | ||||
| 			address &= 0x1fff; | ||||
|  | ||||
| 			if(address < 0x1400) { | ||||
| 				if(isReadOperation(operation)) *value = ram_[address & 1023]; | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if(address < 0x1800) { | ||||
| 				ram_[address & 1023] = *value; | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if(isReadOperation(operation)) *value = rom_[address & 2047]; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t ram_[1024]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeCommaVid_hpp */ | ||||
							
								
								
									
										68
									
								
								Machines/Atari2600/Cartridges/CartridgeMNetwork.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| // | ||||
| //  CartridgeMNetwork.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeMNetwork_hpp | ||||
| #define Atari2600_CartridgeMNetwork_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | ||||
| 	public: | ||||
| 		CartridgeMNetwork(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_[0] = rom_.data() + rom_.size() - 4096; | ||||
| 			rom_ptr_[1] = rom_ptr_[0] + 2048; | ||||
| 			high_ram_ptr_ = high_ram_; | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1fe0 && address <= 0x1fe6) { | ||||
| 				rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048; | ||||
| 			} else if(address == 0x1fe7) { | ||||
| 				rom_ptr_[0] = nullptr; | ||||
| 			} else if(address >= 0x1ff8 && address <= 0x1ffb) { | ||||
| 				int offset = (address - 0x1ff8) * 256; | ||||
| 				high_ram_ptr_ = &high_ram_[offset]; | ||||
| 			} | ||||
|  | ||||
| 			if(address & 0x800) { | ||||
| 				if(address < 0x1900) { | ||||
| 					high_ram_ptr_[address & 255] = *value; | ||||
| 				} else if(address < 0x1a00) { | ||||
| 					if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255]; | ||||
| 				} else { | ||||
| 					if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047]; | ||||
| 				} | ||||
| 			} else { | ||||
| 				if(rom_ptr_[0]) { | ||||
| 					if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047]; | ||||
| 				} else { | ||||
| 					if(address < 0x1400) { | ||||
| 						low_ram_[address & 1023] = *value; | ||||
| 					} else { | ||||
| 						if(isReadOperation(operation)) *value = low_ram_[address & 1023]; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_[2]; | ||||
| 		uint8_t *high_ram_ptr_; | ||||
| 		uint8_t low_ram_[1024], high_ram_[1024]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeMNetwork_hpp */ | ||||
							
								
								
									
										45
									
								
								Machines/Atari2600/Cartridges/CartridgeMegaBoy.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| // | ||||
| //  CartridgeMegaBoy.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeMegaBoy_hpp | ||||
| #define Atari2600_CartridgeMegaBoy_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> { | ||||
| 	public: | ||||
| 		CartridgeMegaBoy(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom), | ||||
| 			current_page_(0) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address == 0x1ff0) { | ||||
| 				current_page_ = (current_page_ + 1) & 15; | ||||
| 				rom_ptr_ = rom_.data() + current_page_ * 4096; | ||||
| 			} | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[address & 4095]; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t current_page_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* CartridgeMegaBoy_h */ | ||||
							
								
								
									
										46
									
								
								Machines/Atari2600/Cartridges/CartridgeParkerBros.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | ||||
| // | ||||
| //  CartridgeParkerBros.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeParkerBros_hpp | ||||
| #define Atari2600_CartridgeParkerBros_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> { | ||||
| 	public: | ||||
| 		CartridgeParkerBros(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_[0] = rom_.data() + 4096; | ||||
| 			rom_ptr_[1] = rom_ptr_[0] + 1024; | ||||
| 			rom_ptr_[2] = rom_ptr_[1] + 1024; | ||||
| 			rom_ptr_[3] = rom_ptr_[2] + 1024; | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			if(address >= 0x1fe0 && address < 0x1ff8) { | ||||
| 				int slot = (address >> 3)&3; | ||||
| 				rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024); | ||||
| 			} | ||||
|  | ||||
| 			if(isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[(address >> 10)&3][address & 1023]; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_[4]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeParkerBros_hpp */ | ||||
							
								
								
									
										134
									
								
								Machines/Atari2600/Cartridges/CartridgePitfall2.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,134 @@ | ||||
| // | ||||
| //  CartridgePitfall2.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgePitfall2_hpp | ||||
| #define Atari2600_CartridgePitfall2_hpp | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | ||||
| 	public: | ||||
| 		CartridgePitfall2(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom), | ||||
| 			random_number_generator_(0), | ||||
| 			featcher_address_{0, 0, 0, 0, 0, 0, 0, 0}, | ||||
| 			mask_{0, 0, 0, 0, 0, 0, 0, 0}, | ||||
| 			cycles_since_audio_update_(0) { | ||||
| 			rom_ptr_ = rom_.data(); | ||||
| 		} | ||||
|  | ||||
| 		void advance_cycles(unsigned int cycles) { | ||||
| 			cycles_since_audio_update_ += cycles; | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			address &= 0x1fff; | ||||
| 			if(!(address & 0x1000)) return; | ||||
|  | ||||
| 			switch(address) { | ||||
|  | ||||
| #pragma mark - Reads | ||||
|  | ||||
| 				// The random number generator | ||||
| 				case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004: | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						*value = random_number_generator_; | ||||
| 					} | ||||
| 					random_number_generator_ = (uint8_t)( | ||||
| 						(random_number_generator_ << 1) | | ||||
| 						(~(	(random_number_generator_ >> 7) ^ | ||||
| 							(random_number_generator_ >> 5) ^ | ||||
| 							(random_number_generator_ >> 4) ^ | ||||
| 							(random_number_generator_ >> 3) | ||||
| 						) & 1)); | ||||
| 				break; | ||||
|  | ||||
| 				case 0x1005: case 0x1006: case 0x1007: | ||||
| 					*value = update_audio(); | ||||
| 				break; | ||||
|  | ||||
| 				case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: | ||||
| 					*value = rom_[8192 + address_for_counter(address & 7)]; | ||||
| 				break; | ||||
|  | ||||
| 				case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: | ||||
| 					*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; | ||||
| 				break; | ||||
|  | ||||
| #pragma mark - Writes | ||||
|  | ||||
| 				case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047: | ||||
| 					top_[address & 7] = *value; | ||||
| 				break; | ||||
| 				case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f: | ||||
| 					bottom_[address & 7] = *value; | ||||
| 				break; | ||||
| 				case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057: | ||||
| 					featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value; | ||||
| 					mask_[address & 7] = 0x00; | ||||
| 				break; | ||||
| 				case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f: | ||||
| 					featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8); | ||||
| 				break; | ||||
| 				case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077: | ||||
| 					random_number_generator_ = 0; | ||||
| 				break; | ||||
|  | ||||
| #pragma mark - Paging | ||||
|  | ||||
| 				case 0x1ff8: rom_ptr_ = rom_.data();		break; | ||||
| 				case 0x1ff9: rom_ptr_ = rom_.data() + 4096;	break; | ||||
|  | ||||
| #pragma mark - Business as usual | ||||
|  | ||||
| 				default: | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						*value = rom_ptr_[address & 4095]; | ||||
| 					} | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		inline uint16_t address_for_counter(int counter) { | ||||
| 			uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047; | ||||
| 			if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff; | ||||
| 			if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00; | ||||
| 			featcher_address_[counter]--; | ||||
| 			return fetch_address; | ||||
| 		} | ||||
|  | ||||
| 		inline uint8_t update_audio() { | ||||
| 			const unsigned int clock_divisor = 57; | ||||
| 			unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor; | ||||
| 			cycles_since_audio_update_ %= clock_divisor; | ||||
|  | ||||
| 			int table_position = 0; | ||||
| 			for(int c = 0; c < 3; c++) { | ||||
| 				audio_channel_[c] = (audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]); | ||||
| 				if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) { | ||||
| 					table_position |= 0x4 >> c; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf }; | ||||
| 			return level_table[table_position]; | ||||
| 		} | ||||
|  | ||||
| 		uint16_t featcher_address_[8]; | ||||
| 		uint8_t top_[8], bottom_[8], mask_[8]; | ||||
| 		uint8_t music_mode_[3]; | ||||
| 		uint8_t random_number_generator_; | ||||
| 		uint8_t *rom_ptr_; | ||||
| 		uint8_t audio_channel_[3]; | ||||
| 		unsigned int cycles_since_audio_update_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgePitfall2_hpp */ | ||||
							
								
								
									
										40
									
								
								Machines/Atari2600/Cartridges/CartridgeTigervision.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | ||||
| // | ||||
| //  CartridgeTigervision.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeTigervision_hpp | ||||
| #define Atari2600_CartridgeTigervision_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeTigervision: public Cartridge<CartridgeTigervision> { | ||||
| 	public: | ||||
| 		CartridgeTigervision(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) { | ||||
| 			rom_ptr_[0] = rom_.data() + rom_.size() - 4096; | ||||
| 			rom_ptr_[1] = rom_ptr_[0] + 2048; | ||||
| 		} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			if((address&0x1fff) == 0x3f) { | ||||
| 				int offset = ((*value) * 2048) & (rom_.size() - 1); | ||||
| 				rom_ptr_[0] = rom_.data() + offset; | ||||
| 				return; | ||||
| 			} else if((address&0x1000) && isReadOperation(operation)) { | ||||
| 				*value = rom_ptr_[(address >> 11)&1][address & 2047]; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t *rom_ptr_[2]; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeTigervision_hpp */ | ||||
							
								
								
									
										30
									
								
								Machines/Atari2600/Cartridges/CartridgeUnpaged.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,30 @@ | ||||
| // | ||||
| //  CartridgeUnpaged.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 17/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Atari2600_CartridgeUnpaged_hpp | ||||
| #define Atari2600_CartridgeUnpaged_hpp | ||||
|  | ||||
| #include "Cartridge.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> { | ||||
| 	public: | ||||
| 		CartridgeUnpaged(const std::vector<uint8_t> &rom) : | ||||
| 			Cartridge(rom) {} | ||||
|  | ||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 			if(isReadOperation(operation) && (address & 0x1000)) { | ||||
| 				*value = rom_[address & (rom_.size() - 1)]; | ||||
| 			} | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* Atari2600_CartridgeUnpaged_hpp */ | ||||
| @@ -15,13 +15,11 @@ namespace Atari2600 { | ||||
|  | ||||
| class PIA: public MOS::MOS6532<PIA> { | ||||
| 	public: | ||||
| 		inline uint8_t get_port_input(int port) | ||||
| 		{ | ||||
| 		inline uint8_t get_port_input(int port) { | ||||
| 			return port_values_[port]; | ||||
| 		} | ||||
|  | ||||
| 		inline void update_port_input(int port, uint8_t mask, bool set) | ||||
| 		{ | ||||
| 		inline void update_port_input(int port, uint8_t mask, bool set) { | ||||
| 			if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask; | ||||
| 			set_port_did_change(port); | ||||
| 		} | ||||
|   | ||||
| @@ -16,23 +16,20 @@ Atari2600::Speaker::Speaker() : | ||||
| 	poly9_counter_{0x1ff, 0x1ff} | ||||
| {} | ||||
|  | ||||
| void Atari2600::Speaker::set_volume(int channel, uint8_t volume) | ||||
| { | ||||
| void Atari2600::Speaker::set_volume(int channel, uint8_t volume) { | ||||
| 	enqueue([=]() { | ||||
| 		volume_[channel] = volume & 0xf; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Atari2600::Speaker::set_divider(int channel, uint8_t divider) | ||||
| { | ||||
| void Atari2600::Speaker::set_divider(int channel, uint8_t divider) { | ||||
| 	enqueue([=]() { | ||||
| 		divider_[channel] = divider & 0x1f; | ||||
| 		divider_counter_[channel] = 0; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Atari2600::Speaker::set_control(int channel, uint8_t control) | ||||
| { | ||||
| void Atari2600::Speaker::set_control(int channel, uint8_t control) { | ||||
| 	enqueue([=]() { | ||||
| 		control_[channel] = control & 0xf; | ||||
| 	}); | ||||
| @@ -42,17 +39,13 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control) | ||||
| #define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010) | ||||
| #define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100) | ||||
|  | ||||
| void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| { | ||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) | ||||
| 	{ | ||||
| void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | ||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | ||||
| 		target[c] = 0; | ||||
| 		for(int channel = 0; channel < 2; channel++) | ||||
| 		{ | ||||
| 		for(int channel = 0; channel < 2; channel++) { | ||||
| 			divider_counter_[channel] ++; | ||||
| 			int level = 0; | ||||
| 			switch(control_[channel]) | ||||
| 			{ | ||||
| 			switch(control_[channel]) { | ||||
| 				case 0x0: case 0xb:	// constant 1 | ||||
| 					level = 1; | ||||
| 				break; | ||||
| @@ -75,8 +68,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta | ||||
|  | ||||
| 				case 0x1:			// 4-bit poly | ||||
| 					level = poly4_counter_[channel]&1; | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) | ||||
| 					{ | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) { | ||||
| 						divider_counter_[channel] = 0; | ||||
| 						advance_poly4(channel); | ||||
| 					} | ||||
| @@ -84,18 +76,15 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta | ||||
|  | ||||
| 				case 0x2:			// 4-bit poly div31 | ||||
| 					level = poly4_counter_[channel]&1; | ||||
| 					if(divider_counter_[channel]%(30*(divider_[channel]+1)) == 18) | ||||
| 					{ | ||||
| 					if(divider_counter_[channel]%(30*(divider_[channel]+1)) == 18) { | ||||
| 						advance_poly4(channel); | ||||
| 					} | ||||
| 				break; | ||||
|  | ||||
| 				case 0x3:			// 5/4-bit poly | ||||
| 					level = output_state_[channel]; | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) | ||||
| 					{ | ||||
| 						if(poly5_counter_[channel]&1) | ||||
| 						{ | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) { | ||||
| 						if(poly5_counter_[channel]&1) { | ||||
| 							output_state_[channel] = poly4_counter_[channel]&1; | ||||
| 							advance_poly4(channel); | ||||
| 						} | ||||
| @@ -105,8 +94,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta | ||||
|  | ||||
| 				case 0x7: case 0x9:	// 5-bit poly | ||||
| 					level = poly5_counter_[channel]&1; | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) | ||||
| 					{ | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) { | ||||
| 						divider_counter_[channel] = 0; | ||||
| 						advance_poly5(channel); | ||||
| 					} | ||||
| @@ -114,8 +102,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta | ||||
|  | ||||
| 				case 0xf:			// 5-bit poly div6 | ||||
| 					level = poly5_counter_[channel]&1; | ||||
| 					if(divider_counter_[channel] == (divider_[channel]+1)*3) | ||||
| 					{ | ||||
| 					if(divider_counter_[channel] == (divider_[channel]+1)*3) { | ||||
| 						divider_counter_[channel] = 0; | ||||
| 						advance_poly5(channel); | ||||
| 					} | ||||
| @@ -123,8 +110,7 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta | ||||
|  | ||||
| 				case 0x8:			// 9-bit poly | ||||
| 					level = poly9_counter_[channel]&1; | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) | ||||
| 					{ | ||||
| 					if(divider_counter_[channel] == divider_[channel]+1) { | ||||
| 						divider_counter_[channel] = 0; | ||||
| 						advance_poly9(channel); | ||||
| 					} | ||||
|   | ||||
							
								
								
									
										679
									
								
								Machines/Atari2600/TIA.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,679 @@ | ||||
| // | ||||
| //  TIA.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 28/01/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "TIA.hpp" | ||||
| #include <cassert> | ||||
|  | ||||
| using namespace Atari2600; | ||||
| namespace { | ||||
| 	const int cycles_per_line = 228; | ||||
| 	const int first_pixel_cycle = 68; | ||||
|  | ||||
| 	const int sync_flag	= 0x1; | ||||
| 	const int blank_flag = 0x2; | ||||
|  | ||||
| 	uint8_t reverse_table[256]; | ||||
| } | ||||
|  | ||||
| TIA::TIA(bool create_crt) : | ||||
| 	horizontal_counter_(0), | ||||
| 	pixels_start_location_(0), | ||||
| 	output_mode_(0), | ||||
| 	pixel_target_(nullptr), | ||||
| 	background_{0, 0}, | ||||
| 	background_half_mask_(0), | ||||
| 	horizontal_blank_extend_(false), | ||||
| 	collision_flags_(0) | ||||
| { | ||||
| 	if(create_crt) { | ||||
| 		crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); | ||||
| 		crt_->set_output_device(Outputs::CRT::Television); | ||||
| 		set_output_mode(OutputMode::NTSC); | ||||
| 	} | ||||
|  | ||||
| 	for(int c = 0; c < 256; c++) { | ||||
| 		reverse_table[c] = (uint8_t)( | ||||
| 			((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) | | ||||
| 			((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	for(int c = 0; c < 64; c++) { | ||||
| 		bool has_playfield = c & (int)(CollisionType::Playfield); | ||||
| 		bool has_ball = c & (int)(CollisionType::Ball); | ||||
| 		bool has_player0 = c & (int)(CollisionType::Player0); | ||||
| 		bool has_player1 = c & (int)(CollisionType::Player1); | ||||
| 		bool has_missile0 = c & (int)(CollisionType::Missile0); | ||||
| 		bool has_missile1 = c & (int)(CollisionType::Missile1); | ||||
|  | ||||
| 		uint8_t collision_registers[8]; | ||||
| 		collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00)		|	((has_missile0 && has_player0) ? 0x40 : 0x00); | ||||
| 		collision_registers[1] = ((has_missile1 && has_player0) ? 0x80 : 0x00)		|	((has_missile1 && has_player1) ? 0x40 : 0x00); | ||||
| 		collision_registers[2] = ((has_playfield && has_player0) ? 0x80 : 0x00)		|	((has_ball && has_player0) ? 0x40 : 0x00); | ||||
| 		collision_registers[3] = ((has_playfield && has_player1) ? 0x80 : 0x00)		|	((has_ball && has_player1) ? 0x40 : 0x00); | ||||
| 		collision_registers[4] = ((has_playfield && has_missile0) ? 0x80 : 0x00)	|	((has_ball && has_missile0) ? 0x40 : 0x00); | ||||
| 		collision_registers[5] = ((has_playfield && has_missile1) ? 0x80 : 0x00)	|	((has_ball && has_missile1) ? 0x40 : 0x00); | ||||
| 		collision_registers[6] = ((has_playfield && has_ball) ? 0x80 : 0x00); | ||||
| 		collision_registers[7] = ((has_player0 && has_player1) ? 0x80 : 0x00)		|	((has_missile0 && has_missile1) ? 0x40 : 0x00); | ||||
| 		collision_flags_by_buffer_vaules_[c] = | ||||
| 			(collision_registers[0] >> 6) | | ||||
| 			(collision_registers[1] >> 4) | | ||||
| 			(collision_registers[2] >> 2) | | ||||
| 			(collision_registers[3] >> 0) | | ||||
| 			(collision_registers[4] << 2) | | ||||
| 			(collision_registers[5] << 4) | | ||||
| 			(collision_registers[6] << 6) | | ||||
| 			(collision_registers[7] << 8); | ||||
|  | ||||
| 		// all priority modes show the background if nothing else is present | ||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = | ||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | ||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = | ||||
| 		colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background; | ||||
|  | ||||
| 		// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour | ||||
| 		if(has_playfield || has_ball) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall; | ||||
| 		} | ||||
|  | ||||
| 		// test 1 for score mode: if there is a ball pixel, plot that colour | ||||
| 		if(has_ball) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall; | ||||
| 		} | ||||
|  | ||||
| 		// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour | ||||
| 		if(has_player1 || has_missile1) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1; | ||||
| 		} | ||||
|  | ||||
| 		// in the right-hand side of score mode, the playfield has the same priority as player 1 | ||||
| 		if(has_playfield) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1; | ||||
| 		} | ||||
|  | ||||
| 		// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead | ||||
| 		if(has_player0 || has_missile0) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0; | ||||
| 		} | ||||
|  | ||||
| 		// if this is the left-hand side of score mode, the playfield has the same priority as player 0 | ||||
| 		if(has_playfield) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0; | ||||
| 		} | ||||
|  | ||||
| 		// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others | ||||
| 		if(has_playfield || has_ball) { | ||||
| 			colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TIA::TIA() : TIA(true) {} | ||||
|  | ||||
| TIA::TIA(std::function<void(uint8_t *output_buffer)> line_end_function) : TIA(false) { | ||||
| 	line_end_function_ = line_end_function; | ||||
| } | ||||
|  | ||||
| void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { | ||||
| 	Outputs::CRT::DisplayType display_type; | ||||
|  | ||||
| 	if(output_mode == OutputMode::NTSC) { | ||||
| 		crt_->set_composite_sampling_function( | ||||
| 			"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||
| 			"{" | ||||
| 				"uint c = texture(texID, coordinate).r;" | ||||
| 				"uint y = c & 14u;" | ||||
| 				"uint iPhase = (c >> 4);" | ||||
|  | ||||
| 				"float phaseOffset = 6.283185308 * (float(iPhase) + 3.0) / 13.0  + 5.074880441076923;" | ||||
| 				"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" | ||||
| 			"}"); | ||||
| 		display_type = Outputs::CRT::DisplayType::NTSC60; | ||||
| 	} else { | ||||
| 		crt_->set_composite_sampling_function( | ||||
| 			"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||
| 			"{" | ||||
| 				"uint c = texture(texID, coordinate).r;" | ||||
| 				"uint y = c & 14u;" | ||||
| 				"uint iPhase = (c >> 4);" | ||||
|  | ||||
| 				"uint direction = iPhase & 1u;" | ||||
| 				"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" | ||||
| 				"phaseOffset *= 6.283185308 / 12.0;" | ||||
| 				"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" | ||||
| 			"}"); | ||||
| 		display_type = Outputs::CRT::DisplayType::PAL50; | ||||
| 	} | ||||
| 	// 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 | ||||
| 	// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply | ||||
| 	// cycles_per_line * 2 cycles of information from one sync edge to the next | ||||
| 	crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type); | ||||
|  | ||||
| /*	speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ | ||||
| } | ||||
|  | ||||
| void TIA::run_for_cycles(int number_of_cycles) | ||||
| { | ||||
| 	// if part way through a line, definitely perform a partial, at most up to the end of the line | ||||
| 	if(horizontal_counter_) { | ||||
| 		int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); | ||||
| 		output_for_cycles(cycles); | ||||
| 		number_of_cycles -= cycles; | ||||
| 	} | ||||
|  | ||||
| 	// output full lines for as long as possible | ||||
| 	while(number_of_cycles >= cycles_per_line) { | ||||
| 		output_line(); | ||||
| 		number_of_cycles -= cycles_per_line; | ||||
| 	} | ||||
|  | ||||
| 	// partly start a new line if necessary | ||||
| 	if(number_of_cycles) { | ||||
| 		output_for_cycles(number_of_cycles); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TIA::set_sync(bool sync) { | ||||
| 	output_mode_ = (output_mode_ & ~sync_flag) | (sync ? sync_flag : 0); | ||||
| } | ||||
|  | ||||
| void TIA::set_blank(bool blank) { | ||||
| 	output_mode_ = (output_mode_ & ~blank_flag) | (blank ? blank_flag : 0); | ||||
| } | ||||
|  | ||||
| void TIA::reset_horizontal_counter() { | ||||
| } | ||||
|  | ||||
| int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) { | ||||
| 	return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line; | ||||
| } | ||||
|  | ||||
| void TIA::set_background_colour(uint8_t colour) { | ||||
| 	colour_palette_[(int)ColourIndex::Background] = colour; | ||||
| } | ||||
|  | ||||
| void TIA::set_playfield(uint16_t offset, uint8_t value) { | ||||
| 	assert(offset >= 0 && offset < 3); | ||||
| 	switch(offset) { | ||||
| 		case 0: | ||||
| 			background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16); | ||||
| 			background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4); | ||||
| 		break; | ||||
| 		case 1: | ||||
| 			background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8); | ||||
| 			background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4); | ||||
| 		break; | ||||
| 		case 2: | ||||
| 			background_[1] = (background_[1] & 0xfff00) | reverse_table[value]; | ||||
| 			background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TIA::set_playfield_control_and_ball_size(uint8_t value) { | ||||
| 	background_half_mask_ = value & 1; | ||||
| 	switch(value & 6) { | ||||
| 		case 0: | ||||
| 			playfield_priority_ = PlayfieldPriority::Standard; | ||||
| 		break; | ||||
| 		case 2: | ||||
| 			playfield_priority_ = PlayfieldPriority::Score; | ||||
| 		break; | ||||
| 		case 4: | ||||
| 		case 6: | ||||
| 			playfield_priority_ = PlayfieldPriority::OnTop; | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	ball_.size = 1 << ((value >> 4)&3); | ||||
| } | ||||
|  | ||||
| void TIA::set_playfield_ball_colour(uint8_t colour) { | ||||
| 	colour_palette_[(int)ColourIndex::PlayfieldBall] = colour; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_number_and_size(int player, uint8_t value) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	int size = 0; | ||||
| 	switch(value & 7) { | ||||
| 		case 0: case 1: case 2: case 3: case 4: | ||||
| 			player_[player].copy_flags = value & 7; | ||||
| 		break; | ||||
| 		case 5: | ||||
| 			size = 1; | ||||
| 			player_[player].copy_flags = 0; | ||||
| 		break; | ||||
| 		case 6: | ||||
| 			player_[player].copy_flags = 6; | ||||
| 		break; | ||||
| 		case 7: | ||||
| 			size = 2; | ||||
| 			player_[player].copy_flags = 0; | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	missile_[player].size = 1 << ((value >> 4)&3); | ||||
| 	missile_[player].copy_flags = player_[player].copy_flags; | ||||
| 	player_[player].adder = 4 >> size; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_graphic(int player, uint8_t value) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	player_[player].graphic[1] = value; | ||||
| 	player_[player^1].graphic[0] = player_[player^1].graphic[1]; | ||||
| 	if(player) ball_.enabled[0] = ball_.enabled[1]; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_reflected(int player, bool reflected) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	player_[player].reverse_mask = reflected ? 7 : 0; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_delay(int player, bool delay) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	player_[player].graphic_index = delay ? 0 : 1; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_position(int player) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	// players have an extra clock of delay before output and don't display upon reset; | ||||
| 	// both aims are achieved by setting to -1 because: (i) it causes the clock to be | ||||
| 	// one behind its real hardware value, creating the extra delay; and (ii) the player | ||||
| 	// code is written to start a draw upon wraparound from 159 to 0, so -1 is the | ||||
| 	// correct option rather than 159. | ||||
| 	player_[player].position = -1; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_motion(int player, uint8_t motion) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	player_[player].motion = (motion >> 4)&0xf; | ||||
| } | ||||
|  | ||||
| void TIA::set_player_missile_colour(int player, uint8_t colour) { | ||||
| 	assert(player >= 0 && player < 2); | ||||
| 	colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour; | ||||
| } | ||||
|  | ||||
| void TIA::set_missile_enable(int missile, bool enabled) { | ||||
| 	assert(missile >= 0 && missile < 2); | ||||
| 	missile_[missile].enabled = enabled; | ||||
| } | ||||
|  | ||||
| void TIA::set_missile_position(int missile) { | ||||
| 	assert(missile >= 0 && missile < 2); | ||||
| 	missile_[missile].position = 0; | ||||
| } | ||||
|  | ||||
| void TIA::set_missile_position_to_player(int missile, bool lock) { | ||||
| 	assert(missile >= 0 && missile < 2); | ||||
| 	missile_[missile].locked_to_player = lock; | ||||
| 	player_[missile].latched_pixel4_time = -1; | ||||
| } | ||||
|  | ||||
| void TIA::set_missile_motion(int missile, uint8_t motion) { | ||||
| 	assert(missile >= 0 && missile < 2); | ||||
| 	missile_[missile].motion = (motion >> 4)&0xf; | ||||
| } | ||||
|  | ||||
| void TIA::set_ball_enable(bool enabled) { | ||||
| 	ball_.enabled[1] = enabled; | ||||
| } | ||||
|  | ||||
| void TIA::set_ball_delay(bool delay) { | ||||
| 	ball_.enabled_index = delay ? 0 : 1; | ||||
| } | ||||
|  | ||||
| void TIA::set_ball_position() { | ||||
| 	ball_.position = 0; | ||||
|  | ||||
| 	// setting the ball position also triggers a draw | ||||
| 	ball_.reset_pixels(0); | ||||
| } | ||||
|  | ||||
| void TIA::set_ball_motion(uint8_t motion) { | ||||
| 	ball_.motion = (motion >> 4) & 0xf; | ||||
| } | ||||
|  | ||||
| void TIA::move() { | ||||
| 	horizontal_blank_extend_ = true; | ||||
| 	player_[0].is_moving = player_[1].is_moving = missile_[0].is_moving = missile_[1].is_moving = ball_.is_moving = true; | ||||
| 	player_[0].motion_step = player_[1].motion_step = missile_[0].motion_step = missile_[1].motion_step = ball_.motion_step = 15; | ||||
| 	player_[0].motion_time = player_[1].motion_time = missile_[0].motion_time = missile_[1].motion_time = ball_.motion_time = (horizontal_counter_ + 3) & ~3; | ||||
| } | ||||
|  | ||||
| void TIA::clear_motion() { | ||||
| 	player_[0].motion = player_[1].motion = missile_[0].motion = missile_[1].motion = ball_.motion = 0; | ||||
| } | ||||
|  | ||||
| uint8_t TIA::get_collision_flags(int offset) { | ||||
| 	return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0; | ||||
| } | ||||
|  | ||||
| void TIA::clear_collision_flags() { | ||||
| 	collision_flags_ = 0; | ||||
| } | ||||
|  | ||||
| void TIA::output_for_cycles(int number_of_cycles) { | ||||
| 	/* | ||||
| 		Line timing is oriented around 0 being the start of the right-hand side vertical blank; | ||||
| 		a wsync synchronises the CPU to horizontal_counter_ = 0. All timing below is in terms of the | ||||
| 		NTSC colour clock. | ||||
|  | ||||
| 		Therefore, each line is composed of: | ||||
|  | ||||
| 			16 cycles:	blank					; -> 16 | ||||
| 			16 cycles:	sync					; -> 32 | ||||
| 			16 cycles:	colour burst			; -> 48 | ||||
| 			20 cycles:	blank					; -> 68 | ||||
| 			8 cycles:	blank or pixels, depending on whether the blank extend bit is set | ||||
| 			152 cycles:	pixels | ||||
| 	*/ | ||||
| 	int output_cursor = horizontal_counter_; | ||||
| 	horizontal_counter_ += number_of_cycles; | ||||
| 	bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224; | ||||
|  | ||||
| 	if(!output_cursor) { | ||||
| 		if(line_end_function_) line_end_function_(collision_buffer_); | ||||
| 		memset(collision_buffer_, 0, sizeof(collision_buffer_)); | ||||
|  | ||||
| 		ball_.motion_time %= 228; | ||||
| 		player_[0].motion_time %= 228; | ||||
| 		player_[1].motion_time %= 228; | ||||
| 		missile_[0].motion_time %= 228; | ||||
| 		missile_[1].motion_time %= 228; | ||||
| 	} | ||||
|  | ||||
| 	// accumulate an OR'd version of the output into the collision buffer | ||||
| 	int latent_start = output_cursor + 4; | ||||
| 	int latent_end = horizontal_counter_ + 4; | ||||
| 	draw_playfield(latent_start, latent_end); | ||||
| 	draw_object<Player>(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_); | ||||
| 	draw_object<Player>(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_); | ||||
| 	draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_); | ||||
| 	draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_); | ||||
| 	draw_object<Ball>(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_); | ||||
|  | ||||
| 	// convert to television signals | ||||
|  | ||||
| #define Period(function, target)	\ | ||||
| 	if(output_cursor < target) { \ | ||||
| 		if(horizontal_counter_ <= target) { \ | ||||
| 			if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \ | ||||
| 			horizontal_counter_ %= cycles_per_line; \ | ||||
| 			return; \ | ||||
| 		} else { \ | ||||
| 			if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \ | ||||
| 			output_cursor = target; \ | ||||
| 		} \ | ||||
| 	} | ||||
|  | ||||
| 	switch(output_mode_) { | ||||
| 		default: | ||||
| 			Period(output_blank, 16) | ||||
| 			Period(output_sync, 32) | ||||
| 			Period(output_default_colour_burst, 48) | ||||
| 			Period(output_blank, 68) | ||||
| 		break; | ||||
| 		case sync_flag: | ||||
| 		case sync_flag | blank_flag: | ||||
| 			Period(output_sync, 16) | ||||
| 			Period(output_blank, 32) | ||||
| 			Period(output_default_colour_burst, 48) | ||||
| 			Period(output_sync, 228) | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| #undef Period | ||||
|  | ||||
| 	if(output_mode_ & blank_flag) { | ||||
| 		if(pixel_target_) { | ||||
| 			output_pixels(pixels_start_location_, output_cursor); | ||||
| 			if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2); | ||||
| 			pixel_target_ = nullptr; | ||||
| 			pixels_start_location_ = 0; | ||||
| 		} | ||||
| 		int duration = std::min(228, horizontal_counter_) - output_cursor; | ||||
| 		if(crt_) crt_->output_blank((unsigned int)(duration * 2)); | ||||
| 	} else { | ||||
| 		if(!pixels_start_location_ && crt_) { | ||||
| 			pixels_start_location_ = output_cursor; | ||||
| 			pixel_target_ = crt_->allocate_write_area(160); | ||||
| 		} | ||||
|  | ||||
| 		// convert that into pixels | ||||
| 		if(pixel_target_) output_pixels(output_cursor, horizontal_counter_); | ||||
|  | ||||
| 		// accumulate collision flags | ||||
| 		while(output_cursor < horizontal_counter_) { | ||||
| 			collision_flags_ |= collision_flags_by_buffer_vaules_[collision_buffer_[output_cursor - first_pixel_cycle]]; | ||||
| 			output_cursor++; | ||||
| 		} | ||||
|  | ||||
| 		if(horizontal_counter_ == cycles_per_line && crt_) { | ||||
| 			crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2); | ||||
| 			pixel_target_ = nullptr; | ||||
| 			pixels_start_location_ = 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(is_reset) horizontal_blank_extend_ = false; | ||||
|  | ||||
| 	horizontal_counter_ %= cycles_per_line; | ||||
| } | ||||
|  | ||||
| void TIA::output_pixels(int start, int end) { | ||||
| 	start = std::max(start, pixels_start_location_); | ||||
| 	int target_position = start - pixels_start_location_; | ||||
|  | ||||
| 	if(start < first_pixel_cycle+8 && horizontal_blank_extend_) { | ||||
| 		while(start < end && start < first_pixel_cycle+8) { | ||||
| 			pixel_target_[target_position] = 0; | ||||
| 			start++; | ||||
| 			target_position++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(playfield_priority_ == PlayfieldPriority::Score) { | ||||
| 		while(start < end && start < first_pixel_cycle + 80) { | ||||
| 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | ||||
| 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]]; | ||||
| 			start++; | ||||
| 			target_position++; | ||||
| 		} | ||||
| 		while(start < end) { | ||||
| 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | ||||
| 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]]; | ||||
| 			start++; | ||||
| 			target_position++; | ||||
| 		} | ||||
| 	} else { | ||||
| 		int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop); | ||||
| 		while(start < end) { | ||||
| 			uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; | ||||
| 			pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]]; | ||||
| 			start++; | ||||
| 			target_position++; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TIA::output_line() { | ||||
| 	switch(output_mode_) { | ||||
| 		default: | ||||
| 			// TODO: optimise special case | ||||
| 			output_for_cycles(cycles_per_line); | ||||
| 		break; | ||||
| 		case sync_flag: | ||||
| 		case sync_flag | blank_flag: | ||||
| 			if(crt_) { | ||||
| 				crt_->output_sync(32); | ||||
| 				crt_->output_blank(32); | ||||
| 				crt_->output_sync(392); | ||||
| 			} | ||||
| 			horizontal_blank_extend_ = false; | ||||
| 		break; | ||||
| 		case blank_flag: | ||||
| 			if(crt_) { | ||||
| 				crt_->output_blank(32); | ||||
| 				crt_->output_sync(32); | ||||
| 				crt_->output_default_colour_burst(32); | ||||
| 				crt_->output_blank(360); | ||||
| 			} | ||||
| 			horizontal_blank_extend_ = false; | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #pragma mark - Playfield output | ||||
|  | ||||
| void TIA::draw_playfield(int start, int end) { | ||||
| 	// don't do anything if this window ends too early | ||||
| 	if(end < first_pixel_cycle) return; | ||||
|  | ||||
| 	// clip to drawable bounds | ||||
| 	start = std::max(start, first_pixel_cycle); | ||||
| 	end = std::min(end, 228); | ||||
|  | ||||
| 	// proceed along four-pixel boundaries, plotting four pixels at a time | ||||
| 	int aligned_position = (start + 3)&~3; | ||||
| 	while(aligned_position < end) { | ||||
| 		int offset = (aligned_position - first_pixel_cycle) >> 2; | ||||
| 		uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101; | ||||
| 		*(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value; | ||||
| 		aligned_position += 4; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #pragma mark - Motion | ||||
|  | ||||
| template<class T> void TIA::perform_motion_step(T &object) { | ||||
| 	if((object.motion_step ^ (object.motion ^ 8)) == 0xf) { | ||||
| 		object.is_moving = false; | ||||
| 	} else { | ||||
| 		if(object.position == 159) object.reset_pixels(0); | ||||
| 		else if(object.position == 15 && object.copy_flags&1) object.reset_pixels(1); | ||||
| 		else if(object.position == 31 && object.copy_flags&2) object.reset_pixels(2); | ||||
| 		else if(object.position == 63 && object.copy_flags&4) object.reset_pixels(3); | ||||
| 		else object.skip_pixels(1, object.motion_time); | ||||
| 		object.position = (object.position + 1) % 160; | ||||
| 		object.motion_step --; | ||||
| 		object.motion_time += 4; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| template<class T> void TIA::perform_border_motion(T &object, int start, int end) { | ||||
| 	while(object.is_moving && object.motion_time < end) | ||||
| 		perform_motion_step<T>(object); | ||||
| } | ||||
|  | ||||
| template<class T> void TIA::draw_object(T &object, const uint8_t collision_identity, int start, int end) { | ||||
| 	int first_pixel = first_pixel_cycle - 4 + (horizontal_blank_extend_ ? 8 : 0); | ||||
|  | ||||
| 	object.dequeue_pixels(collision_buffer_, collision_identity, end - first_pixel_cycle); | ||||
|  | ||||
| 	// movement works across the entire screen, so do work that falls outside of the pixel area | ||||
| 	if(start < first_pixel) { | ||||
| 		perform_border_motion<T>(object, start, std::min(end, first_pixel)); | ||||
| 	} | ||||
|  | ||||
| 	// don't continue to do any drawing if this window ends too early | ||||
| 	if(end < first_pixel) return; | ||||
| 	if(start < first_pixel) start = first_pixel; | ||||
| 	if(start >= end) return; | ||||
|  | ||||
| 	// perform the visible part of the line, if any | ||||
| 	if(start < 224) { | ||||
| 		draw_object_visible<T>(object, collision_identity, start - first_pixel_cycle + 4, std::min(end - first_pixel_cycle + 4, 160), end - first_pixel_cycle); | ||||
| 	} | ||||
|  | ||||
| 	// move further if required | ||||
| 	if(object.is_moving && end >= 224 && object.motion_time < end) { | ||||
| 		perform_motion_step<T>(object); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| template<class T> void TIA::draw_object_visible(T &object, const uint8_t collision_identity, int start, int end, int time_now) { | ||||
| 	// perform a miniature event loop on (i) triggering draws; (ii) drawing; and (iii) motion | ||||
| 	int next_motion_time = object.motion_time - first_pixel_cycle + 4; | ||||
| 	while(start < end) { | ||||
| 		int next_event_time = end; | ||||
|  | ||||
| 		// is the next event a movement tick? | ||||
| 		if(object.is_moving && next_motion_time < next_event_time) { | ||||
| 			next_event_time = next_motion_time; | ||||
| 		} | ||||
|  | ||||
| 		// is the next event a graphics trigger? | ||||
| 		int next_copy = 160; | ||||
| 		int next_copy_id = 0; | ||||
| 		if(object.copy_flags) { | ||||
| 			if(object.position < 16 && object.copy_flags&1) { | ||||
| 				next_copy = 16; | ||||
| 				next_copy_id = 1; | ||||
| 			} else if(object.position < 32 && object.copy_flags&2) { | ||||
| 				next_copy = 32; | ||||
| 				next_copy_id = 2; | ||||
| 			} else if(object.position < 64 && object.copy_flags&4) { | ||||
| 				next_copy = 64; | ||||
| 				next_copy_id = 3; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		int next_copy_time = start + next_copy - object.position; | ||||
| 		if(next_copy_time < next_event_time) next_event_time = next_copy_time; | ||||
|  | ||||
| 		// the decision is to progress by length | ||||
| 		const int length = next_event_time - start; | ||||
|  | ||||
| 		// enqueue a future intention to draw pixels if spitting them out now would violate accuracy; | ||||
| 		// otherwise draw them now | ||||
| 		if(object.enqueues && next_event_time > time_now) { | ||||
| 			if(start < time_now) { | ||||
| 				object.output_pixels(&collision_buffer_[start], time_now - start, collision_identity, start + first_pixel_cycle - 4); | ||||
| 				object.enqueue_pixels(time_now, next_event_time, time_now + first_pixel_cycle - 4); | ||||
| 			} else { | ||||
| 				object.enqueue_pixels(start, next_event_time, start + first_pixel_cycle - 4); | ||||
| 			} | ||||
| 		} else { | ||||
| 			object.output_pixels(&collision_buffer_[start], length, collision_identity, start + first_pixel_cycle - 4); | ||||
| 		} | ||||
|  | ||||
| 		// the next interesting event is after next_event_time cycles, so progress | ||||
| 		object.position = (object.position + length) % 160; | ||||
| 		start = next_event_time; | ||||
|  | ||||
| 		// if the event is a motion tick, apply; if it's a draw trigger, trigger a draw | ||||
| 		if(object.is_moving && start == next_motion_time) { | ||||
| 			perform_motion_step(object); | ||||
| 			next_motion_time += 4; | ||||
| 		} else if(start == next_copy_time) { | ||||
| 			object.reset_pixels(next_copy_id); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #pragma mark - Missile drawing | ||||
|  | ||||
| void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) { | ||||
| 	if(!missile.locked_to_player || player.latched_pixel4_time < 0) { | ||||
| 		draw_object<Missile>(missile, collision_identity, start, end); | ||||
| 	} else { | ||||
| 		draw_object<Missile>(missile, collision_identity, start, player.latched_pixel4_time); | ||||
| 		missile.position = 0; | ||||
| 		draw_object<Missile>(missile, collision_identity, player.latched_pixel4_time, end); | ||||
| 		player.latched_pixel4_time = -1; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										333
									
								
								Machines/Atari2600/TIA.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,333 @@ | ||||
| // | ||||
| //  TIA.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 28/01/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef TIA_hpp | ||||
| #define TIA_hpp | ||||
|  | ||||
| #include <cstdint> | ||||
| #include "../CRTMachine.hpp" | ||||
|  | ||||
| namespace Atari2600 { | ||||
|  | ||||
| class TIA { | ||||
| 	public: | ||||
| 		TIA(); | ||||
| 		// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will | ||||
| 		// be called with the latest collision buffer upon the conclusion of each line. What's a collision | ||||
| 		// buffer? It's an implementation detail. If you're not writing a unit test, leave it alone. | ||||
| 		TIA(std::function<void(uint8_t *output_buffer)> line_end_function); | ||||
|  | ||||
| 		enum class OutputMode { | ||||
| 			NTSC, PAL | ||||
| 		}; | ||||
|  | ||||
| 		/*! | ||||
| 			Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the | ||||
| 			first cycle performed. | ||||
| 		*/ | ||||
| 		void run_for_cycles(int number_of_cycles); | ||||
| 		void set_output_mode(OutputMode output_mode); | ||||
|  | ||||
| 		void set_sync(bool sync); | ||||
| 		void set_blank(bool blank); | ||||
| 		void reset_horizontal_counter(); 						// Reset is delayed by four cycles. | ||||
|  | ||||
| 		/*! | ||||
| 			@returns the number of cycles between (current TIA time) + from_offset to the current or | ||||
| 			next horizontal blanking period. Returns numbers in the range [0, 227]. | ||||
| 		*/ | ||||
| 		int get_cycles_until_horizontal_blank(unsigned int from_offset); | ||||
|  | ||||
| 		void set_background_colour(uint8_t colour); | ||||
|  | ||||
| 		void set_playfield(uint16_t offset, uint8_t value); | ||||
| 		void set_playfield_control_and_ball_size(uint8_t value); | ||||
| 		void set_playfield_ball_colour(uint8_t colour); | ||||
|  | ||||
| 		void set_player_number_and_size(int player, uint8_t value); | ||||
| 		void set_player_graphic(int player, uint8_t value); | ||||
| 		void set_player_reflected(int player, bool reflected); | ||||
| 		void set_player_delay(int player, bool delay); | ||||
| 		void set_player_position(int player); | ||||
| 		void set_player_motion(int player, uint8_t motion); | ||||
| 		void set_player_missile_colour(int player, uint8_t colour); | ||||
|  | ||||
| 		void set_missile_enable(int missile, bool enabled); | ||||
| 		void set_missile_position(int missile); | ||||
| 		void set_missile_position_to_player(int missile, bool lock); | ||||
| 		void set_missile_motion(int missile, uint8_t motion); | ||||
|  | ||||
| 		void set_ball_enable(bool enabled); | ||||
| 		void set_ball_delay(bool delay); | ||||
| 		void set_ball_position(); | ||||
| 		void set_ball_motion(uint8_t motion); | ||||
|  | ||||
| 		void move(); | ||||
| 		void clear_motion(); | ||||
|  | ||||
| 		uint8_t get_collision_flags(int offset); | ||||
| 		void clear_collision_flags(); | ||||
|  | ||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; } | ||||
|  | ||||
| 	private: | ||||
| 		TIA(bool create_crt); | ||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | ||||
| 		std::function<void(uint8_t *output_buffer)> line_end_function_; | ||||
|  | ||||
| 		// the master counter; counts from 0 to 228 with all visible pixels being in the final 160 | ||||
| 		int horizontal_counter_; | ||||
|  | ||||
| 		// contains flags to indicate whether sync or blank are currently active | ||||
| 		int output_mode_; | ||||
|  | ||||
| 		// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer | ||||
| 		alignas(alignof(uint32_t)) uint8_t collision_buffer_[160]; | ||||
| 		enum class CollisionType : uint8_t { | ||||
| 			Playfield	= (1 << 0), | ||||
| 			Ball		= (1 << 1), | ||||
| 			Player0		= (1 << 2), | ||||
| 			Player1		= (1 << 3), | ||||
| 			Missile0	= (1 << 4), | ||||
| 			Missile1	= (1 << 5) | ||||
| 		}; | ||||
|  | ||||
| 		int collision_flags_; | ||||
| 		int collision_flags_by_buffer_vaules_[64]; | ||||
|  | ||||
| 		// colour mapping tables | ||||
| 		enum class ColourMode { | ||||
| 			Standard = 0, | ||||
| 			ScoreLeft, | ||||
| 			ScoreRight, | ||||
| 			OnTop | ||||
| 		}; | ||||
| 		uint8_t colour_mask_by_mode_collision_flags_[4][64];	// maps from [ColourMode][CollisionMark] to colour_pallete_ entry | ||||
|  | ||||
| 		enum class ColourIndex { | ||||
| 			Background = 0, | ||||
| 			PlayfieldBall, | ||||
| 			PlayerMissile0, | ||||
| 			PlayerMissile1 | ||||
| 		}; | ||||
| 		uint8_t colour_palette_[4]; | ||||
|  | ||||
| 		// playfield state | ||||
| 		int background_half_mask_; | ||||
| 		enum class PlayfieldPriority { | ||||
| 			Standard, | ||||
| 			Score, | ||||
| 			OnTop | ||||
| 		} playfield_priority_; | ||||
| 		uint32_t background_[2];	// contains two 20-bit bitfields representing the background state; | ||||
| 									// at index 0 is the left-hand side of the playfield with bit 0 being | ||||
| 									// the first bit to display, bit 1 the second, etc. Index 1 contains | ||||
| 									// a mirror image of index 0. If the playfield is being displayed in | ||||
| 									// mirroring mode, background_[0] will be output on the left and | ||||
| 									// background_[1] on the right; otherwise background_[0] will be | ||||
| 									// output twice. | ||||
|  | ||||
| 		// objects | ||||
| 		template<class T> struct Object { | ||||
| 			// the two programmer-set values | ||||
| 			int position; | ||||
| 			int motion; | ||||
|  | ||||
| 			// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire | ||||
| 			int motion_step; | ||||
| 			int motion_time; | ||||
|  | ||||
| 			// indicates whether this object is currently undergoing motion | ||||
| 			bool is_moving; | ||||
|  | ||||
| 			Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {}; | ||||
| 		}; | ||||
|  | ||||
| 		// player state | ||||
| 		struct Player: public Object<Player> { | ||||
| 			Player() : | ||||
| 				adder(4), | ||||
| 				copy_flags(0), | ||||
| 				graphic{0, 0}, | ||||
| 				reverse_mask(false), | ||||
| 				graphic_index(0), | ||||
| 				pixel_position(32), | ||||
| 				pixel_counter(0), | ||||
| 				latched_pixel4_time(-1), | ||||
| 				copy_index_(0), | ||||
| 				queue_read_pointer_(0), | ||||
| 				queue_write_pointer_(0) {} | ||||
|  | ||||
| 			int adder; | ||||
| 			int copy_flags;		// a bit field, corresponding to the first few values of NUSIZ | ||||
| 			uint8_t graphic[2];	// the player graphic; 1 = new, 0 = current | ||||
| 			int reverse_mask;	// 7 for a reflected player, 0 for normal | ||||
| 			int graphic_index; | ||||
|  | ||||
| 			int pixel_position, pixel_counter; | ||||
| 			int latched_pixel4_time; | ||||
| 			const bool enqueues = true; | ||||
|  | ||||
| 			inline void skip_pixels(const int count, int from_horizontal_counter) { | ||||
| 				int old_pixel_counter = pixel_counter; | ||||
| 				pixel_position = std::min(32, pixel_position + count * adder); | ||||
| 				pixel_counter += count; | ||||
| 				if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) { | ||||
| 					latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			inline void reset_pixels(int copy) { | ||||
| 				pixel_position = pixel_counter = 0; | ||||
| 				copy_index_ = copy; | ||||
| 			} | ||||
|  | ||||
| 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | ||||
| 				output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask); | ||||
| 				skip_pixels(count, from_horizontal_counter); | ||||
| 			} | ||||
|  | ||||
| 			void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) { | ||||
| 				while(queue_read_pointer_ != queue_write_pointer_) { | ||||
| 					uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start]; | ||||
| 					if(queue_[queue_read_pointer_].end > time_now) { | ||||
| 						const int length = time_now - queue_[queue_read_pointer_].start; | ||||
| 						output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); | ||||
| 						queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder; | ||||
| 						queue_[queue_read_pointer_].start = time_now; | ||||
| 						return; | ||||
| 					} else { | ||||
| 						output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); | ||||
| 					} | ||||
| 					queue_read_pointer_ = (queue_read_pointer_ + 1)&3; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			void enqueue_pixels(const int start, const int end, int from_horizontal_counter) { | ||||
| 				queue_[queue_write_pointer_].start = start; | ||||
| 				queue_[queue_write_pointer_].end = end; | ||||
| 				queue_[queue_write_pointer_].pixel_position = pixel_position; | ||||
| 				queue_[queue_write_pointer_].adder = adder; | ||||
| 				queue_[queue_write_pointer_].reverse_mask = reverse_mask; | ||||
| 				queue_write_pointer_ = (queue_write_pointer_ + 1)&3; | ||||
| 				skip_pixels(end - start, from_horizontal_counter); | ||||
| 			} | ||||
|  | ||||
| 			private: | ||||
| 				int copy_index_; | ||||
| 				struct QueuedPixels { | ||||
| 					int start, end; | ||||
| 					int pixel_position; | ||||
| 					int adder; | ||||
| 					int reverse_mask; | ||||
| 					QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {} | ||||
| 				} queue_[4]; | ||||
| 				int queue_read_pointer_, queue_write_pointer_; | ||||
|  | ||||
| 				inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) { | ||||
| 					if(pixel_position == 32 || !graphic[graphic_index]) return; | ||||
| 					int output_cursor = 0; | ||||
| 					while(pixel_position < 32 && output_cursor < count) { | ||||
| 						int shift = (pixel_position >> 2) ^ reverse_mask; | ||||
| 						target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity; | ||||
| 						output_cursor++; | ||||
| 						pixel_position += adder; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 		} player_[2]; | ||||
|  | ||||
| 		// common actor for things that appear as a horizontal run of pixels | ||||
| 		struct HorizontalRun: public Object<HorizontalRun> { | ||||
| 			int pixel_position; | ||||
| 			int size; | ||||
| 			const bool enqueues = false; | ||||
|  | ||||
| 			inline void skip_pixels(const int count, int from_horizontal_counter) { | ||||
| 				pixel_position = std::max(0, pixel_position - count); | ||||
| 			} | ||||
|  | ||||
| 			inline void reset_pixels(int copy) { | ||||
| 				pixel_position = size; | ||||
| 			} | ||||
|  | ||||
| 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | ||||
| 				int output_cursor = 0; | ||||
| 				while(pixel_position && output_cursor < count) | ||||
| 				{ | ||||
| 					target[output_cursor] |= collision_identity; | ||||
| 					output_cursor++; | ||||
| 					pixel_position--; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {} | ||||
| 			void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {} | ||||
|  | ||||
| 			HorizontalRun() : pixel_position(0), size(1) {} | ||||
| 		}; | ||||
|  | ||||
|  | ||||
| 		// missile state | ||||
| 		struct Missile: public HorizontalRun { | ||||
| 			bool enabled; | ||||
| 			bool locked_to_player; | ||||
| 			int copy_flags; | ||||
|  | ||||
| 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | ||||
| 				if(!pixel_position) return; | ||||
| 				if(enabled && !locked_to_player) { | ||||
| 					HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); | ||||
| 				} else { | ||||
| 					skip_pixels(count, from_horizontal_counter); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			Missile() : enabled(false), copy_flags(0) {} | ||||
| 		} missile_[2]; | ||||
|  | ||||
| 		// ball state | ||||
| 		struct Ball: public HorizontalRun { | ||||
| 			bool enabled[2]; | ||||
| 			int enabled_index; | ||||
| 			const int copy_flags = 0; | ||||
|  | ||||
| 			inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { | ||||
| 				if(!pixel_position) return; | ||||
| 				if(enabled[enabled_index]) { | ||||
| 					HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); | ||||
| 				} else { | ||||
| 					skip_pixels(count, from_horizontal_counter); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			Ball() : enabled{false, false}, enabled_index(0) {} | ||||
| 		} ball_; | ||||
|  | ||||
| 		// motion | ||||
| 		bool horizontal_blank_extend_; | ||||
| 		template<class T> void perform_border_motion(T &object, int start, int end); | ||||
| 		template<class T> void perform_motion_step(T &object); | ||||
|  | ||||
| 		// drawing methods and state | ||||
| 		void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end); | ||||
| 		template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end); | ||||
| 		template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now); | ||||
| 		inline void draw_playfield(int start, int end); | ||||
|  | ||||
| 		inline void output_for_cycles(int number_of_cycles); | ||||
| 		inline void output_line(); | ||||
|  | ||||
| 		int pixels_start_location_; | ||||
| 		uint8_t *pixel_target_; | ||||
| 		inline void output_pixels(int start, int end); | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* TIA_hpp */ | ||||
| @@ -16,8 +16,7 @@ Machine::Machine() : | ||||
| 		shift_register_(0), | ||||
| 		Storage::Disk::Controller(1000000, 4, 300), | ||||
| 		serial_port_(new SerialPort), | ||||
| 	serial_port_VIA_(new SerialPortVIA) | ||||
| { | ||||
| 		serial_port_VIA_(new SerialPortVIA) { | ||||
| 	// attach the serial port to its VIA and vice versa | ||||
| 	serial_port_->set_serial_port_via(serial_port_VIA_); | ||||
| 	serial_port_VIA_->set_serial_port(serial_port_); | ||||
| @@ -31,13 +30,11 @@ Machine::Machine() : | ||||
| 	set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); | ||||
| } | ||||
|  | ||||
| void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) | ||||
| { | ||||
| void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | ||||
| 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | ||||
| } | ||||
|  | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) | ||||
| { | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 	/* | ||||
| 		Memory map (given that I'm unsure yet on any potential mirroring): | ||||
|  | ||||
| @@ -46,27 +43,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 			0x1c00–0x1c0f	the drive VIA | ||||
| 			0xc000–0xffff	ROM | ||||
| 	*/ | ||||
| 	if(address < 0x800) | ||||
| 	{ | ||||
| 	if(address < 0x800) { | ||||
| 		if(isReadOperation(operation)) | ||||
| 			*value = ram_[address]; | ||||
| 		else | ||||
| 			ram_[address] = *value; | ||||
| 	} | ||||
| 	else if(address >= 0xc000) | ||||
| 	{ | ||||
| 	} else if(address >= 0xc000) { | ||||
| 		if(isReadOperation(operation)) | ||||
| 			*value = rom_[address & 0x3fff]; | ||||
| 	} | ||||
| 	else if(address >= 0x1800 && address <= 0x180f) | ||||
| 	{ | ||||
| 	} else if(address >= 0x1800 && address <= 0x180f) { | ||||
| 		if(isReadOperation(operation)) | ||||
| 			*value = serial_port_VIA_->get_register(address); | ||||
| 		else | ||||
| 			serial_port_VIA_->set_register(address, *value); | ||||
| 	} | ||||
| 	else if(address >= 0x1c00 && address <= 0x1c0f) | ||||
| 	{ | ||||
| 	} else if(address >= 0x1c00 && address <= 0x1c0f) { | ||||
| 		if(isReadOperation(operation)) | ||||
| 			*value = drive_VIA_.get_register(address); | ||||
| 		else | ||||
| @@ -79,20 +69,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| void Machine::set_rom(const uint8_t *rom) | ||||
| { | ||||
| void Machine::set_rom(const uint8_t *rom) { | ||||
| 	memcpy(rom_, rom, sizeof(rom_)); | ||||
| } | ||||
|  | ||||
| void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) | ||||
| { | ||||
| void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | ||||
| 	std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive); | ||||
| 	drive->set_disk(disk); | ||||
| 	set_drive(drive); | ||||
| } | ||||
|  | ||||
| void Machine::run_for_cycles(int number_of_cycles) | ||||
| { | ||||
| void Machine::run_for_cycles(int number_of_cycles) { | ||||
| 	CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); | ||||
| 	set_motor_on(drive_VIA_.get_motor_enabled()); | ||||
| 	if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down | ||||
| @@ -101,38 +88,30 @@ void Machine::run_for_cycles(int number_of_cycles) | ||||
|  | ||||
| #pragma mark - 6522 delegate | ||||
|  | ||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) | ||||
| { | ||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { | ||||
| 	// both VIAs are connected to the IRQ line | ||||
| 	set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line()); | ||||
| } | ||||
|  | ||||
| #pragma mark - Disk drive | ||||
|  | ||||
| void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) | ||||
| { | ||||
| void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) { | ||||
| 	shift_register_ = (shift_register_ << 1) | value; | ||||
| 	if((shift_register_ & 0x3ff) == 0x3ff) | ||||
| 	{ | ||||
| 	if((shift_register_ & 0x3ff) == 0x3ff) { | ||||
| 		drive_VIA_.set_sync_detected(true); | ||||
| 		bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		drive_VIA_.set_sync_detected(false); | ||||
| 	} | ||||
| 	bit_window_offset_++; | ||||
| 	if(bit_window_offset_ == 8) | ||||
| 	{ | ||||
| 	if(bit_window_offset_ == 8) { | ||||
| 		drive_VIA_.set_data_input((uint8_t)shift_register_); | ||||
| 		bit_window_offset_ = 0; | ||||
| 		if(drive_VIA_.get_should_set_overflow()) | ||||
| 		{ | ||||
| 		if(drive_VIA_.get_should_set_overflow()) { | ||||
| 			set_overflow_line(true); | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 		set_overflow_line(false); | ||||
| 	else set_overflow_line(false); | ||||
| } | ||||
|  | ||||
| // the 1540 does not recognise index holes | ||||
| @@ -140,32 +119,26 @@ void Machine::process_index_hole()	{} | ||||
|  | ||||
| #pragma mak - Drive VIA delegate | ||||
|  | ||||
| void Machine::drive_via_did_step_head(void *driveVIA, int direction) | ||||
| { | ||||
| void Machine::drive_via_did_step_head(void *driveVIA, int direction) { | ||||
| 	step(direction); | ||||
| } | ||||
|  | ||||
| void Machine::drive_via_did_set_data_density(void *driveVIA, int density) | ||||
| { | ||||
| void Machine::drive_via_did_set_data_density(void *driveVIA, int density) { | ||||
| 	set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density)); | ||||
| } | ||||
|  | ||||
| #pragma mark - SerialPortVIA | ||||
|  | ||||
| SerialPortVIA::SerialPortVIA() : | ||||
| 	port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) | ||||
| {} | ||||
| 	port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {} | ||||
|  | ||||
| uint8_t SerialPortVIA::get_port_input(Port port) | ||||
| { | ||||
| uint8_t SerialPortVIA::get_port_input(Port port) { | ||||
| 	if(port) return port_b_; | ||||
| 	return 0xff; | ||||
| } | ||||
|  | ||||
| void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) | ||||
| { | ||||
| 	if(port) | ||||
| 	{ | ||||
| void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { | ||||
| 	if(port) { | ||||
| 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||
| 		if(serialPort) { | ||||
| 			attention_acknowledge_level_ = !(value&0x10); | ||||
| @@ -177,10 +150,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) | ||||
| { | ||||
| 	switch(line) | ||||
| 	{ | ||||
| void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { | ||||
| 	switch(line) { | ||||
| 		default: break; | ||||
| 		case ::Commodore::Serial::Line::Data:		port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01);		break; | ||||
| 		case ::Commodore::Serial::Line::Clock:		port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04);		break; | ||||
| @@ -193,16 +164,13 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) | ||||
| { | ||||
| void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) { | ||||
| 	serial_port_ = serialPort; | ||||
| } | ||||
|  | ||||
| void SerialPortVIA::update_data_line() | ||||
| { | ||||
| void SerialPortVIA::update_data_line() { | ||||
| 	std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||
| 	if(serialPort) | ||||
| 	{ | ||||
| 	if(serialPort) { | ||||
| 		// "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, | ||||
| 			(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); | ||||
| @@ -211,8 +179,7 @@ void SerialPortVIA::update_data_line() | ||||
|  | ||||
| #pragma mark - DriveVIA | ||||
|  | ||||
| void DriveVIA::set_delegate(Delegate *delegate) | ||||
| { | ||||
| void DriveVIA::set_delegate(Delegate *delegate) { | ||||
| 	delegate_ = delegate; | ||||
| } | ||||
|  | ||||
| @@ -246,22 +213,19 @@ void DriveVIA::set_control_line_output(Port port, Line line, bool value) { | ||||
| } | ||||
|  | ||||
| void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) { | ||||
| 	if(port) | ||||
| 	{ | ||||
| 	if(port) { | ||||
| 		// record drive motor state | ||||
| 		drive_motor_ = !!(value&4); | ||||
|  | ||||
| 		// check for a head step | ||||
| 		int step_difference = ((value&3) - (previous_port_b_output_&3))&3; | ||||
| 		if(step_difference) | ||||
| 		{ | ||||
| 		if(step_difference) { | ||||
| 			if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1); | ||||
| 		} | ||||
|  | ||||
| 		// check for a change in density | ||||
| 		int density_difference = (previous_port_b_output_^value) & (3 << 5); | ||||
| 		if(density_difference && delegate_) | ||||
| 		{ | ||||
| 		if(density_difference && delegate_) { | ||||
| 			delegate_->drive_via_did_set_data_density(this, (value >> 5)&3); | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -10,10 +10,8 @@ | ||||
|  | ||||
| using namespace Commodore::Serial; | ||||
|  | ||||
| const char *::Commodore::Serial::StringForLine(Line line) | ||||
| { | ||||
| 	switch(line) | ||||
| 	{ | ||||
| const char *::Commodore::Serial::StringForLine(Line line) { | ||||
| 	switch(line) { | ||||
| 		case ServiceRequest: return "Service request"; | ||||
| 		case Attention: return "Attention"; | ||||
| 		case Clock: return "Clock"; | ||||
| @@ -22,17 +20,14 @@ const char *::Commodore::Serial::StringForLine(Line line) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) | ||||
| { | ||||
| void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) { | ||||
| 	port->set_serial_bus(bus); | ||||
| 	bus->add_port(port); | ||||
| } | ||||
|  | ||||
| void Bus::add_port(std::shared_ptr<Port> port) | ||||
| { | ||||
| void Bus::add_port(std::shared_ptr<Port> port) { | ||||
| 	ports_.push_back(port); | ||||
| 	for(int line = (int)ServiceRequest; line <= (int)Reset; line++) | ||||
| 	{ | ||||
| 	for(int line = (int)ServiceRequest; line <= (int)Reset; line++) { | ||||
| 		// the addition of a new device may change the line output... | ||||
| 		set_line_output_did_change((Line)line); | ||||
|  | ||||
| @@ -41,29 +36,23 @@ void Bus::add_port(std::shared_ptr<Port> port) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Bus::set_line_output_did_change(Line line) | ||||
| { | ||||
| void Bus::set_line_output_did_change(Line line) { | ||||
| 	// i.e. I believe these lines to be open collector | ||||
| 	LineLevel new_line_level = High; | ||||
| 	for(std::weak_ptr<Port> port : ports_) | ||||
| 	{ | ||||
| 	for(std::weak_ptr<Port> port : ports_) { | ||||
| 		std::shared_ptr<Port> locked_port = port.lock(); | ||||
| 		if(locked_port) | ||||
| 		{ | ||||
| 		if(locked_port) { | ||||
| 			new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(line)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// post an update only if one occurred | ||||
| 	if(new_line_level != line_levels_[line]) | ||||
| 	{ | ||||
| 	if(new_line_level != line_levels_[line]) { | ||||
| 		line_levels_[line] = new_line_level; | ||||
|  | ||||
| 		for(std::weak_ptr<Port> port : ports_) | ||||
| 		{ | ||||
| 		for(std::weak_ptr<Port> port : ports_) { | ||||
| 			std::shared_ptr<Port> locked_port = port.lock(); | ||||
| 			if(locked_port) | ||||
| 			{ | ||||
| 			if(locked_port) { | ||||
| 				locked_port->set_input(line, new_line_level); | ||||
| 			} | ||||
| 		} | ||||
| @@ -72,19 +61,14 @@ void Bus::set_line_output_did_change(Line line) | ||||
|  | ||||
| #pragma mark - The debug port | ||||
|  | ||||
| void DebugPort::set_input(Line line, LineLevel value) | ||||
| { | ||||
| void DebugPort::set_input(Line line, LineLevel value) { | ||||
| 	input_levels_[line] = value; | ||||
|  | ||||
| 	printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low"); | ||||
| 	if(!incoming_count_) | ||||
| 	{ | ||||
| 	if(!incoming_count_) { | ||||
| 		incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if(line == Line::Clock && value) | ||||
| 		{ | ||||
| 	} else { | ||||
| 		if(line == Line::Clock && value) { | ||||
| 			incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00); | ||||
| 		} | ||||
| 		incoming_count_--; | ||||
|   | ||||
| @@ -78,8 +78,7 @@ namespace Serial { | ||||
| 				Sets the current level of an output line on this serial port. | ||||
| 			*/ | ||||
| 			void set_output(Line line, LineLevel level) { | ||||
| 				if(line_levels_[line] != level) | ||||
| 				{ | ||||
| 				if(line_levels_[line] != level) { | ||||
| 					line_levels_[line] = level; | ||||
| 					std::shared_ptr<Bus> bus = serial_bus_.lock(); | ||||
| 					if(bus) bus->set_line_output_did_change(line); | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| #include "Vic20.hpp" | ||||
|  | ||||
| uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) | ||||
| { | ||||
| uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) { | ||||
| #define KEYS(...)	{__VA_ARGS__, TerminateSequence} | ||||
| #define SHIFT(...)	{KeyLShift, __VA_ARGS__, TerminateSequence} | ||||
| #define X			{NotMapped} | ||||
|   | ||||
| @@ -17,8 +17,7 @@ using namespace Commodore::Vic20; | ||||
| Machine::Machine() : | ||||
| 		rom_(nullptr), | ||||
| 		is_running_at_zero_cost_(false), | ||||
| 	tape_(1022727) | ||||
| { | ||||
| 		tape_(1022727) { | ||||
| 	// create 6522s, serial port and bus | ||||
| 	user_port_via_.reset(new UserPortVIA); | ||||
| 	keyboard_via_.reset(new KeyboardVIA); | ||||
| @@ -48,13 +47,11 @@ Machine::Machine() : | ||||
| //	serial_bus_->add_port(_debugPort); | ||||
| } | ||||
|  | ||||
| void Machine::set_memory_size(MemorySize size) | ||||
| { | ||||
| void Machine::set_memory_size(MemorySize size) { | ||||
| 	memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); | ||||
| 	memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); | ||||
|  | ||||
| 	switch(size) | ||||
| 	{ | ||||
| 	switch(size) { | ||||
| 		default: break; | ||||
| 		case ThreeKB: | ||||
| 			write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000); | ||||
| @@ -79,31 +76,26 @@ void Machine::set_memory_size(MemorySize size) | ||||
| 	write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); | ||||
|  | ||||
| 	// install the inserted ROM if there is one | ||||
| 	if(rom_) | ||||
| 	{ | ||||
| 	if(rom_) { | ||||
| 		write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) | ||||
| { | ||||
| void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { | ||||
| 	address >>= 10; | ||||
| 	length >>= 10; | ||||
| 	while(length--) | ||||
| 	{ | ||||
| 	while(length--) { | ||||
| 		map[address] = area; | ||||
| 		area += 0x400; | ||||
| 		address++; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Machine::~Machine() | ||||
| { | ||||
| Machine::~Machine() { | ||||
| 	delete[] rom_; | ||||
| } | ||||
|  | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) | ||||
| { | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| //	static int logCount = 0; | ||||
| //	if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500; | ||||
| //	if(operation == CPU6502::BusOperation::ReadOpcode && logCount) { | ||||
| @@ -111,8 +103,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| //		printf("%04x\n", address); | ||||
| //	} | ||||
|  | ||||
| //	if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) | ||||
| //	{ | ||||
| //	if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) { | ||||
| //		printf("\n[%04x] <- %02x\n", address, *value); | ||||
| //	} | ||||
|  | ||||
| @@ -120,11 +111,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 	if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1); | ||||
|  | ||||
| 	// 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; | ||||
| 		if((address&0xfc00) == 0x9000) | ||||
| 		{ | ||||
| 		if((address&0xfc00) == 0x9000) { | ||||
| 			if((address&0xff00) == 0x9000)	result &= mos6560_->get_register(address); | ||||
| 			if((address&0xfc10) == 0x9010)	result &= user_port_via_->get_register(address); | ||||
| 			if((address&0xfc20) == 0x9020)	result &= keyboard_via_->get_register(address); | ||||
| @@ -135,22 +124,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 		// 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_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode) | ||||
| 		{ | ||||
| 			while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end()) | ||||
| 			{ | ||||
| 		if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode) { | ||||
| 			while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end()) { | ||||
| 				user_port_via_->run_for_cycles(1); | ||||
| 				keyboard_via_->run_for_cycles(1); | ||||
| 				tape_.run_for_cycles(1); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		uint8_t *ram = processor_write_memory_map_[address >> 10]; | ||||
| 		if(ram) ram[address & 0x3ff] = *value; | ||||
| 		if((address&0xfc00) == 0x9000) | ||||
| 		{ | ||||
| 		if((address&0xfc00) == 0x9000) { | ||||
| 			if((address&0xff00) == 0x9000)	mos6560_->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); | ||||
| @@ -159,10 +143,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
|  | ||||
| 	user_port_via_->run_for_cycles(1); | ||||
| 	keyboard_via_->run_for_cycles(1); | ||||
| 	if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) | ||||
| 	{ | ||||
| 		if(!typer_->type_next_character()) | ||||
| 		{ | ||||
| 	if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) { | ||||
| 		if(!typer_->type_next_character()) { | ||||
| 			clear_all_keys(); | ||||
| 			typer_.reset(); | ||||
| 		} | ||||
| @@ -181,18 +163,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 	// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits | ||||
| 	// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting | ||||
| 	// the CPU out of the action. | ||||
| 	if(use_fast_tape_hack_ && tape_.has_tape()) | ||||
| 	{ | ||||
| 		if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) | ||||
| 		{ | ||||
| 	if(use_fast_tape_hack_ && tape_.has_tape()) { | ||||
| 		if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) { | ||||
| 			is_running_at_zero_cost_ = true; | ||||
| 			set_clock_is_unlimited(true); | ||||
| 		} | ||||
| 		if( | ||||
| 			(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) || | ||||
| 			tape_.get_tape()->is_at_end() | ||||
| 		) | ||||
| 		{ | ||||
| 		) { | ||||
| 			is_running_at_zero_cost_ = false; | ||||
| 			set_clock_is_unlimited(false); | ||||
| 		} | ||||
| @@ -203,31 +182,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
|  | ||||
| #pragma mark - 6522 delegate | ||||
|  | ||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) | ||||
| { | ||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { | ||||
| 	set_nmi_line(user_port_via_->get_interrupt_line()); | ||||
| 	set_irq_line(keyboard_via_->get_interrupt_line()); | ||||
| } | ||||
|  | ||||
| #pragma mark - Setup | ||||
|  | ||||
| void Machine::set_region(Commodore::Vic20::Region region) | ||||
| { | ||||
| void Machine::set_region(Commodore::Vic20::Region region) { | ||||
| 	region_ = region; | ||||
| 	switch(region) | ||||
| 	{ | ||||
| 	switch(region) { | ||||
| 		case PAL: | ||||
| 			set_clock_rate(1108404); | ||||
| 			if(mos6560_) | ||||
| 			{ | ||||
| 			if(mos6560_) { | ||||
| 				mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL); | ||||
| 				mos6560_->set_clock_rate(1108404); | ||||
| 			} | ||||
| 		break; | ||||
| 		case NTSC: | ||||
| 			set_clock_rate(1022727); | ||||
| 			if(mos6560_) | ||||
| 			{ | ||||
| 			if(mos6560_) { | ||||
| 				mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC); | ||||
| 				mos6560_->set_clock_rate(1022727); | ||||
| 			} | ||||
| @@ -235,8 +209,7 @@ void Machine::set_region(Commodore::Vic20::Region region) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::setup_output(float aspect_ratio) | ||||
| { | ||||
| void Machine::setup_output(float aspect_ratio) { | ||||
| 	mos6560_.reset(new Vic6560()); | ||||
| 	mos6560_->get_speaker()->set_high_frequency_cut_off(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | ||||
| 	set_region(region_); | ||||
| @@ -248,17 +221,14 @@ void Machine::setup_output(float aspect_ratio) | ||||
| 	mos6560_->colour_memory = colour_memory_; | ||||
| } | ||||
|  | ||||
| void Machine::close_output() | ||||
| { | ||||
| void Machine::close_output() { | ||||
| 	mos6560_ = nullptr; | ||||
| } | ||||
|  | ||||
| void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) | ||||
| { | ||||
| void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) { | ||||
| 	uint8_t *target = nullptr; | ||||
| 	size_t max_length = 0x2000; | ||||
| 	switch(slot) | ||||
| 	{ | ||||
| 	switch(slot) { | ||||
| 		case Kernel:		target = kernel_rom_;								break; | ||||
| 		case Characters:	target = character_rom_;	max_length = 0x1000;	break; | ||||
| 		case BASIC:			target = basic_rom_;								break; | ||||
| @@ -269,29 +239,23 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if(target) | ||||
| 	{ | ||||
| 	if(target) { | ||||
| 		size_t length_to_copy = std::min(max_length, length); | ||||
| 		memcpy(target, data, length_to_copy); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| //void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) | ||||
| //{ | ||||
| //	if(length > 2) | ||||
| //	{ | ||||
| //void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) { | ||||
| //	if(length > 2) { | ||||
| //		_rom_address = (uint16_t)(data[0] | (data[1] << 8)); | ||||
| //		_rom_length = (uint16_t)(length - 2); | ||||
| // | ||||
| //		// install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism | ||||
| //		if(_rom_address == 0xa000) | ||||
| //		{ | ||||
| //		if(_rom_address == 0xa000) { | ||||
| //			_rom = new uint8_t[0x2000]; | ||||
| //			memcpy(_rom, &data[2], length - 2); | ||||
| //			write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000); | ||||
| //		} | ||||
| //		else | ||||
| //		{ | ||||
| //		} else { | ||||
| //			set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name))); | ||||
| //		} | ||||
| //	} | ||||
| @@ -299,15 +263,12 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) | ||||
|  | ||||
| #pragma mar - Tape | ||||
|  | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| { | ||||
| 	if(target.tapes.size()) | ||||
| 	{ | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { | ||||
| 	if(target.tapes.size()) { | ||||
| 		tape_.set_tape(target.tapes.front()); | ||||
| 	} | ||||
|  | ||||
| 	if(target.disks.size()) | ||||
| 	{ | ||||
| 	if(target.disks.size()) { | ||||
| 		// construct the 1540 | ||||
| 		c1540_.reset(new ::Commodore::C1540::Machine); | ||||
|  | ||||
| @@ -321,8 +282,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| 		install_disk_rom(); | ||||
| 	} | ||||
|  | ||||
| 	if(target.cartridges.size()) | ||||
| 	{ | ||||
| 	if(target.cartridges.size()) { | ||||
| 		rom_address_ = 0xa000; | ||||
| 		std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data; | ||||
| 		rom_length_ = (uint16_t)(rom_image.size()); | ||||
| @@ -332,15 +292,12 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| 		write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); | ||||
| 	} | ||||
|  | ||||
| 	if(should_automatically_load_media_) | ||||
| 	{ | ||||
| 		if(target.loadingCommand.length())	// TODO: and automatic loading option enabled | ||||
| 		{ | ||||
| 	if(should_automatically_load_media_) { | ||||
| 		if(target.loadingCommand.length()) {	// TODO: and automatic loading option enabled | ||||
| 			set_typer_for_string(target.loadingCommand.c_str()); | ||||
| 		} | ||||
|  | ||||
| 		switch(target.vic20.memory_model) | ||||
| 		{ | ||||
| 		switch(target.vic20.memory_model) { | ||||
| 			case StaticAnalyser::Vic20MemoryModel::Unexpanded: | ||||
| 				set_memory_size(Default); | ||||
| 			break; | ||||
| @@ -354,17 +311,14 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) | ||||
| { | ||||
| void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { | ||||
| 	keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input()); | ||||
| } | ||||
|  | ||||
| #pragma mark - Disc | ||||
|  | ||||
| void Machine::install_disk_rom() | ||||
| { | ||||
| 	if(drive_rom_ && c1540_) | ||||
| 	{ | ||||
| void Machine::install_disk_rom() { | ||||
| 	if(drive_rom_ && c1540_) { | ||||
| 		c1540_->set_rom(drive_rom_.get()); | ||||
| 		c1540_->run_for_cycles(2000000); | ||||
| 		drive_rom_.reset(); | ||||
| @@ -373,45 +327,36 @@ void Machine::install_disk_rom() | ||||
|  | ||||
| #pragma mark - UserPortVIA | ||||
|  | ||||
| uint8_t UserPortVIA::get_port_input(Port port) | ||||
| { | ||||
| 	if(!port) | ||||
| 	{ | ||||
| uint8_t UserPortVIA::get_port_input(Port port) { | ||||
| 	if(!port) { | ||||
| 		return port_a_;	// TODO: bit 6 should be high if there is no tape, low otherwise | ||||
| 	} | ||||
| 	return 0xff; | ||||
| } | ||||
|  | ||||
| void UserPortVIA::set_control_line_output(Port port, Line line, bool value) | ||||
| { | ||||
| void UserPortVIA::set_control_line_output(Port port, Line line, bool value) { | ||||
| //	if(port == Port::A && line == Line::Two) { | ||||
| //		printf("Tape motor %s\n", value ? "on" : "off"); | ||||
| //	} | ||||
| } | ||||
|  | ||||
| void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) | ||||
| { | ||||
| 	switch(line) | ||||
| 	{ | ||||
| void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { | ||||
| 	switch(line) { | ||||
| 		default: break; | ||||
| 		case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00);	break; | ||||
| 		case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00);	break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void UserPortVIA::set_joystick_state(JoystickInput input, bool value) | ||||
| { | ||||
| 	if(input != JoystickInput::Right) | ||||
| 	{ | ||||
| void UserPortVIA::set_joystick_state(JoystickInput input, bool value) { | ||||
| 	if(input != JoystickInput::Right) { | ||||
| 		port_a_ = (port_a_ & ~input) | (value ? 0 : input); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) | ||||
| { | ||||
| void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { | ||||
| 	// Line 7 of port A is inverted and output as serial ATN | ||||
| 	if(!port) | ||||
| 	{ | ||||
| 	if(!port) { | ||||
| 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||
| 		if(serialPort) | ||||
| 			serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); | ||||
| @@ -420,38 +365,31 @@ void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) | ||||
|  | ||||
| UserPortVIA::UserPortVIA() : port_a_(0xbf) {} | ||||
|  | ||||
| void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) | ||||
| { | ||||
| void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { | ||||
| 	serial_port_ = serialPort; | ||||
| } | ||||
|  | ||||
| #pragma mark - KeyboardVIA | ||||
|  | ||||
| KeyboardVIA::KeyboardVIA() : port_b_(0xff) | ||||
| { | ||||
| KeyboardVIA::KeyboardVIA() : port_b_(0xff) { | ||||
| 	clear_all_keys(); | ||||
| } | ||||
|  | ||||
| void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) | ||||
| { | ||||
| void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) { | ||||
| 	if(isPressed) | ||||
| 		columns_[key & 7] &= ~(key >> 3); | ||||
| 	else | ||||
| 		columns_[key & 7] |= (key >> 3); | ||||
| } | ||||
|  | ||||
| void KeyboardVIA::clear_all_keys() | ||||
| { | ||||
| void KeyboardVIA::clear_all_keys() { | ||||
| 	memset(columns_, 0xff, sizeof(columns_)); | ||||
| } | ||||
|  | ||||
| uint8_t KeyboardVIA::get_port_input(Port port) | ||||
| { | ||||
| 	if(!port) | ||||
| 	{ | ||||
| uint8_t KeyboardVIA::get_port_input(Port port) { | ||||
| 	if(!port) { | ||||
| 		uint8_t result = 0xff; | ||||
| 		for(int c = 0; c < 8; c++) | ||||
| 		{ | ||||
| 		for(int c = 0; c < 8; c++) { | ||||
| 			if(!(activation_mask_&(1 << c))) | ||||
| 				result &= columns_[c]; | ||||
| 		} | ||||
| @@ -461,19 +399,15 @@ uint8_t KeyboardVIA::get_port_input(Port port) | ||||
| 	return port_b_; | ||||
| } | ||||
|  | ||||
| void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) | ||||
| { | ||||
| void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { | ||||
| 	if(port) | ||||
| 		activation_mask_ = (value & mask) | (~mask); | ||||
| } | ||||
|  | ||||
| void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) | ||||
| { | ||||
| 	if(line == Line::Two) | ||||
| 	{ | ||||
| void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) { | ||||
| 	if(line == Line::Two) { | ||||
| 		std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); | ||||
| 		if(serialPort) | ||||
| 		{ | ||||
| 		if(serialPort) { | ||||
| 			// CB2 is inverted to become serial data; CA2 is inverted to become serial clock | ||||
| 			if(port == Port::A) | ||||
| 				serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); | ||||
| @@ -483,28 +417,23 @@ void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) | ||||
| { | ||||
| 	if(input == JoystickInput::Right) | ||||
| 	{ | ||||
| void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) { | ||||
| 	if(input == JoystickInput::Right) { | ||||
| 		port_b_ = (port_b_ & ~input) | (value ? 0 : input); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) | ||||
| { | ||||
| void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { | ||||
| 	serial_port_ = serialPort; | ||||
| } | ||||
|  | ||||
| #pragma mark - SerialPort | ||||
|  | ||||
| void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) | ||||
| { | ||||
| void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { | ||||
| 	std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock(); | ||||
| 	if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level); | ||||
| } | ||||
|  | ||||
| void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) | ||||
| { | ||||
| void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) { | ||||
| 	user_port_via_ = userPortVIA; | ||||
| } | ||||
|   | ||||
| @@ -129,8 +129,7 @@ class SerialPort : public ::Commodore::Serial::Port { | ||||
|  | ||||
| class Vic6560: public MOS::MOS6560<Vic6560> { | ||||
| 	public: | ||||
| 		inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) | ||||
| 		{ | ||||
| 		inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { | ||||
| 			*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO | ||||
| 			*colour_data = colour_memory[address & 0x03ff]; | ||||
| 		} | ||||
|   | ||||
| @@ -18,8 +18,7 @@ Machine::Machine() : | ||||
| 		cycles_since_display_update_(0), | ||||
| 		cycles_since_audio_update_(0), | ||||
| 		use_fast_tape_hack_(false), | ||||
| 	cycles_until_display_interrupt_(0) | ||||
| { | ||||
| 		cycles_until_display_interrupt_(0) { | ||||
| 	memset(key_states_, 0, sizeof(key_states_)); | ||||
| 	for(int c = 0; c < 16; c++) | ||||
| 		memset(roms_[c], 0xff, 16384); | ||||
| @@ -30,8 +29,7 @@ Machine::Machine() : | ||||
|  | ||||
| #pragma mark - Output | ||||
|  | ||||
| void Machine::setup_output(float aspect_ratio) | ||||
| { | ||||
| void Machine::setup_output(float aspect_ratio) { | ||||
| 	video_output_.reset(new VideoOutput(ram_)); | ||||
|  | ||||
| 	// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; | ||||
| @@ -41,36 +39,29 @@ void Machine::setup_output(float aspect_ratio) | ||||
| 	speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider); | ||||
| } | ||||
|  | ||||
| void Machine::close_output() | ||||
| { | ||||
| void Machine::close_output() { | ||||
| 	video_output_.reset(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() | ||||
| { | ||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() { | ||||
| 	return video_output_->get_crt(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() | ||||
| { | ||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() { | ||||
| 	return speaker_; | ||||
| } | ||||
|  | ||||
| #pragma mark - The keyboard | ||||
|  | ||||
| void Machine::clear_all_keys() | ||||
| { | ||||
| void Machine::clear_all_keys() { | ||||
| 	memset(key_states_, 0, sizeof(key_states_)); | ||||
| 	if(is_holding_shift_) set_key_state(KeyShift, true); | ||||
| } | ||||
|  | ||||
| void Machine::set_key_state(uint16_t key, bool isPressed) | ||||
| { | ||||
| 	if(key == KeyBreak) | ||||
| 	{ | ||||
| void Machine::set_key_state(uint16_t key, bool isPressed) { | ||||
| 	if(key == KeyBreak) { | ||||
| 		set_reset_line(isPressed); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		if(isPressed) | ||||
| 			key_states_[key >> 4] |= key&0xf; | ||||
| 		else | ||||
| @@ -80,23 +71,18 @@ void Machine::set_key_state(uint16_t key, bool isPressed) | ||||
|  | ||||
| #pragma mark - Machine configuration | ||||
|  | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| { | ||||
| 	if(target.tapes.size()) | ||||
| 	{ | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { | ||||
| 	if(target.tapes.size()) { | ||||
| 		tape_.set_tape(target.tapes.front()); | ||||
| 	} | ||||
|  | ||||
| 	if(target.disks.size()) | ||||
| 	{ | ||||
| 	if(target.disks.size()) { | ||||
| 		plus3_.reset(new Plus3); | ||||
|  | ||||
| 		if(target.acorn.has_dfs) | ||||
| 		{ | ||||
| 		if(target.acorn.has_dfs) { | ||||
| 			set_rom(ROMSlot0, dfs_, true); | ||||
| 		} | ||||
| 		if(target.acorn.has_adfs) | ||||
| 		{ | ||||
| 		if(target.acorn.has_adfs) { | ||||
| 			set_rom(ROMSlot4, adfs_, true); | ||||
| 			set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true); | ||||
| 		} | ||||
| @@ -105,29 +91,23 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| 	} | ||||
|  | ||||
| 	ROMSlot slot = ROMSlot12; | ||||
| 	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) | ||||
| 	{ | ||||
| 	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) { | ||||
| 		set_rom(slot, cartridge->get_segments().front().data, false); | ||||
| 		slot = (ROMSlot)(((int)slot + 1)&15); | ||||
| 	} | ||||
|  | ||||
| 	if(target.loadingCommand.length())	// TODO: and automatic loading option enabled | ||||
| 	{ | ||||
| 	if(target.loadingCommand.length()) {	// TODO: and automatic loading option enabled | ||||
| 		set_typer_for_string(target.loadingCommand.c_str()); | ||||
| 	} | ||||
|  | ||||
| 	if(target.acorn.should_hold_shift) | ||||
| 	{ | ||||
| 		set_key_state(KeyShift, true); | ||||
| 		is_holding_shift_ = true; | ||||
| 	if(target.acorn.should_shift_restart) { | ||||
| 		shift_restart_counter_ = 1000000; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) | ||||
| { | ||||
| void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) { | ||||
| 	uint8_t *target = nullptr; | ||||
| 	switch(slot) | ||||
| 	{ | ||||
| 	switch(slot) { | ||||
| 		case ROMSlotDFS:	dfs_ = data;			return; | ||||
| 		case ROMSlotADFS:	adfs_ = data;			return; | ||||
|  | ||||
| @@ -143,18 +123,13 @@ void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable | ||||
|  | ||||
| #pragma mark - The bus | ||||
|  | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) | ||||
| { | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 	unsigned int cycles = 1; | ||||
|  | ||||
| 	if(address < 0x8000) | ||||
| 	{ | ||||
| 		if(isReadOperation(operation)) | ||||
| 		{ | ||||
| 	if(address < 0x8000) { | ||||
| 		if(isReadOperation(operation)) { | ||||
| 			*value = ram_[address]; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display(); | ||||
| 			ram_[address] = *value; | ||||
| 		} | ||||
| @@ -162,30 +137,22 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 		// for the entire frame, RAM is accessible only on odd cycles; in modes below 4 | ||||
| 		// it's also accessible only outside of the pixel regions | ||||
| 		cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1)); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		switch(address & 0xff0f) | ||||
| 		{ | ||||
| 	} else { | ||||
| 		switch(address & 0xff0f) { | ||||
| 			case 0xfe00: | ||||
| 				if(isReadOperation(operation)) | ||||
| 				{ | ||||
| 				if(isReadOperation(operation)) { | ||||
| 					*value = interrupt_status_; | ||||
| 					interrupt_status_ &= ~PowerOnReset; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 				} else { | ||||
| 					interrupt_control_ = (*value) & ~1; | ||||
| 					evaluate_interrupts(); | ||||
| 				} | ||||
| 			break; | ||||
| 			case 0xfe07: | ||||
| 				if(!isReadOperation(operation)) | ||||
| 				{ | ||||
| 				if(!isReadOperation(operation)) { | ||||
| 					// update speaker mode | ||||
| 					bool new_speaker_is_enabled = (*value & 6) == 2; | ||||
| 					if(new_speaker_is_enabled != speaker_is_enabled_) | ||||
| 					{ | ||||
| 					if(new_speaker_is_enabled != speaker_is_enabled_) { | ||||
| 						update_audio(); | ||||
| 						speaker_->set_is_enabled(new_speaker_is_enabled); | ||||
| 						speaker_is_enabled_ = new_speaker_is_enabled; | ||||
| @@ -202,8 +169,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 			case 0xfe02: case 0xfe03: | ||||
| 			case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: | ||||
| 			case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: | ||||
| 				if(!isReadOperation(operation)) | ||||
| 				{ | ||||
| 				if(!isReadOperation(operation)) { | ||||
| 					update_display(); | ||||
| 					video_output_->set_register(address, *value); | ||||
| 					video_access_range_ = video_output_->get_memory_access_range(); | ||||
| @@ -211,23 +177,18 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 				} | ||||
| 			break; | ||||
| 			case 0xfe04: | ||||
| 				if(isReadOperation(operation)) | ||||
| 				{ | ||||
| 				if(isReadOperation(operation)) { | ||||
| 					*value = tape_.get_data_register(); | ||||
| 					tape_.clear_interrupts(Interrupt::ReceiveDataFull); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 				} else { | ||||
| 					tape_.set_data_register(*value); | ||||
| 					tape_.clear_interrupts(Interrupt::TransmitDataEmpty); | ||||
| 				} | ||||
| 			break; | ||||
| 			case 0xfe05: | ||||
| 				if(!isReadOperation(operation)) | ||||
| 				{ | ||||
| 				if(!isReadOperation(operation)) { | ||||
| 					const uint8_t interruptDisable = (*value)&0xf0; | ||||
| 					if( interruptDisable ) | ||||
| 					{ | ||||
| 					if( interruptDisable ) { | ||||
| 						if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd; | ||||
| 						if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock; | ||||
| 						if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect; | ||||
| @@ -240,15 +201,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 					active_rom_ = (Electron::ROMSlot)(*value & 0xf); | ||||
|  | ||||
| 					// apply the ULA's test | ||||
| 					if(*value & 0x08) | ||||
| 					{ | ||||
| 						if(*value & 0x04) | ||||
| 						{ | ||||
| 					if(*value & 0x08) { | ||||
| 						if(*value & 0x04) { | ||||
| 							keyboard_is_active_ = false; | ||||
| 							basic_is_active_ = false; | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 						} else { | ||||
| 							keyboard_is_active_ = !(*value & 0x02); | ||||
| 							basic_is_active_ = !keyboard_is_active_; | ||||
| 						} | ||||
| @@ -256,8 +213,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 				} | ||||
| 			break; | ||||
| 			case 0xfe06: | ||||
| 				if(!isReadOperation(operation)) | ||||
| 				{ | ||||
| 				if(!isReadOperation(operation)) { | ||||
| 					update_audio(); | ||||
| 					speaker_->set_divider(*value); | ||||
| 					tape_.set_counter(*value); | ||||
| @@ -265,10 +221,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 			break; | ||||
|  | ||||
| 			case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: | ||||
| 				if(plus3_ && (address&0x00f0) == 0x00c0) | ||||
| 				{ | ||||
| 					if(is_holding_shift_ && address == 0xfcc4) | ||||
| 					{ | ||||
| 				if(plus3_ && (address&0x00f0) == 0x00c0) { | ||||
| 					if(is_holding_shift_ && address == 0xfcc4) { | ||||
| 						is_holding_shift_ = false; | ||||
| 						set_key_state(KeyShift, false); | ||||
| 					} | ||||
| @@ -279,22 +233,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 				} | ||||
| 			break; | ||||
| 			case 0xfc00: | ||||
| 				if(plus3_ && (address&0x00f0) == 0x00c0) | ||||
| 				{ | ||||
| 					if(!isReadOperation(operation)) | ||||
| 					{ | ||||
| 				if(plus3_ && (address&0x00f0) == 0x00c0) { | ||||
| 					if(!isReadOperation(operation)) { | ||||
| 						plus3_->set_control_register(*value); | ||||
| 					} | ||||
| 					else | ||||
| 						*value = 1; | ||||
| 					} else *value = 1; | ||||
| 				} | ||||
| 			break; | ||||
|  | ||||
| 			default: | ||||
| 				if(address >= 0xc000) | ||||
| 				{ | ||||
| 					if(isReadOperation(operation)) | ||||
| 					{ | ||||
| 				if(address >= 0xc000) { | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						if( | ||||
| 							use_fast_tape_hack_ && | ||||
| 							tape_.has_tape() && | ||||
| @@ -314,21 +262,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 																				// and set A to zero to report that action was taken, then | ||||
| 																				// allow the PC read to return an RTS. | ||||
| 							) | ||||
| 						) | ||||
| 						{ | ||||
| 						) { | ||||
| 							uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); | ||||
| 							if(address == 0xf0a8) | ||||
| 							{ | ||||
| 								if(!ram_[0x247] && service_call == 14) | ||||
| 								{ | ||||
| 							if(address == 0xf0a8) { | ||||
| 								if(!ram_[0x247] && service_call == 14) { | ||||
| 									tape_.set_delegate(nullptr); | ||||
|  | ||||
| 									// TODO: handle tape wrap around. | ||||
|  | ||||
| 									int cycles_left_while_plausibly_in_data = 50; | ||||
| 									tape_.clear_interrupts(Interrupt::ReceiveDataFull); | ||||
| 									while(!tape_.get_tape()->is_at_end()) | ||||
| 									{ | ||||
| 									while(!tape_.get_tape()->is_at_end()) { | ||||
| 										tape_.run_for_input_pulse(); | ||||
| 										cycles_left_while_plausibly_in_data--; | ||||
| 										if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false; | ||||
| @@ -345,37 +289,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 									set_value_of_register(CPU6502::Register::Y, tape_.get_data_register()); | ||||
| 									*value = 0x60; // 0x60 is RTS | ||||
| 								} | ||||
| 								else | ||||
| 									*value = os_[address & 16383]; | ||||
| 								else *value = os_[address & 16383]; | ||||
| 							} | ||||
| 							else | ||||
| 								*value = 0xea; | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							else *value = 0xea; | ||||
| 						} else { | ||||
| 							*value = os_[address & 16383]; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					if(isReadOperation(operation)) | ||||
| 					{ | ||||
| 				} else { | ||||
| 					if(isReadOperation(operation)) { | ||||
| 						*value = roms_[active_rom_][address & 16383]; | ||||
| 						if(keyboard_is_active_) | ||||
| 						{ | ||||
| 						if(keyboard_is_active_) { | ||||
| 							*value &= 0xf0; | ||||
| 							for(int address_line = 0; address_line < 14; address_line++) | ||||
| 							{ | ||||
| 							for(int address_line = 0; address_line < 14; address_line++) { | ||||
| 								if(!(address&(1 << address_line))) *value |= key_states_[address_line]; | ||||
| 							} | ||||
| 						} | ||||
| 						if(basic_is_active_) | ||||
| 						{ | ||||
| 						if(basic_is_active_) { | ||||
| 							*value &= roms_[ROMSlotBASIC][address & 16383]; | ||||
| 						} | ||||
| 					} else if(rom_write_masks_[active_rom_]) | ||||
| 					{ | ||||
| 					} else if(rom_write_masks_[active_rom_]) { | ||||
| 						roms_[active_rom_][address & 16383] = *value; | ||||
| 					} | ||||
| 				} | ||||
| @@ -389,8 +322,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 	tape_.run_for_cycles(cycles); | ||||
|  | ||||
| 	cycles_until_display_interrupt_ -= cycles; | ||||
| 	if(cycles_until_display_interrupt_ < 0) | ||||
| 	{ | ||||
| 	if(cycles_until_display_interrupt_ < 0) { | ||||
| 		signal_interrupt(next_display_interrupt_); | ||||
| 		update_display(); | ||||
| 		queue_next_display_interrupt(); | ||||
| @@ -398,12 +330,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
|  | ||||
| 	if(typer_) typer_->update((int)cycles); | ||||
| 	if(plus3_) plus3_->run_for_cycles(4*cycles); | ||||
| 	if(shift_restart_counter_) { | ||||
| 		shift_restart_counter_ -= cycles; | ||||
| 		if(shift_restart_counter_ <= 0) { | ||||
| 			shift_restart_counter_ = 0; | ||||
| 			set_power_on(true); | ||||
| 			set_key_state(KeyShift, true); | ||||
| 			is_holding_shift_ = true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return cycles; | ||||
| } | ||||
|  | ||||
| void Machine::synchronise() | ||||
| { | ||||
| void Machine::synchronise() { | ||||
| 	update_display(); | ||||
| 	update_audio(); | ||||
| 	speaker_->flush(); | ||||
| @@ -411,26 +351,21 @@ void Machine::synchronise() | ||||
|  | ||||
| #pragma mark - Deferred scheduling | ||||
|  | ||||
| inline void Machine::update_display() | ||||
| { | ||||
| 	if(cycles_since_display_update_) | ||||
| 	{ | ||||
| inline void Machine::update_display() { | ||||
| 	if(cycles_since_display_update_) { | ||||
| 		video_output_->run_for_cycles((int)cycles_since_display_update_); | ||||
| 		cycles_since_display_update_ = 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| inline void Machine::queue_next_display_interrupt() | ||||
| { | ||||
| inline void Machine::queue_next_display_interrupt() { | ||||
| 	VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt(); | ||||
| 	cycles_until_display_interrupt_ = next_interrupt.cycles; | ||||
| 	next_display_interrupt_ = next_interrupt.interrupt; | ||||
| } | ||||
|  | ||||
| inline void Machine::update_audio() | ||||
| { | ||||
| 	if(cycles_since_audio_update_) | ||||
| 	{ | ||||
| inline void Machine::update_audio() { | ||||
| 	if(cycles_since_audio_update_) { | ||||
| 		unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider; | ||||
| 		cycles_since_audio_update_ %= Speaker::clock_rate_divider; | ||||
| 		speaker_->run_for_cycles(difference); | ||||
| @@ -439,26 +374,20 @@ inline void Machine::update_audio() | ||||
|  | ||||
| #pragma mark - Interrupts | ||||
|  | ||||
| inline void Machine::signal_interrupt(Electron::Interrupt interrupt) | ||||
| { | ||||
| inline void Machine::signal_interrupt(Electron::Interrupt interrupt) { | ||||
| 	interrupt_status_ |= interrupt; | ||||
| 	evaluate_interrupts(); | ||||
| } | ||||
|  | ||||
| inline void Machine::clear_interrupt(Electron::Interrupt interrupt) | ||||
| { | ||||
| inline void Machine::clear_interrupt(Electron::Interrupt interrupt) { | ||||
| 	interrupt_status_ &= ~interrupt; | ||||
| 	evaluate_interrupts(); | ||||
| } | ||||
|  | ||||
| inline void Machine::evaluate_interrupts() | ||||
| { | ||||
| 	if(interrupt_status_ & interrupt_control_) | ||||
| 	{ | ||||
| inline void Machine::evaluate_interrupts() { | ||||
| 	if(interrupt_status_ & interrupt_control_) { | ||||
| 		interrupt_status_ |= 1; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		interrupt_status_ &= ~1; | ||||
| 	} | ||||
| 	set_irq_line(interrupt_status_ & 1); | ||||
| @@ -466,8 +395,7 @@ inline void Machine::evaluate_interrupts() | ||||
|  | ||||
| #pragma mark - Tape::Delegate | ||||
|  | ||||
| void Machine::tape_did_change_interrupt_status(Tape *tape) | ||||
| { | ||||
| void Machine::tape_did_change_interrupt_status(Tape *tape) { | ||||
| 	interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); | ||||
| 	evaluate_interrupts(); | ||||
| } | ||||
|   | ||||
| @@ -144,6 +144,7 @@ class Machine: | ||||
| 		// Disk | ||||
| 		std::unique_ptr<Plus3> plus3_; | ||||
| 		bool is_holding_shift_; | ||||
| 		int shift_restart_counter_; | ||||
|  | ||||
| 		// Outputs | ||||
| 		std::unique_ptr<VideoOutput> video_output_; | ||||
|   | ||||
| @@ -10,23 +10,19 @@ | ||||
|  | ||||
| using namespace Electron; | ||||
|  | ||||
| Plus3::Plus3() : WD1770(P1770), last_control_(0) | ||||
| { | ||||
| Plus3::Plus3() : WD1770(P1770), last_control_(0) { | ||||
| 	set_control_register(last_control_, 0xff); | ||||
| } | ||||
|  | ||||
| void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) | ||||
| { | ||||
| 	if(!drives_[drive]) | ||||
| 	{ | ||||
| void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | ||||
| 	if(!drives_[drive]) { | ||||
| 		drives_[drive].reset(new Storage::Disk::Drive); | ||||
| 		if(drive == selected_drive_) set_drive(drives_[drive]); | ||||
| 	} | ||||
| 	drives_[drive]->set_disk(disk); | ||||
| } | ||||
|  | ||||
| void Plus3::set_control_register(uint8_t control) | ||||
| { | ||||
| void Plus3::set_control_register(uint8_t control) { | ||||
| 	//	bit 0 => enable or disable drive 1 | ||||
| 	//	bit 1 => enable or disable drive 2 | ||||
| 	//	bit 2 => side select | ||||
| @@ -37,19 +33,15 @@ void Plus3::set_control_register(uint8_t control) | ||||
| 	set_control_register(control, changes); | ||||
| } | ||||
|  | ||||
| void Plus3::set_control_register(uint8_t control, uint8_t changes) | ||||
| { | ||||
| 	if(changes&3) | ||||
| 	{ | ||||
| 		switch(control&3) | ||||
| 		{ | ||||
| void Plus3::set_control_register(uint8_t control, uint8_t changes) { | ||||
| 	if(changes&3) { | ||||
| 		switch(control&3) { | ||||
| 			case 0:		selected_drive_ = -1;	set_drive(nullptr);		break; | ||||
| 			default:	selected_drive_ = 0;	set_drive(drives_[0]);	break; | ||||
| 			case 2:		selected_drive_ = 1;	set_drive(drives_[1]);	break; | ||||
| 		} | ||||
| 	} | ||||
| 	if(changes & 0x04) | ||||
| 	{ | ||||
| 	if(changes & 0x04) { | ||||
| 		invalidate_track(); | ||||
| 		if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); | ||||
| 		if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); | ||||
|   | ||||
| @@ -10,37 +10,29 @@ | ||||
|  | ||||
| using namespace Electron; | ||||
|  | ||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) | ||||
| { | ||||
| 	if(is_enabled_) | ||||
| 	{ | ||||
| 		while(number_of_samples--) | ||||
| 		{ | ||||
| void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { | ||||
| 	if(is_enabled_) { | ||||
| 		while(number_of_samples--) { | ||||
| 			*target = (int16_t)((counter_ / (divider_+1)) * 8192); | ||||
| 			target++; | ||||
| 			counter_ = (counter_ + 1) % ((divider_+1) * 2); | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		memset(target, 0, sizeof(int16_t) * number_of_samples); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Speaker::skip_samples(unsigned int number_of_samples) | ||||
| { | ||||
| void Speaker::skip_samples(unsigned int number_of_samples) { | ||||
| 	counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2); | ||||
| } | ||||
|  | ||||
| void Speaker::set_divider(uint8_t divider) | ||||
| { | ||||
| void Speaker::set_divider(uint8_t divider) { | ||||
| 	enqueue([=]() { | ||||
| 		divider_ = divider * 32 / clock_rate_divider; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Speaker::set_is_enabled(bool is_enabled) | ||||
| { | ||||
| void Speaker::set_is_enabled(bool is_enabled) { | ||||
| 	enqueue([=]() { | ||||
| 		is_enabled_ = is_enabled; | ||||
| 		counter_ = 0; | ||||
|   | ||||
| @@ -17,19 +17,15 @@ Tape::Tape() : | ||||
| 		delegate_(nullptr), | ||||
| 		output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}), | ||||
| 		last_posted_interrupt_status_(0), | ||||
| 	interrupt_status_(0) | ||||
| {} | ||||
| 		interrupt_status_(0) {} | ||||
|  | ||||
| void Tape::push_tape_bit(uint16_t bit) | ||||
| { | ||||
| void Tape::push_tape_bit(uint16_t bit) { | ||||
| 	data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10)); | ||||
|  | ||||
| 	if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--; | ||||
| 	if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull; | ||||
| 	if(!input_.minimum_bits_until_full) | ||||
| 	{ | ||||
| 		if((data_register_&0x3) == 0x1) | ||||
| 		{ | ||||
| 	if(!input_.minimum_bits_until_full) { | ||||
| 		if((data_register_&0x3) == 0x1) { | ||||
| 			interrupt_status_ |= Interrupt::ReceiveDataFull; | ||||
| 			if(is_in_input_mode_) input_.minimum_bits_until_full = 9; | ||||
| 		} | ||||
| @@ -44,66 +40,53 @@ void Tape::push_tape_bit(uint16_t bit) | ||||
| 	evaluate_interrupts(); | ||||
| } | ||||
|  | ||||
| void Tape::evaluate_interrupts() | ||||
| { | ||||
| 	if(last_posted_interrupt_status_ != interrupt_status_) | ||||
| 	{ | ||||
| void Tape::evaluate_interrupts() { | ||||
| 	if(last_posted_interrupt_status_ != interrupt_status_) { | ||||
| 		last_posted_interrupt_status_ = interrupt_status_; | ||||
| 		if(delegate_) delegate_->tape_did_change_interrupt_status(this); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Tape::clear_interrupts(uint8_t interrupts) | ||||
| { | ||||
| void Tape::clear_interrupts(uint8_t interrupts) { | ||||
| 	interrupt_status_ &= ~interrupts; | ||||
| 	evaluate_interrupts(); | ||||
| } | ||||
|  | ||||
| void Tape::set_is_in_input_mode(bool is_in_input_mode) | ||||
| { | ||||
| void Tape::set_is_in_input_mode(bool is_in_input_mode) { | ||||
| 	is_in_input_mode_ = is_in_input_mode; | ||||
| } | ||||
|  | ||||
| void Tape::set_counter(uint8_t value) | ||||
| { | ||||
| void Tape::set_counter(uint8_t value) { | ||||
| 	output_.cycles_into_pulse = 0; | ||||
| 	output_.bits_remaining_until_empty = 0; | ||||
| } | ||||
|  | ||||
| void Tape::set_data_register(uint8_t value) | ||||
| { | ||||
| void Tape::set_data_register(uint8_t value) { | ||||
| 	data_register_ = (uint16_t)((value << 2) | 1); | ||||
| 	output_.bits_remaining_until_empty = 9; | ||||
| } | ||||
|  | ||||
| uint8_t Tape::get_data_register() | ||||
| { | ||||
| uint8_t Tape::get_data_register() { | ||||
| 	return (uint8_t)(data_register_ >> 2); | ||||
| } | ||||
|  | ||||
| void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) | ||||
| { | ||||
| void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) { | ||||
| 	crossings_[0] = crossings_[1]; | ||||
| 	crossings_[1] = crossings_[2]; | ||||
| 	crossings_[2] = crossings_[3]; | ||||
|  | ||||
| 	crossings_[3] = Tape::Unrecognised; | ||||
| 	if(pulse.type != Storage::Tape::Tape::Pulse::Zero) | ||||
| 	{ | ||||
| 	if(pulse.type != Storage::Tape::Tape::Pulse::Zero) { | ||||
| 		float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate; | ||||
| 		if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short; | ||||
| 		if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long; | ||||
| 	} | ||||
|  | ||||
| 	if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) | ||||
| 	{ | ||||
| 	if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) { | ||||
| 		push_tape_bit(0); | ||||
| 		crossings_[0] = crossings_[1] = Tape::Recognised; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) | ||||
| 		{ | ||||
| 	} else { | ||||
| 		if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) { | ||||
| 			push_tape_bit(1); | ||||
| 			crossings_[0] = crossings_[1] = | ||||
| 			crossings_[2] = crossings_[3] = Tape::Recognised; | ||||
| @@ -111,23 +94,16 @@ void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Tape::run_for_cycles(unsigned int number_of_cycles) | ||||
| { | ||||
| 	if(is_enabled_) | ||||
| 	{ | ||||
| 		if(is_in_input_mode_) | ||||
| 		{ | ||||
| 			if(is_running_) | ||||
| 			{ | ||||
| void Tape::run_for_cycles(unsigned int number_of_cycles) { | ||||
| 	if(is_enabled_) { | ||||
| 		if(is_in_input_mode_) { | ||||
| 			if(is_running_) { | ||||
| 				TapePlayer::run_for_cycles((int)number_of_cycles); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			output_.cycles_into_pulse += number_of_cycles; | ||||
| 			while(output_.cycles_into_pulse > 1664)		// 1664 = the closest you can get to 1200 baud if you're looking for something | ||||
| 			{											// that divides the 125,000Hz clock that the sound divider runs off. | ||||
| 				output_.cycles_into_pulse -= 1664; | ||||
| 			while(output_.cycles_into_pulse > 1664) {	// 1664 = the closest you can get to 1200 baud if you're looking for something | ||||
| 				output_.cycles_into_pulse -= 1664;		// that divides the 125,000Hz clock that the sound divider runs off. | ||||
| 				push_tape_bit(1); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -8,18 +8,15 @@ | ||||
|  | ||||
| #include "Electron.hpp" | ||||
|  | ||||
| int Electron::Machine::get_typer_delay() | ||||
| { | ||||
| int Electron::Machine::get_typer_delay() { | ||||
| 	return get_is_resetting() ? 625*25*128 : 0;	// wait one second if resetting | ||||
| } | ||||
|  | ||||
| int Electron::Machine::get_typer_frequency() | ||||
| { | ||||
| int Electron::Machine::get_typer_frequency() { | ||||
| 	return 625*128*2;	// accept a new character every two frames | ||||
| } | ||||
|  | ||||
| uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) | ||||
| { | ||||
| uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) { | ||||
| #define KEYS(...)	{__VA_ARGS__, TerminateSequence} | ||||
| #define SHIFT(...)	{KeyShift, __VA_ARGS__, TerminateSequence} | ||||
| #define CTRL(...)	{KeyControl, __VA_ARGS__, TerminateSequence} | ||||
|   | ||||
| @@ -42,8 +42,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : | ||||
| 		output_position_(0), | ||||
| 		screen_mode_(6), | ||||
| 		screen_map_pointer_(0), | ||||
| 	cycles_into_draw_action_(0) | ||||
| { | ||||
| 		cycles_into_draw_action_(0) { | ||||
| 	memset(palette_, 0xf, sizeof(palette_)); | ||||
| 	setup_screen_map(); | ||||
| 	setup_base_address(); | ||||
| @@ -62,33 +61,26 @@ VideoOutput::VideoOutput(uint8_t *memory) : | ||||
|  | ||||
| #pragma mark - CRT getter | ||||
|  | ||||
| std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() | ||||
| { | ||||
| std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() { | ||||
| 	return crt_; | ||||
| } | ||||
|  | ||||
| #pragma mark - Display update methods | ||||
|  | ||||
| void VideoOutput::start_pixel_line() | ||||
| { | ||||
| void VideoOutput::start_pixel_line() { | ||||
| 	current_pixel_line_ = (current_pixel_line_+1)&255; | ||||
| 	if(!current_pixel_line_) | ||||
| 	{ | ||||
| 	if(!current_pixel_line_) { | ||||
| 		start_line_address_ = start_screen_address_; | ||||
| 		current_character_row_ = 0; | ||||
| 		is_blank_line_ = false; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3); | ||||
| 		is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249))); | ||||
|  | ||||
| 		if(!is_blank_line_) | ||||
| 		{ | ||||
| 		if(!is_blank_line_) { | ||||
| 			start_line_address_++; | ||||
|  | ||||
| 			if(current_character_row_ > 7) | ||||
| 			{ | ||||
| 			if(current_character_row_ > 7) { | ||||
| 				start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8; | ||||
| 				current_character_row_ = 0; | ||||
| 			} | ||||
| @@ -99,52 +91,41 @@ void VideoOutput::start_pixel_line() | ||||
| 	initial_output_target_ = current_output_target_ = nullptr; | ||||
| } | ||||
|  | ||||
| void VideoOutput::end_pixel_line() | ||||
| { | ||||
| void VideoOutput::end_pixel_line() { | ||||
| 	if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); | ||||
| 	current_character_row_++; | ||||
| } | ||||
|  | ||||
| void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| { | ||||
| void VideoOutput::output_pixels(unsigned int number_of_cycles) { | ||||
| 	if(!number_of_cycles) return; | ||||
|  | ||||
| 	if(is_blank_line_) | ||||
| 	{ | ||||
| 	if(is_blank_line_) { | ||||
| 		crt_->output_blank(number_of_cycles * crt_cycles_multiplier); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		unsigned int divider = 0; | ||||
| 		switch(screen_mode_) | ||||
| 		{ | ||||
| 	} else { | ||||
| 		unsigned int divider = 1; | ||||
| 		switch(screen_mode_) { | ||||
| 			case 0: case 3: divider = 2; break; | ||||
| 			case 1: case 4: case 6: divider = 4; break; | ||||
| 			case 2: case 5: divider = 8; break; | ||||
| 		} | ||||
|  | ||||
| 		if(!initial_output_target_ || divider != current_output_divider_) | ||||
| 		{ | ||||
| 		if(!initial_output_target_ || divider != current_output_divider_) { | ||||
| 			if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_); | ||||
| 			current_output_divider_ = divider; | ||||
| 			initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_); | ||||
| 		} | ||||
|  | ||||
| #define get_pixel()	\ | ||||
| 				if(current_screen_address_&32768)\ | ||||
| 				{\ | ||||
| 				if(current_screen_address_&32768) {\ | ||||
| 					current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\ | ||||
| 				}\ | ||||
| 				last_pixel_byte_ = ram_[current_screen_address_];\ | ||||
| 				current_screen_address_ = current_screen_address_+8 | ||||
|  | ||||
| 		switch(screen_mode_) | ||||
| 		{ | ||||
| 		switch(screen_mode_) { | ||||
| 			case 0: case 3: | ||||
| 				if(initial_output_target_) | ||||
| 				{ | ||||
| 					while(number_of_cycles--) | ||||
| 					{ | ||||
| 				if(initial_output_target_) { | ||||
| 					while(number_of_cycles--) { | ||||
| 						get_pixel(); | ||||
| 						*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 4; | ||||
| @@ -154,10 +135,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 			break; | ||||
|  | ||||
| 			case 1: | ||||
| 				if(initial_output_target_) | ||||
| 				{ | ||||
| 					while(number_of_cycles--) | ||||
| 					{ | ||||
| 				if(initial_output_target_) { | ||||
| 					while(number_of_cycles--) { | ||||
| 						get_pixel(); | ||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 2; | ||||
| @@ -167,10 +146,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 			break; | ||||
|  | ||||
| 			case 2: | ||||
| 				if(initial_output_target_) | ||||
| 				{ | ||||
| 					while(number_of_cycles--) | ||||
| 					{ | ||||
| 				if(initial_output_target_) { | ||||
| 					while(number_of_cycles--) { | ||||
| 						get_pixel(); | ||||
| 						*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 1; | ||||
| @@ -180,10 +157,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 			break; | ||||
|  | ||||
| 			case 4: case 6: | ||||
| 				if(initial_output_target_) | ||||
| 				{ | ||||
| 					if(current_pixel_column_&1) | ||||
| 					{ | ||||
| 				if(initial_output_target_) { | ||||
| 					if(current_pixel_column_&1) { | ||||
| 						last_pixel_byte_ <<= 4; | ||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 2; | ||||
| @@ -191,8 +166,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 						number_of_cycles--; | ||||
| 						current_pixel_column_++; | ||||
| 					} | ||||
| 					while(number_of_cycles > 1) | ||||
| 					{ | ||||
| 					while(number_of_cycles > 1) { | ||||
| 						get_pixel(); | ||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 2; | ||||
| @@ -204,8 +178,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 						number_of_cycles -= 2; | ||||
| 						current_pixel_column_+=2; | ||||
| 					} | ||||
| 					if(number_of_cycles) | ||||
| 					{ | ||||
| 					if(number_of_cycles) { | ||||
| 						get_pixel(); | ||||
| 						*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 2; | ||||
| @@ -215,10 +188,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 			break; | ||||
|  | ||||
| 			case 5: | ||||
| 				if(initial_output_target_) | ||||
| 				{ | ||||
| 					if(current_pixel_column_&1) | ||||
| 					{ | ||||
| 				if(initial_output_target_) { | ||||
| 					if(current_pixel_column_&1) { | ||||
| 						last_pixel_byte_ <<= 2; | ||||
| 						*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 1; | ||||
| @@ -226,8 +197,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 						number_of_cycles--; | ||||
| 						current_pixel_column_++; | ||||
| 					} | ||||
| 					while(number_of_cycles > 1) | ||||
| 					{ | ||||
| 					while(number_of_cycles > 1) { | ||||
| 						get_pixel(); | ||||
| 						*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 1; | ||||
| @@ -239,8 +209,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 						number_of_cycles -= 2; | ||||
| 						current_pixel_column_+=2; | ||||
| 					} | ||||
| 					if(number_of_cycles) | ||||
| 					{ | ||||
| 					if(number_of_cycles) { | ||||
| 						get_pixel(); | ||||
| 						*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_]; | ||||
| 						current_output_target_ += 1; | ||||
| @@ -254,21 +223,17 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| { | ||||
| void VideoOutput::run_for_cycles(int number_of_cycles) { | ||||
| 	output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame; | ||||
| 	while(number_of_cycles) | ||||
| 	{ | ||||
| 	while(number_of_cycles) { | ||||
| 		int draw_action_length = screen_map_[screen_map_pointer_].length; | ||||
| 		int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_); | ||||
| 		if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action); | ||||
|  | ||||
| 		number_of_cycles -= time_left_in_action; | ||||
| 		cycles_into_draw_action_ += time_left_in_action; | ||||
| 		if(cycles_into_draw_action_ == draw_action_length) | ||||
| 		{ | ||||
| 			switch(screen_map_[screen_map_pointer_].type) | ||||
| 			{ | ||||
| 		if(cycles_into_draw_action_ == draw_action_length) { | ||||
| 			switch(screen_map_[screen_map_pointer_].type) { | ||||
| 				case DrawAction::Sync:			crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier));					break; | ||||
| 				case DrawAction::ColourBurst:	crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier));	break; | ||||
| 				case DrawAction::Blank:			crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier));					break; | ||||
| @@ -283,10 +248,8 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
|  | ||||
| #pragma mark - Register hub | ||||
|  | ||||
| void VideoOutput::set_register(int address, uint8_t value) | ||||
| { | ||||
| 	switch(address & 0xf) | ||||
| 	{ | ||||
| void VideoOutput::set_register(int address, uint8_t value) { | ||||
| 	switch(address & 0xf) { | ||||
| 		case 0x02: | ||||
| 			start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1); | ||||
| 			if(!start_screen_address_) start_screen_address_ |= 0x8000; | ||||
| @@ -295,21 +258,18 @@ void VideoOutput::set_register(int address, uint8_t value) | ||||
| 			start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9); | ||||
| 			if(!start_screen_address_) start_screen_address_ |= 0x8000; | ||||
| 		break; | ||||
| 		case 0x07: | ||||
| 		{ | ||||
| 		case 0x07: { | ||||
| 			// update screen mode | ||||
| 			uint8_t new_screen_mode = (value >> 3)&7; | ||||
| 			if(new_screen_mode == 7) new_screen_mode = 4; | ||||
| 			if(new_screen_mode != screen_mode_) | ||||
| 			{ | ||||
| 			if(new_screen_mode != screen_mode_) { | ||||
| 				screen_mode_ = new_screen_mode; | ||||
| 				setup_base_address(); | ||||
| 			} | ||||
| 		} | ||||
| 		break; | ||||
| 		case 0x08: case 0x09: case 0x0a: case 0x0b: | ||||
| 		case 0x0c: case 0x0d: case 0x0e: case 0x0f: | ||||
| 		{ | ||||
| 		case 0x0c: case 0x0d: case 0x0e: case 0x0f: { | ||||
| 			static const int registers[4][4] = { | ||||
| 				{10, 8, 2, 0}, | ||||
| 				{14, 12, 6, 4}, | ||||
| @@ -318,8 +278,7 @@ void VideoOutput::set_register(int address, uint8_t value) | ||||
| 			}; | ||||
| 			const int index = (address >> 1)&3; | ||||
| 			const uint8_t colour = ~value; | ||||
| 			if(address&1) | ||||
| 			{ | ||||
| 			if(address&1) { | ||||
| 				palette_[registers[index][0]]	= (palette_[registers[index][0]]&3)	| ((colour >> 1)&4); | ||||
| 				palette_[registers[index][1]]	= (palette_[registers[index][1]]&3)	| ((colour >> 0)&4); | ||||
| 				palette_[registers[index][2]]	= (palette_[registers[index][2]]&3)	| ((colour << 1)&4); | ||||
| @@ -327,9 +286,7 @@ void VideoOutput::set_register(int address, uint8_t value) | ||||
|  | ||||
| 				palette_[registers[index][2]]	= (palette_[registers[index][2]]&5)	| ((colour >> 4)&2); | ||||
| 				palette_[registers[index][3]]	= (palette_[registers[index][3]]&5)	| ((colour >> 3)&2); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				palette_[registers[index][0]]	= (palette_[registers[index][0]]&6)	| ((colour >> 7)&1); | ||||
| 				palette_[registers[index][1]]	= (palette_[registers[index][1]]&6)	| ((colour >> 6)&1); | ||||
| 				palette_[registers[index][2]]	= (palette_[registers[index][2]]&6)	| ((colour >> 5)&1); | ||||
| @@ -341,8 +298,7 @@ void VideoOutput::set_register(int address, uint8_t value) | ||||
|  | ||||
| 			// regenerate all palette tables for now | ||||
| #define pack(a, b) (uint8_t)((a << 4) | (b)) | ||||
| 			for(int byte = 0; byte < 256; byte++) | ||||
| 			{ | ||||
| 			for(int byte = 0; byte < 256; byte++) { | ||||
| 				uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte]; | ||||
| 				target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]); | ||||
| 				target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]); | ||||
| @@ -367,10 +323,8 @@ void VideoOutput::set_register(int address, uint8_t value) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VideoOutput::setup_base_address() | ||||
| { | ||||
| 	switch(screen_mode_) | ||||
| 	{ | ||||
| void VideoOutput::setup_base_address() { | ||||
| 	switch(screen_mode_) { | ||||
| 		case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break; | ||||
| 		case 3: screen_mode_base_address_ = 0x4000; break; | ||||
| 		case 4: case 5: screen_mode_base_address_ = 0x5800; break; | ||||
| @@ -380,33 +334,28 @@ void VideoOutput::setup_base_address() | ||||
|  | ||||
| #pragma mark - Interrupts | ||||
|  | ||||
| VideoOutput::Interrupt VideoOutput::get_next_interrupt() | ||||
| { | ||||
| VideoOutput::Interrupt VideoOutput::get_next_interrupt() { | ||||
| 	VideoOutput::Interrupt interrupt; | ||||
|  | ||||
| 	if(output_position_ < real_time_clock_interrupt_1) | ||||
| 	{ | ||||
| 	if(output_position_ < real_time_clock_interrupt_1) { | ||||
| 		interrupt.cycles = real_time_clock_interrupt_1 - output_position_; | ||||
| 		interrupt.interrupt = RealTimeClock; | ||||
| 		return interrupt; | ||||
| 	} | ||||
|  | ||||
| 	if(output_position_ < display_end_interrupt_1) | ||||
| 	{ | ||||
| 	if(output_position_ < display_end_interrupt_1) { | ||||
| 		interrupt.cycles = display_end_interrupt_1 - output_position_; | ||||
| 		interrupt.interrupt = DisplayEnd; | ||||
| 		return interrupt; | ||||
| 	} | ||||
|  | ||||
| 	if(output_position_ < real_time_clock_interrupt_2) | ||||
| 	{ | ||||
| 	if(output_position_ < real_time_clock_interrupt_2) { | ||||
| 		interrupt.cycles = real_time_clock_interrupt_2 - output_position_; | ||||
| 		interrupt.interrupt = RealTimeClock; | ||||
| 		return interrupt; | ||||
| 	} | ||||
|  | ||||
| 	if(output_position_ < display_end_interrupt_2) | ||||
| 	{ | ||||
| 	if(output_position_ < display_end_interrupt_2) { | ||||
| 		interrupt.cycles = display_end_interrupt_2 - output_position_; | ||||
| 		interrupt.interrupt = DisplayEnd; | ||||
| 		return interrupt; | ||||
| @@ -419,34 +368,28 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt() | ||||
|  | ||||
| #pragma mark - RAM timing and access information | ||||
|  | ||||
| unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) | ||||
| { | ||||
| unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) { | ||||
| 	unsigned int result = 0; | ||||
| 	int position = output_position_ + from_time; | ||||
|  | ||||
| 	result += 1 + (position&1); | ||||
| 	if(screen_mode_ < 4) | ||||
| 	{ | ||||
| 	if(screen_mode_ < 4) { | ||||
| 		const int current_column = graphics_column(position + (position&1)); | ||||
| 		int current_line = graphics_line(position); | ||||
| 		if(current_column < 80 && current_line < 256) | ||||
| 		{ | ||||
| 			if(screen_mode_ == 3) | ||||
| 			{ | ||||
| 		if(current_column < 80 && current_line < 256) { | ||||
| 			if(screen_mode_ == 3) { | ||||
| 				int output_position_line = graphics_line(output_position_); | ||||
| 				int implied_row = current_character_row_ + (current_line - output_position_line) % 10; | ||||
| 				if(implied_row < 8) | ||||
| 					result += (unsigned int)(80 - current_column); | ||||
| 			} | ||||
| 			else | ||||
| 				result += (unsigned int)(80 - current_column); | ||||
| 			else result += (unsigned int)(80 - current_column); | ||||
| 		} | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| VideoOutput::Range VideoOutput::get_memory_access_range() | ||||
| { | ||||
| VideoOutput::Range VideoOutput::get_memory_access_range() { | ||||
| 	// This can't be more specific than this without applying a lot more thought because of mixed modes: | ||||
| 	// suppose a program runs half the screen in an 80-column mode then switches to 40 columns. Then the | ||||
| 	// real end address will be at 128*80 + 128*40 after the original base, subject to wrapping that depends | ||||
| @@ -460,8 +403,7 @@ VideoOutput::Range VideoOutput::get_memory_access_range() | ||||
|  | ||||
| #pragma mark - The screen map | ||||
|  | ||||
| void VideoOutput::setup_screen_map() | ||||
| { | ||||
| void VideoOutput::setup_screen_map() { | ||||
| 	/* | ||||
|  | ||||
| 		Odd field:					Even field: | ||||
| @@ -475,15 +417,11 @@ void VideoOutput::setup_screen_map() | ||||
| 		|-B- | ||||
|  | ||||
| 	*/ | ||||
| 	for(int c = 0; c < 2; c++) | ||||
| 	{ | ||||
| 		if(c&1) | ||||
| 		{ | ||||
| 	for(int c = 0; c < 2; c++) { | ||||
| 		if(c&1) { | ||||
| 			screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1); | ||||
| 			screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1); | ||||
| 			screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1); | ||||
| 		} | ||||
| @@ -494,15 +432,13 @@ void VideoOutput::setup_screen_map() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VideoOutput::emplace_blank_line() | ||||
| { | ||||
| void VideoOutput::emplace_blank_line() { | ||||
| 	screen_map_.emplace_back(DrawAction::Sync, 9); | ||||
| 	screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9); | ||||
| 	screen_map_.emplace_back(DrawAction::Blank, 128 - 24); | ||||
| } | ||||
|  | ||||
| void VideoOutput::emplace_pixel_line() | ||||
| { | ||||
| void VideoOutput::emplace_pixel_line() { | ||||
| 	// output format is: | ||||
| 	// 9 cycles: sync | ||||
| 	// ... to 24 cycles: colour burst | ||||
|   | ||||
| @@ -10,18 +10,15 @@ | ||||
|  | ||||
| #include <cstdlib> | ||||
|  | ||||
| void Memory::Fuzz(uint8_t *buffer, size_t size) | ||||
| { | ||||
| void Memory::Fuzz(uint8_t *buffer, size_t size) { | ||||
| 	unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256; | ||||
| 	unsigned int shift = 1, value = 1; | ||||
| 	while(value < divider) | ||||
| 	{ | ||||
| 	while(value < divider) { | ||||
| 		value <<= 1; | ||||
| 		shift++; | ||||
| 	} | ||||
|  | ||||
| 	for(size_t c = 0; c < size; c++) | ||||
| 	{ | ||||
| 		buffer[c] = (uint8_t)(rand() >> shift); | ||||
| 	for(size_t c = 0; c < size; c++) { | ||||
| 		buffer[c] = (uint8_t)(std::rand() >> shift); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -24,116 +24,94 @@ Microdisc::Microdisc() : | ||||
| 		paging_flags_(BASICDisable), | ||||
| 		head_load_request_counter_(-1), | ||||
| 		WD1770(P1793), | ||||
| 	last_control_(0) | ||||
| { | ||||
| 		last_control_(0) { | ||||
| 	set_control_register(last_control_, 0xff); | ||||
| } | ||||
|  | ||||
| void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) | ||||
| { | ||||
| 	if(!drives_[drive]) | ||||
| 	{ | ||||
| void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | ||||
| 	if(!drives_[drive]) { | ||||
| 		drives_[drive].reset(new Storage::Disk::Drive); | ||||
| 		if(drive == selected_drive_) set_drive(drives_[drive]); | ||||
| 	} | ||||
| 	drives_[drive]->set_disk(disk); | ||||
| } | ||||
|  | ||||
| void Microdisc::set_control_register(uint8_t control) | ||||
| { | ||||
| void Microdisc::set_control_register(uint8_t control) { | ||||
| 	uint8_t changes = last_control_ ^ control; | ||||
| 	last_control_ = control; | ||||
| 	set_control_register(control, changes); | ||||
| } | ||||
|  | ||||
| void Microdisc::set_control_register(uint8_t control, uint8_t changes) | ||||
| { | ||||
| void Microdisc::set_control_register(uint8_t control, uint8_t changes) { | ||||
| 	// b2: data separator clock rate select (1 = double)	[TODO] | ||||
|  | ||||
| 	// b65: drive select | ||||
| 	if((changes >> 5)&3) | ||||
| 	{ | ||||
| 	if((changes >> 5)&3) { | ||||
| 		selected_drive_ = (control >> 5)&3; | ||||
| 		set_drive(drives_[selected_drive_]); | ||||
| 	} | ||||
|  | ||||
| 	// b4: side select | ||||
| 	if(changes & 0x10) | ||||
| 	{ | ||||
| 	if(changes & 0x10) { | ||||
| 		unsigned int head = (control & 0x10) ? 1 : 0; | ||||
| 		for(int c = 0; c < 4; c++) | ||||
| 		{ | ||||
| 		for(int c = 0; c < 4; c++) { | ||||
| 			if(drives_[c]) drives_[c]->set_head(head); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// b3: double density select (0 = double) | ||||
| 	if(changes & 0x08) | ||||
| 	{ | ||||
| 	if(changes & 0x08) { | ||||
| 		set_is_double_density(!(control & 0x08)); | ||||
| 	} | ||||
|  | ||||
| 	// b0: IRQ enable | ||||
| 	if(changes & 0x01) | ||||
| 	{ | ||||
| 	if(changes & 0x01) { | ||||
| 		bool had_irq = get_interrupt_request_line(); | ||||
| 		irq_enable_ = !!(control & 0x01); | ||||
| 		bool has_irq = get_interrupt_request_line(); | ||||
| 		if(has_irq != had_irq && delegate_) | ||||
| 		{ | ||||
| 		if(has_irq != had_irq && delegate_) { | ||||
| 			delegate_->wd1770_did_change_output(this); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// b7: EPROM select (0 = select) | ||||
| 	// b1: ROM disable (0 = disable) | ||||
| 	if(changes & 0x82) | ||||
| 	{ | ||||
| 	if(changes & 0x82) { | ||||
| 		paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0); | ||||
| 		if(delegate_) delegate_->microdisc_did_change_paging_flags(this); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool Microdisc::get_interrupt_request_line() | ||||
| { | ||||
| bool Microdisc::get_interrupt_request_line() { | ||||
| 	return irq_enable_ && WD1770::get_interrupt_request_line(); | ||||
| } | ||||
|  | ||||
| uint8_t Microdisc::get_interrupt_request_register() | ||||
| { | ||||
| uint8_t Microdisc::get_interrupt_request_register() { | ||||
| 	return 0x7f | (WD1770::get_interrupt_request_line() ? 0x00 : 0x80); | ||||
| } | ||||
|  | ||||
| uint8_t Microdisc::get_data_request_register() | ||||
| { | ||||
| uint8_t Microdisc::get_data_request_register() { | ||||
| 	return 0x7f | (get_data_request_line() ? 0x00 : 0x80); | ||||
| } | ||||
|  | ||||
| void Microdisc::set_head_load_request(bool head_load) | ||||
| { | ||||
| void Microdisc::set_head_load_request(bool head_load) { | ||||
| 	set_motor_on(head_load); | ||||
| 	if(head_load) | ||||
| 	{ | ||||
| 	if(head_load) { | ||||
| 		head_load_request_counter_ = 0; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		head_load_request_counter_ = head_load_request_counter_target; | ||||
| 		set_head_loaded(head_load); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Microdisc::run_for_cycles(unsigned int number_of_cycles) | ||||
| { | ||||
| 	if(head_load_request_counter_ < head_load_request_counter_target) | ||||
| 	{ | ||||
| void Microdisc::run_for_cycles(unsigned int number_of_cycles) { | ||||
| 	if(head_load_request_counter_ < head_load_request_counter_target) { | ||||
| 		head_load_request_counter_ += number_of_cycles; | ||||
| 		if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true); | ||||
| 	} | ||||
| 	WD::WD1770::run_for_cycles(number_of_cycles); | ||||
| } | ||||
|  | ||||
| bool Microdisc::get_drive_is_ready() | ||||
| { | ||||
| bool Microdisc::get_drive_is_ready() { | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -19,8 +19,7 @@ Machine::Machine() : | ||||
| 		keyboard_(new Keyboard), | ||||
| 		ram_top_(0xbfff), | ||||
| 		paged_rom_(rom_), | ||||
| 	microdisc_is_enabled_(false) | ||||
| { | ||||
| 		microdisc_is_enabled_(false) { | ||||
| 	set_clock_rate(1000000); | ||||
| 	via_.set_interrupt_delegate(this); | ||||
| 	via_.keyboard = keyboard_; | ||||
| @@ -29,43 +28,35 @@ Machine::Machine() : | ||||
| 	Memory::Fuzz(ram_, sizeof(ram_)); | ||||
| } | ||||
|  | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| { | ||||
| 	if(target.tapes.size()) | ||||
| 	{ | ||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { | ||||
| 	if(target.tapes.size()) { | ||||
| 		via_.tape->set_tape(target.tapes.front()); | ||||
| 	} | ||||
|  | ||||
| 	if(target.loadingCommand.length())	// TODO: and automatic loading option enabled | ||||
| 	{ | ||||
| 	if(target.loadingCommand.length()) {	// TODO: and automatic loading option enabled | ||||
| 		set_typer_for_string(target.loadingCommand.c_str()); | ||||
| 	} | ||||
|  | ||||
| 	if(target.oric.has_microdisc) | ||||
| 	{ | ||||
| 	if(target.oric.has_microdisc) { | ||||
| 		microdisc_is_enabled_ = true; | ||||
| 		microdisc_did_change_paging_flags(µdisc_); | ||||
| 		microdisc_.set_delegate(this); | ||||
| 	} | ||||
|  | ||||
| 	int drive_index = 0; | ||||
| 	for(auto disk : target.disks) | ||||
| 	{ | ||||
| 	for(auto disk : target.disks) { | ||||
| 		if(drive_index < 4) microdisc_.set_disk(disk, drive_index); | ||||
| 		drive_index++; | ||||
| 	} | ||||
|  | ||||
| 	if(target.oric.use_atmos_rom) | ||||
| 	{ | ||||
| 	if(target.oric.use_atmos_rom) { | ||||
| 		memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_))); | ||||
|  | ||||
| 		is_using_basic11_ = true; | ||||
| 		tape_get_byte_address_ = 0xe6c9; | ||||
| 		scan_keyboard_address_ = 0xf495; | ||||
| 		tape_speed_address_ = 0x024d; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_))); | ||||
|  | ||||
| 		is_using_basic11_ = false; | ||||
| @@ -75,10 +66,8 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) | ||||
| { | ||||
| 	switch(rom) | ||||
| 	{ | ||||
| void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) { | ||||
| 	switch(rom) { | ||||
| 		case BASIC11:	basic11_rom_ = std::move(data);		break; | ||||
| 		case BASIC10:	basic10_rom_ = std::move(data);		break; | ||||
| 		case Microdisc:	microdisc_rom_ = std::move(data);	break; | ||||
| @@ -89,30 +78,22 @@ void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) | ||||
| { | ||||
| 	if(address > ram_top_) | ||||
| 	{ | ||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 	if(address > ram_top_) { | ||||
| 		if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; | ||||
|  | ||||
| 		// 024D = 0 => fast; otherwise slow | ||||
| 		// E6C9 = read byte: return byte in A | ||||
| 		if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) | ||||
| 		{ | ||||
| 		if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) { | ||||
| 			uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]); | ||||
| 			set_value_of_register(CPU6502::A, next_byte); | ||||
| 			set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero); | ||||
| 			*value = 0x60; // i.e. RTS | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if((address & 0xff00) == 0x0300) | ||||
| 		{ | ||||
| 			if(microdisc_is_enabled_ && address >= 0x0310) | ||||
| 			{ | ||||
| 				switch(address) | ||||
| 				{ | ||||
| 	} else { | ||||
| 		if((address & 0xff00) == 0x0300) { | ||||
| 			if(microdisc_is_enabled_ && address >= 0x0310) { | ||||
| 				switch(address) { | ||||
| 					case 0x0310: case 0x0311: case 0x0312: case 0x0313: | ||||
| 						if(isReadOperation(operation)) *value = microdisc_.get_register(address); | ||||
| 						else microdisc_.set_register(address, *value); | ||||
| @@ -125,32 +106,25 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 						if(isReadOperation(operation)) *value = microdisc_.get_data_request_register(); | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				if(isReadOperation(operation)) *value = via_.get_register(address); | ||||
| 				else via_.set_register(address, *value); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			if(isReadOperation(operation)) | ||||
| 				*value = ram_[address]; | ||||
| 			else | ||||
| 			{ | ||||
| 			else { | ||||
| 				if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; } | ||||
| 				ram_[address] = *value; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) | ||||
| 	{ | ||||
| 	if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) { | ||||
| 		// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't | ||||
| 		// do anything until at least the second, regardless of machine | ||||
| 		if(!keyboard_read_count_) keyboard_read_count_++; | ||||
| 		else if(!typer_->type_next_character()) | ||||
| 		{ | ||||
| 		else if(!typer_->type_next_character()) { | ||||
| 			clear_all_keys(); | ||||
| 			typer_.reset(); | ||||
| 		} | ||||
| @@ -162,45 +136,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| void Machine::synchronise() | ||||
| { | ||||
| void Machine::synchronise() { | ||||
| 	update_video(); | ||||
| 	via_.synchronise(); | ||||
| } | ||||
|  | ||||
| void Machine::update_video() | ||||
| { | ||||
| void Machine::update_video() { | ||||
| 	video_output_->run_for_cycles(cycles_since_video_update_); | ||||
| 	cycles_since_video_update_ = 0; | ||||
| } | ||||
|  | ||||
| void Machine::setup_output(float aspect_ratio) | ||||
| { | ||||
| void Machine::setup_output(float aspect_ratio) { | ||||
| 	via_.ay8910.reset(new GI::AY38910()); | ||||
| 	via_.ay8910->set_clock_rate(1000000); | ||||
| 	video_output_.reset(new VideoOutput(ram_)); | ||||
| 	if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); | ||||
| } | ||||
|  | ||||
| void Machine::close_output() | ||||
| { | ||||
| void Machine::close_output() { | ||||
| 	video_output_.reset(); | ||||
| 	via_.ay8910.reset(); | ||||
| } | ||||
|  | ||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) | ||||
| { | ||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { | ||||
| 	set_interrupt_line(); | ||||
| } | ||||
|  | ||||
| void Machine::set_key_state(uint16_t key, bool isPressed) | ||||
| { | ||||
| 	if(key == KeyNMI) | ||||
| 	{ | ||||
| void Machine::set_key_state(uint16_t key, bool isPressed) { | ||||
| 	if(key == KeyNMI) { | ||||
| 		set_nmi_line(isPressed); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		if(isPressed) | ||||
| 			keyboard_->rows[key >> 8] |= (key & 0xff); | ||||
| 		else | ||||
| @@ -208,39 +173,32 @@ void Machine::set_key_state(uint16_t key, bool isPressed) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::clear_all_keys() | ||||
| { | ||||
| void Machine::clear_all_keys() { | ||||
| 	memset(keyboard_->rows, 0, sizeof(keyboard_->rows)); | ||||
| } | ||||
|  | ||||
| void Machine::set_use_fast_tape_hack(bool activate) | ||||
| { | ||||
| void Machine::set_use_fast_tape_hack(bool activate) { | ||||
| 	use_fast_tape_hack_ = activate; | ||||
| } | ||||
|  | ||||
| void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) | ||||
| { | ||||
| void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) { | ||||
| 	video_output_->set_output_device(output_device); | ||||
| } | ||||
|  | ||||
| void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) | ||||
| { | ||||
| void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) { | ||||
| 	// set CB1 | ||||
| 	via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input()); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() | ||||
| { | ||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() { | ||||
| 	return video_output_->get_crt(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() | ||||
| { | ||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() { | ||||
| 	return via_.ay8910; | ||||
| } | ||||
|  | ||||
| void Machine::run_for_cycles(int number_of_cycles) | ||||
| { | ||||
| void Machine::run_for_cycles(int number_of_cycles) { | ||||
| 	CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); | ||||
| } | ||||
|  | ||||
| @@ -251,57 +209,44 @@ Machine::VIA::VIA() : | ||||
| 		cycles_since_ay_update_(0), | ||||
| 		tape(new TapePlayer) {} | ||||
|  | ||||
| void Machine::VIA::set_control_line_output(Port port, Line line, bool value) | ||||
| { | ||||
| 	if(line) | ||||
| 	{ | ||||
| void Machine::VIA::set_control_line_output(Port port, Line line, bool value) { | ||||
| 	if(line) { | ||||
| 		if(port) ay_bdir_ = value; else ay_bc1_ = value; | ||||
| 		update_ay(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) | ||||
| { | ||||
| 	if(port) | ||||
| 	{ | ||||
| void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) { | ||||
| 	if(port) { | ||||
| 		keyboard->row = value; | ||||
| 		tape->set_motor_control(value & 0x40); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		ay8910->set_data_input(value); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t Machine::VIA::get_port_input(Port port) | ||||
| { | ||||
| 	if(port) | ||||
| 	{ | ||||
| uint8_t Machine::VIA::get_port_input(Port port) { | ||||
| 	if(port) { | ||||
| 		uint8_t column = ay8910->get_port_output(false) ^ 0xff; | ||||
| 		return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		return ay8910->get_data_output(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::VIA::synchronise() | ||||
| { | ||||
| void Machine::VIA::synchronise() { | ||||
| 	ay8910->run_for_cycles(cycles_since_ay_update_); | ||||
| 	ay8910->flush(); | ||||
| 	cycles_since_ay_update_ = 0; | ||||
| } | ||||
|  | ||||
| void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) | ||||
| { | ||||
| void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) { | ||||
| 	cycles_since_ay_update_ += number_of_cycles; | ||||
| 	MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles); | ||||
| 	tape->run_for_cycles((int)number_of_cycles); | ||||
| } | ||||
|  | ||||
| void Machine::VIA::update_ay() | ||||
| { | ||||
| void Machine::VIA::update_ay() { | ||||
| 	ay8910->run_for_cycles(cycles_since_ay_update_); | ||||
| 	cycles_since_ay_update_ = 0; | ||||
| 	ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); | ||||
| @@ -310,45 +255,34 @@ void Machine::VIA::update_ay() | ||||
| #pragma mark - TapePlayer | ||||
|  | ||||
| Machine::TapePlayer::TapePlayer() : | ||||
| 	Storage::Tape::BinaryTapePlayer(1000000) | ||||
| {} | ||||
| 		Storage::Tape::BinaryTapePlayer(1000000) {} | ||||
|  | ||||
| uint8_t Machine::TapePlayer::get_next_byte(bool fast) | ||||
| { | ||||
| uint8_t Machine::TapePlayer::get_next_byte(bool fast) { | ||||
| 	return (uint8_t)parser_.get_next_byte(get_tape(), fast); | ||||
| } | ||||
|  | ||||
| #pragma mark - Microdisc | ||||
|  | ||||
| void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) | ||||
| { | ||||
| void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) { | ||||
| 	int flags = microdisc->get_paging_flags(); | ||||
| 	if(!(flags&Microdisc::PagingFlags::BASICDisable)) | ||||
| 	{ | ||||
| 	if(!(flags&Microdisc::PagingFlags::BASICDisable)) { | ||||
| 		ram_top_ = 0xbfff; | ||||
| 		paged_rom_ = rom_; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if(flags&Microdisc::PagingFlags::MicrodscDisable) | ||||
| 		{ | ||||
| 	} else { | ||||
| 		if(flags&Microdisc::PagingFlags::MicrodscDisable) { | ||||
| 			ram_top_ = 0xffff; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			ram_top_ = 0xdfff; | ||||
| 			paged_rom_ = microdisc_rom_.data(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) | ||||
| { | ||||
| void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) { | ||||
| 	set_interrupt_line(); | ||||
| } | ||||
|  | ||||
| void Machine::set_interrupt_line() | ||||
| { | ||||
| void Machine::set_interrupt_line() { | ||||
| 	set_irq_line( | ||||
| 		via_.get_interrupt_line() || | ||||
| 		(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line())); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| #include "Oric.hpp" | ||||
|  | ||||
| uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) | ||||
| { | ||||
| uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) { | ||||
| #define KEYS(...)	{__VA_ARGS__, TerminateSequence} | ||||
| #define SHIFT(...)	{KeyLeftShift, __VA_ARGS__, TerminateSequence} | ||||
| #define X			{NotMapped} | ||||
|   | ||||
| @@ -26,8 +26,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : | ||||
| 		character_set_base_address_(0xb400), | ||||
| 		v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition), | ||||
| 		counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false), | ||||
| 	crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) | ||||
| { | ||||
| 		crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) { | ||||
| 	crt_->set_rgb_sampling_function( | ||||
| 		"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" | ||||
| 		"{" | ||||
| @@ -48,16 +47,13 @@ VideoOutput::VideoOutput(uint8_t *memory) : | ||||
| 	crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); | ||||
| } | ||||
|  | ||||
| void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) | ||||
| { | ||||
| void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) { | ||||
| 	output_device_ = output_device; | ||||
| 	crt_->set_output_device(output_device); | ||||
| } | ||||
|  | ||||
| void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) | ||||
| { | ||||
| 	for(size_t c = 0; c < 8; c++) | ||||
| 	{ | ||||
| void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) { | ||||
| 	for(size_t c = 0; c < 8; c++) { | ||||
| 		size_t index = (c << 2); | ||||
| 		uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]); | ||||
| 		rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0); | ||||
| @@ -66,52 +62,42 @@ void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) | ||||
|  | ||||
| 	// check for big endianness and byte swap if required | ||||
| 	uint16_t test_value = 0x0001; | ||||
| 	if(*(uint8_t *)&test_value != 0x01) | ||||
| 	{ | ||||
| 		for(size_t c = 0; c < 8; c++) | ||||
| 		{ | ||||
| 	if(*(uint8_t *)&test_value != 0x01) { | ||||
| 		for(size_t c = 0; c < 8; c++) { | ||||
| 			colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8)); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() | ||||
| { | ||||
| std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() { | ||||
| 	return crt_; | ||||
| } | ||||
|  | ||||
| void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| { | ||||
| void VideoOutput::run_for_cycles(int number_of_cycles) { | ||||
| 	// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst | ||||
| 	// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync | ||||
|  | ||||
| #define clamp(action)	\ | ||||
| 	if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles; | ||||
|  | ||||
| 	while(number_of_cycles) | ||||
| 	{ | ||||
| 	while(number_of_cycles) { | ||||
| 		int h_counter = counter_ & 63; | ||||
| 		int cycles_run_for = 0; | ||||
|  | ||||
| 		if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) | ||||
| 		{ | ||||
| 		if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) { | ||||
| 			// this is a sync line | ||||
| 			cycles_run_for = v_sync_end_position_ - counter_; | ||||
| 			clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6)); | ||||
| 		} | ||||
| 		else if(counter_ < 224*64 && h_counter < 40) | ||||
| 		{ | ||||
| 		} else if(counter_ < 224*64 && h_counter < 40) { | ||||
| 			// this is a pixel line | ||||
| 			if(!h_counter) | ||||
| 			{ | ||||
| 			if(!h_counter) { | ||||
| 				ink_ = 0x7; | ||||
| 				paper_ = 0x0; | ||||
| 				use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false; | ||||
| 				set_character_set_base_address(); | ||||
| 				pixel_target_ = (uint16_t *)crt_->allocate_write_area(240); | ||||
|  | ||||
| 				if(!counter_) | ||||
| 				{ | ||||
| 				if(!counter_) { | ||||
| 					frame_counter_++; | ||||
|  | ||||
| 					v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition; | ||||
| @@ -126,16 +112,12 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| 			int character_base_address = 0xbb80 + (counter_ >> 9) * 40; | ||||
| 			uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff; | ||||
|  | ||||
| 			while(columns--) | ||||
| 			{ | ||||
| 			while(columns--) { | ||||
| 				uint8_t pixels, control_byte; | ||||
|  | ||||
| 				if(is_graphics_mode_ && counter_ < 200*64) | ||||
| 				{ | ||||
| 				if(is_graphics_mode_ && counter_ < 200*64) { | ||||
| 					control_byte = pixels = ram_[pixel_base_address + h_counter]; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 				} else { | ||||
| 					int address = character_base_address + h_counter; | ||||
| 					control_byte = ram_[address]; | ||||
| 					int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7); | ||||
| @@ -145,18 +127,13 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| 				uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0; | ||||
| 				pixels &= blink_mask; | ||||
|  | ||||
| 				if(control_byte & 0x60) | ||||
| 				{ | ||||
| 					if(pixel_target_) | ||||
| 					{ | ||||
| 				if(control_byte & 0x60) { | ||||
| 					if(pixel_target_) { | ||||
| 						uint16_t colours[2]; | ||||
| 						if(output_device_ == Outputs::CRT::Monitor) | ||||
| 						{ | ||||
| 						if(output_device_ == Outputs::CRT::Monitor) { | ||||
| 							colours[0] = (uint8_t)(paper_ ^ inverse_mask); | ||||
| 							colours[1] = (uint8_t)(ink_ ^ inverse_mask); | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 						} else { | ||||
| 							colours[0] = colour_forms_[paper_ ^ inverse_mask]; | ||||
| 							colours[1] = colour_forms_[ink_ ^ inverse_mask]; | ||||
| 						} | ||||
| @@ -167,11 +144,8 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| 						pixel_target_[4] = colours[(pixels >> 1)&1]; | ||||
| 						pixel_target_[5] = colours[(pixels >> 0)&1]; | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					switch(control_byte & 0x1f) | ||||
| 					{ | ||||
| 				} else { | ||||
| 					switch(control_byte & 0x1f) { | ||||
| 						case 0x00:		ink_ = 0x0;	break; | ||||
| 						case 0x01:		ink_ = 0x4;	break; | ||||
| 						case 0x02:		ink_ = 0x2;	break; | ||||
| @@ -206,8 +180,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
|  | ||||
| 						default: break; | ||||
| 					} | ||||
| 					if(pixel_target_) | ||||
| 					{ | ||||
| 					if(pixel_target_) { | ||||
| 						pixel_target_[0] = pixel_target_[1] = | ||||
| 						pixel_target_[2] = pixel_target_[3] = | ||||
| 						pixel_target_[4] = pixel_target_[5] = | ||||
| @@ -218,34 +191,24 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| 				h_counter++; | ||||
| 			} | ||||
|  | ||||
| 			if(h_counter == 40) | ||||
| 			{ | ||||
| 			if(h_counter == 40) { | ||||
| 				crt_->output_data(40 * 6, 1); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 		} else { | ||||
| 			// this is a blank line (or the equivalent part of a pixel line) | ||||
| 			if(h_counter < 48) | ||||
| 			{ | ||||
| 			if(h_counter < 48) { | ||||
| 				cycles_run_for = 48 - h_counter; | ||||
| 				clamp( | ||||
| 					int period = (counter_ < 224*64) ? 8 : 48; | ||||
| 					crt_->output_blank((unsigned int)period * 6); | ||||
| 				); | ||||
| 			} | ||||
| 			else if(h_counter < 54) | ||||
| 			{ | ||||
| 			} else if(h_counter < 54) { | ||||
| 				cycles_run_for = 54 - h_counter; | ||||
| 				clamp(crt_->output_sync(6 * 6)); | ||||
| 			} | ||||
| 			else if(h_counter < 56) | ||||
| 			{ | ||||
| 			} else if(h_counter < 56) { | ||||
| 				cycles_run_for = 56 - h_counter; | ||||
| 				clamp(crt_->output_default_colour_burst(2 * 6)); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				cycles_run_for = 64 - h_counter; | ||||
| 				clamp(crt_->output_blank(8 * 6)); | ||||
| 			} | ||||
| @@ -256,8 +219,7 @@ void VideoOutput::run_for_cycles(int number_of_cycles) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VideoOutput::set_character_set_base_address() | ||||
| { | ||||
| void VideoOutput::set_character_set_base_address() { | ||||
| 	if(is_graphics_mode_) character_set_base_address_ = use_alternative_character_set_ ? 0x9c00 : 0x9800; | ||||
| 	else character_set_base_address_ = use_alternative_character_set_ ? 0xb800 : 0xb400; | ||||
| } | ||||
|   | ||||
| @@ -12,76 +12,61 @@ | ||||
| using namespace Utility; | ||||
|  | ||||
| Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) : | ||||
| 	counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) | ||||
| { | ||||
| 		counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) { | ||||
| 	size_t string_size = strlen(string) + 3; | ||||
| 	string_ = (char *)malloc(string_size); | ||||
| 	snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString); | ||||
| } | ||||
|  | ||||
| void Typer::update(int duration) | ||||
| { | ||||
| 	if(string_) | ||||
| 	{ | ||||
| 		if(counter_ < 0 && counter_ + duration >= 0) | ||||
| 		{ | ||||
| 			if(!type_next_character()) | ||||
| 			{ | ||||
| void Typer::update(int duration) { | ||||
| 	if(string_) { | ||||
| 		if(counter_ < 0 && counter_ + duration >= 0) { | ||||
| 			if(!type_next_character()) { | ||||
| 				delegate_->typer_reset(this); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		counter_ += duration; | ||||
| 		while(string_ && counter_ > frequency_) | ||||
| 		{ | ||||
| 		while(string_ && counter_ > frequency_) { | ||||
| 			counter_ -= frequency_; | ||||
| 			if(!type_next_character()) | ||||
| 			{ | ||||
| 			if(!type_next_character()) { | ||||
| 				delegate_->typer_reset(this); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool Typer::type_next_character() | ||||
| { | ||||
| bool Typer::type_next_character() { | ||||
| 	if(string_ == nullptr) return false; | ||||
|  | ||||
| 	if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) | ||||
| 	{ | ||||
| 	if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) { | ||||
| 		phase_ = 0; | ||||
| 		if(!string_[string_pointer_]) | ||||
| 		{ | ||||
| 		if(!string_[string_pointer_]) { | ||||
| 			free(string_); | ||||
| 			string_ = nullptr; | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		string_pointer_++; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		phase_++; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| Typer::~Typer() | ||||
| { | ||||
| Typer::~Typer() { | ||||
| 	free(string_); | ||||
| } | ||||
|  | ||||
| #pragma mark - Delegate | ||||
|  | ||||
| bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) | ||||
| { | ||||
| bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) { | ||||
| 	uint16_t *sequence = sequence_for_character(typer, character); | ||||
| 	if(!sequence) return true; | ||||
|  | ||||
| 	if(!phase) clear_all_keys(); | ||||
| 	else | ||||
| 	{ | ||||
| 	else { | ||||
| 		set_key_state(sequence[phase - 1], true); | ||||
| 		return sequence[phase] == Typer::Delegate::EndSequence; | ||||
| 	} | ||||
| @@ -89,7 +74,6 @@ bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char chara | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) | ||||
| { | ||||
| uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) { | ||||
| 	return nullptr; | ||||
| } | ||||
|   | ||||
| @@ -45,13 +45,11 @@ class Typer { | ||||
|  | ||||
| class TypeRecipient: public Typer::Delegate { | ||||
| 	public: | ||||
| 		void set_typer_for_string(const char *string) | ||||
| 		{ | ||||
| 		void set_typer_for_string(const char *string) { | ||||
| 			typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this)); | ||||
| 		} | ||||
|  | ||||
| 		void typer_reset(Typer *typer) | ||||
| 		{ | ||||
| 		void typer_reset(Typer *typer) { | ||||
| 			clear_all_keys(); | ||||
| 			typer_.reset(); | ||||
| 		} | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| // | ||||
| //  CRC.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 18/09/2016. | ||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "CRC.hpp" | ||||
|  | ||||
| using namespace NumberTheory; | ||||
| @@ -16,13 +16,10 @@ namespace NumberTheory { | ||||
| class CRC16 { | ||||
| 	public: | ||||
| 		CRC16(uint16_t polynomial, uint16_t reset_value) : | ||||
| 			reset_value_(reset_value), value_(reset_value) | ||||
| 		{ | ||||
| 			for(int c = 0; c < 256; c++) | ||||
| 			{ | ||||
| 				reset_value_(reset_value), value_(reset_value) { | ||||
| 			for(int c = 0; c < 256; c++) { | ||||
| 				uint16_t shift_value = (uint16_t)(c << 8); | ||||
| 				for(int b = 0; b < 8; b++) | ||||
| 				{ | ||||
| 				for(int b = 0; b < 8; b++) { | ||||
| 					uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000; | ||||
| 					shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or; | ||||
| 				} | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
| 		4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; }; | ||||
| 		4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; }; | ||||
| 		4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; }; | ||||
| 		4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; | ||||
| 		4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; }; | ||||
| 		4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; }; | ||||
| 		4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; }; | ||||
| 		4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; }; | ||||
| @@ -29,8 +29,10 @@ | ||||
| 		4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; | ||||
| 		4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; }; | ||||
| 		4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; | ||||
| 		4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AF8681E513FC20027EE29 /* TIATests.mm */; }; | ||||
| 		4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; | ||||
| 		4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; | ||||
| 		4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; }; | ||||
| 		4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; | ||||
| 		4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; | ||||
| 		4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; }; | ||||
| @@ -67,6 +69,9 @@ | ||||
| 		4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; | ||||
| 		4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; }; | ||||
| 		4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; | ||||
| 		4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; | ||||
| 		4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; }; | ||||
| 		4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; }; | ||||
| 		4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; }; | ||||
| 		4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; | ||||
| 		4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; }; | ||||
| @@ -80,6 +85,8 @@ | ||||
| 		4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; }; | ||||
| 		4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; }; | ||||
| 		4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */; }; | ||||
| 		4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; }; | ||||
| 		4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; }; | ||||
| 		4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; | ||||
| 		4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; }; | ||||
| 		4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; }; | ||||
| @@ -391,6 +398,7 @@ | ||||
| 		4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; | ||||
| 		4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; | ||||
| 		4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; | ||||
| 		4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; | ||||
| 		4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; | ||||
| 		4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; }; | ||||
| 		4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; }; | ||||
| @@ -400,10 +408,8 @@ | ||||
| 		4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; | ||||
| 		4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; }; | ||||
| 		4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; }; | ||||
| 		4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; }; | ||||
| 		4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; }; | ||||
| 		4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; }; | ||||
| 		4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; }; | ||||
| /* End PBXBuildFile section */ | ||||
|  | ||||
| /* Begin PBXContainerItemProxy section */ | ||||
| @@ -447,7 +453,7 @@ | ||||
| 		4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; }; | ||||
| 		4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; }; | ||||
| 		4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; }; | ||||
| 		4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; }; | ||||
| 		4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; }; | ||||
| 		4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; }; | ||||
| 		4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; }; | ||||
| 		4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; }; | ||||
| @@ -467,10 +473,12 @@ | ||||
| 		4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.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>"; }; | ||||
| 		4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; }; | ||||
| 		4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; }; | ||||
| 		4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; }; | ||||
| 		4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; }; | ||||
| 		4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; }; | ||||
| 		4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; }; | ||||
| 		4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; }; | ||||
| 		4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; }; | ||||
| @@ -538,6 +546,9 @@ | ||||
| 		4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; }; | ||||
| 		4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; }; | ||||
| 		4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; }; | ||||
| 		4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; }; | ||||
| 		4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; }; | ||||
| 		4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; }; | ||||
| 		4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; }; | ||||
| 		4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; }; | ||||
| 		4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; }; | ||||
| @@ -558,6 +569,8 @@ | ||||
| 		4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; }; | ||||
| 		4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+DataResource.m"; sourceTree = "<group>"; }; | ||||
| 		4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronOptionsPanel.swift; sourceTree = "<group>"; }; | ||||
| 		4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = "<group>"; }; | ||||
| 		4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; }; | ||||
| 		4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; }; | ||||
| 		4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = "<group>"; }; | ||||
| 		4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; }; | ||||
| @@ -913,6 +926,8 @@ | ||||
| 		4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; }; | ||||
| 		4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; }; | ||||
| 		4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; }; | ||||
| 		4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; }; | ||||
| 		4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; }; | ||||
| 		4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; }; | ||||
| @@ -921,6 +936,20 @@ | ||||
| 		4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = "<group>"; }; | ||||
| 		4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeActivisionStack.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari16k.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari32k.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari8k.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCBSRAMPlus.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCommaVid.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMegaBoy.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMNetwork.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeParkerBros.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeTigervision.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeUnpaged.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CartridgePitfall2.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; }; | ||||
| 		4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; }; | ||||
| 		4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; }; | ||||
| @@ -932,13 +961,11 @@ | ||||
| 		4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; }; | ||||
| 		4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; }; | ||||
| 		4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; }; | ||||
| 		4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; }; | ||||
| 		4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; }; | ||||
| 		4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; }; | ||||
| 		4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; }; | ||||
| 		4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; }; | ||||
| 		4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; }; | ||||
| 		4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; }; | ||||
| 		4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; }; | ||||
| /* End PBXFileReference section */ | ||||
|  | ||||
| @@ -995,6 +1022,7 @@ | ||||
| 		4B1414631B588A1100E04248 /* Test Binaries */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4B9252CD1E74D28200B76AF1 /* Atari ROMs */, | ||||
| 				4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, | ||||
| 				4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */, | ||||
| 				4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, | ||||
| @@ -1011,6 +1039,18 @@ | ||||
| 			path = 6532; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		4B1EDB411E39A0AC009D6819 /* Icons */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4B2C45411E3C3896002A2389 /* cartridge.png */, | ||||
| 				4B79E4411E3AF38600141F11 /* cassette.png */, | ||||
| 				4B79E4421E3AF38600141F11 /* floppy35.png */, | ||||
| 				4B79E4431E3AF38600141F11 /* floppy525.png */, | ||||
| 				4B1EDB431E39A0AC009D6819 /* chip.png */, | ||||
| 			); | ||||
| 			path = Icons; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		4B2409591C45DF85004DA684 /* SignalProcessing */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| @@ -1072,10 +1112,14 @@ | ||||
| 			children = ( | ||||
| 				4B2E2D971C3A06EC00138695 /* Atari2600.cpp */, | ||||
| 				4BEA52641DF3472B007E74F2 /* Speaker.cpp */, | ||||
| 				4BE7C9161E3D397100A5496D /* TIA.cpp */, | ||||
| 				4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */, | ||||
| 				4B2E2D981C3A06EC00138695 /* Atari2600.hpp */, | ||||
| 				4BEA52651DF3472B007E74F2 /* Speaker.hpp */, | ||||
| 				4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */, | ||||
| 				4BEA52671DF34909007E74F2 /* PIA.hpp */, | ||||
| 				4BEA52651DF3472B007E74F2 /* Speaker.hpp */, | ||||
| 				4BE7C9171E3D397100A5496D /* TIA.hpp */, | ||||
| 				4BEAC0801E7E0DF800EE56B2 /* Cartridges */, | ||||
| 			); | ||||
| 			path = Atari2600; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -1103,7 +1147,6 @@ | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4B0CCC411C62D0B3001CAC5F /* CRT */, | ||||
| 				4B2409531C45AB05004DA684 /* Speaker.cpp */, | ||||
| 				4B2409541C45AB05004DA684 /* Speaker.hpp */, | ||||
| 			); | ||||
| 			name = Outputs; | ||||
| @@ -1619,7 +1662,6 @@ | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4BB697C61D4B558F00248BDF /* Factors.hpp */, | ||||
| 				4BF8295E1D8F3C87001BAE39 /* CRC.cpp */, | ||||
| 				4BF8295F1D8F3C87001BAE39 /* CRC.hpp */, | ||||
| 			); | ||||
| 			name = NumberTheory; | ||||
| @@ -1693,10 +1735,12 @@ | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, | ||||
| 				4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, | ||||
| 				4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, | ||||
| 				4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */, | ||||
| 				4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, | ||||
| 				4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, | ||||
| 				4B2AF8681E513FC20027EE29 /* TIATests.mm */, | ||||
| 				4B1D08051E0F7A1100763741 /* TimeTests.mm */, | ||||
| 				4BB73EB81B587A5100552FC2 /* Info.plist */, | ||||
| 				4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */, | ||||
| @@ -1868,7 +1912,6 @@ | ||||
| 			children = ( | ||||
| 				4BF829641D8F732B001BAE39 /* Disk.cpp */, | ||||
| 				4BF829651D8F732B001BAE39 /* Disk.hpp */, | ||||
| 				4BF829671D8F7361001BAE39 /* File.cpp */, | ||||
| 				4BF829681D8F7361001BAE39 /* File.hpp */, | ||||
| 				4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */, | ||||
| 				4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */, | ||||
| @@ -1899,11 +1942,32 @@ | ||||
| 		4BE5F85A1C3E1C2500C43F01 /* Resources */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4B1EDB411E39A0AC009D6819 /* Icons */, | ||||
| 				4BC9DF441D044FCA00F44158 /* ROMImages */, | ||||
| 			); | ||||
| 			path = Resources; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		4BEAC0801E7E0DF800EE56B2 /* Cartridges */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */, | ||||
| 				4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */, | ||||
| 				4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */, | ||||
| 				4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */, | ||||
| 				4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */, | ||||
| 				4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */, | ||||
| 				4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */, | ||||
| 				4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */, | ||||
| 				4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */, | ||||
| 				4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */, | ||||
| 				4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */, | ||||
| 				4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */, | ||||
| 				4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */, | ||||
| 			); | ||||
| 			path = Cartridges; | ||||
| 			sourceTree = "<group>"; | ||||
| 		}; | ||||
| 		4BEE0A691D72496600532C7B /* Cartridge */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| @@ -2052,13 +2116,18 @@ | ||||
| 			isa = PBXResourcesBuildPhase; | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				4B2C45421E3C3896002A2389 /* cartridge.png in Resources */, | ||||
| 				4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, | ||||
| 				4B79E4451E3AF38600141F11 /* floppy35.png in Resources */, | ||||
| 				4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, | ||||
| 				4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, | ||||
| 				4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, | ||||
| 				4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, | ||||
| 				4B79E4441E3AF38600141F11 /* cassette.png in Resources */, | ||||
| 				4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */, | ||||
| 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | ||||
| 				4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */, | ||||
| 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | ||||
| 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| @@ -2189,6 +2258,7 @@ | ||||
| 				4BB299661B587D8400A49093 /* inszx in Resources */, | ||||
| 				4BB299101B587D8400A49093 /* asoz in Resources */, | ||||
| 				4BB2998B1B587D8400A49093 /* lseiy in Resources */, | ||||
| 				4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */, | ||||
| 				4BB2997D1B587D8400A49093 /* ldxay in Resources */, | ||||
| 				4BB299D71B587D8400A49093 /* staax in Resources */, | ||||
| 				4BB2990C1B587D8400A49093 /* asoax in Resources */, | ||||
| @@ -2366,7 +2436,6 @@ | ||||
| 				4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */, | ||||
| 				4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, | ||||
| 				4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, | ||||
| 				4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */, | ||||
| 				4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, | ||||
| 				4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, | ||||
| 				4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, | ||||
| @@ -2389,7 +2458,6 @@ | ||||
| 				4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, | ||||
| 				4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, | ||||
| 				4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, | ||||
| 				4BF829691D8F7361001BAE39 /* File.cpp in Sources */, | ||||
| 				4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */, | ||||
| 				4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */, | ||||
| 				4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, | ||||
| @@ -2399,13 +2467,13 @@ | ||||
| 				4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, | ||||
| 				4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, | ||||
| 				4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, | ||||
| 				4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, | ||||
| 				4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */, | ||||
| 				4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, | ||||
| 				4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, | ||||
| 				4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, | ||||
| 				4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, | ||||
| 				4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */, | ||||
| 				4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */, | ||||
| 				4B1E85751D170228001EF87D /* Typer.cpp in Sources */, | ||||
| 				4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */, | ||||
| 				4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, | ||||
| @@ -2459,7 +2527,9 @@ | ||||
| 				4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, | ||||
| 				4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */, | ||||
| 				4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */, | ||||
| 				4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */, | ||||
| 				4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */, | ||||
| 				4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */, | ||||
| 				4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, | ||||
| 				4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */, | ||||
| 				4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, | ||||
|   | ||||
| @@ -26,7 +26,8 @@ | ||||
|       buildConfiguration = "Debug" | ||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||
|       shouldUseLaunchSchemeArgsEnv = "YES"> | ||||
|       shouldUseLaunchSchemeArgsEnv = "YES" | ||||
|       codeCoverageEnabled = "YES"> | ||||
|       <Testables> | ||||
|          <TestableReference | ||||
|             skipped = "NO"> | ||||
|   | ||||
| @@ -12,32 +12,48 @@ | ||||
| #define AudioQueueBufferMaxLength		8192 | ||||
| #define NumberOfStoredAudioQueueBuffer	16 | ||||
|  | ||||
| static NSLock *CSAudioQueueDeallocLock; | ||||
|  | ||||
| /*! | ||||
| 	Holds a weak reference to a CSAudioQueue. Used to work around an apparent AudioQueue bug. | ||||
| 	See -[CSAudioQueue dealloc]. | ||||
| */ | ||||
| @interface CSWeakAudioQueuePointer: NSObject | ||||
| @property(nonatomic, weak) CSAudioQueue *queue; | ||||
| @end | ||||
|  | ||||
| @implementation CSWeakAudioQueuePointer | ||||
| @end | ||||
|  | ||||
| @implementation CSAudioQueue | ||||
| { | ||||
| 	AudioQueueRef _audioQueue; | ||||
|  | ||||
| 	AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer]; | ||||
| 	NSLock *_storedBuffersLock; | ||||
| 	CSWeakAudioQueuePointer *_weakPointer; | ||||
| } | ||||
|  | ||||
| #pragma mark - AudioQueue callbacks | ||||
|  | ||||
| - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer | ||||
| { | ||||
| 	[self.delegate audioQueueIsRunningDry:self]; | ||||
|  | ||||
| 	@synchronized(self) | ||||
| /*! | ||||
| 	@returns @c YES if the queue is running dry; @c NO otherwise. | ||||
| */ | ||||
| - (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer | ||||
| { | ||||
| 	[_storedBuffersLock lock]; | ||||
| 	for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) | ||||
| 	{ | ||||
| 		if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity) | ||||
| 		{ | ||||
| 			if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]); | ||||
| 			_storedBuffers[c] = buffer; | ||||
| 				return; | ||||
| 			} | ||||
| 			[_storedBuffersLock unlock]; | ||||
| 			return YES; | ||||
| 		} | ||||
| 	} | ||||
| 	[_storedBuffersLock unlock]; | ||||
| 	AudioQueueFreeBuffer(_audioQueue, buffer); | ||||
| 	return YES; | ||||
| } | ||||
|  | ||||
| static void audioOutputCallback( | ||||
| @@ -45,7 +61,17 @@ static void audioOutputCallback( | ||||
| 	AudioQueueRef inAQ, | ||||
| 	AudioQueueBufferRef inBuffer) | ||||
| { | ||||
| 	[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; | ||||
| 	// Pull the delegate call for audio queue running dry outside of the locked region, to allow non-deadlocking | ||||
| 	// lifecycle -dealloc events to result from it. | ||||
| 	if([CSAudioQueueDeallocLock tryLock]) | ||||
| 	{ | ||||
| 		CSAudioQueue *queue = ((__bridge CSWeakAudioQueuePointer *)inUserData).queue; | ||||
| 		BOOL isRunningDry = NO; | ||||
| 		isRunningDry = [queue audioQueue:inAQ didCallbackWithBuffer:inBuffer]; | ||||
| 		id<CSAudioQueueDelegate> delegate = queue.delegate; | ||||
| 		[CSAudioQueueDeallocLock unlock]; | ||||
| 		if(isRunningDry) [delegate audioQueueIsRunningDry:queue]; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #pragma mark - Standard object lifecycle | ||||
| @@ -56,6 +82,12 @@ static void audioOutputCallback( | ||||
|  | ||||
| 	if(self) | ||||
| 	{ | ||||
| 		if(!CSAudioQueueDeallocLock) | ||||
| 		{ | ||||
| 			CSAudioQueueDeallocLock = [[NSLock alloc] init]; | ||||
| 		} | ||||
| 		_storedBuffersLock = [[NSLock alloc] init]; | ||||
|  | ||||
| 		_samplingRate = samplingRate; | ||||
|  | ||||
| 		// determine preferred buffer sizes | ||||
| @@ -80,11 +112,13 @@ static void audioOutputCallback( | ||||
|  | ||||
| 		outputDescription.mReserved = 0; | ||||
|  | ||||
| 		// create an audio output queue along those lines | ||||
| 		// create an audio output queue along those lines; see -dealloc re: the CSWeakAudioQueuePointer | ||||
| 		_weakPointer = [[CSWeakAudioQueuePointer alloc] init]; | ||||
| 		_weakPointer.queue = self; | ||||
| 		if(!AudioQueueNewOutput( | ||||
| 				&outputDescription, | ||||
| 				audioOutputCallback, | ||||
| 				(__bridge void *)(self), | ||||
| 				(__bridge void *)(_weakPointer), | ||||
| 				NULL, | ||||
| 				kCFRunLoopCommonModes, | ||||
| 				0, | ||||
| @@ -104,7 +138,31 @@ static void audioOutputCallback( | ||||
|  | ||||
| - (void)dealloc | ||||
| { | ||||
| 	if(_audioQueue) AudioQueueDispose(_audioQueue, NO); | ||||
| 	[CSAudioQueueDeallocLock lock]; | ||||
| 	if(_audioQueue) | ||||
| 	{ | ||||
| 		AudioQueueDispose(_audioQueue, true); | ||||
| 		_audioQueue = NULL; | ||||
| 	} | ||||
| 	[CSAudioQueueDeallocLock unlock]; | ||||
|  | ||||
| 	// Yuck. Horrid hack happening here. At least under macOS v10.12, I am frequently seeing calls to | ||||
| 	// my registered audio callback (audioOutputCallback in this case) that occur **after** the call | ||||
| 	// to AudioQueueDispose above, even though the second parameter there asks for a synchronous shutdown. | ||||
| 	// So this appears to be a bug on Apple's side. | ||||
| 	// | ||||
| 	// Since the audio callback receives a void * pointer that identifies the class it should branch into, | ||||
| 	// it's therefore unsafe to pass 'self'. Instead I pass a CSWeakAudioQueuePointer which points to the actual | ||||
| 	// queue. The lifetime of that class is the lifetime of this instance plus 1 second, as effected by the | ||||
| 	// artificial dispatch_after below — it serves only to keep pointerSaviour alive for an extra second. | ||||
| 	// | ||||
| 	// Why a second? That's definitely quite a lot longer than any amount of audio that may be queued. So | ||||
| 	// probably safe. As and where Apple's audio queue works properly, CSAudioQueueDeallocLock should provide | ||||
| 	// absolute safety; elsewhere the CSWeakAudioQueuePointer provides probabilistic. | ||||
| 	CSWeakAudioQueuePointer *pointerSaviour = _weakPointer; | ||||
| 	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | ||||
| 		[pointerSaviour hash]; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| #pragma mark - Audio enqueuer | ||||
| @@ -113,8 +171,7 @@ static void audioOutputCallback( | ||||
| { | ||||
| 	size_t bufferBytes = lengthInSamples * sizeof(int16_t); | ||||
|  | ||||
| 	@synchronized(self) | ||||
| 	{ | ||||
| 	[_storedBuffersLock lock]; | ||||
| 	for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) | ||||
| 	{ | ||||
| 		if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes) | ||||
| @@ -124,9 +181,11 @@ static void audioOutputCallback( | ||||
|  | ||||
| 			AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL); | ||||
| 			_storedBuffers[c] = NULL; | ||||
| 			[_storedBuffersLock unlock]; | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	[_storedBuffersLock unlock]; | ||||
|  | ||||
| 	AudioQueueBufferRef newBuffer; | ||||
| 	AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer); | ||||
| @@ -135,7 +194,6 @@ static void audioOutputCallback( | ||||
|  | ||||
| 	AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL); | ||||
| } | ||||
| } | ||||
|  | ||||
| #pragma mark - Sampling Rate getters | ||||
|  | ||||
|   | ||||
| @@ -31,24 +31,14 @@ class MachineDocument: | ||||
| 		return NSSize(width: 4.0, height: 3.0) | ||||
| 	} | ||||
|  | ||||
| 	@IBOutlet weak var openGLView: CSOpenGLView! { | ||||
| 		didSet { | ||||
| 			openGLView.delegate = self | ||||
| 			openGLView.responderDelegate = self | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@IBOutlet weak var openGLView: CSOpenGLView! | ||||
| 	@IBOutlet var optionsPanel: MachinePanel! | ||||
| 	@IBAction func showOptions(_ sender: AnyObject!) { | ||||
| 		optionsPanel?.setIsVisible(true) | ||||
| 	} | ||||
|  | ||||
| 	fileprivate var audioQueue: CSAudioQueue! = nil | ||||
| 	fileprivate lazy var bestEffortUpdater: CSBestEffortUpdater = { | ||||
| 		let updater = CSBestEffortUpdater() | ||||
| 		updater.delegate = self | ||||
| 		return updater | ||||
| 	}() | ||||
| 	fileprivate var bestEffortUpdater: CSBestEffortUpdater! | ||||
|  | ||||
| 	override var windowNibName: String? { | ||||
| 		return "MachineDocument" | ||||
| @@ -64,12 +54,22 @@ class MachineDocument: | ||||
| 			self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height)) | ||||
| 		}) | ||||
|  | ||||
| 		setupClockRate() | ||||
| 		self.machine.delegate = self | ||||
| 		self.bestEffortUpdater = CSBestEffortUpdater() | ||||
|  | ||||
| 		// 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 | ||||
| 		self.openGLView.delegate = self | ||||
| 		self.openGLView.responderDelegate = self | ||||
|  | ||||
| 		setupClockRate() | ||||
| 		self.optionsPanel?.establishStoredOptions() | ||||
|  | ||||
| 		// bring OpenGL view-holding window on top of the options panel | ||||
| 		self.openGLView.window!.makeKeyAndOrderFront(self) | ||||
|  | ||||
| 		// start accepting best effort updates | ||||
| 		self.bestEffortUpdater.delegate = self | ||||
| 	} | ||||
|  | ||||
| 	func machineDidChangeClockRate(_ machine: CSMachine!) { | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
| 				<string>bin</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string></string> | ||||
| 			<string>cartridge</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Atari 2600 Cartridge</string> | ||||
| 			<key>CFBundleTypeOSTypes</key> | ||||
| @@ -27,27 +27,13 @@ | ||||
| 			<key>NSDocumentClass</key> | ||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||
| 		</dict> | ||||
| 		<dict> | ||||
| 			<key>CFBundleTypeExtensions</key> | ||||
| 			<array> | ||||
| 				<string>uef</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Electron/BBC Tape Image</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| 			<string>Viewer</string> | ||||
| 			<key>LSItemContentTypes</key> | ||||
| 			<array/> | ||||
| 			<key>LSTypeIsPackage</key> | ||||
| 			<integer>0</integer> | ||||
| 			<key>NSDocumentClass</key> | ||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||
| 		</dict> | ||||
| 		<dict> | ||||
| 			<key>CFBundleTypeExtensions</key> | ||||
| 			<array> | ||||
| 				<string>rom</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>chip</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>ROM Image</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -65,6 +51,8 @@ | ||||
| 				<string>uef</string> | ||||
| 				<string>uef.gz</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>cassette</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Electron/BBC UEF Image</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -79,6 +67,8 @@ | ||||
| 			<array> | ||||
| 				<string>prg</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>floppy525</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Commodore Program</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -93,6 +83,8 @@ | ||||
| 			<array> | ||||
| 				<string>tap</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>cassette</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Tape Image</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -107,6 +99,8 @@ | ||||
| 			<array> | ||||
| 				<string>g64</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>floppy525</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Commodore Disk</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -121,6 +115,8 @@ | ||||
| 			<array> | ||||
| 				<string>d64</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>floppy525</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Commodore 1540/1 Disk</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -139,6 +135,8 @@ | ||||
| 				<string>adl</string> | ||||
| 				<string>adm</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>floppy35</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Electron/BBC Disk Image</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
| @@ -151,6 +149,8 @@ | ||||
| 			<array> | ||||
| 				<string>dsk</string> | ||||
| 			</array> | ||||
| 			<key>CFBundleTypeIconFile</key> | ||||
| 			<string>floppy35</string> | ||||
| 			<key>CFBundleTypeName</key> | ||||
| 			<string>Disk Image</string> | ||||
| 			<key>CFBundleTypeRole</key> | ||||
|   | ||||
| @@ -11,42 +11,8 @@ | ||||
| #include "Atari2600.hpp" | ||||
| #import "CSMachine+Subclassing.h" | ||||
|  | ||||
| @interface CSAtari2600 () | ||||
| - (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs; | ||||
| @end | ||||
|  | ||||
| struct CRTDelegate: public Outputs::CRT::Delegate { | ||||
| 	__weak CSAtari2600 *atari2600; | ||||
| 	void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { | ||||
| 		[atari2600 crt:crt didEndBatchOfFrames:number_of_frames withUnexpectedVerticalSyncs:number_of_unexpected_vertical_syncs]; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @implementation CSAtari2600 { | ||||
| 	Atari2600::Machine _atari2600; | ||||
| 	CRTDelegate _crtDelegate; | ||||
|  | ||||
| 	int _frameCount; | ||||
| 	int _hitCount; | ||||
| 	BOOL _didDecideRegion; | ||||
| 	int _batchesReceived; | ||||
| } | ||||
|  | ||||
| - (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs { | ||||
| 	if(!_didDecideRegion) | ||||
| 	{ | ||||
| 		_batchesReceived++; | ||||
| 		if(_batchesReceived == 2) | ||||
| 		{ | ||||
| 			_didDecideRegion = YES; | ||||
| 			if(numberOfUnexpectedSyncs >= numberOfFrames >> 1) | ||||
| 			{ | ||||
| 				[self.view performWithGLContext:^{ | ||||
| 					_atari2600.switch_region(); | ||||
| 				}]; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| - (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed { | ||||
| @@ -78,8 +44,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate { | ||||
| - (void)setupOutputWithAspectRatio:(float)aspectRatio { | ||||
| 	@synchronized(self) { | ||||
| 		[super setupOutputWithAspectRatio:aspectRatio]; | ||||
| 		_atari2600.get_crt()->set_delegate(&_crtDelegate); | ||||
| 		_crtDelegate.atari2600 = self; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/cartridge.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 111 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/cassette.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 194 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/chip.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 105 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/floppy35.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 102 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/floppy525.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 126 KiB | 
| @@ -53,7 +53,7 @@ | ||||
| */ | ||||
| @interface CSOpenGLView : NSOpenGLView | ||||
|  | ||||
| @property (nonatomic, weak, nullable) id <CSOpenGLViewDelegate> delegate; | ||||
| @property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate; | ||||
| @property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate; | ||||
|  | ||||
| /*! | ||||
|   | ||||
| @@ -21,14 +21,25 @@ class MOS6532Tests: XCTestCase { | ||||
| 		with6532 { | ||||
| 			// set a count of 128 at single-clock intervals | ||||
| 			$0.setValue(128, forRegister:0x14) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 128) | ||||
|  | ||||
| 			// run for one clock and the count should now be 127 | ||||
| 			// run for one more clock and the count should now be 127 | ||||
| 			$0.run(forCycles: 1) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 127, "A single tick should decrease the counter once") | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 127) | ||||
|  | ||||
| 			// run for a further 200 clock counts; timer should reach -73 = 183 | ||||
| 			$0.run(forCycles: 200) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 183, "Timer should underflow and keep counting") | ||||
| 			// run for 127 clocks and the timer should be zero, but the timer flag will not yet be set | ||||
| 			$0.run(forCycles: 127) | ||||
| 			XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 0) | ||||
|  | ||||
| 			// after one more cycle the counter should be 255 and the timer flag will now be set | ||||
| 			$0.run(forCycles: 1) | ||||
| 			XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 255) | ||||
|  | ||||
| 			// run for a further 55 clock counts; timer should reach -200 | ||||
| 			$0.run(forCycles: 55) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 200) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -37,26 +48,40 @@ class MOS6532Tests: XCTestCase { | ||||
| 		with6532 { | ||||
| 			// set a count of 28 at eight-clock intervals | ||||
| 			$0.setValue(28, forRegister:0x15) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 28) | ||||
|  | ||||
| 			// run for seven clock and the count should still be 28 | ||||
| 			$0.run(forCycles: 7) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 28, "The timer should remain unchanged for seven clocks") | ||||
|  | ||||
| 			// run for a further clock and the count should now be 27 | ||||
| 			// one further cycle and the timer should hit 27 | ||||
| 			$0.run(forCycles: 1) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 27, "The timer should have decremented once after 8 cycles") | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 27) | ||||
|  | ||||
| 			// run for a further 7 + 27*8 + 5 = 228 clock counts; timer should reach -5 = 0xfb | ||||
| 			$0.run(forCycles: 228) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should underflow and start counting at single-clock pace") | ||||
| 			// run for seven clock and the count should still be 27 | ||||
| 			$0.run(forCycles: 7) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 27) | ||||
|  | ||||
| 			// run for a further clock and the count should now be 26 | ||||
| 			$0.run(forCycles: 1) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 26) | ||||
|  | ||||
| 			// run for another 26 * 8 = 208 cycles and the count should hit zero without setting the timer flag, and | ||||
| 			// stay there for seven more cycles | ||||
| 			$0.run(forCycles: 208) | ||||
| 			for _ in 0 ..< 8 { | ||||
| 				XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0) | ||||
| 				XCTAssertEqual($0.value(forRegister: 4), 0) | ||||
| 				$0.run(forCycles: 1) | ||||
| 			} | ||||
|  | ||||
| 			// run six more, and the timer should reach 249, with the interrupt flag set | ||||
| 			$0.run(forCycles: 6) | ||||
| 			XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80) | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 249) | ||||
|  | ||||
| 			// timer should now resume dividing by eight | ||||
| 			$0.run(forCycles: 7) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should remain unchanged for seven cycles") | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 249) | ||||
|  | ||||
| 			// timer should now resume dividing by eight | ||||
| 			$0.run(forCycles: 1) | ||||
| 			XCTAssert($0.value(forRegister: 4) == 0xfa, "Timer should decrement after eighth cycle") | ||||
| 			XCTAssertEqual($0.value(forRegister: 4), 248) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -64,8 +89,6 @@ class MOS6532Tests: XCTestCase { | ||||
| 		with6532 { | ||||
| 			// set a count of 1 at single-clock intervals | ||||
| 			$0.setValue(1, forRegister:0x1c) | ||||
|  | ||||
| 			// run for one clock and the count should now be zero | ||||
| 			$0.run(forCycles: 1) | ||||
|  | ||||
| 			// interrupt shouldn't be signalled yet, bit should not be set | ||||
|   | ||||
							
								
								
									
										3
									
								
								OSBindings/Mac/Clock SignalTests/Atari ROMs/readme.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| This folder is intended to contain commercial Atari ROM images; these are used to unit test the Atari static analyser. | ||||
|  | ||||
| Those tested are (i) everything presently available on AtariAge, and (ii) a selection of things from Pouët. | ||||
							
								
								
									
										614
									
								
								OSBindings/Mac/Clock SignalTests/AtariStaticAnalyserTests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,614 @@ | ||||
| // | ||||
| //  AtariStaticAnalyserTests.m | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 11/03/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <XCTest/XCTest.h> | ||||
|  | ||||
| #import <CommonCrypto/CommonDigest.h> | ||||
| #include "../../../StaticAnalyser/StaticAnalyser.hpp" | ||||
|  | ||||
| @interface AtariROMRecord : NSObject | ||||
| @property(nonatomic, readonly) StaticAnalyser::Atari2600PagingModel pagingModel; | ||||
| @property(nonatomic, readonly) BOOL usesSuperchip; | ||||
| + (instancetype)recordWithPagingModel:(StaticAnalyser::Atari2600PagingModel)pagingModel usesSuperchip:(BOOL)usesSuperchip; | ||||
| @end | ||||
|  | ||||
| @implementation AtariROMRecord | ||||
| + (instancetype)recordWithPagingModel:(StaticAnalyser::Atari2600PagingModel)pagingModel usesSuperchip:(BOOL)usesSuperchip | ||||
| { | ||||
| 	AtariROMRecord *record = [[AtariROMRecord alloc] init]; | ||||
| 	record->_pagingModel = pagingModel; | ||||
| 	record->_usesSuperchip = usesSuperchip; | ||||
| 	return record; | ||||
| } | ||||
| @end | ||||
|  | ||||
| #define Record(sha, model, uses) sha : [AtariROMRecord recordWithPagingModel:StaticAnalyser::Atari2600PagingModel::model usesSuperchip:uses], | ||||
| static NSDictionary<NSString *, AtariROMRecord *> *romRecordsBySHA1 = @{ | ||||
| 	Record(@"58dbcbdffbe80be97746e94a0a75614e64458fdc", None, NO)			// 4kraVCS | ||||
| 	Record(@"9967a76efb68017f793188f691159f04e6bb4447", None, NO)			// 'X'Mission | ||||
| 	Record(@"21d983f2f52b84c22ecae84b0943678ae2c31c10", None, NO)			// 3d Tic-Tac-Toe | ||||
| 	Record(@"d7c62df8300a68b21ce672cfaa4d0f2f4b3d0ce1", Atari16k, NO)		// Acid Drop | ||||
| 	Record(@"924ca836aa08eeffc141d487ac6b9b761b2f8ed5", None, NO)			// Action Force | ||||
| 	Record(@"e07e48d463d30321239a8acc00c490f27f1f7422", None, NO)			// Adventure | ||||
| 	Record(@"03a495c7bfa0671e24aa4d9460d232731f68cb43", None, NO)			// Adventures of Tron | ||||
| 	Record(@"6e420544bf91f603639188824a2b570738bb7e02", None, NO)			// Adventures On GX12 | ||||
| 	Record(@"3b02e7dacb418c44d0d3dc77d60a9663b90b0fbc", None, NO)			// Air Raid | ||||
| 	Record(@"29f5c73d1fe806a4284547274dd73f9972a7ed70", None, NO)			// Air Raiders | ||||
| 	Record(@"af5b9f33ccb7778b42957da4f20f2bc000992366", None, NO)			// Air-Sea Battle | ||||
| 	Record(@"0376c242819b785310b8af43c03b1d1156bd5f02", None, NO)			// Airlock | ||||
| 	Record(@"fb870ec3d51468fa4cf40e0efae9617e60c1c91c", None, NO)			// AKA Space Adventure | ||||
| 	Record(@"01d99bf307262825db58631e8002dd008a42cb1e", None, NO)			// Alien | ||||
| 	Record(@"a1f660827ce291f19719a5672f2c5d277d903b03", Atari8k, NO)		// Alpha Beam with Ernie | ||||
| 	Record(@"b89a5ac6593e83fbebee1fe7d4cec81a7032c544", None, NO)			// Amidar | ||||
| 	Record(@"ac58ac94ceab78725a1182cc7b907376c011b0c8", None, NO)			// Angriff der Luftflotten | ||||
| 	Record(@"7d132ab776ff755b86bf4f204165aa54e9e1f1cf", Atari8k, NO)		// Aquaventure | ||||
| 	Record(@"9b6a54969240baf64928118741c3affee148d721", None, NO)			// Armor Ambush | ||||
| 	Record(@"8c249e9eaa83fc6be16039f05ec304efdf987beb", Atari8k, NO)		// Artillery Duel | ||||
| 	Record(@"0c03eba97df5178eec5d4d0aea4a6fe2f961c88f", None, NO)			// Assault | ||||
| 	Record(@"1a094f92e46a8127d9c29889b5389865561c0a6f", Atari8k, NO)		// Asterix (NTSC) | ||||
| 	Record(@"f14408429a911854ec76a191ad64231cc2ed7d11", Atari8k, NO)		// Asterix (PAL) | ||||
| 	Record(@"8f4a00cb4ab6a6f809be0e055d97e8fe17f19e7d", None, NO)			// Asteroid Fire | ||||
| 	Record(@"8423f99092b454aed89f89f5d7da658caf7af016", Atari8k, NO)		// Asteroids | ||||
| 	Record(@"b850bd72d18906d9684e1c7251cb699588cbcf64", None, NO)			// Astroblast | ||||
| 	Record(@"d1563c24208766cf8d28de7af995021a9f89d7e1", None, NO)			// Atari Video Cube | ||||
| 	Record(@"f4e838de9159c149ac080ab85e4f830d5b299963", None, NO)			// Atlantis II | ||||
| 	Record(@"c6b1dcdb2f024ab682316db45763bacc6949c33c", None, NO)			// Atlantis | ||||
| 	Record(@"75e7efa861f7e7d8e367c09bf7c0cc351b472f03", None, NO)			// Bachelor Party | ||||
| 	Record(@"b88ca823aaa10a7a4a3d325023881b2de969c156", None, NO)			// Bachelorette Party | ||||
| 	Record(@"9b1da7fbd0bf6fcadf1b60c11eeb31b6a61a03c3", None, NO)			// Backgammon | ||||
| 	Record(@"80d4020575b14e130f28146bf45921e001f9f649", None, NO)			// Bank Heist | ||||
| 	Record(@"372663097b419ced64f44ef743fe8d0af4317f46", None, NO)			// Barnstorming | ||||
| 	Record(@"d0bdd609ebc6e69fb351ba469ff322406bcbab50", None, NO)			// Base Attack | ||||
| 	Record(@"6c56fad688b2e9bb783f8a5a2360c80ad2338e47", None, NO)			// Basic Programming | ||||
| 	Record(@"bffe99454cb055552e5d612f0dba25470137328d", None, NO)			// Basketball | ||||
| 	Record(@"e4134a3b4a065c856802bc935c12fa7e9868110a", Atari8k, NO)		// Battlezone | ||||
| 	Record(@"47619edb352f7f955f811cbb03a00746c8e099b1", Atari8k, NO)		// Beamrider | ||||
| 	Record(@"fad0c97331a525a4aeba67987552ba324629a7a0", None, NO)			// Beany Bopper | ||||
| 	Record(@"e2c29d0a73a4575028b62dca745476a17f07c8f0", None, NO)			// Beat 'Em & Eat 'Em | ||||
| 	Record(@"c3afd7909b72b49ca7d4485465b622d5e55f8913", Atari8k, NO)		// Berenstain Bears | ||||
| 	Record(@"fcad0e5130de24f06b98fb86a7c3214841ca42e2", None, NO)			// Bermuda Triangle | ||||
| 	Record(@"08bcbc8954473e8f0242b881315b0af4466998ae", None, NO)			// Berzerk | ||||
| 	Record(@"5e4517db83c061926130ab65975e3b83d9401cc9", Atari8k, NO)		// Big Bird's Egg Catch | ||||
| 	Record(@"512e4d047f1f813bc805c8d2a5f7cbdb34b9ea46", None, NO)			// bin00016 | ||||
| 	Record(@"f6a41507b8cf890ab7c59bb1424f0500534385ce", Atari8k, NO)		// Bionic Breakthrough | ||||
| 	Record(@"edfd905a34870196f8acb2a9cd41f79f4326f88d", None, NO)			// Blackjack | ||||
| 	Record(@"0fadef01ce28192880f745b23a5fbb64c5a96efe", Atari8k, NO)		// Blueprint | ||||
| 	Record(@"ff25ed062dcc430448b358d2ac745787410e1169", Atari16k, NO)		// BMX Air Master | ||||
| 	Record(@"50e26688fdd3eadcfa83240616267a8f60216c25", None, NO)			// Bobby is Going Home | ||||
| 	Record(@"282cad17482f5f87805065d1a62e49e662d5b4bb", None, NO)			// Bogey Blaster | ||||
| 	Record(@"d106bb41a38ed222dead608d839e8a3f0d0ecc18", None, NO)			// Boing! | ||||
| 	Record(@"cf6ce244b3edaad7ad5e9ca5f01668135c2f93d0", None, NO)			// Bowling | ||||
| 	Record(@"14b9cd91188c7fb0d4566442d639870f8d6f174d", None, NO)			// Boxing | ||||
| 	Record(@"238915cafd26f69bc8a3b9aa7d880dde59f6f12d", None, NO)			// Brain Games | ||||
| 	Record(@"8d473b87b70e26890268e6c417c0bb7f01e402eb", None, NO)			// Breakout | ||||
| 	Record(@"2873eb6effd35003d13e2f8f997b76dbc85d0f64", None, NO)			// Bridge | ||||
| 	Record(@"a65dea2d9790f3eb308c048a01566e35e8c24549", Atari8k, NO)		// Buck Rogers — Planet of Zoom | ||||
| 	Record(@"9c0e13af336a986c271fe828fafdca250afba647", Atari8k, NO)		// Bugs Bunny | ||||
| 	Record(@"67387d0d3d48a44800c44860bf15339a81f41aa9", None, NO)			// Bugs | ||||
| 	Record(@"1819ef408c1216c83dcfeceec28d13f6ea5ca477", MNetwork, NO)		// Bump 'n' Jump | ||||
| 	Record(@"6c199782c79686dc0cbce6d5fe805f276a86a3f5", None, NO)			// Bumper Bash | ||||
| 	Record(@"49e01b8048ae344cb65838f6b1c1de0e1f416f29", MNetwork, NO)		// BurgerTime | ||||
| 	Record(@"b233c37aa5164a54e2e7cc3dc621b331ddc6e55b", None, NO)			// Burning Desire | ||||
| 	Record(@"3f1f17cf620f462355009f5302cddffa730fa2fa", None, NO)			// Cakewalk | ||||
| 	Record(@"609c20365c3a71ce45cb277c66ec3ce6b2c50980", Atari16k, NO)		// California Games | ||||
| 	Record(@"b89443a0029e765c2716774fe2582be37650115c", None, NO)			// Canyon Bomber | ||||
| 	Record(@"e1acf7a845b56e4b3d18192a75a81c7afa6f341a", None, NO)			// Carnival | ||||
| 	Record(@"54ed2864f58ef3768579ec96cca445ee62078521", None, NO)			// cart | ||||
| 	Record(@"08598101e38756916613f37581ef1b61c719016f", None, NO)			// Casino | ||||
| 	Record(@"e979de719cecab2115affd9c0552c6c596b1999a", None, NO)			// Cat Trax | ||||
| 	Record(@"6adf70e0b7b5dab74cf4778f56000de7605e8713", None, NO)			// Cathouse Blues | ||||
| 	Record(@"0b5914bc1526a9beaf54d7fd11408175cd8fcc72", Atari8k, NO)		// Centipede | ||||
| 	Record(@"b2b1bd165b3c10cde5316ed0f9f05a509aac828d", None, NO)			// Challenge (Zellers) | ||||
| 	Record(@"ac9b0c62ba0ca7a975d08fabbbc7c7448ecdf18d", None, NO)			// Challenge of… Nexar | ||||
| 	Record(@"e81b5e49cfbb283edba2c8f21f31a8148d8645a1", None, NO)			// Challenge | ||||
| 	Record(@"872b2f9aa7edbcbb2368de0db3696c90998ff016", None, NO)			// Chase the Chuckwagon | ||||
| 	Record(@"39b5bb27a6c4cb6532bd9d4cc520415c59dac653", None, NO)			// Checkers | ||||
| 	Record(@"0b1bb76769ae3f8b4936f0f95f4941d276791bde", None, NO)			// China Syndrome | ||||
| 	Record(@"51a53bbfdbcc22925515ae0af79df434df6ee68a", None, NO)			// Chopper Command | ||||
| 	Record(@"8a91ecdbd8bf9d412da051c3422abb004eab8603", None, NO)			// Circus | ||||
| 	Record(@"3f56d1a376702b64b3992b2d5652a3842c56ffad", None, NO)			// Coco Nuts | ||||
| 	Record(@"137bd3d3f36e2549c6e1cc3a60f2a7574f767775", None, NO)			// Codebreaker | ||||
| 	Record(@"53c324ae736afa92a83d619b04e4fe72182281a6", None, NO)			// Color Bar Generator | ||||
| 	Record(@"66014de1f8e9f39483ee3f97ca0d97d026ffc3bb", Atari8k, NO)		// Combat Two | ||||
| 	Record(@"ce7580059e8b41cb4a1e734c9b35ce3774bf777a", None, NO)			// Combat | ||||
| 	Record(@"8dad05085657e95e567f47836502be515b42f66b", None, NO)			// Commando Raid | ||||
| 	Record(@"68a7cb3ff847cd987a551f3dd9cda5f90ce0a3bf", Atari16k, NO)		// Commando | ||||
| 	Record(@"dbc0c0451dee44425810e04df8f1d26d1c2d3993", None, NO)			// Computer Chess | ||||
| 	Record(@"5512a0ed4306edc007a78bb52dbcf492adf798ec", None, NO)			// Confrontation | ||||
| 	Record(@"3a77db43b6583e8689435f0f14aa04b9e57bdded", Atari8k, NO)		// Congo Bongo | ||||
| 	Record(@"f4a62ba0ff59803c5f40d59eeed1e126fe37979b", Atari8k, NO)		// Cookie Monster Munch | ||||
| 	Record(@"187983fd14d37498437d0ef8f3fbd05675feb6ae", None, NO)			// Cosmic Ark | ||||
| 	Record(@"3717c97bbb0f547e4389db8fc954d1bad992444c", None, NO)			// Cosmic Commuter | ||||
| 	Record(@"8b9dfef6c6757a6a59e01d783b751e4ab9541d9e", None, NO)			// Cosmic Corridor | ||||
| 	Record(@"22ff281b1e698e8a5d7a6f6173c86c46d3cd8561", None, NO)			// Cosmic Creeps | ||||
| 	Record(@"c01354760f2ca8d6e4d01b230f31611973c6ae2d", None, NO)			// Cosmic Swarm | ||||
| 	Record(@"6ee0a26af4643ff250198dfc1c2b7c6568b4f207", None, NO)			// Crackpots | ||||
| 	Record(@"73d68f32d1fb73883ceb183d5150bff5f1065de4", None, NO)			// Crash Dive | ||||
| 	Record(@"70e723aa67d68f8549d9bd8f96d8b1262cbdac3c", Atari8k, NO)		// Crazy Climber | ||||
| 	Record(@"dd385886fdd20727c060bad6c92541938661e2b4", None, NO)			// Crazy Valet | ||||
| 	Record(@"b1b3d8d6afe94b73a43c36668cc756c5b6fdc1c3", None, NO)			// Cross Force | ||||
| 	Record(@"5da3d089ccda960ce244adb855975877c670e615", Atari16k, NO)		// Crossbow | ||||
| 	Record(@"a57062f44e7ac793d4c39d1350521dc5bc2a665f", None, NO)			// Crypts of Chaos | ||||
| 	Record(@"2e4ee5ee040b08be1fe568602d1859664e607efb", Atari16k, YES)		// Crystal Castles | ||||
| 	Record(@"0dd72a3461b4167f2d68c93511ed4985d97e6adc", None, NO)			// Cubicolor | ||||
| 	Record(@"4b3d02b59e17520b4d60236568d5cb50a4e6aeb3", None, NO)			// Custer's Revenge | ||||
| 	Record(@"07e94a7d357e859dcff77180981713ce8119324e", None, NO)			// Dancing Plate | ||||
| 	Record(@"0ae2fc87f87a5cc199c3b9a17444bf3c2f6a829b", None, NO)			// Dark Cavern | ||||
| 	Record(@"fbb4814973fcb4e101521515e04daa6424c45f5c", Atari16k, YES)		// Dark Chambers | ||||
| 	Record(@"c5af53c4b64c3db552d4717b8583d6fe8d3e7952", Atari8k, NO)		// Dark Mage | ||||
| 	Record(@"68de291d5e9cbebfed72d2f9039e60581b6dbdc5", None, NO)			// Deadly Duck | ||||
| 	Record(@"2ad9db4b5aec2da36ecc3178599b02619c3c462e", ParkerBros, NO)		// Death Star Battle | ||||
| 	Record(@"5f710a1148740760b4ebcc42861a1f9c3384799e", None, NO)			// Death Trap | ||||
| 	Record(@"717656f561823edaa69240471c3106963f5c307e", ActivisionStack, NO)// Decathlon | ||||
| 	Record(@"d7b506b84f28e1b917a2978753d5a40eb197537a", Atari8k, YES)		// Defender 2 | ||||
| 	Record(@"79facc1bf70e642685057999f5c2b8e94b102439", None, NO)			// Defender | ||||
| 	Record(@"5aae618292a728b55ad7f00242d870736b5356d3", None, NO)			// Demolition Herby | ||||
| 	Record(@"a580d886c191a069f6b9036c3f879e83e09500c2", None, NO)			// Demon Attack | ||||
| 	Record(@"b45582de81c48b04c2bb758d69021e8088c70ce7", None, NO)			// Demons to Diamonds | ||||
| 	Record(@"ccea2d5095441d7e1b1468e3879a6ab556dc8b7a", Atari16k, YES)		// Desert Falcon | ||||
| 	Record(@"538108ca9821265e23f06fa7672965631bdf8175", None, NO)			// Diagnostic Test Cartridge 2.6 | ||||
| 	Record(@"81022ef30e682183d51b18bff145ce425c6f924e", None, NO)			// Dice Puzzle | ||||
| 	Record(@"79e746524520da546249149c33614fc23a4f2a51", Atari16k, YES)		// Dig Dug | ||||
| 	Record(@"7485cf55201ef98ded201aec73c4141f9f74f863", None, NO)			// Dishaster | ||||
| 	Record(@"157117df23cb5229386d06bbdb3af20a208722e0", None, NO)			// Dodge 'Em | ||||
| 	Record(@"e3985d759f8a8f4705f543ce7eb5e93bf63722b5", None, NO)			// Dolphin | ||||
| 	Record(@"4606c0751f560200aede6598ec9c8e6249a105f5", Atari8k, NO)		// Donald Duck's Speedboat | ||||
| 	Record(@"359e662da02bf0a2184472e25d05bc597b6c497a", None, NO)			// Donkey Kong (1983) (CBS Electronics) (PAL) [!] | ||||
| 	Record(@"98f98ac0728c68de66afda6500cafbdffe8ab50a", Atari8k, NO)		// Donkey Kong Junior | ||||
| 	Record(@"6e6e37ec8d66aea1c13ed444863e3db91497aa35", None, NO)			// Donkey Kong | ||||
| 	Record(@"251e02ac583d84eb43f1451d55b62c7c70e9644f", Atari16k, NO)		// Double Dragon | ||||
| 	Record(@"8e2ea320b23994dc87abe69d61249489f3a0fccc", Atari16k, NO)		// Double Dunk | ||||
| 	Record(@"b446381fe480156077b0b3c51747d156e5dde89f", None, NO)			// Dragon Treasures | ||||
| 	Record(@"944c52de85464070a946813b050518977750e939", None, NO)			// Dragster | ||||
| 	Record(@"9caf114c9582d5e0c14396b13d2bd1a89cad90b1", None, NO)			// Dukes of Hazzard (1980) | ||||
| 	Record(@"c061d753435dcb7275a8764f4ad003b05fa100ed", Atari16k, NO)		// Dukes of Hazzard (1983) | ||||
| 	Record(@"d16eba13ab1313f375e86b488181567f846f1dc4", Atari8k, NO)		// Dumbo's Flying Circus | ||||
| 	Record(@"9e34f9ca51573c92918720f8a259b9449a0cd65e", Atari8k, NO)		// E.T. — The Extra-Terrestrial | ||||
| 	Record(@"68cbfadf097ae2d1e838f315c7cc7b70bbf2ccc8", None, NO)			// Eggomania | ||||
| 	Record(@"bab872ee41695cefe41d88e4932132eca6c4e69c", Atari8k, YES)		// Elevator Action | ||||
| 	Record(@"475fc2b23c0ee273388539a4eeafa34f8f8d3fd8", None, NO)			// Eli's Ladder | ||||
| 	Record(@"3983e109fc0b38c0b559a09a001f3e5f2bb1dc2a", Atari8k, NO)		// Elk Attack | ||||
| 	Record(@"205af4051ea39fb5a038a8545c78bff91df321b7", None, NO)			// Encounter at L-5 | ||||
| 	Record(@"82e9b2dd6d99f15381506a76ef958a1773a7ba21", None, NO)			// Enduro | ||||
| 	Record(@"7905aee90a6dd64d9538e0b8e772f833ba9feb83", None, NO)			// Entombed | ||||
| 	Record(@"27d925d482553deff23f0889b3051091977d6920", Tigervision, NO)	// Espial | ||||
| 	Record(@"c17801c0190ade27f438e2aa98dde81b3ae66267", None, NO)			// Exocet | ||||
| 	Record(@"6297dd336a6343f98cd142d1d3d76ce84770a488", None, NO)			// Fantastic Voyage | ||||
| 	Record(@"a5614c751f29118ddb3dec9794612b98a0f00b98", None, NO)			// Fast Eddie | ||||
| 	Record(@"c62a70645939480b184e3b2e378ec4bcbd484bc7", None, NO)			// Fast Food | ||||
| 	Record(@"d0bb58ea1fc37e929e5f7cdead037bb14a166451", Atari32k, YES)		// Fatal Run | ||||
| 	Record(@"686427cc47b69980d292d04597270347942773ff", Atari8k, NO)		// Fathom | ||||
| 	Record(@"684275b22f2bac7d577cf48cf42fa14fa6f69678", Atari16k, NO)		// Fighter Pilot | ||||
| 	Record(@"ba9a8ccfeb552dd756c660ea843a39619d3c77e9", None, NO)			// Final Approach | ||||
| 	Record(@"f76cc14afd7aef367c5a5defbd84f3bbb2f98ba3", None, NO)			// Fire Fighter | ||||
| 	Record(@"df5420eb0f71e681e7222ede8e211a7601e7a327", None, NO)			// Fire Fly | ||||
| 	Record(@"531e995aef6cd47b0efea72ae3e56aeee449d798", None, NO)			// Fishing Derby | ||||
| 	Record(@"ac05f05f3365f5e348e1e618410065a1c2a88ee4", None, NO)			// Flag Capture | ||||
| 	Record(@"c6fe4ce24bc1ebd538258d98cfe829963323acca", None, NO)			// Football | ||||
| 	Record(@"c6023bf73818c78b2e477a9c6dac411cdbf9c0aa", None, NO)			// Frankenstein's Monster | ||||
| 	Record(@"91cc7e5cd6c0d4a6f42ed66353b7ee7bb972fa3f", None, NO)			// Freeway | ||||
| 	Record(@"de6fc1b51d41b34dcda92f579b2aa4df8eccf586", Atari8k, NO)		// Frog Pond | ||||
| 	Record(@"f344d5a8dc895c5a2ae0288f3c6cb66650e49167", None, NO)			// Frogflys | ||||
| 	Record(@"6b9e591cc53844795725fc66c564f0364d1fbe40", ParkerBros, NO)		// Frogger II | ||||
| 	Record(@"e859b935a36494f3c4b4bf5547392600fb9c96f0", None, NO)			// Frogger | ||||
| 	Record(@"cf32bfcd7f2c3b7d2a6ad2f298aea2dfad8242e7", Atari8k, NO)		// Front Line | ||||
| 	Record(@"b9e60437e7691d5ef9002cfc7d15ae95f1c03a12", None, NO)			// Frostbite | ||||
| 	Record(@"5cc4010eb2858afe8ce77f53a89d37c7584e15b4", None, NO)			// Fun with Numbers | ||||
| 	Record(@"9cfb6288a5c2dae63ee6f5e9325200ccd21a3055", None, NO)			// G.I. Joe — Cobra Strike | ||||
| 	Record(@"8e708e0067d3302327900fa322aeb8e2df2022d7", Atari8k, NO)		// Galaxian Enhanced Graphics | ||||
| 	Record(@"b081b327ac32d951c36cb4b3ff812be95685d52f", Atari8k, NO)		// Galaxian | ||||
| 	Record(@"8cf49d43bd62308df788cfacbfcd80e9226c7590", None, NO)			// Gangster Alley | ||||
| 	Record(@"bc0d1edc251d8d4db3d5234ec83dee171642a547", Atari16k, NO)		// Garfield | ||||
| 	Record(@"0da1f2de5a9b5a6604ccdb0f30b9da4e5f799b40", None, NO)			// Gas Hog | ||||
| 	Record(@"73adae38d86d50360b1a247244df05892e33da46", None, NO)			// Gauntlet | ||||
| 	Record(@"3b1fb93342c7f014a28dddf6f16895d11ac7d6f0", None, NO)			// General Re-Treat | ||||
| 	Record(@"4b533776dcd9d538f9206ad1e28b30116d08df1e", Atari8k, NO)		// Ghost Manor | ||||
| 	Record(@"1bcf03e1129015a46ad7028e0e74253653944e86", Atari16k, NO)		// Ghostbusters II (alternate) | ||||
| 	Record(@"e032876305647a95b622e5c4971f7096ef72acdb", Atari16k, NO)		// Ghostbusters II | ||||
| 	Record(@"5ed0b2cb346d20720e3c526da331551aa16a23a4", Atari8k, NO)		// Ghostbusters | ||||
| 	Record(@"b64ed2d5a2f8fdac4ff0ce56939ba72e343fec33", None, NO)			// Gigolo | ||||
| 	Record(@"3a3d7206afee36786026d6287fe956c2ebc80ea7", None, NO)			// Glacier Patrol | ||||
| 	Record(@"7bca0f7a0f992782e4e4c90772bac976ca963a6d", None, NO)			// Glib | ||||
| 	Record(@"f78c478aacf6536522e8d37a3888a975e1a383cd", None, NO)			// Go Go Home Monster (2) | ||||
| 	Record(@"4c41379f0dd9880384fcbb46bad9fbaaf109a477", None, NO)			// Go Go Home Monster | ||||
| 	Record(@"a25d52770408314dec6f41aaf5f9f0a2a3e2c18f", None, NO)			// Golf | ||||
| 	Record(@"97fb489ba4ce0f8a306563063563617321352cfb", None, NO)			// Gopher | ||||
| 	Record(@"35f8341c73c7e6e896cb065977427b3f98ae9f08", None, NO)			// Gorf | ||||
| 	Record(@"24fab817728216582b6d95558c361ace66abf96f", None, NO)			// Grand Prix | ||||
| 	Record(@"a372d4dd3d95b3866553cae2336e4565e00cc25b", Atari8k, NO)		// Gravitar | ||||
| 	Record(@"7a027329309e018b0d51adcb6ae13c9d13e54f4a", Atari8k, NO)		// Gremlins | ||||
| 	Record(@"c90acaee066f97efc6a520deb7fa3e5760a471fa", Atari8k, NO)		// Grover's Music Maker | ||||
| 	Record(@"7d30ff565ad7b2a3143d049c5b39e4a6ac3f9cd5", None, NO)			// Guardian | ||||
| 	Record(@"45f3f98735798e19427a9100a9000d97917b932f", None, NO)			// Gunfight (NTSC) | ||||
| 	Record(@"7ac6356224cc718ee5731d1ce14aea6fb2335439", None, NO)			// Gunfight (PAL) | ||||
| 	Record(@"4bd87ba8b3b6d7850e3ea41b4d494c3b12659f27", ParkerBros, NO)		// Gyruss | ||||
| 	Record(@"282f94817401e3725c622b73a0c05685ce761783", Atari8k, NO)		// H.E.R.O. | ||||
| 	Record(@"4c72cec151f219866bf870fa7ac749a19ca501c9", None, NO)			// Halloween | ||||
| 	Record(@"561bccf508e162bc70c42d85c170cf0d1d4691a3", None, NO)			// Hangman | ||||
| 	Record(@"d6db71da02ae194140bf812be34d6e8a6785d138", None, NO)			// Harbor Escape | ||||
| 	Record(@"1476c869619075b551b20f2c7f95b11e0d16aec1", None, NO)			// Haunted House | ||||
| 	Record(@"8196209ef7048c5494dbdc932adbf1c7abf79f4e", Atari8k, NO)		// Holey Moley | ||||
| 	Record(@"f362d2b3a50e5ae3c2b412b6c08ecdcfee47a688", None, NO)			// Home Run | ||||
| 	Record(@"d4b0b2aa379893356c72414ee0065a3a91cf9f97", None, NO)			// Human Cannonball | ||||
| 	Record(@"a6e42c63138a2fd527cdbe9b7e60f5feabdd55c8", None, NO)			// Hunt & Score — Memory Match | ||||
| 	Record(@"f7e782214b5f9227e34c00f590be50534f1fda91", None, NO)			// I Want my Mommy | ||||
| 	Record(@"21de0f034e5dad03fa91eb7ae6cc081c142be35c", None, NO)			// Ice Hockey | ||||
| 	Record(@"e5b9c3a3638bd42f96a26b651463da96a9432315", Atari16k, NO)		// Ikari Warriors | ||||
| 	Record(@"620ab88d63cdd3f8ce67deac00a22257c7205c8b", None, NO)			// Indy 500 | ||||
| 	Record(@"922cd171ef132bf6c5bed00ad01410ada4b20729", None, NO)			// Infiltrate | ||||
| 	Record(@"19fc37f2a24e31a59a17f9cbf3cc03416a8bab9a", None, NO)			// Invaders | ||||
| 	Record(@"2bbc124cead9aa49b364268735dad8cb1eb6594f", ParkerBros, NO)		// James Bond 007 | ||||
| 	Record(@"ea5c827052886908c0deaa0a03d6f8da8e4f298d", None, NO)			// Jammed Demo | ||||
| 	Record(@"af4d6867a8bc4818fc6bb701a765a3c907feb628", None, NO)			// Jaw Breaker | ||||
| 	Record(@"36b9edc7150311203f375c1be10d0510efde6476", None, NO)			// Jedi Arena | ||||
| 	Record(@"0d94c1caacb862d9e0b4c2dda121cd4d74a1cced", None, NO)			// John K Harvey's Equalizer | ||||
| 	Record(@"928eaa424b36d98078f9251d67fb13a8fddfafbd", None, NO)			// Journey Escape | ||||
| 	Record(@"cb94dc316cba282a0036871db2417257e960786b", Atari8k, NO)		// Joust | ||||
| 	Record(@"cd2cf245d6e924ff2100cc93d20223c4a231e160", Atari16k, YES)		// Jr. Pac-Man | ||||
| 	Record(@"9a0ee845d9928d4db003b07b927bb2c1f628e725", None, NO)			// Jungle Fever | ||||
| 	Record(@"83a32a2d686355438c915540cfe0bb13b76c1113", Atari8k, NO)		// Jungle Hunt | ||||
| 	Record(@"ce8ac88b799c282567495ce509402a5a4c2c4d82", None, NO)			// Kabobber | ||||
| 	Record(@"40d4df4f8e4a69a299ae7678c17e72bedeb70105", None, NO)			// Kaboom! | ||||
| 	Record(@"a82aaeef44ad88de605c50d23fb4f6cec73f3ab4", None, NO)			// Kamikaze Saucers | ||||
| 	Record(@"01fd30311e028944eafb6d14bb001035f816ced7", Atari8k, NO)		// Kangaroo | ||||
| 	Record(@"c0db7d295e2ce5e00e00b8a83075b1103688ea15", None, NO)			// Karate | ||||
| 	Record(@"3eefc193dec3b242bcfd43f5a4d9f023e55378a4", None, NO)			// Keystone Kapers | ||||
| 	Record(@"839e13ffedcbed22d51a24c001900c3474a078f2", None, NO)			// King Kong | ||||
| 	Record(@"3162259c6dbfbb57a2ea41d849155702151ee39b", Atari16k, YES)		// Klax | ||||
| 	Record(@"759597d1d779cfdfd7aa30fd28a59acc58ca2533", None, NO)			// Knight on the Town | ||||
| 	Record(@"2f550743e237f6dc8c75c389a01b02e9a396fdad", None, NO)			// Kool-Aid Man | ||||
| 	Record(@"4bdf1cf73316bdb0002606facf11b6ddcb287207", Atari8k, NO)		// Krull | ||||
| 	Record(@"1637b6b9cd1a918339ec054cf95b924e7ce4789a", Atari8k, NO)		// Kung Fu Superkicks | ||||
| 	Record(@"3b93a34ba2a6b7db387ea588c48d939eee5d71a1", Atari8k, NO)		// Kung-Fu Master | ||||
| 	Record(@"6d59dfea26b7a06545a817f03f62a59be8993587", None, NO)			// Lady in Wading | ||||
| 	Record(@"ea8ecc2f6818e1c9479f55c0a3356edcf7a4d657", None, NO)			// Laser Blast | ||||
| 	Record(@"cdf55b73b4322428a001e545019eaa591d3479cf", None, NO)			// Laser Gates | ||||
| 	Record(@"afab795719386a776b5fb2165fc84f4858e16e05", None, NO)			// Laser Volley | ||||
| 	Record(@"fe208ad775cbf9523e7a99632b9f10f2c9c7aa87", None, NO)			// Lochjaw | ||||
| 	Record(@"fc3d75d46d917457aa1701bf47844817d0ba96c3", None, NO)			// Lock 'n' Chase | ||||
| 	Record(@"f92b0b83db3cd840d16ee2726011f5f0144103d5", None, NO)			// London Blitz | ||||
| 	Record(@"ef02fdb94ac092247bfcd5f556e01a68c06a4832", ParkerBros, NO)		// Lord of the Rings | ||||
| 	Record(@"e8492fe9d62750df682358fe59a4d4272655eb96", None, NO)			// Lost Luggage | ||||
| 	Record(@"dcd96913a1c840c8b57848986242eeb928bfd2ff", None, NO)			// M*A*S*H | ||||
| 	Record(@"d6e2b7765a9d30f91c9b2b8d0adf61ec5dc2b30a", None, NO)			// M.A.D. | ||||
| 	Record(@"4c66b84ab0d25e46729bbcf23f985d59ca8520ad", CommaVid, NO)		// MagiCard | ||||
| 	Record(@"cdc7e65d965a7a00adda1e8bedfbe6200e349497", None, NO)			// Malagai | ||||
| 	Record(@"ee8f9bf7cdb55f25f4d99e1a23f4c90106fadc39", None, NO)			// Mangia | ||||
| 	Record(@"249a11bb4872a24f22dff1027ff256c1408140c2", None, NO)			// Marauder | ||||
| 	Record(@"dd9e94ca96c75a212f1414aa511fd99ecdadaf44", None, NO)			// Marine Wars | ||||
| 	Record(@"49425ff154b92ca048abb4ce5e8d485c24935035", Atari8k, NO)		// Mario Bros. | ||||
| 	Record(@"fbe7a78764407743b43a91136903ede65306f4e7", None, NO)			// Master Builder | ||||
| 	Record(@"6db8fa65755db86438ada3d90f4c39cc288dcf84", MNetwork, NO)		// Masters of the Universe | ||||
| 	Record(@"18fac606400c08a0469aebd9b071ae3aec2a3cf2", None, NO)			// Math Gran Prix | ||||
| 	Record(@"aba25089d87cd6fee8d206b880baa5d938aae255", None, NO)			// Maze Craze | ||||
| 	Record(@"0ae118373c7bda97da2f8d9c113e1e09ea7e49e1", None, NO)			// Mega Force | ||||
| 	Record(@"46977baf0e1ee6124b524258879c46f80d624fae", MegaBoy, NO)		// MegaBoy | ||||
| 	Record(@"9c5748b38661dbadcbc9cd1ec6a6b0c550b0e3da", None, NO)			// MegaMania | ||||
| 	Record(@"debb1572eadb20beb0e4cd2df8396def8eb02098", None, NO)			// Meltdown | ||||
| 	Record(@"7fcf95459ea597a332bf5b6f56c8f891307b45b4", Atari16k, NO)		// Midnight Magic | ||||
| 	Record(@"0616f0dde6d697816dda92ed9e5a4c3d77a39408", Atari16k, YES)		// Millipede | ||||
| 	Record(@"5edbf8a24fcba9763983befe20e2311f61b986d4", Tigervision, NO)	// Miner 2049er Volume 2 | ||||
| 	Record(@"0e56b48e88f69d405eabf544e57663bd180b3b1e", Tigervision, NO)	// Miner 2049er | ||||
| 	Record(@"34773998d7740e1e8c206b3b22a19e282ca132e1", None, NO)			// Mines of Minos | ||||
| 	Record(@"be24b42e3744a81fb217c86c4ed5ce51bff28e65", None, NO)			// Miniature Golf | ||||
| 	Record(@"f721d1f750e19b9e1788eed5e3872923ab46a91d", Atari8k, NO)		// Miss Piggy's Wedding | ||||
| 	Record(@"faa06bb0643dbf556b13591c31917d277a83110b", None, NO)			// Missile Command | ||||
| 	Record(@"224e7a310afdb91c6915743e72b7b53b38eb5754", None, NO)			// Missile Control | ||||
| 	Record(@"999dc390a7a3f7be7c88022506c70bd4208b26d8", None, NO)			// Mission 3,000 AD | ||||
| 	Record(@"93520821ce406a7aa6cc30472f76bca543805fd4", None, NO)			// Mission Survive | ||||
| 	Record(@"0b74a90a22a7a16f9c2131fabd76b7742de0473e", None, NO)			// Mogul Maniac | ||||
| 	Record(@"81a4d56820b1e00130e368a3532c409929aff5fb", Atari8k, NO)		// Monstercise | ||||
| 	Record(@"7dfeb1a8ec863c1e0f297113a1cc4185c215e81c", ParkerBros, NO)		// Montezuma's Revenge | ||||
| 	Record(@"dce778f397a325113f035722b7769492645d69eb", Atari8k, NO)		// Moon Patrol | ||||
| 	Record(@"05ab04dc30eae31b98ebf6f43fec6793a53e0a23", Atari8k, NO)		// Moonsweeper | ||||
| 	Record(@"c4d495d42ea5bd354af04e1f2b68cce0fb43175d", Atari8k, NO)		// Motocross Racer | ||||
| 	Record(@"ece97bda734faffcf847a8bcdfa474789c377d8d", Atari16k, NO)		// MotoRodeo | ||||
| 	Record(@"ef4112e86d6a3e8f7b8e482d294a5917f162b38c", CBSRamPlus, NO)		// Mountain King | ||||
| 	Record(@"cf6347dedcfec213c28dd92111ec6f41e74b6f64", None, NO)			// Mouse Trap | ||||
| 	Record(@"e4c912199779bba25f1b9950007f14dca3d19c84", Atari8k, NO)		// Mr Do! | ||||
| 	Record(@"330c2c67399e07c40f4101f9e18670fef070475e", ParkerBros, NO)		// Mr. Do!'s Castle | ||||
| 	Record(@"62b933cdd8844bb1816ce57889203954fe782603", Atari8k, NO)		// Ms. Pac-Man | ||||
| 	Record(@"b2df23b1bf6df9d253ad0705592d3fce352a837b", Atari8k, NO)		// My Golf | ||||
| 	Record(@"2b4a0535ca83b963906eb0a5d60ce0e21f07905d", None, NO)			// Name This Game | ||||
| 	Record(@"372771aeb4e2fb2cd1dead5497e3821e4236d5fc", None, NO)			// Night Driver | ||||
| 	Record(@"26e1309bc848cf5880b831d7566488ec5b3db58c", None, NO)			// Night Stalker (2) | ||||
| 	Record(@"281ff7e55c27656522b144b84cba08eb148e2f0a", None, NO)			// Night Stalker | ||||
| 	Record(@"e2e8750b8856dd44d914c43a7d277188cc148e5c", None, NO)			// No Escape! | ||||
| 	Record(@"771a2a87b3b457c0b83f556ce00d1e9c54caeabc", None, NO)			// Nothing | ||||
| 	Record(@"be3f4beeb322cddc7223d6d77e17302aa811e43a", Atari8k, NO)		// Obelix | ||||
| 	Record(@"1f8d06b99db94b0aa8ca320c7cb212639ac9591f", None, NO)			// Ocean City | ||||
| 	Record(@"3dcfe93399044148561586056288c6f8e5c96e2b", Atari16k, YES)		// Off the Wall | ||||
| 	Record(@"7ad74a7c36318f1304f5dc454401cf257fa60d7a", None, NO)			// Off Your Rocker | ||||
| 	Record(@"ea674cf2c90d407b8f8b96eac692690b602b73f9", None, NO)			// Oink! | ||||
| 	Record(@"7bd1cbddefcf3bd24da570be015234d0c444a7e5", None, NO)			// Okie Dokie | ||||
| 	Record(@"dcaab259e7617c7ac7d349893451896a9ca0e292", CBSRamPlus, NO)		// Omega Race | ||||
| 	Record(@"cd968c2a983f9adf4d8d8d56823923b31c33980f", None, NO)			// Open Sesame | ||||
| 	Record(@"7905709fcc85cbcfc28ca2ed543ffa737a5483ae", Atari8k, NO)		// Oscar's Trash Race | ||||
| 	Record(@"cbecf1a32d9366a3dd4ad643916cd59cdc820a8b", None, NO)			// Othello | ||||
| 	Record(@"344d6942723513c376a7a844779804e10f357b85", None, NO)			// Out of Control | ||||
| 	Record(@"f8eeaaf4635ac39b4bdf7ded1348bce46313ef9f", None, NO)			// Outlaw | ||||
| 	Record(@"412e8a2438379878ee4de5c6bf4e5a9ee2707c8b", None, NO)			// Oystron | ||||
| 	Record(@"923c969d31cef450075932436f03f1404e1cab0e", None, NO)			// Pac-Kong | ||||
| 	Record(@"0940fea7f04cdb6d4b90c5ad1a7e344e68f6dbb1", None, NO)			// Pac-Man | ||||
| 	Record(@"b529c1664ed1abc8f5f962a1fed65c0e4440219c", None, NO)			// Peek-A-Boo | ||||
| 	Record(@"832283530f5dee332f29cf8c4854dd554f2030a0", None, NO)			// Pele's Soccer | ||||
| 	Record(@"461c2ea3e4d24f86ec02215c1f4743d250796c11", Atari8k, NO)		// Pengo (prototype) | ||||
| 	Record(@"89b991a7a251f78f422bcdf9cf7d4475fdf33e97", Atari8k, NO)		// Pengo | ||||
| 	Record(@"c5317035e73f60e959c123d89600c81b7c45701f", None, NO)			// Pepsi Invaders | ||||
| 	Record(@"19c3ad034466c0433501a415a996ed7155d6063a", Atari16k, NO)		// Pete Rose Baseball | ||||
| 	Record(@"959aca4b44269b1e5ac58791fc3c7c461a6a4a17", None, NO)			// Phantom Tank | ||||
| 	Record(@"b299df2792c5cca73118925dff85695b73a16228", None, NO)			// Philly Flasher | ||||
| 	Record(@"010d51e3f522ba60f021d56819437d7c85897cdd", Atari8k, NO)		// Phoenix | ||||
| 	Record(@"a5917537cf1093aa350903d85d9e271e8a11d2cf", Atari16k, NO)		// Pick 'n' Pile | ||||
| 	Record(@"483fc907471c5c358fb3e624097861a2fc9c1e45", None, NO)			// Picnic | ||||
| 	Record(@"57774193081acea010bd935a0449bc8f53157128", None, NO)			// Piece o' Cake | ||||
| 	Record(@"d08b30ca2e5e351cac3bd3fb760b87a1a30aa300", Atari8k, NO)		// Pigs in Space | ||||
| 	Record(@"920cfbd517764ad3fa6a7425c031bd72dc7d927c", Pitfall2, NO)		// Pitfall II | ||||
| 	Record(@"d0ec08b88d032627701ad72337524d91b26c656b", None, NO)			// Pitfall! (PAL) | ||||
| 	Record(@"8d525480445d48cc48460dc666ebad78c8fb7b73", None, NO)			// Pitfall! (NTSC) | ||||
| 	Record(@"dcca30e4ae58c85a070f0c6cfaa4d27be2970d61", None, NO)			// Planet of the Apes | ||||
| 	Record(@"ccfcbf52815a441158977292b719f7c5ed80c515", None, NO)			// Planet Patrol | ||||
| 	Record(@"103398dd35ebd39450c5cac760fa332aac3f9458", None, NO)			// Plaque Attack | ||||
| 	Record(@"2410931a8a18b915993b6982fbabab0f437967a4", Tigervision, NO)	// Polaris | ||||
| 	Record(@"9d334da07352a9399cbbd9b41c6923232d0cdcd3", Atari8k, NO)		// Pole Position | ||||
| 	Record(@"c0af0188028cd899c49ba18f52bd1678e573bff2", None, NO)			// Polo | ||||
| 	Record(@"954d2980ea8f8d9a76921612c378889f24c35639", None, NO)			// Pompeii | ||||
| 	Record(@"b7a002025c24ab2ec4a03f62212db7b96c0e5ffd", None, NO)			// Pooyan | ||||
| 	Record(@"1772a22df3e9a1f3842387ac63eeddff7f04b01c", ParkerBros, NO)		// Popeye | ||||
| 	Record(@"70afc2cc870be546dc976fa0c6811f7e01ebc471", Atari8k, NO)		// Porky's | ||||
| 	Record(@"8b001373be485060f88182e9a7afcf55b4d07a57", Atari8k, NO)		// Pressure Cooker | ||||
| 	Record(@"1ea6bea907a6b5607c76f222730f812a99cd1015", Atari8k, NO)		// Private Eye | ||||
| 	Record(@"feb6bd37e5d722bd080433587972b980afff5fa5", None, NO)			// Pumruckl I | ||||
| 	Record(@"a61be3702437b5d16e19c0d2cd92393515d42f23", ParkerBros, NO)		// Q-Bert's Qubes | ||||
| 	Record(@"f3ef9787b4287a32e4d9ac7b9c3358edc16315b2", None, NO)			// Q-Bert | ||||
| 	Record(@"1e634a8733cbc50462d363562b80013343d2fac3", Atari8k, NO)		// Quadrun | ||||
| 	Record(@"d83c740d2968343e6401828d62f58be6aea8e858", Atari8k, NO)		// Quest for Quintana Roo | ||||
| 	Record(@"33a47f79610c4525802c9881f67ad1f3f8c1b55d", None, NO)			// Quick Step! | ||||
| 	Record(@"7bf945ea667e683ec24a4ed779e88bbe55dc4b26", Atari8k, NO)		// Rabbit Transit | ||||
| 	Record(@"4af6008152f1d38626d84016a7ef753406b48b46", None, NO)			// Racquetball | ||||
| 	Record(@"33f016c941fab01e1e2d0d7ba7930e3bcd8feaa3", Atari16k, YES)		// Radar Lock | ||||
| 	Record(@"5f1f2b5b407b0624b59409e02060a3a9e8eed8fc", None, NO)			// Radar | ||||
| 	Record(@"a79f6e0f4fd76878e5c3ba6b52d17e88acdbe9f6", None, NO)			// Raft Rider | ||||
| 	Record(@"7ae70783969709318e56f189cf03da92320a6aba", Atari8k, NO)		// Raiders of the Lost Ark | ||||
| 	Record(@"fd0a69c06eb3f7c9328951c890644f93c4bad6ad", None, NO)			// Ram It | ||||
| 	Record(@"7bb7df255829d5fbbee0d944915e50f89a5e7075", Atari16k, NO)		// Rampage! | ||||
| 	Record(@"5adf9b530321472380ebceb2539de2ffbb0310bc", None, NO)			// Reactor | ||||
| 	Record(@"ace97b89b8b6ab947434dbfd263951c6c0b349ac", Atari8k, NO)		// RealSports Baseball | ||||
| 	Record(@"bc2e6bdaa950bc06be040899dfeb9ad0938f4e98", Atari8k, NO)		// RealSports Basketball | ||||
| 	Record(@"22dedbfce6cc9055a6c4caec013ca80200e51971", Atari16k, NO)		// RealSports Boxing | ||||
| 	Record(@"200d04c1e7f41a5a3730287ed0c3f9293628f195", Atari8k, NO)		// RealSports Football | ||||
| 	Record(@"e3d964d918b7f2c420776acd3370ec1ee62744ea", Atari8k, NO)		// RealSports Soccer | ||||
| 	Record(@"702c1c7d985d0d22f935265bd284d1ed50df2527", Atari8k, NO)		// RealSports Tennis | ||||
| 	Record(@"2025e1d868595fad36e5d9e7384ffd24c206208d", None, NO)			// RealSports Volleyball | ||||
| 	Record(@"94e94810bf6c72eee49157f9218c3c170b65c836", None, NO)			// Rescue Terra I | ||||
| 	Record(@"f8a9dd46f9bad232f74d1ee2671ccb26ea1b3029", None, NO)			// Revenge of the Beefsteak Tomatoes | ||||
| 	Record(@"acb2430b4e6c72ce13f321d9d3a38986dc4768ef", None, NO)			// Riddle of the Sphinx | ||||
| 	Record(@"6715493dce54b22362741229078815b3360988ae", Tigervision, NO)	// River Patrol | ||||
| 	Record(@"40329780402f8247f294fe884ffc56cc3da0c62d", None, NO)			// River Raid copy | ||||
| 	Record(@"a08c3eae3368334c937a5e03329782e95f7b57c7", Atari16k, NO)		// River Raid II | ||||
| 	Record(@"325a2374800b2cb78ab7ff9e4017759865109d7d", None, NO)			// River Raid | ||||
| 	Record(@"7f9c2321c9f22cf2cdbcf1b3f0e563a1c53f68ca", Atari8k, NO)		// Robin Hood | ||||
| 	Record(@"f45dfcd6db0dae5458e1c0ae8eeaa75b553cdfec", Atari16k, NO)		// Road Runner | ||||
| 	Record(@"21a3ee57cb622f410ffd51986ab80acadb8d44b7", ActivisionStack, NO)// Robot Tank | ||||
| 	Record(@"0abf0a292d4a24df5a5ebe19a9729f3a8f883c8b", Atari8k, NO)		// Roc 'n Rope | ||||
| 	Record(@"fd243c480e769b20b7bf3e74bcd86e4ac99dab19", None, NO)			// Room of Doom | ||||
| 	Record(@"85752ac6eb7045a9083425cd166609882a1c2c58", Atari8k, NO)		// Saboteur | ||||
| 	Record(@"ecd8ef49ae23ddd3e10ec60839b95c8e7764ea27", Atari16k, YES)		// Save Mary! | ||||
| 	Record(@"e566d7b1f4eb6c2b110eb4fc676eb0ce9e90fe1e", None, NO)			// SCIScide 1.32 | ||||
| 	Record(@"8e9b320d8966315a8b07f1babc0ba2662f761102", None, NO)			// SCSIcide 1.30 | ||||
| 	Record(@"e8f5ae861ca1f410c6a9af116a96ed65d9a3abb2", None, NO)			// Scuba Diver | ||||
| 	Record(@"e5558ae30acc1fa5b4ffe782ae480622586a32ca", None, NO)			// Sea Hunt | ||||
| 	Record(@"4ec6982b2da25b29840428fd993a391e63f53730", None, NO)			// Seahawk | ||||
| 	Record(@"7324a1ebc695a477c8884718ffcad27732a98ab0", None, NO)			// Seaquest | ||||
| 	Record(@"1914f57ab0a6f221f2ad344b244a3cdd7b1d991a", None, NO)			// Secret Agent | ||||
| 	Record(@"af11f1666d345267196a1c35223727e2ef93483a", Atari16k, YES)		// Secret Quest | ||||
| 	Record(@"6518078b3786ac26f75067f0646aef4e83f2db15", None, NO)			// Self_Portrait_by_Tjoppen | ||||
| 	Record(@"fcf5f8a7d6e59a339c2002e3d4084d87deb670fe", Atari16k, NO)		// Sentinel | ||||
| 	Record(@"6e91759756c34f40a2c26936df6c0ca1a3850e80", None, NO)			// Shark Attack | ||||
| 	Record(@"cfb4b41e318c7cd0070e75e412f67c973e124d8e", None, NO)			// Shootin' Gallery | ||||
| 	Record(@"6e6daa34878d3e331c630359c7125a4ffba1b22d", Atari16k, YES)		// Shooting Arcade | ||||
| 	Record(@"08952192ea6bf0ef94373520a7e855f58bae6179", None, NO)			// Shuttle Orbiter | ||||
| 	Record(@"242fc23def80da96da22c2c7238d48635489abb0", Atari8k, NO)		// Sinistar | ||||
| 	Record(@"e9fa52f8e7f747cd9685ddb18bdeed2f66255324", Atari8k, NO)		// Sir Lancelot | ||||
| 	Record(@"a26fe0b5a43fe8116ab0ae6656d6b11644d871ec", Atari8k, NO)		// Skate Boardin' | ||||
| 	Record(@"5ea6d2eb27c76e85f477ba6c799deb7c416ebbc3", None, NO)			// Skeet Shoot | ||||
| 	Record(@"6581846f983b50cffb75d1c1b902238ba7dd4e92", None, NO)			// Skiing | ||||
| 	Record(@"4dde18d4abc139562fdd7a9d2fd49a1f00a9e64a", None, NO)			// Sky Diver | ||||
| 	Record(@"105f722dcf9a89b683c10ddd7f684c5966c8e1db", None, NO)			// Sky Jinks | ||||
| 	Record(@"fc5f1e30db3b2469c9701dadfa95f3268fd1e4cb", Atari8k, NO)		// Sky Patrol | ||||
| 	Record(@"ef0a7ecfe8f3b5d1e67a736552a0cdc472803be9", None, NO)			// Sky Skipper | ||||
| 	Record(@"7239d1c64f3dfc2a1613be325cce13803dd2baa5", None, NO)			// Slot Machine | ||||
| 	Record(@"a2b13017d759346174e3d8dd53b6347222d3b85d", None, NO)			// Slot Racers | ||||
| 	Record(@"530c7883fed4c5b9d78e35d48770b56e328999a3", Atari8k, NO)		// Smurfs: Rescue in Gargamel's Castle | ||||
| 	Record(@"c0ae3965fcfab0294f770af0af57d7d1adc17750", Atari8k, NO)		// Smurfs Save the Day | ||||
| 	Record(@"e7bf450cf3a3f40de9d24d89968a4bc89b53cb18", None, NO)			// Snail Against Squirrel | ||||
| 	Record(@"843e3c2fc71af2db3f2ae98eb350fde26334cfd1", None, NO)			// Sneak 'n Peak | ||||
| 	Record(@"972bc0a77e76f3e4e1270ec1c2fc395e9826bc07", Atari8k, NO)		// Snoopy and the Red Baron | ||||
| 	Record(@"9d725002e94b04e29d8cbce3c71d3bb2a84352fa", None, NO)			// Soccer | ||||
| 	Record(@"09ea74f14db8d21ea785d0c8209ed670e4ce88be", Atari8k, NO)		// Solar Fox | ||||
| 	Record(@"ec65ef9e47239a7d15db9bca7e625b166e8ac242", None, NO)			// Solar Storm | ||||
| 	Record(@"33b16fbc95c2cdc52d84d98ca471f10dae3f9dbf", Atari16k, NO)		// Solaris | ||||
| 	Record(@"ae3009e921f23254bb71f67c8cb2d7d6de2845a5", Atari8k, NO)		// Sorceror's Apprentice | ||||
| 	Record(@"70e912379060d834aa9fb2baa2e6a438f3b5d3b6", None, NO)			// Sorceror | ||||
| 	Record(@"560563613bc309a532d611f11a1cf2b9af1e2f16", None, NO)			// Space Attack | ||||
| 	Record(@"26c6c47e9b654e81f47875c5fcb4e6212125f329", None, NO)			// Space Canyon | ||||
| 	Record(@"b757b883ee114054c650027f3b9a8f15548cbf32", None, NO)			// Space Cavern | ||||
| 	Record(@"31d9668fe5812c3d2e076987ca327ac6b2e280bf", None, NO)			// Space Invaders | ||||
| 	Record(@"5bdd8af54020fa43065750bd4239a497695d403b", None, NO)			// Space Jockey | ||||
| 	Record(@"bcec5a66f8dff1a751769626b0fce305fab44ca2", Atari8k, NO)		// Space Shuttle | ||||
| 	Record(@"ce4432bb48921a3565d996b80b65fdf73bbfc39b", None, NO)			// Space Tunnel | ||||
| 	Record(@"23510ba617431097668eaf104aa1e36233173093", None, NO)			// Space War | ||||
| 	Record(@"b356294e35827bf81add95fee5453b0ca0f497ad", None, NO)			// Spacechase | ||||
| 	Record(@"983b1aff97ab1243e283ba62d3a6a75ad186d225", None, NO)			// SpaceMaster X-7 | ||||
| 	Record(@"06820ad3c957913847f9849d920bc8725f535f11", None, NO)			// Spider Fighter | ||||
| 	Record(@"5d6f918bba4bd046e85b707da3b7d643cc2e1f1f", None, NO)			// Spider Maze | ||||
| 	Record(@"60af23a860b33e1a85081b8de779d2ddfe36b19a", None, NO)			// Spider Monster | ||||
| 	Record(@"912c5f5571ac59a6782da412183cdd6277345816", None, NO)			// Spider-Man | ||||
| 	Record(@"904118b0c1be484782ec2a60a24436059608b36d", None, NO)			// Spiderdroid | ||||
| 	Record(@"205241a12778829981e9281d9c6fa137f11e1376", Atari8k, NO)		// Spike's Peak | ||||
| 	Record(@"165de0ebca628eb1e9f564390c9eedfe289c7a1d", None, NO)			// Spitfire Attack | ||||
| 	Record(@"6da0aa8aa40cd9c78dc014deb9074529688d91d0", Tigervision, NO)	// Springer | ||||
| 	Record(@"c0e29b86fc1cc41a1c8afa37572c3c5698ae70b2", Atari16k, YES)		// Sprint Master | ||||
| 	Record(@"1d0acf064d06a026a04b6028285db78c834e9854", Atari8k, NO)		// Spy Hunter | ||||
| 	Record(@"033148faebc97d4ed3a86c97fe0cdee21bd261f7", None, NO)			// Squeeze Box | ||||
| 	Record(@"11a9dd44787f011ec540159248377cb27fb8f7bb", None, NO)			// Squoosh | ||||
| 	Record(@"46aabde3074acded8890a2efa5586d6b8bd76b5d", None, NO)			// Sssnake | ||||
| 	Record(@"277184c4e61ced14393049a21a304e941d05993f", None, NO)			// Stampede | ||||
| 	Record(@"1b95e07437ddc1523d7ec21c460273e91dbf36c7", None, NO)			// Star Fox | ||||
| 	Record(@"3359aa7a6a5fa25beaa3ae5868d0034d52de9882", None, NO)			// Star Gunner | ||||
| 	Record(@"e10cce1a438c82bd499e1eb31a3f07d7254198f5", Atari8k, NO)		// Star Raiders | ||||
| 	Record(@"878e78ed46e29c44949d0904a2198826e412ed81", None, NO)			// Star Ship | ||||
| 	Record(@"de05d1ca8ad1e7a85df3faf25b1aa90b159afded", None, NO)			// Star Strike | ||||
| 	Record(@"61a3ebbffa0bfb761295c66e189b62915f4818d9", Atari8k, NO)		// Star Trek — Strategic Operations Simulator | ||||
| 	Record(@"ccc5b829c4aa71acb7976e741fdbf59c8ef9eb55", None, NO)			// Star Voyager | ||||
| 	Record(@"c9d201935bbe6373793241ba9c03cc02f1df31c9", ParkerBros, NO)		// Star Wars — Ewok Adventure | ||||
| 	Record(@"8823fe3d8e3aeadc6b61ca51914e3b15aa13801c", ParkerBros, NO)		// Star Wars — The Arcade Game | ||||
| 	Record(@"ad5b2c9df558ab23ad2954fe49ed5b37a06009bf", None, NO)			// Star Wars — The Empire Strikes Back | ||||
| 	Record(@"4f87be0ef16a1d0389226d1fbda9b4c16b06e13e", Atari8k, YES)		// Stargate | ||||
| 	Record(@"814876ed270114912363e4718a84123dee213b6f", None, NO)			// StarMaster | ||||
| 	Record(@"e56ef1c0313d6d04e25446c4e34f9bb7eda8efac", None, NO)			// Steeplechase copy | ||||
| 	Record(@"bd7f0005fa018f13ed7e942c83c1751fb746a317", None, NO)			// Steeplechase | ||||
| 	Record(@"de2c146fa7a701d6c37728f5415563ce923a3e5d", None, NO)			// Stella_Lives! | ||||
| 	Record(@"7d97d014c22a2ed3a5bc4b310f5a7be1b1d3520f", None, NO)			// Stellar Track | ||||
| 	Record(@"9d6decda6e8ab263f7380ff662c814b8cb8caf34", None, NO)			// Strategy X | ||||
| 	Record(@"7ca8f9cd74cfa505c493757ff37bf127ff467bb4", None, NO)			// Strawberry Shortcake — Musical Match-Ups | ||||
| 	Record(@"bffb3d41916c83398624151eb00aa2a3acd23ab8", None, NO)			// Street Racer | ||||
| 	Record(@"2cfe280fdbb6b5c8cda8a4620df12a5154e123be", None, NO)			// Stronghold | ||||
| 	Record(@"2e19d7e16cf17682b043baaa30e345e6fa4540e5", None, NO)			// Stunt Cycle | ||||
| 	Record(@"3aec7ea8af72bbe105b9d2903a92f5ad2b37bddb", None, NO)			// Stunt Man | ||||
| 	Record(@"ccd75f0141b917656ef2b86c068fba3238d18a0c", None, NO)			// Sub-Scan | ||||
| 	Record(@"b22ba7cbde60a21ecbbe3953cc4a5c0bf007cc26", None, NO)			// Submarine Commander | ||||
| 	Record(@"2abc6bbcab27985f19e42915530fd556b6b1ae23", Atari8k, NO)		// Subterrenea | ||||
| 	Record(@"65f4a708e6af565f1f75d0fbdc8942cb149cf299", Atari16k, NO)		// Summer Games | ||||
| 	Record(@"b066a60ea1df1db0a55271c7608b0e19e4d18a1e", Atari16k, NO)		// Super Baseball | ||||
| 	Record(@"e380e243c671e954e86aa1a3a0bfeb36d5e0c3e2", None, NO)			// Super Breakout | ||||
| 	Record(@"dfce4d6436f91d8d385f8b01f0d8e3488400407b", None, NO)			// Super Challenge Baseball | ||||
| 	Record(@"5c1338ec76828cfa4a85b5bd8db1c00c8095c330", None, NO)			// Super Challenge Football | ||||
| 	Record(@"bac0a0256509f8fd1feea93d74ba4c7d82c1edc6", ParkerBros, NO)		// Super Cobra | ||||
| 	Record(@"eaca6b474fd552ab4aaf75526618828165a91934", Atari16k, YES)		// Super Football | ||||
| 	Record(@"b9dee027c8d7dd2a46be111ab0b8363c1becc081", None, NO)			// Superman | ||||
| 	Record(@"cf84e21ada55730d689cfac7d26e2295317222bc", Atari8k, NO)		// Surf's Up | ||||
| 	Record(@"e754c8985ca7f5780c23a856656099b710e89919", None, NO)			// Surfer's Paradise | ||||
| 	Record(@"b7988373b81992d08056560d15d3e32d9d3888bc", None, NO)			// Surround | ||||
| 	Record(@"6c993b4c70cfed390f1f436fdbaa1f81495be18e", None, NO)			// Survival Run | ||||
| 	Record(@"e2b3b43cadf2f2c91c1ec615651ff9b1e295d065", None, NO)			// sv2k12 | ||||
| 	Record(@"0d59545b22e15019a33de16999a57dae1f998283", None, NO)			// Swordfight | ||||
| 	Record(@"05db3d09fa3dac80c70aae2e39f1ad7c31c62f02", Atari8k, NO)		// SwordQuest — EarthWorld | ||||
| 	Record(@"5c3cf976edbea5ded66634a284787f965616d97e", Atari8k, NO)		// SwordQuest — FireWorld | ||||
| 	Record(@"569fcb67ca1674b48e2f3a2e7af7077a374402de", Atari8k, NO)		// SwordQuest — WaterWorld | ||||
| 	Record(@"55e98fe14b07460734781a6aa2f4f1646830c0af", None, NO)			// Tac-Scan | ||||
| 	Record(@"13a9d86cbde32a1478ef0c7ef412427b13bd6222", None, NO)			// Tanks But No Tanks | ||||
| 	Record(@"ee8bc1710a67c33e9f95bb05cc3d8f841093fde2", None, NO)			// Tapeworm | ||||
| 	Record(@"e986e1818e747beb9b33ce4dff1cdc6b55bdb620", Atari8k, NO)		// Tapper | ||||
| 	Record(@"bae73700ba6532e9e6415b6471d115bdb7805464", None, NO)			// Task Force | ||||
| 	Record(@"7aaf6be610ba6ea1205bdd5ed60838ccb8280d57", Atari8k, NO)		// Tax Avoiders | ||||
| 	Record(@"476f0c565f54accecafd72c63b0464f469ed20ea", Atari8k, NO)		// Taz | ||||
| 	Record(@"7efc0ebe334dde84e25fa020ecde4fddcbea9e8f", Atari8k, NO)		// Telepathy | ||||
| 	Record(@"bf4d570c1c738a4d6d00237e25c62e9c3225f98f", Atari8k, NO)		// Tempest | ||||
| 	Record(@"3d30e7ed617168d852923def2000c9c0a8b728c6", None, NO)			// Tennis | ||||
| 	Record(@"53413577afe7def1d390e3892c45822405513c07", Atari8k, NO)		// The A-Team | ||||
| 	Record(@"1f834923eac271bf04c18621ac2aada68d426917", None, NO)			// The Earth Dies Screaming | ||||
| 	Record(@"5a641caa2ab3c7c0cd5deb027acbc58efccf8d6a", None, NO)			// The Music Machine | ||||
| 	Record(@"717122a4184bc8db41e65ab7c369c40b21c048d9", None, NO)			// The Texas Chainsaw Massacre | ||||
| 	Record(@"49bebad3e2eb210591be709a6ec7e4f0864265ab", None, NO)			// This Planet Sucks | ||||
| 	Record(@"9a52fa88bd7455044f00548e9615452131d1c711", None, NO)			// Threshold | ||||
| 	Record(@"09608cfaa7c6e9638f12a1cff9dd5036c9effa43", Atari16k, NO)		// Thrust | ||||
| 	Record(@"3cc8bcc0ff5164303433f469aa4da2eb256d1ad0", None, NO)			// Thunderground | ||||
| 	Record(@"53ee70d4b35ee3df3ffb95fa360bddb4f2f56ab2", ActivisionStack, NO)// Thwocker | ||||
| 	Record(@"387358514964d0b6b55f9431576a59b55869f7ab", Atari8k, NO)		// Time Pilot | ||||
| 	Record(@"979d9b0b0f32b40c0a0568be65a0bc5ef36ca6d0", Atari8k, NO)		// Title Match Pro Wrestling | ||||
| 	Record(@"fcd339065a012c9fe47efbec62969cbc32f3fbf0", Atari8k, NO)		// Tomarc the Barbarian | ||||
| 	Record(@"d82ac7237df54cc8688e3074b58433a7dd6b7d11", ParkerBros, NO)		// Tooth Protectors | ||||
| 	Record(@"b344b3e042447afbb3e40292dc4ca063d5d1110d", None, NO)			// Towering Inferno | ||||
| 	Record(@"005a6a53f5a856f0bdbca519af1ef236aaa1494d", Atari16k, NO)		// Track and Field | ||||
| 	Record(@"9a9917d82362c77b4d396f56966219fc248edf47", None, NO)			// Treasure Below | ||||
| 	Record(@"86c563db11db9afbffbd73c55e9fae9b2f69be4f", None, NO)			// Trick Shot | ||||
| 	Record(@"0ec58a3a5a27d1b82a5f9aabab02f9a8387b6956", None, NO)			// TRON — Deadly Discs | ||||
| 	Record(@"fc1a0b58765a7dcbd8e33562e1074ddd9e0ac624", CBSRamPlus, NO)		// Tunnel Runner | ||||
| 	Record(@"1162fe46977f01b4d25efab813e0d05ec90aeadc", None, NO)			// Turmoil | ||||
| 	Record(@"a4d6bac854a70d2c55946932f1511cc62db7d4aa", ParkerBros, NO)		// Tutankham | ||||
| 	Record(@"bd0ca4884c85db2323f5a4be5266aabb99d84542", None, NO)			// TVNoise.bin | ||||
| 	Record(@"286106fb973530bc3e2af13240f28c4bcb37e642", None, NO)			// Universal Chaos | ||||
| 	Record(@"6bde671a50330af154ed15e73fdba3fa55f23d87", Atari8k, NO)		// Up 'n Down | ||||
| 	Record(@"01475d037cb7a2a892be09d67083102fa9159216", Atari8k, NO)		// Vanguard | ||||
| 	Record(@"dce98883e813d77e03a5de975d4c52bfb34e7f77", None, NO)			// Vault Assault | ||||
| 	Record(@"17626ae7bfd10bcde14903040baee0923ecf41dd", None, NO)			// Venture II | ||||
| 	Record(@"0305dfc99bf192e53452a1e0408ccc148940afcd", None, NO)			// Venture | ||||
| 	Record(@"babae88a832b76d8c5af6ea63b8f10a0da5bb992", None, NO)			// Video Checkers | ||||
| 	Record(@"043ef523e4fcb9fc2fc2fda21f15671bf8620fc3", None, NO)			// Video Chess | ||||
| 	Record(@"1554b146d076b64776bf49136cea01f60eeba4c1", None, NO)			// Video Jogger | ||||
| 	Record(@"3b18db73933747851eba9a0ffa3c12b9f602a95c", CommaVid, NO)		// Video Life | ||||
| 	Record(@"1ffe89d79d55adabc0916b95cc37e18619ef7830", None, NO)			// Video Olympics | ||||
| 	Record(@"2c16c1a6374c8e22275d152d93dd31ffba26271f", None, NO)			// Video Pinball | ||||
| 	Record(@"dec2a3e9b366dce2b63dc1c13662d3f22420a22e", None, NO)			// Video Reflex | ||||
| 	Record(@"a345a5696f1d63a879e7bb7e3a74c825e97ef7c3", None, NO)			// Video Simon | ||||
| 	Record(@"2ebd0f43ee76833f75759ac1bbb45a8e0c3b86e9", None, NO)			// Vulture Attack | ||||
| 	Record(@"73072295721453065d62d9136343b81310a4d225", None, NO)			// Wabbit | ||||
| 	Record(@"482bd349222b8c702e125c27fd516e73af13967b", None, NO)			// Wall Ball | ||||
| 	Record(@"6de42bbc4766b26301e291ba00f7f7a9ac748639", None, NO)			// Wall Break | ||||
| 	Record(@"2d7563d337cbc0cdf4fc14f69853ab6757697788", None, NO)			// Warlords | ||||
| 	Record(@"232a370519a7fcce121e15f850d0d3671909f8b8", None, NO)			// Warplock | ||||
| 	Record(@"e325c5c0501ff527f06e6518526f9eefed309e89", None, NO)			// Waring Worms | ||||
| 	Record(@"472215fcb46cec905576d539efc8043488efc4ed", None, NO)			// Westward Ho | ||||
| 	Record(@"16df34446af2e6035ca871a00e1e8a008cfb8df4", Atari8k, NO)		// Wing War | ||||
| 	Record(@"6850d329e8ab403bdae38850665a2eff91278e92", Atari16k, NO)		// Winter Games | ||||
| 	Record(@"326e5e63a54ec6a0231fd38e62e352004d4719fe", None, NO)			// Wizard of Wor | ||||
| 	Record(@"e4b0f68abff3273cdd2b973639d607ae4a700adc", None, NO)			// Wizard | ||||
| 	Record(@"806c5a8a7b042a1a3ada1b6f29451a3446f93da3", None, NO)			// Word Zapper | ||||
| 	Record(@"36a1e73eb5aa5c3cd0b01af5117d19b8c36071e4", None, NO)			// Worm War 1 | ||||
| 	Record(@"1c5d151e86c0a0bbdf3b33ef153888c6be78c36b", None, NO)			// X-Man | ||||
| 	Record(@"160b6e36437ad6acbc2686fbde1002e2fa88c5fb", Atari16k, NO)		// Xenophobe | ||||
| 	Record(@"73133b81196e5cbc1cec99eefc1223ddb8f4ca83", Atari8k, NO)		// Xevious | ||||
| 	Record(@"6a1e0142c6886a6589a58e029e5aec6b72f7d27f", None, NO)			// Yahtzee | ||||
| 	Record(@"e2cd8996c1cf929e29130690024d1ec23d3b0bde", None, NO)			// Yars' Revenge | ||||
| 	Record(@"58c2f6abc5599cd35c0e722f24bcc128ac8f9a30", Atari8k, NO)		// Zaxxon | ||||
| }; | ||||
| #undef Record | ||||
|  | ||||
| @interface AtariStaticAnalyserTests : XCTestCase | ||||
| @end | ||||
|  | ||||
| @implementation AtariStaticAnalyserTests | ||||
|  | ||||
| - (void)testROMsOfSize:(NSInteger)size | ||||
| { | ||||
| 	NSString *basePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"Atari ROMs"]; | ||||
| 	for(NSString *testFile in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:basePath error:nil]) | ||||
| 	{ | ||||
| 		NSString *fullPath = [basePath stringByAppendingPathComponent:testFile]; | ||||
|  | ||||
| 		// get a SHA1 for the file | ||||
| 		NSData *fileData = [NSData dataWithContentsOfFile:fullPath]; | ||||
| 		if(size > 0 && [fileData length] != (NSUInteger)size) continue; | ||||
| 		uint8_t sha1Bytes[CC_SHA1_DIGEST_LENGTH]; | ||||
| 		CC_SHA1([fileData bytes], (CC_LONG)[fileData length], sha1Bytes); | ||||
| 		NSMutableString *sha1 = [[NSMutableString alloc] init]; | ||||
| 		for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]]; | ||||
|  | ||||
| 		// get an analysis of the file | ||||
| 		std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([fullPath UTF8String]); | ||||
|  | ||||
| 		// grab the ROM record | ||||
| 		AtariROMRecord *romRecord = romRecordsBySHA1[sha1]; | ||||
| 		if(!romRecord) continue; | ||||
|  | ||||
| 		// assert equality | ||||
| 		XCTAssert(targets.front().atari.paging_model == romRecord.pagingModel, @"%@; should be %d, is %d", testFile, romRecord.pagingModel, targets.front().atari.paging_model); | ||||
| 		XCTAssert(targets.front().atari.uses_superchip == romRecord.usesSuperchip, @"%@; should be %@", testFile, romRecord.usesSuperchip ? @"true" : @"false"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| - (void)testAtariROMs	{	[self testROMsOfSize:-1];		}	// This will duplicate all tests below, but also catch anything that isn't 2, 4, 8, 12, 16 or 32kb in size. | ||||
| - (void)test2kROMs		{	[self testROMsOfSize:2048];		} | ||||
| - (void)test4kROMs		{	[self testROMsOfSize:4096];		} | ||||
| - (void)test8kROMs		{	[self testROMsOfSize:8192];		} | ||||
| - (void)test12kROMs		{	[self testROMsOfSize:12288];	} | ||||
| - (void)test16kROMs		{	[self testROMsOfSize:16384];	} | ||||
| - (void)test32kROMs		{	[self testROMsOfSize:32768];	} | ||||
|  | ||||
| @end | ||||
							
								
								
									
										119
									
								
								OSBindings/Mac/Clock SignalTests/TIATests.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,119 @@ | ||||
| // | ||||
| //  TIATests.m | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 12/02/2017. | ||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <XCTest/XCTest.h> | ||||
|  | ||||
| #include "TIA.hpp" | ||||
|  | ||||
| static uint8_t *line; | ||||
| static void receive_line(uint8_t *next_line) | ||||
| { | ||||
| 	line = next_line; | ||||
| } | ||||
|  | ||||
| @interface TIATests : XCTestCase | ||||
| @end | ||||
|  | ||||
| @implementation TIATests { | ||||
| 	std::unique_ptr<Atari2600::TIA> _tia; | ||||
| } | ||||
|  | ||||
| - (void)setUp | ||||
| { | ||||
| 	[super setUp]; | ||||
| 	std::function<void(uint8_t *)> function = receive_line; | ||||
| 	_tia.reset(new Atari2600::TIA(function)); | ||||
| 	line = nullptr; | ||||
|  | ||||
| 	_tia->set_playfield(0, 0x00); | ||||
| 	_tia->set_playfield(1, 0x00); | ||||
| 	_tia->set_playfield(2, 0x00); | ||||
| 	_tia->set_player_graphic(0, 0x00); | ||||
| 	_tia->set_player_graphic(1, 0x00); | ||||
| 	_tia->set_ball_enable(false); | ||||
| 	_tia->set_missile_enable(0, false); | ||||
| 	_tia->set_missile_enable(1, false); | ||||
| } | ||||
|  | ||||
| - (void)testReflectedPlayfield | ||||
| { | ||||
| 	// set reflected, bit pattern 1000 | ||||
| 	_tia->set_playfield_control_and_ball_size(1); | ||||
| 	_tia->set_playfield(0, 0x10); | ||||
| 	_tia->set_playfield(1, 0xf0); | ||||
| 	_tia->set_playfield(2, 0x0e); | ||||
| 	_tia->run_for_cycles(228); | ||||
|  | ||||
| 	XCTAssert(line != nullptr, @"228 cycles should have ended the line"); | ||||
|  | ||||
| 	uint8_t expected_line[] = { | ||||
| 		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		1, 1, 1, 1 | ||||
| 	}; | ||||
| 	XCTAssert(!memcmp(expected_line, line, sizeof(expected_line))); | ||||
| } | ||||
|  | ||||
| - (void)testRepeatedPlayfield | ||||
| { | ||||
| 	// set reflected, bit pattern 1000 | ||||
| 	_tia->set_playfield_control_and_ball_size(0); | ||||
| 	_tia->set_playfield(0, 0x10); | ||||
| 	_tia->set_playfield(1, 0xf0); | ||||
| 	_tia->set_playfield(2, 0x0e); | ||||
|  | ||||
| 	_tia->run_for_cycles(228); | ||||
| 	XCTAssert(line != nullptr, @"228 cycles should have ended the line"); | ||||
|  | ||||
| 	uint8_t expected_line[] = { | ||||
| 		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		1, 1, 1, 1,		1, 1, 1, 1,		1, 1, 1, 1,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 	}; | ||||
| 	XCTAssert(!memcmp(expected_line, line, sizeof(expected_line))); | ||||
| } | ||||
|  | ||||
| - (void)testSinglePlayer | ||||
| { | ||||
| 	// set a player graphic, reset position so that it'll appear from column 1 | ||||
| 	_tia->set_player_graphic(0, 0xff); | ||||
| 	_tia->set_player_position(0); | ||||
|  | ||||
| 	_tia->run_for_cycles(228); | ||||
| 	uint8_t first_expected_line[] = { | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 	}; | ||||
| 	XCTAssert(line != nullptr, @"228 cycles should have ended the line"); | ||||
| 	XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line))); | ||||
| 	line = nullptr; | ||||
|  | ||||
| 	_tia->run_for_cycles(228); | ||||
| 	uint8_t second_expected_line[] = { | ||||
| 		0, 4, 4, 4,		4, 4, 4, 4,		4, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0,		0, 0, 0, 0, | ||||
| 	}; | ||||
| 	XCTAssert(line != nullptr, @"228 cycles should have ended the line"); | ||||
| 	XCTAssert(!memcmp(second_expected_line, line, sizeof(second_expected_line))); | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -14,8 +14,7 @@ | ||||
|  | ||||
| using namespace Outputs::CRT; | ||||
|  | ||||
| void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) | ||||
| { | ||||
| void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) { | ||||
| 	openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); | ||||
|  | ||||
| 	const unsigned int syncCapacityLineChargeThreshold = 2; | ||||
| @@ -29,15 +28,16 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di | ||||
| 																//	for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." | ||||
|  | ||||
| 	time_multiplier_ = IntermediateBufferWidth / cycles_per_line; | ||||
| 	phase_denominator_ = cycles_per_line * colour_cycle_denominator; | ||||
| 	phase_denominator_ = cycles_per_line * colour_cycle_denominator * time_multiplier_; | ||||
| 	phase_numerator_ = 0; | ||||
| 	colour_cycle_numerator_ = colour_cycle_numerator * time_multiplier_; | ||||
| 	colour_cycle_numerator_ = colour_cycle_numerator; | ||||
| 	phase_alternates_ = should_alternate; | ||||
| 	is_alernate_line_ &= phase_alternates_; | ||||
| 	cycles_per_line_ = cycles_per_line; | ||||
| 	unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; | ||||
|  | ||||
| 	// generate timing values implied by the given arbuments | ||||
| 	sync_capacitor_charge_threshold_ = (int)(syncCapacityLineChargeThreshold * multiplied_cycles_per_line); | ||||
| 	// generate timing values implied by the given arguments | ||||
| 	sync_capacitor_charge_threshold_ = ((int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4; | ||||
|  | ||||
| 	// create the two flywheels | ||||
| 	horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6)); | ||||
| @@ -50,16 +50,14 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di | ||||
| 	openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_); | ||||
| } | ||||
|  | ||||
| void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) | ||||
| { | ||||
| 	switch(displayType) | ||||
| 	{ | ||||
| void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) { | ||||
| 	switch(displayType) { | ||||
| 		case DisplayType::PAL50: | ||||
| 			set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true);	// i.e. 283.7516 | ||||
| 		break; | ||||
|  | ||||
| 		case DisplayType::NTSC60: | ||||
| 			set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2, false); | ||||
| 			set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, false);	// i.e. 227.5 | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| @@ -76,26 +74,22 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : | ||||
| 	is_alernate_line_(false) {} | ||||
|  | ||||
| CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth) : | ||||
| 	CRT(common_output_divisor, buffer_depth) | ||||
| { | ||||
| 		CRT(common_output_divisor, buffer_depth) { | ||||
| 	set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate); | ||||
| } | ||||
|  | ||||
| CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : | ||||
| 	CRT(common_output_divisor, buffer_depth) | ||||
| { | ||||
| 		CRT(common_output_divisor, buffer_depth) { | ||||
| 	set_new_display_type(cycles_per_line, displayType); | ||||
| } | ||||
|  | ||||
| #pragma mark - Sync loop | ||||
|  | ||||
| Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) | ||||
| { | ||||
| Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { | ||||
| 	return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced); | ||||
| } | ||||
|  | ||||
| Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) | ||||
| { | ||||
| Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { | ||||
| 	return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); | ||||
| } | ||||
|  | ||||
| @@ -112,10 +106,8 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, | ||||
| #define source_output_position_x2()	(*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2]) | ||||
| #define source_phase()				next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0] | ||||
| #define source_amplitude()			next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2] | ||||
| #define source_phase_time()			next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1] | ||||
|  | ||||
| void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type) | ||||
| { | ||||
| void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type) { | ||||
| 	std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock(); | ||||
| 	number_of_cycles *= time_multiplier_; | ||||
|  | ||||
| @@ -138,36 +130,26 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo | ||||
|  | ||||
| 		bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace()); | ||||
| 		uint8_t *next_run = nullptr; | ||||
| 		if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) | ||||
| 		{ | ||||
| 		if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) { | ||||
| 			next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize); | ||||
| 		} | ||||
|  | ||||
| 		if(next_run) | ||||
| 		{ | ||||
| 		if(next_run) { | ||||
| 			// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region | ||||
| 			source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position(); | ||||
| 			source_phase() = colour_burst_phase_; | ||||
| 			source_amplitude() = colour_burst_amplitude_; | ||||
| 			source_phase_time() = (uint8_t)colour_burst_time_; // assumption: burst was within the first 1/16 of the line | ||||
| 		} | ||||
|  | ||||
| 		// decrement the number of cycles left to run for and increment the | ||||
| 		// horizontal counter appropriately | ||||
| 		number_of_cycles -= next_run_length; | ||||
|  | ||||
| 		// either charge or deplete the vertical retrace capacitor (making sure it stops at 0) | ||||
| 		if(vsync_charging) | ||||
| 			sync_capacitor_charge_level_ += next_run_length; | ||||
| 		else | ||||
| 			sync_capacitor_charge_level_ = std::max(sync_capacitor_charge_level_ - (int)next_run_length, 0); | ||||
|  | ||||
| 		// react to the incoming event... | ||||
| 		horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None); | ||||
| 		vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None); | ||||
|  | ||||
| 		if(next_run) | ||||
| 		{ | ||||
| 		if(next_run) { | ||||
| 			source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); | ||||
| 		} | ||||
|  | ||||
| @@ -181,68 +163,58 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo | ||||
|  | ||||
| 		if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) is_alernate_line_ ^= phase_alternates_; | ||||
|  | ||||
| 		if(needs_endpoint) | ||||
| 		{ | ||||
| 		if(needs_endpoint) { | ||||
| 			if( | ||||
| 				!openGL_output_builder_.array_builder.is_full() && | ||||
| 				!openGL_output_builder_.composite_output_buffer_is_full()) | ||||
| 			{ | ||||
| 				if(!is_writing_composite_run_) | ||||
| 				{ | ||||
| 				!openGL_output_builder_.composite_output_buffer_is_full()) { | ||||
|  | ||||
| 				if(!is_writing_composite_run_) { | ||||
| 					output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position(); | ||||
| 					output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 				} else { | ||||
| 					// Get and write all those previously unwritten output ys | ||||
| 					const uint16_t output_y = openGL_output_builder_.get_composite_output_y(); | ||||
|  | ||||
| 					// Construct the output run | ||||
| 					uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize); | ||||
| 					if(next_run) | ||||
| 					{ | ||||
| 					if(next_run) { | ||||
| 						output_x1() = output_run_.x1; | ||||
| 						output_position_y() = output_run_.y; | ||||
| 						output_tex_y() = output_y; | ||||
| 						output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); | ||||
| 					} | ||||
| 					openGL_output_builder_.array_builder.flush( | ||||
| 						[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) | ||||
| 						{ | ||||
| 						[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) { | ||||
| 							openGL_output_builder_.texture_builder.flush( | ||||
| 								[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) | ||||
| 								{ | ||||
| 									for(size_t run = 0; run < number_of_write_areas; run++) | ||||
| 									{ | ||||
| 								[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) { | ||||
| 									for(size_t run = 0; run < number_of_write_areas; run++) { | ||||
| 										*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0] = write_areas[run].x; | ||||
| 										*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y; | ||||
| 										*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfEnds + 0] = write_areas[run].x + write_areas[run].length; | ||||
| 									} | ||||
| 								}); | ||||
| 							for(size_t position = 0; position < input_size; position += SourceVertexSize) | ||||
| 							{ | ||||
| 							for(size_t position = 0; position < input_size; position += SourceVertexSize) { | ||||
| 								(*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y; | ||||
| 							} | ||||
| 						}); | ||||
| 					colour_burst_amplitude_ = 0; | ||||
| 				} | ||||
| 				is_writing_composite_run_ ^= true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) | ||||
| 		{ | ||||
| 		if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { | ||||
| 			openGL_output_builder_.increment_composite_output_y(); | ||||
| 		} | ||||
|  | ||||
| 		// if this is vertical retrace then adcance a field | ||||
| 		if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) | ||||
| 		{ | ||||
| 			if(delegate_) | ||||
| 			{ | ||||
| 		if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) { | ||||
| 			if(delegate_) { | ||||
| 				frames_since_last_delegate_call_++; | ||||
| 				if(frames_since_last_delegate_call_ == 20) | ||||
| 				{ | ||||
| 				if(frames_since_last_delegate_call_ == 20) { | ||||
| 					output_lock.unlock(); | ||||
| 					delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises()); | ||||
| 					output_lock.lock(); | ||||
| 					frames_since_last_delegate_call_ = 0; | ||||
| 				} | ||||
| 			} | ||||
| @@ -267,42 +239,53 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo | ||||
|  | ||||
| #pragma mark - stream feeding methods | ||||
|  | ||||
| void CRT::output_scan(const Scan *const scan) | ||||
| { | ||||
| void CRT::output_scan(const Scan *const scan) { | ||||
| 	const bool this_is_sync = (scan->type == Scan::Type::Sync); | ||||
| 	const bool is_trailing_edge = (is_receiving_sync_ && !this_is_sync); | ||||
| 	const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync); | ||||
| 	is_receiving_sync_ = this_is_sync; | ||||
|  | ||||
| 	// Accumulate: (i) a total of the amount of time in sync; and (ii) the amount of time since sync. | ||||
| 	if(this_is_sync) { cycles_of_sync_ += scan->number_of_cycles; cycles_since_sync_ = 0; } | ||||
| 	else cycles_since_sync_ += scan->number_of_cycles; | ||||
|  | ||||
| 	bool vsync_requested = false; | ||||
| 	// If it has been at least half a line since sync ended, then it is safe to decide whether what ended | ||||
| 	// was vertical sync. | ||||
| 	if(cycles_since_sync_ > (cycles_per_line_ >> 1)) { | ||||
| 		// If it was vertical sync, set that flag. If it wasn't, clear the summed amount of sync to avoid | ||||
| 		// a mistaken vertical sync due to an aggregate of horizontals. | ||||
| 		vsync_requested = (cycles_of_sync_ > sync_capacitor_charge_threshold_); | ||||
| 		if(vsync_requested || cycles_of_sync_ < (cycles_per_line_ >> 2)) | ||||
| 			cycles_of_sync_ = 0; | ||||
| 	} | ||||
|  | ||||
| 	// This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not | ||||
| 	// recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek | ||||
| 	// the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal | ||||
| 	// flywheel. I'm currently unclear whether this is an accurate solution to this problem. | ||||
| 	const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync(); | ||||
| 	const bool vsync_requested = is_trailing_edge && (sync_capacitor_charge_level_ >= sync_capacitor_charge_threshold_); | ||||
|  | ||||
| 	// simplified colour burst logic: if it's within the back porch we'll take it | ||||
| 	if(scan->type == Scan::Type::ColourBurst) | ||||
| 	{ | ||||
| 		if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) | ||||
| 		{ | ||||
| 			colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time(); | ||||
| 			colour_burst_phase_ = scan->phase; | ||||
| 	if(scan->type == Scan::Type::ColourBurst) { | ||||
| 		if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) { | ||||
| 			unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_; | ||||
| 			colour_burst_phase_ = (position_phase + scan->phase) & 255; | ||||
| 			colour_burst_amplitude_ = scan->amplitude; | ||||
|  | ||||
| 			colour_burst_phase_ = (colour_burst_phase_ & ~63) + 32; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// TODO: inspect raw data for potential colour burst if required | ||||
|  | ||||
| 	sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0; | ||||
| 	advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, this_is_sync, scan->type); | ||||
| 	advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, scan->type); | ||||
| } | ||||
|  | ||||
| /* | ||||
| 	These all merely channel into advance_cycles, supplying appropriate arguments | ||||
| */ | ||||
| void CRT::output_sync(unsigned int number_of_cycles) | ||||
| { | ||||
| void CRT::output_sync(unsigned int number_of_cycles) { | ||||
| 	Scan scan{ | ||||
| 		.type = Scan::Type::Sync, | ||||
| 		.number_of_cycles = number_of_cycles | ||||
| @@ -310,8 +293,7 @@ void CRT::output_sync(unsigned int number_of_cycles) | ||||
| 	output_scan(&scan); | ||||
| } | ||||
|  | ||||
| void CRT::output_blank(unsigned int number_of_cycles) | ||||
| { | ||||
| void CRT::output_blank(unsigned int number_of_cycles) { | ||||
| 	Scan scan { | ||||
| 		.type = Scan::Type::Blank, | ||||
| 		.number_of_cycles = number_of_cycles | ||||
| @@ -319,8 +301,7 @@ void CRT::output_blank(unsigned int number_of_cycles) | ||||
| 	output_scan(&scan); | ||||
| } | ||||
|  | ||||
| void CRT::output_level(unsigned int number_of_cycles) | ||||
| { | ||||
| void CRT::output_level(unsigned int number_of_cycles) { | ||||
| 	Scan scan { | ||||
| 		.type = Scan::Type::Level, | ||||
| 		.number_of_cycles = number_of_cycles, | ||||
| @@ -328,8 +309,7 @@ void CRT::output_level(unsigned int number_of_cycles) | ||||
| 	output_scan(&scan); | ||||
| } | ||||
|  | ||||
| void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) | ||||
| { | ||||
| void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) { | ||||
| 	Scan scan { | ||||
| 		.type = Scan::Type::ColourBurst, | ||||
| 		.number_of_cycles = number_of_cycles, | ||||
| @@ -339,19 +319,17 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint | ||||
| 	output_scan(&scan); | ||||
| } | ||||
|  | ||||
| void CRT::output_default_colour_burst(unsigned int number_of_cycles) | ||||
| { | ||||
| void CRT::output_default_colour_burst(unsigned int number_of_cycles) { | ||||
| 	Scan scan { | ||||
| 		.type = Scan::Type::ColourBurst, | ||||
| 		.number_of_cycles = number_of_cycles, | ||||
| 		.phase = (uint8_t)((phase_numerator_ * 255) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)), | ||||
| 		.phase = (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)), | ||||
| 		.amplitude = 32 | ||||
| 	}; | ||||
| 	output_scan(&scan); | ||||
| } | ||||
|  | ||||
| void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) | ||||
| { | ||||
| void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { | ||||
| 	openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider); | ||||
| 	Scan scan { | ||||
| 		.type = Scan::Type::Data, | ||||
| @@ -360,8 +338,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider | ||||
| 	output_scan(&scan); | ||||
| } | ||||
|  | ||||
| Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) | ||||
| { | ||||
| Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) { | ||||
| 	first_cycle_after_sync *= time_multiplier_; | ||||
| 	number_of_cycles *= time_multiplier_; | ||||
|  | ||||
| @@ -397,13 +374,10 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_ | ||||
| 	// adjust to ensure aspect ratio is correct | ||||
| 	float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f); | ||||
| 	float ideal_width = height * adjusted_aspect_ratio; | ||||
| 	if(ideal_width > width) | ||||
| 	{ | ||||
| 	if(ideal_width > width) { | ||||
| 		start_x -= (ideal_width - width) * 0.5f; | ||||
| 		width = ideal_width; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		float ideal_height = width / adjusted_aspect_ratio; | ||||
| 		start_y -= (ideal_height - height) * 0.5f; | ||||
| 		height = ideal_height; | ||||
|   | ||||
| @@ -60,14 +60,13 @@ class CRT { | ||||
| 		void output_scan(const Scan *scan); | ||||
|  | ||||
| 		uint8_t colour_burst_phase_, colour_burst_amplitude_; | ||||
| 		uint16_t colour_burst_time_; | ||||
| 		bool is_writing_composite_run_; | ||||
|  | ||||
| 		unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_; | ||||
| 		bool is_alernate_line_, phase_alternates_; | ||||
|  | ||||
| 		// the outer entry point for dispatching output_sync, output_blank, output_level and output_data | ||||
| 		void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type); | ||||
| 		void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type); | ||||
|  | ||||
| 		// the inner entry point that determines whether and when the next sync event will occur within | ||||
| 		// the current output window | ||||
| @@ -82,10 +81,23 @@ class CRT { | ||||
| 			uint16_t x1, y; | ||||
| 		} output_run_; | ||||
|  | ||||
| 		// The delegate | ||||
| 		// the delegate | ||||
| 		Delegate *delegate_; | ||||
| 		unsigned int frames_since_last_delegate_call_; | ||||
|  | ||||
| 		// queued tasks for the OpenGL queue; performed before the next draw | ||||
| 		std::mutex function_mutex_; | ||||
| 		std::vector<std::function<void(void)>> enqueued_openGL_functions_; | ||||
| 		inline void enqueue_openGL_function(const std::function<void(void)> &function) { | ||||
| 			std::lock_guard<std::mutex> function_guard(function_mutex_); | ||||
| 			enqueued_openGL_functions_.push_back(function); | ||||
| 		} | ||||
|  | ||||
| 		// sync counter, for determining vertical sync | ||||
| 		unsigned int cycles_of_sync_; | ||||
| 		unsigned int cycles_since_sync_; | ||||
| 		unsigned int cycles_per_line_; | ||||
|  | ||||
| 	public: | ||||
| 		/*!	Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. | ||||
| 			The requested number of buffers, each with the requested number of bytes per pixel, | ||||
| @@ -194,8 +206,7 @@ class CRT { | ||||
| 			@param required_length The number of samples to allocate. | ||||
| 			@returns A pointer to the allocated area if room is available; @c nullptr otherwise. | ||||
| 		*/ | ||||
| 		inline uint8_t *allocate_write_area(size_t required_length) | ||||
| 		{ | ||||
| 		inline uint8_t *allocate_write_area(size_t required_length) { | ||||
| 			std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock(); | ||||
| 			return openGL_output_builder_.texture_builder.allocate_write_area(required_length); | ||||
| 		} | ||||
| @@ -203,8 +214,15 @@ class CRT { | ||||
| 		/*!	Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. | ||||
| 			The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. | ||||
| 		*/ | ||||
| 		inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) | ||||
| 		inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { | ||||
| 			{ | ||||
| 				std::lock_guard<std::mutex> function_guard(function_mutex_); | ||||
| 				for(std::function<void(void)> function : enqueued_openGL_functions_) | ||||
| 				{ | ||||
| 					function(); | ||||
| 				} | ||||
| 				enqueued_openGL_functions_.clear(); | ||||
| 			} | ||||
| 			openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty); | ||||
| 		} | ||||
|  | ||||
| @@ -215,9 +233,10 @@ class CRT { | ||||
| 			currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If | ||||
| 			@c false then the references are simply marked as invalid. | ||||
| 		*/ | ||||
| 		inline void set_openGL_context_will_change(bool should_delete_resources) | ||||
| 		{ | ||||
| 		inline void set_openGL_context_will_change(bool should_delete_resources) { | ||||
| 			enqueue_openGL_function([should_delete_resources, this] { | ||||
| 				openGL_output_builder_.set_openGL_context_will_change(should_delete_resources); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		/*!	Sets a function that will map from whatever data the machine provided to a composite signal. | ||||
| @@ -227,9 +246,10 @@ class CRT { | ||||
| 			that evaluates to the composite signal level as a function of a source buffer, sampling location, colour | ||||
| 			carrier phase and amplitude. | ||||
| 		*/ | ||||
| 		inline void set_composite_sampling_function(const char *shader) | ||||
| 		{ | ||||
| 		inline void set_composite_sampling_function(const std::string &shader) { | ||||
| 			enqueue_openGL_function([shader, this] { | ||||
| 				openGL_output_builder_.set_composite_sampling_function(shader); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		/*!	Sets a function that will map from whatever data the machine provided to an RGB signal. | ||||
| @@ -245,25 +265,27 @@ class CRT { | ||||
| 			* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and | ||||
| 			* `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking. | ||||
| 		*/ | ||||
| 		inline void set_rgb_sampling_function(const char *shader) | ||||
| 		{ | ||||
| 		inline void set_rgb_sampling_function(const std::string &shader) { | ||||
| 			enqueue_openGL_function([shader, this] { | ||||
| 				openGL_output_builder_.set_rgb_sampling_function(shader); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		inline void set_output_device(OutputDevice output_device) | ||||
| 		{ | ||||
| 		inline void set_output_device(OutputDevice output_device) { | ||||
| 			enqueue_openGL_function([output_device, this] { | ||||
| 				openGL_output_builder_.set_output_device(output_device); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		inline void set_visible_area(Rect visible_area) | ||||
| 		{ | ||||
| 		inline void set_visible_area(Rect visible_area) { | ||||
| 			enqueue_openGL_function([visible_area, this] { | ||||
| 				openGL_output_builder_.set_visible_area(visible_area); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio); | ||||
|  | ||||
| 		inline void set_delegate(Delegate *delegate) | ||||
| 		{ | ||||
| 		inline void set_delegate(Delegate *delegate) { | ||||
| 			delegate_ = delegate; | ||||
| 		} | ||||
| }; | ||||
|   | ||||
| @@ -12,36 +12,29 @@ using namespace Outputs::CRT; | ||||
|  | ||||
| ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) : | ||||
| 		output_(output_size, nullptr), | ||||
| 	input_(input_size, nullptr) | ||||
| {} | ||||
| 		input_(input_size, nullptr) {} | ||||
|  | ||||
| ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) : | ||||
| 		output_(output_size, submission_function), | ||||
| 	input_(input_size, submission_function) | ||||
| {} | ||||
| 		input_(input_size, submission_function) {} | ||||
|  | ||||
| bool ArrayBuilder::is_full() | ||||
| { | ||||
| bool ArrayBuilder::is_full() { | ||||
| 	bool was_full; | ||||
| 	was_full = is_full_; | ||||
| 	return was_full; | ||||
| } | ||||
|  | ||||
| uint8_t *ArrayBuilder::get_input_storage(size_t size) | ||||
| { | ||||
| uint8_t *ArrayBuilder::get_input_storage(size_t size) { | ||||
| 	return get_storage(size, input_); | ||||
| } | ||||
|  | ||||
| uint8_t *ArrayBuilder::get_output_storage(size_t size) | ||||
| { | ||||
| uint8_t *ArrayBuilder::get_output_storage(size_t size) { | ||||
| 	return get_storage(size, output_); | ||||
| } | ||||
|  | ||||
| void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function) | ||||
| { | ||||
| 	if(!is_full_) | ||||
| 	{ | ||||
| 		size_t input_size, output_size; | ||||
| void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function) { | ||||
| 	if(!is_full_) { | ||||
| 		size_t input_size = 0, output_size = 0; | ||||
| 		uint8_t *input = input_.get_unflushed(input_size); | ||||
| 		uint8_t *output = output_.get_unflushed(output_size); | ||||
| 		function(input, input_size, output, output_size); | ||||
| @@ -51,24 +44,20 @@ void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_s | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ArrayBuilder::bind_input() | ||||
| { | ||||
| void ArrayBuilder::bind_input() { | ||||
| 	input_.bind(); | ||||
| } | ||||
|  | ||||
| void ArrayBuilder::bind_output() | ||||
| { | ||||
| void ArrayBuilder::bind_output() { | ||||
| 	output_.bind(); | ||||
| } | ||||
|  | ||||
| ArrayBuilder::Submission ArrayBuilder::submit() | ||||
| { | ||||
| ArrayBuilder::Submission ArrayBuilder::submit() { | ||||
| 	ArrayBuilder::Submission submission; | ||||
|  | ||||
| 	submission.input_size = input_.submit(true); | ||||
| 	submission.output_size = output_.submit(false); | ||||
| 	if(is_full_) | ||||
| 	{ | ||||
| 	if(is_full_) { | ||||
| 		is_full_ = false; | ||||
| 		input_.reset(); | ||||
| 		output_.reset(); | ||||
| @@ -80,10 +69,8 @@ ArrayBuilder::Submission ArrayBuilder::submit() | ||||
| ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) : | ||||
| 		is_full(false), | ||||
| 		submission_function_(submission_function), | ||||
| 	allocated_data(0), flushed_data(0), submitted_data(0) | ||||
| { | ||||
| 	if(!submission_function_) | ||||
| 	{ | ||||
| 		allocated_data(0), flushed_data(0), submitted_data(0) { | ||||
| 	if(!submission_function_) { | ||||
| 		glGenBuffers(1, &buffer); | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, buffer); | ||||
| 		glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW); | ||||
| @@ -91,23 +78,19 @@ ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint | ||||
| 	data.resize(size); | ||||
| } | ||||
|  | ||||
| ArrayBuilder::Buffer::~Buffer() | ||||
| { | ||||
| ArrayBuilder::Buffer::~Buffer() { | ||||
| 	if(!submission_function_) | ||||
| 		glDeleteBuffers(1, &buffer); | ||||
| } | ||||
|  | ||||
| uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) | ||||
| { | ||||
| uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) { | ||||
| 	uint8_t *pointer = buffer.get_storage(size); | ||||
| 	if(!pointer) is_full_ = true; | ||||
| 	return pointer; | ||||
| } | ||||
|  | ||||
| uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) | ||||
| { | ||||
| 	if(is_full || allocated_data + size > data.size()) | ||||
| 	{ | ||||
| uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) { | ||||
| 	if(is_full || allocated_data + size > data.size()) { | ||||
| 		is_full = true; | ||||
| 		return nullptr; | ||||
| 	} | ||||
| @@ -116,21 +99,17 @@ uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) | ||||
| 	return pointer; | ||||
| } | ||||
|  | ||||
| uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) | ||||
| { | ||||
| 	if(is_full) | ||||
| 	{ | ||||
| uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) { | ||||
| 	if(is_full) { | ||||
| 		return nullptr; | ||||
| 	} | ||||
| 	size = allocated_data - flushed_data; | ||||
| 	return &data[flushed_data]; | ||||
| } | ||||
|  | ||||
| void ArrayBuilder::Buffer::flush() | ||||
| { | ||||
| 	if(submitted_data) | ||||
| 	{ | ||||
| 		memcpy(data.data(), &data[submitted_data], allocated_data - submitted_data); | ||||
| void ArrayBuilder::Buffer::flush() { | ||||
| 	if(submitted_data) { | ||||
| 		memmove(data.data(), &data[submitted_data], allocated_data - submitted_data); | ||||
| 		allocated_data -= submitted_data; | ||||
| 		flushed_data -= submitted_data; | ||||
| 		submitted_data = 0; | ||||
| @@ -139,13 +118,11 @@ void ArrayBuilder::Buffer::flush() | ||||
| 	flushed_data = allocated_data; | ||||
| } | ||||
|  | ||||
| size_t ArrayBuilder::Buffer::submit(bool is_input) | ||||
| { | ||||
| size_t ArrayBuilder::Buffer::submit(bool is_input) { | ||||
| 	size_t length = flushed_data; | ||||
| 	if(submission_function_) | ||||
| 	if(submission_function_) { | ||||
| 		submission_function_(is_input, data.data(), length); | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		glBindBuffer(GL_ARRAY_BUFFER, buffer); | ||||
| 		uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); | ||||
| 		memcpy(destination, data.data(), length); | ||||
| @@ -156,13 +133,11 @@ size_t ArrayBuilder::Buffer::submit(bool is_input) | ||||
| 	return length; | ||||
| } | ||||
|  | ||||
| void ArrayBuilder::Buffer::bind() | ||||
| { | ||||
| void ArrayBuilder::Buffer::bind() { | ||||
| 	glBindBuffer(GL_ARRAY_BUFFER, buffer); | ||||
| } | ||||
|  | ||||
| void ArrayBuilder::Buffer::reset() | ||||
| { | ||||
| void ArrayBuilder::Buffer::reset() { | ||||
| 	is_full = false; | ||||
| 	allocated_data = 0; | ||||
| 	flushed_data = 0; | ||||
|   | ||||
| @@ -36,7 +36,7 @@ const GLsizei InputBufferBuilderWidth = 2048; | ||||
| const GLsizei InputBufferBuilderHeight = 512; | ||||
|  | ||||
| // This is the size of the intermediate buffers used during composite to RGB conversion | ||||
| const GLsizei IntermediateBufferWidth = 4096; | ||||
| const GLsizei IntermediateBufferWidth = 2048; | ||||
| const GLsizei IntermediateBufferHeight = 512; | ||||
|  | ||||
| // Some internal buffer sizes | ||||
|   | ||||
| @@ -6,8 +6,9 @@ | ||||
| // | ||||
|  | ||||
| #include "CRT.hpp" | ||||
| #include <stdlib.h> | ||||
| #include <math.h> | ||||
|  | ||||
| #include <cstdlib> | ||||
| #include <cmath> | ||||
|  | ||||
| #include "CRTOpenGL.hpp" | ||||
| #include "../../../SignalProcessing/FIRFilter.hpp" | ||||
| @@ -16,29 +17,24 @@ | ||||
| using namespace Outputs::CRT; | ||||
|  | ||||
| namespace { | ||||
| 	static const GLenum composite_texture_unit			= GL_TEXTURE0; | ||||
| 	static const GLenum separated_texture_unit			= GL_TEXTURE1; | ||||
| 	static const GLenum filtered_y_texture_unit			= GL_TEXTURE2; | ||||
| 	static const GLenum filtered_texture_unit			= GL_TEXTURE3; | ||||
| 	static const GLenum source_data_texture_unit		= GL_TEXTURE4; | ||||
| 	static const GLenum pixel_accumulation_texture_unit	= GL_TEXTURE5; | ||||
| 	static const GLenum source_data_texture_unit		= GL_TEXTURE0; | ||||
| 	static const GLenum pixel_accumulation_texture_unit	= GL_TEXTURE1; | ||||
|  | ||||
| 	static const GLenum composite_texture_unit			= GL_TEXTURE2; | ||||
| 	static const GLenum separated_texture_unit			= GL_TEXTURE3; | ||||
| 	static const GLenum filtered_texture_unit			= GL_TEXTURE4; | ||||
|  | ||||
| 	static const GLenum work_texture_unit				= GL_TEXTURE2; | ||||
| } | ||||
|  | ||||
| OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : | ||||
| 		visible_area_(Rect(0, 0, 1, 1)), | ||||
| 		composite_src_output_y_(0), | ||||
| 	composite_shader_(nullptr), | ||||
| 	rgb_shader_(nullptr), | ||||
| 		last_output_width_(0), | ||||
| 		last_output_height_(0), | ||||
| 		fence_(nullptr), | ||||
| 		texture_builder(bytes_per_pixel, source_data_texture_unit), | ||||
| 	array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize), | ||||
| 	composite_texture_(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit), | ||||
| 	separated_texture_(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit), | ||||
| 	filtered_y_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit), | ||||
| 	filtered_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit) | ||||
| { | ||||
| 		array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) { | ||||
| 	glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); | ||||
| 	glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); | ||||
|  | ||||
| @@ -47,24 +43,43 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : | ||||
|  | ||||
| 	// create the source vertex array | ||||
| 	glGenVertexArrays(1, &source_vertex_array_); | ||||
|  | ||||
| 	bool supports_texture_barrier = false; | ||||
| #ifdef GL_NV_texture_barrier | ||||
| 	GLint number_of_extensions; | ||||
| 	glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions); | ||||
|  | ||||
| 	for(GLuint c = 0; c < (GLuint)number_of_extensions; c++) { | ||||
| 		const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c); | ||||
| 		if(!strcmp(extension_name, "GL_NV_texture_barrier")) { | ||||
| 			supports_texture_barrier = true; | ||||
| 		} | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| //	if(supports_texture_barrier) { | ||||
| //		work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit)); | ||||
| //	} else { | ||||
| 		composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST)); | ||||
| 		separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST)); | ||||
| 		filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR)); | ||||
| //	} | ||||
| } | ||||
|  | ||||
| OpenGLOutputBuilder::~OpenGLOutputBuilder() | ||||
| { | ||||
| OpenGLOutputBuilder::~OpenGLOutputBuilder() { | ||||
| 	glDeleteVertexArrays(1, &output_vertex_array_); | ||||
|  | ||||
| 	free(composite_shader_); | ||||
| 	free(rgb_shader_); | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) | ||||
| { | ||||
| bool OpenGLOutputBuilder::get_is_television_output() { | ||||
| 	return output_device_ == Television || !rgb_input_shader_program_; | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { | ||||
| 	// lock down any other draw_frames | ||||
| 	draw_mutex_.lock(); | ||||
|  | ||||
| 	// establish essentials | ||||
| 	if(!output_shader_program_) | ||||
| 	{ | ||||
| 	if(!output_shader_program_) { | ||||
| 		prepare_composite_input_shaders(); | ||||
| 		prepare_rgb_input_shaders(); | ||||
| 		prepare_source_vertex_array(); | ||||
| @@ -76,11 +91,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | ||||
| 		set_colour_space_uniforms(); | ||||
| 	} | ||||
|  | ||||
| 	if(fence_ != nullptr) | ||||
| 	{ | ||||
| 	if(fence_ != nullptr) { | ||||
| 		// if the GPU is still busy, don't wait; we'll catch it next time | ||||
| 		if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) | ||||
| 		{ | ||||
| 		if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) { | ||||
| 			draw_mutex_.unlock(); | ||||
| 			return; | ||||
| 		} | ||||
| @@ -89,11 +102,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | ||||
| 	} | ||||
|  | ||||
| 	// make sure there's a target to draw to | ||||
| 	if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) | ||||
| 	{ | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit)); | ||||
| 		if(framebuffer_) | ||||
| 		{ | ||||
| 	if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) { | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR)); | ||||
| 		if(framebuffer_) { | ||||
| 			new_framebuffer->bind_framebuffer(); | ||||
| 			glClear(GL_COLOR_BUFFER_BIT); | ||||
|  | ||||
| @@ -123,55 +134,62 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | ||||
| 	output_mutex_.unlock(); | ||||
|  | ||||
| 	struct RenderStage { | ||||
| 		OpenGL::TextureTarget *const target; | ||||
| 		OpenGL::Shader *const shader; | ||||
| 		OpenGL::TextureTarget *const target; | ||||
| 		float clear_colour[3]; | ||||
| 	}; | ||||
|  | ||||
| 	// for composite video, go through four steps to get to something that can be painted to the output | ||||
| 	RenderStage composite_render_stages[] = | ||||
| 	{ | ||||
| 		{&composite_texture_,	composite_input_shader_program_.get(),				{0.0, 0.0, 0.0}}, | ||||
| 		{&separated_texture_,	composite_separation_filter_program_.get(),			{0.0, 0.5, 0.5}}, | ||||
| 		{&filtered_y_texture_,	composite_y_filter_shader_program_.get(),			{0.0, 0.5, 0.5}}, | ||||
| 		{&filtered_texture_,	composite_chrominance_filter_shader_program_.get(),	{0.0, 0.0, 0.0}}, | ||||
| 	RenderStage composite_render_stages[] = { | ||||
| 		{composite_input_shader_program_.get(),					composite_texture_.get(),		{0.0, 0.0, 0.0}}, | ||||
| 		{composite_separation_filter_program_.get(),			separated_texture_.get(),		{0.0, 0.5, 0.5}}, | ||||
| 		{composite_chrominance_filter_shader_program_.get(),	filtered_texture_.get(),		{0.0, 0.0, 0.0}}, | ||||
| 		{nullptr} | ||||
| 	}; | ||||
|  | ||||
| 	// for RGB video, there's only two steps | ||||
| 	RenderStage rgb_render_stages[] = | ||||
| 	{ | ||||
| 		{&composite_texture_,	rgb_input_shader_program_.get(),	{0.0, 0.0, 0.0}}, | ||||
| 		{&filtered_texture_,	rgb_filter_shader_program_.get(),	{0.0, 0.0, 0.0}}, | ||||
| 	RenderStage rgb_render_stages[] = { | ||||
| 		{rgb_input_shader_program_.get(),	composite_texture_.get(),	{0.0, 0.0, 0.0}}, | ||||
| 		{rgb_filter_shader_program_.get(),	filtered_texture_.get(),	{0.0, 0.0, 0.0}}, | ||||
| 		{nullptr} | ||||
| 	}; | ||||
|  | ||||
| 	RenderStage *active_pipeline = (output_device_ == Television || !rgb_input_shader_program_) ? composite_render_stages : rgb_render_stages; | ||||
| 	RenderStage *active_pipeline = get_is_television_output() ? composite_render_stages : rgb_render_stages; | ||||
|  | ||||
| 	if(array_submission.input_size || array_submission.output_size) | ||||
| 	{ | ||||
| 	if(array_submission.input_size || array_submission.output_size) { | ||||
| 		// all drawing will be from the source vertex array and without blending | ||||
| 		glBindVertexArray(source_vertex_array_); | ||||
| 		glDisable(GL_BLEND); | ||||
|  | ||||
| 		while(active_pipeline->target) | ||||
| 		{ | ||||
| #ifdef GL_NV_texture_barrier | ||||
| 		if(work_texture_) { | ||||
| 			work_texture_->bind_framebuffer(); | ||||
| 			glClear(GL_COLOR_BUFFER_BIT); | ||||
| 		} | ||||
| #endif | ||||
|  | ||||
| 		while(active_pipeline->shader) { | ||||
| 			// switch to the framebuffer and shader associated with this stage | ||||
| 			active_pipeline->shader->bind(); | ||||
|  | ||||
| 			if(!work_texture_) { | ||||
| 				active_pipeline->target->bind_framebuffer(); | ||||
|  | ||||
| 				// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out | ||||
| 				// those portions for which no input was provided | ||||
| 			if(!active_pipeline[1].target) | ||||
| 			{ | ||||
| //				if(!active_pipeline[1].shader) { | ||||
| 					glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f); | ||||
| 					glClear(GL_COLOR_BUFFER_BIT); | ||||
| //				} | ||||
| 			} | ||||
|  | ||||
| 			// draw | ||||
| 			glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize); | ||||
| 			glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.input_size / SourceVertexSize); | ||||
|  | ||||
| 			active_pipeline++; | ||||
| #ifdef GL_NV_texture_barrier | ||||
| 			glTextureBarrierNV(); | ||||
| #endif | ||||
| 		} | ||||
|  | ||||
| 		// prepare to transfer to framebuffer | ||||
| @@ -182,8 +200,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | ||||
| 		glEnable(GL_BLEND); | ||||
|  | ||||
| 		// update uniforms, then bind the target | ||||
| 		if(last_output_width_ != output_width || last_output_height_ != output_height) | ||||
| 		{ | ||||
| 		if(last_output_width_ != output_width || last_output_height_ != output_height) { | ||||
| 			output_shader_program_->set_output_size(output_width, output_height, visible_area_); | ||||
| 			last_output_width_ = output_width; | ||||
| 			last_output_height_ = output_height; | ||||
| @@ -194,6 +211,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | ||||
| 		glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize); | ||||
| 	} | ||||
|  | ||||
| #ifdef GL_NV_texture_barrier | ||||
| 	glTextureBarrierNV(); | ||||
| #endif | ||||
|  | ||||
| 	// copy framebuffer to the intended place | ||||
| 	glDisable(GL_BLEND); | ||||
| 	glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||||
| @@ -207,11 +228,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | ||||
| 	draw_mutex_.unlock(); | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::reset_all_OpenGL_state() | ||||
| { | ||||
| void OpenGLOutputBuilder::reset_all_OpenGL_state() { | ||||
| 	composite_input_shader_program_ = nullptr; | ||||
| 	composite_separation_filter_program_ = nullptr; | ||||
| 	composite_y_filter_shader_program_ = nullptr; | ||||
| 	composite_chrominance_filter_shader_program_ = nullptr; | ||||
| 	rgb_input_shader_program_ = nullptr; | ||||
| 	rgb_filter_shader_program_ = nullptr; | ||||
| @@ -220,54 +239,52 @@ void OpenGLOutputBuilder::reset_all_OpenGL_state() | ||||
| 	last_output_width_ = last_output_height_ = 0; | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) | ||||
| { | ||||
| void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) { | ||||
| 	output_mutex_.lock(); | ||||
| 	reset_all_OpenGL_state(); | ||||
| 	output_mutex_.unlock(); | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader) | ||||
| { | ||||
| 	output_mutex_.lock(); | ||||
| 	composite_shader_ = strdup(shader); | ||||
| void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) { | ||||
| 	std::lock_guard<std::mutex> lock_guard(output_mutex_); | ||||
| 	composite_shader_ = shader; | ||||
| 	reset_all_OpenGL_state(); | ||||
| 	output_mutex_.unlock(); | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) | ||||
| { | ||||
| 	output_mutex_.lock(); | ||||
| 	rgb_shader_ = strdup(shader); | ||||
| void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) { | ||||
| 	std::lock_guard<std::mutex> lock_guard(output_mutex_); | ||||
| 	rgb_shader_ = shader; | ||||
| 	reset_all_OpenGL_state(); | ||||
| 	output_mutex_.unlock(); | ||||
| } | ||||
|  | ||||
| #pragma mark - Program compilation | ||||
|  | ||||
| void OpenGLOutputBuilder::prepare_composite_input_shaders() | ||||
| { | ||||
| void OpenGLOutputBuilder::prepare_composite_input_shaders() { | ||||
| 	composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_); | ||||
| 	composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit); | ||||
| 	composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | ||||
|  | ||||
| 	composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader(); | ||||
| 	composite_separation_filter_program_->set_source_texture_unit(composite_texture_unit); | ||||
| 	composite_separation_filter_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : composite_texture_unit); | ||||
| 	composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | ||||
|  | ||||
| 	composite_y_filter_shader_program_ = OpenGL::IntermediateShader::make_luma_filter_shader(); | ||||
| 	composite_y_filter_shader_program_->set_source_texture_unit(separated_texture_unit); | ||||
| 	composite_y_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | ||||
|  | ||||
| 	composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader(); | ||||
| 	composite_chrominance_filter_shader_program_->set_source_texture_unit(filtered_y_texture_unit); | ||||
| 	composite_chrominance_filter_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : separated_texture_unit); | ||||
| 	composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | ||||
|  | ||||
| 	if(work_texture_) { | ||||
| 		composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f); | ||||
| 		composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f); | ||||
| 		composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f); | ||||
| 	} else { | ||||
| 		composite_input_shader_program_->set_is_double_height(false); | ||||
| 		composite_separation_filter_program_->set_is_double_height(false); | ||||
| 		composite_chrominance_filter_shader_program_->set_is_double_height(false); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::prepare_rgb_input_shaders() | ||||
| { | ||||
| 	if(rgb_shader_) | ||||
| 	{ | ||||
| void OpenGLOutputBuilder::prepare_rgb_input_shaders() { | ||||
| 	if(rgb_shader_.size()) { | ||||
| 		rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_); | ||||
| 		rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit); | ||||
| 		rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | ||||
| @@ -278,10 +295,8 @@ void OpenGLOutputBuilder::prepare_rgb_input_shaders() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::prepare_source_vertex_array() | ||||
| { | ||||
| 	if(composite_input_shader_program_) | ||||
| 	{ | ||||
| void OpenGLOutputBuilder::prepare_source_vertex_array() { | ||||
| 	if(composite_input_shader_program_) { | ||||
| 		glBindVertexArray(source_vertex_array_); | ||||
| 		array_builder.bind_input(); | ||||
|  | ||||
| @@ -292,16 +307,15 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::prepare_output_shader() | ||||
| { | ||||
| void OpenGLOutputBuilder::prepare_output_shader() { | ||||
| 	output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); | ||||
| 	output_shader_program_->set_source_texture_unit(filtered_texture_unit); | ||||
| 	output_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : filtered_texture_unit); | ||||
| //	output_shader_program_->set_source_texture_unit(composite_texture_unit); | ||||
| 	output_shader_program_->set_origin_is_double_height(!!work_texture_); | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::prepare_output_vertex_array() | ||||
| { | ||||
| 	if(output_shader_program_) | ||||
| 	{ | ||||
| void OpenGLOutputBuilder::prepare_output_vertex_array() { | ||||
| 	if(output_shader_program_) { | ||||
| 		glBindVertexArray(output_vertex_array_); | ||||
| 		array_builder.bind_output(); | ||||
| 		output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1); | ||||
| @@ -311,19 +325,17 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() | ||||
|  | ||||
| #pragma mark - Public Configuration | ||||
|  | ||||
| void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) | ||||
| { | ||||
| 	if(output_device_ != output_device) | ||||
| 	{ | ||||
| void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { | ||||
| 	if(output_device_ != output_device) { | ||||
| 		output_device_ = output_device; | ||||
| 		composite_src_output_y_ = 0; | ||||
| 		last_output_width_ = 0; | ||||
| 		last_output_height_ = 0; | ||||
| 		set_output_shader_width(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) | ||||
| { | ||||
| void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) { | ||||
| 	output_mutex_.lock(); | ||||
| 	input_frequency_ = input_frequency; | ||||
| 	cycles_per_line_ = cycles_per_line; | ||||
| @@ -338,8 +350,7 @@ void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int | ||||
|  | ||||
| #pragma mark - Internal Configuration | ||||
|  | ||||
| void OpenGLOutputBuilder::set_colour_space_uniforms() | ||||
| { | ||||
| void OpenGLOutputBuilder::set_colour_space_uniforms() { | ||||
| 	GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; | ||||
| 	GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; | ||||
|  | ||||
| @@ -348,8 +359,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() | ||||
|  | ||||
| 	GLfloat *fromRGB, *toRGB; | ||||
|  | ||||
| 	switch(colour_space_) | ||||
| 	{ | ||||
| 	switch(colour_space_) { | ||||
| 		case ColourSpace::YIQ: | ||||
| 			fromRGB = rgbToYIQ; | ||||
| 			toRGB = yiqToRGB; | ||||
| @@ -362,30 +372,48 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() | ||||
| 	} | ||||
|  | ||||
| 	if(composite_input_shader_program_)					composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB); | ||||
| 	if(composite_separation_filter_program_)			composite_separation_filter_program_->set_colour_conversion_matrices(fromRGB, toRGB); | ||||
| 	if(composite_chrominance_filter_shader_program_)	composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB); | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::set_timing_uniforms() | ||||
| { | ||||
| 	OpenGL::IntermediateShader *intermediate_shaders[] = { | ||||
| 		composite_input_shader_program_.get(), | ||||
| 		composite_separation_filter_program_.get(), | ||||
| 		composite_y_filter_shader_program_.get(), | ||||
| 		composite_chrominance_filter_shader_program_.get() | ||||
| 	}; | ||||
| 	bool extends = false; | ||||
| 	float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_); | ||||
| 	for(int c = 0; c < 3; c++) | ||||
| 	{ | ||||
| 		if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends); | ||||
| 		extends = true; | ||||
| float OpenGLOutputBuilder::get_composite_output_width() const { | ||||
| 	return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth); | ||||
| } | ||||
|  | ||||
| 	if(output_shader_program_) output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_); | ||||
|  | ||||
| 	float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_; | ||||
| 	if(composite_separation_filter_program_)			composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency); | ||||
| 	if(composite_y_filter_shader_program_)				composite_y_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.25f); | ||||
| 	if(composite_chrominance_filter_shader_program_)	composite_chrominance_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.5f); | ||||
| 	if(rgb_filter_shader_program_)						rgb_filter_shader_program_->set_filter_coefficients(cycles_per_line_, (float)input_frequency_ * 0.5f); | ||||
| void OpenGLOutputBuilder::set_output_shader_width() { | ||||
| 	if(output_shader_program_) { | ||||
| 		const float width = get_is_television_output() ? get_composite_output_width() : 1.0f; | ||||
| 		output_shader_program_->set_input_width_scaler(width); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OpenGLOutputBuilder::set_timing_uniforms() { | ||||
| 	const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_; | ||||
| 	const float output_width = get_composite_output_width(); | ||||
| 	const float sample_cycles_per_line = cycles_per_line_ / output_width; | ||||
|  | ||||
| 	if(composite_separation_filter_program_) { | ||||
| 		composite_separation_filter_program_->set_width_scalers(output_width, output_width); | ||||
| 		composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency); | ||||
| 		composite_separation_filter_program_->set_extension(6.0f); | ||||
| 	} | ||||
| 	if(composite_chrominance_filter_shader_program_) { | ||||
| 		composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width); | ||||
| 		composite_chrominance_filter_shader_program_->set_extension(5.0f); | ||||
| 	} | ||||
| 	if(rgb_filter_shader_program_) { | ||||
| 		rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f); | ||||
| 		rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, (float)input_frequency_ * 0.5f); | ||||
| 	} | ||||
| 	if(output_shader_program_) { | ||||
| 		set_output_shader_width(); | ||||
| 		output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_); | ||||
| 	} | ||||
| 	if(composite_input_shader_program_) { | ||||
| 		composite_input_shader_program_->set_width_scalers(1.0f, output_width); | ||||
| 		composite_input_shader_program_->set_extension(0.0f); | ||||
| 	} | ||||
| 	if(rgb_input_shader_program_) { | ||||
| 		rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -47,8 +47,8 @@ class OpenGLOutputBuilder { | ||||
| 		Rect visible_area_; | ||||
|  | ||||
| 		// Other things the caller may have provided. | ||||
| 		char *composite_shader_; | ||||
| 		char *rgb_shader_; | ||||
| 		std::string composite_shader_; | ||||
| 		std::string rgb_shader_; | ||||
|  | ||||
| 		// Methods used by the OpenGL code | ||||
| 		void prepare_output_shader(); | ||||
| @@ -66,13 +66,19 @@ class OpenGLOutputBuilder { | ||||
| 		GLsizei composite_src_output_y_; | ||||
|  | ||||
| 		std::unique_ptr<OpenGL::OutputShader> output_shader_program_; | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_; | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_, rgb_filter_shader_program_; | ||||
|  | ||||
| 		OpenGL::TextureTarget composite_texture_;	// receives raw composite levels | ||||
| 		OpenGL::TextureTarget separated_texture_;	// receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B | ||||
| 		OpenGL::TextureTarget filtered_y_texture_;	// receives filtered Y in the R channel plus unfiltered chrominance in G and B | ||||
| 		OpenGL::TextureTarget filtered_texture_;	// receives filtered YIQ or YUV | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_; | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> composite_separation_filter_program_; | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> composite_chrominance_filter_shader_program_; | ||||
|  | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_; | ||||
| 		std::unique_ptr<OpenGL::IntermediateShader> rgb_filter_shader_program_; | ||||
|  | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> composite_texture_;	// receives raw composite levels | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> separated_texture_;	// receives filtered Y in the R channel plus unfiltered but demodulated chrominance in G and B | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> filtered_texture_;	// receives filtered YIQ or YUV | ||||
|  | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> work_texture_;		// used for all intermediate rendering if texture fences are supported | ||||
|  | ||||
| 		std::unique_ptr<OpenGL::TextureTarget> framebuffer_;		// the current pixel output | ||||
|  | ||||
| @@ -88,6 +94,9 @@ class OpenGLOutputBuilder { | ||||
| 		void reset_all_OpenGL_state(); | ||||
|  | ||||
| 		GLsync fence_; | ||||
| 		float get_composite_output_width() const; | ||||
| 		void set_output_shader_width(); | ||||
| 		bool get_is_television_output(); | ||||
|  | ||||
| 	public: | ||||
| 		// These two are protected by output_mutex_. | ||||
| @@ -97,8 +106,7 @@ class OpenGLOutputBuilder { | ||||
| 		OpenGLOutputBuilder(size_t bytes_per_pixel); | ||||
| 		~OpenGLOutputBuilder(); | ||||
|  | ||||
| 		inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) | ||||
| 		{ | ||||
| 		inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) { | ||||
| 			std::lock_guard<std::mutex> output_guard(output_mutex_); | ||||
| 			colour_space_ = colour_space; | ||||
| 			colour_cycle_numerator_ = colour_cycle_numerator; | ||||
| @@ -106,41 +114,35 @@ class OpenGLOutputBuilder { | ||||
| 			set_colour_space_uniforms(); | ||||
| 		} | ||||
|  | ||||
| 		inline void set_visible_area(Rect visible_area) | ||||
| 		{ | ||||
| 		inline void set_visible_area(Rect visible_area) { | ||||
| 			visible_area_ = visible_area; | ||||
| 		} | ||||
|  | ||||
| 		inline std::unique_lock<std::mutex> get_output_lock() | ||||
| 		{ | ||||
| 		inline std::unique_lock<std::mutex> get_output_lock() { | ||||
| 			return std::unique_lock<std::mutex>(output_mutex_); | ||||
| 		} | ||||
|  | ||||
| 		inline OutputDevice get_output_device() | ||||
| 		{ | ||||
| 		inline OutputDevice get_output_device() { | ||||
| 			return output_device_; | ||||
| 		} | ||||
|  | ||||
| 		inline uint16_t get_composite_output_y() | ||||
| 		{ | ||||
| 		inline uint16_t get_composite_output_y() { | ||||
| 			return (uint16_t)composite_src_output_y_; | ||||
| 		} | ||||
|  | ||||
| 		inline bool composite_output_buffer_is_full() | ||||
| 		{ | ||||
| 		inline bool composite_output_buffer_is_full() { | ||||
| 			return composite_src_output_y_ == IntermediateBufferHeight; | ||||
| 		} | ||||
|  | ||||
| 		inline void increment_composite_output_y() | ||||
| 		{ | ||||
| 		inline void increment_composite_output_y() { | ||||
| 			if(!composite_output_buffer_is_full()) | ||||
| 				composite_src_output_y_++; | ||||
| 		} | ||||
|  | ||||
| 		void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); | ||||
| 		void set_openGL_context_will_change(bool should_delete_resources); | ||||
| 		void set_composite_sampling_function(const char *shader); | ||||
| 		void set_rgb_sampling_function(const char *shader); | ||||
| 		void set_composite_sampling_function(const std::string &shader); | ||||
| 		void set_rgb_sampling_function(const std::string &shader); | ||||
| 		void set_output_device(OutputDevice output_device); | ||||
| 		void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); | ||||
| }; | ||||
|   | ||||
| @@ -21,8 +21,7 @@ namespace CRT { | ||||
|  | ||||
| 	The @c Flywheel will attempt to converge with timing implied by synchronisation pulses. | ||||
| */ | ||||
| struct Flywheel | ||||
| { | ||||
| struct Flywheel { | ||||
| 	/*! | ||||
| 		Constructs an instance of @c Flywheel. | ||||
|  | ||||
| @@ -61,26 +60,18 @@ struct Flywheel | ||||
|  | ||||
| 		@returns The next synchronisation event. | ||||
| 	*/ | ||||
| 	inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) | ||||
| 	{ | ||||
| 	inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { | ||||
| 		// do we recognise this hsync, thereby adjusting future time expectations? | ||||
| 		if(sync_is_requested) | ||||
| 		{ | ||||
| 			if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) | ||||
| 			{ | ||||
| 		if(sync_is_requested) { | ||||
| 			if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) { | ||||
| 				unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_; | ||||
| 				expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				number_of_surprises_++; | ||||
|  | ||||
| 				if(counter_ < retrace_time_ + (expected_next_sync_ >> 1)) | ||||
| 				{ | ||||
| 				if(counter_ < retrace_time_ + (expected_next_sync_ >> 1)) { | ||||
| 					expected_next_sync_ = (3*expected_next_sync_ + standard_period_ + sync_error_window_) >> 2; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 				} else { | ||||
| 					expected_next_sync_ = (3*expected_next_sync_ + standard_period_ - sync_error_window_) >> 2; | ||||
| 				} | ||||
| 			} | ||||
| @@ -90,15 +81,13 @@ struct Flywheel | ||||
| 		unsigned int proposed_sync_time = cycles_to_run_for; | ||||
|  | ||||
| 		// will we end an ongoing retrace? | ||||
| 		if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) | ||||
| 		{ | ||||
| 		if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) { | ||||
| 			proposed_sync_time = retrace_time_ - counter_; | ||||
| 			proposed_event = SyncEvent::EndRetrace; | ||||
| 		} | ||||
|  | ||||
| 		// will we start a retrace? | ||||
| 		if(counter_ + proposed_sync_time >= expected_next_sync_) | ||||
| 		{ | ||||
| 		if(counter_ + proposed_sync_time >= expected_next_sync_) { | ||||
| 			proposed_sync_time = expected_next_sync_ - counter_; | ||||
| 			proposed_event = SyncEvent::StartRetrace; | ||||
| 		} | ||||
| @@ -115,12 +104,10 @@ struct Flywheel | ||||
|  | ||||
| 		@param event The synchronisation event to apply after that period. | ||||
| 	*/ | ||||
| 	inline void apply_event(unsigned int cycles_advanced, SyncEvent event) | ||||
| 	{ | ||||
| 	inline void apply_event(unsigned int cycles_advanced, SyncEvent event) { | ||||
| 		counter_ += cycles_advanced; | ||||
|  | ||||
| 		switch(event) | ||||
| 		{ | ||||
| 		switch(event) { | ||||
| 			default: return; | ||||
| 			case StartRetrace: | ||||
| 				counter_before_retrace_ = counter_ - retrace_time_; | ||||
| @@ -135,10 +122,8 @@ struct Flywheel | ||||
|  | ||||
| 		@returns The current output position. | ||||
| 	*/ | ||||
| 	inline unsigned int get_current_output_position() | ||||
| 	{ | ||||
| 		if(counter_ < retrace_time_) | ||||
| 		{ | ||||
| 	inline unsigned int get_current_output_position() { | ||||
| 		if(counter_ < retrace_time_) { | ||||
| 			unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_; | ||||
| 			if(retrace_distance > counter_before_retrace_) return 0; | ||||
| 			return counter_before_retrace_ - retrace_distance; | ||||
| @@ -150,32 +135,28 @@ struct Flywheel | ||||
| 	/*! | ||||
| 		@returns the amount of time since retrace last began. Time then counts monotonically up from zero. | ||||
| 	*/ | ||||
| 	inline unsigned int get_current_time() | ||||
| 	{ | ||||
| 	inline unsigned int get_current_time() { | ||||
| 		return counter_; | ||||
| 	} | ||||
|  | ||||
| 	/*! | ||||
| 		@returns whether the output is currently retracing. | ||||
| 	*/ | ||||
| 	inline bool is_in_retrace() | ||||
| 	{ | ||||
| 	inline bool is_in_retrace() { | ||||
| 		return counter_ < retrace_time_; | ||||
| 	} | ||||
|  | ||||
| 	/*! | ||||
| 		@returns the expected length of the scan period (excluding retrace). | ||||
| 	*/ | ||||
| 	inline unsigned int get_scan_period() | ||||
| 	{ | ||||
| 	inline unsigned int get_scan_period() { | ||||
| 		return standard_period_ - retrace_time_; | ||||
| 	} | ||||
|  | ||||
| 	/*! | ||||
| 		@returns the expected length of a complete scan and retrace cycle. | ||||
| 	*/ | ||||
| 	inline unsigned int get_standard_period() | ||||
| 	{ | ||||
| 	inline unsigned int get_standard_period() { | ||||
| 		return standard_period_; | ||||
| 	} | ||||
|  | ||||
| @@ -183,8 +164,7 @@ struct Flywheel | ||||
| 		@returns the number of synchronisation events that have seemed surprising since the last time this method was called; | ||||
| 		a low number indicates good synchronisation. | ||||
| 	*/ | ||||
| 	inline unsigned int get_and_reset_number_of_surprises() | ||||
| 	{ | ||||
| 	inline unsigned int get_and_reset_number_of_surprises() { | ||||
| 		unsigned int result = number_of_surprises_; | ||||
| 		number_of_surprises_ = 0; | ||||
| 		return result; | ||||
| @@ -193,8 +173,7 @@ struct Flywheel | ||||
| 	/*! | ||||
| 		@returns `true` if a sync is expected soon or the time at which it was expected was recent. | ||||
| 	*/ | ||||
| 	inline bool is_near_expected_sync() | ||||
| 	{ | ||||
| 	inline bool is_near_expected_sync() { | ||||
| 		return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
| 	#else | ||||
| 		#include <OpenGL/OpenGL.h> | ||||
| 		#include <OpenGL/gl3.h> | ||||
| 		#include <OpenGL/gl3ext.h> | ||||
| 	#endif | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -16,8 +16,7 @@ | ||||
| using namespace OpenGL; | ||||
|  | ||||
| namespace { | ||||
| 	const OpenGL::Shader::AttributeBinding bindings[] = | ||||
| 	{ | ||||
| 	const OpenGL::Shader::AttributeBinding bindings[] = { | ||||
| 		{"inputPosition", 0}, | ||||
| 		{"outputPosition", 1}, | ||||
| 		{"phaseAndAmplitude", 2}, | ||||
| @@ -26,8 +25,7 @@ namespace { | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition) | ||||
| { | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition) { | ||||
| 	const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D"; | ||||
| 	const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition"; | ||||
|  | ||||
| @@ -40,11 +38,14 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char * | ||||
| 		"in vec2 ends;" | ||||
| 		"in vec3 phaseTimeAndAmplitude;" | ||||
|  | ||||
| 		"uniform float phaseCyclesPerTick;" | ||||
| 		"uniform ivec2 outputTextureSize;" | ||||
| 		"uniform float extension;" | ||||
| 		"uniform %s texID;" | ||||
| 		"uniform float offsets[5];" | ||||
| 		"uniform vec2 widthScalers;" | ||||
| 		"uniform float inputVerticalOffset;" | ||||
| 		"uniform float outputVerticalOffset;" | ||||
| 		"uniform float textureHeightDivisor;" | ||||
|  | ||||
| 		"out vec2 phaseAndAmplitudeVarying;" | ||||
| 		"out vec2 inputPositionsVarying[11];" | ||||
| @@ -53,35 +54,51 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char * | ||||
|  | ||||
| 		"void main(void)" | ||||
| 		"{" | ||||
| 			// odd vertices are on the left, even on the right | ||||
| 			"float extent = float(gl_VertexID & 1);" | ||||
| 			"float longitudinal = float((gl_VertexID & 2) >> 1);" | ||||
|  | ||||
| 			"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent), inputStart.y);" | ||||
| 			"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent), outputStart.y);" | ||||
| 			// inputPosition.x is either inputStart.x or ends.x, depending on whether it is on the left or the right; | ||||
| 			// outputPosition.x is either outputStart.x or ends.y; | ||||
| 			// .ys are inputStart.y and outputStart.y respectively | ||||
| 			"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent)*widthScalers[0], inputStart.y + inputVerticalOffset);" | ||||
| 			"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent)*widthScalers[1], outputStart.y + outputVerticalOffset);" | ||||
|  | ||||
| 			"inputPosition.y += longitudinal;" | ||||
| 			"outputPosition.y += longitudinal;" | ||||
|  | ||||
| 			// extension is the amount to extend both the input and output by to add a full colour cycle at each end | ||||
| 			"vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (extent - 0.5);" | ||||
|  | ||||
| 			// extended[Input/Output]Position are [input/output]Position with the necessary applied extension | ||||
| 			"vec2 extendedInputPosition = %s + extensionVector;" | ||||
| 			"vec2 extendedOutputPosition = outputPosition + extensionVector;" | ||||
|  | ||||
| 			// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range | ||||
| 			"vec2 textureSize = vec2(textureSize(texID, 0));" | ||||
| 			"iInputPositionVarying = extendedInputPosition;" | ||||
| 			"vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" | ||||
| 			"vec2 mappedInputPosition = extendedInputPosition / textureSize;"	//  + vec2(0.0, 0.5) | ||||
|  | ||||
| 			"inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);" | ||||
| 			// setup input positions spaced as per the supplied offsets; these are for filtering where required | ||||
| 			"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[1] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[2] = mappedInputPosition - (vec2(3.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[3] = mappedInputPosition - (vec2(2.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[4] = mappedInputPosition - (vec2(1.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[5] = mappedInputPosition;" | ||||
| 			"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[6] = mappedInputPosition + (vec2(1.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[7] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[8] = mappedInputPosition + (vec2(3.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[9] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);" | ||||
| 			"inputPositionsVarying[10] = mappedInputPosition + (vec2(5.0, 0.0) / textureSize);" | ||||
| 			"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);" | ||||
|  | ||||
| 			"phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTimeAndAmplitude.y) + (phaseTimeAndAmplitude.x / 256.0)) * 2.0 * 3.141592654;" | ||||
| 			// setup phaseAndAmplitudeVarying.x as colour burst subcarrier phase, in radians; | ||||
| 			// setup phaseAndAmplitudeVarying.x as colour burst amplitude | ||||
| 			"phaseAndAmplitudeVarying.x = (extendedOutputPosition.x + (phaseTimeAndAmplitude.x / 64.0)) * 0.5 * 3.141592654;" | ||||
| 			"phaseAndAmplitudeVarying.y = 0.33;" // TODO: reinstate connection with (phaseTimeAndAmplitude.y/256.0) | ||||
|  | ||||
| 			// determine output position by scaling the output position according to the texture size | ||||
| 			"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(1.0)/outputTextureSize;" | ||||
| 			"gl_Position = vec4(eyePosition, 0.0, 1.0);" | ||||
| 		"}", sampler_type, input_variable); | ||||
| @@ -92,12 +109,11 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char * | ||||
| 	return shader; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader) | ||||
| { | ||||
| 	char *composite_sample = (char *)composite_shader; | ||||
| 	if(!composite_sample) | ||||
| 	{ | ||||
| 		asprintf(&composite_sample, | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader) { | ||||
| 	char *derived_composite_sample = nullptr; | ||||
| 	const char *composite_sample = composite_shader.c_str(); | ||||
| 	if(!composite_shader.size()) { | ||||
| 		asprintf(&derived_composite_sample, | ||||
| 			"%s\n" | ||||
| 			"uniform mat3 rgbToLumaChroma;" | ||||
| 			"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||
| @@ -107,7 +123,8 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s | ||||
| 				"vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;" | ||||
| 				"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));" | ||||
| 			"}", | ||||
| 			rgb_shader); | ||||
| 			rgb_shader.c_str()); | ||||
| 		composite_sample = derived_composite_sample; | ||||
| 	} | ||||
|  | ||||
| 	char *fragment_shader; | ||||
| @@ -129,7 +146,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s | ||||
| 			"fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));" | ||||
| 		"}" | ||||
| 	, composite_sample); | ||||
| 	if(!composite_shader) free(composite_sample); | ||||
| 	free(derived_composite_sample); | ||||
|  | ||||
| 	std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true); | ||||
| 	free(fragment_shader); | ||||
| @@ -137,8 +154,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_s | ||||
| 	return shader; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const char *rgb_shader) | ||||
| { | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const std::string &rgb_shader) { | ||||
| 	char *fragment_shader; | ||||
| 	asprintf(&fragment_shader, | ||||
| 		"#version 150\n" | ||||
| @@ -157,7 +173,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c | ||||
| 		"{" | ||||
| 			"fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);" | ||||
| 		"}" | ||||
| 	, rgb_shader); | ||||
| 	, rgb_shader.c_str()); | ||||
|  | ||||
| 	std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true); | ||||
| 	free(fragment_shader); | ||||
| @@ -165,14 +181,12 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(c | ||||
| 	return shader; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader() | ||||
| { | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader() { | ||||
| 	return make_shader( | ||||
| 		"#version 150\n" | ||||
|  | ||||
| 		"in vec2 phaseAndAmplitudeVarying;" | ||||
| 		"in vec2 inputPositionsVarying[11];" | ||||
| 		"uniform vec4 weights[3];" | ||||
|  | ||||
| 		"out vec3 fragColour;" | ||||
|  | ||||
| @@ -180,44 +194,26 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat | ||||
|  | ||||
| 		"void main(void)" | ||||
| 		"{" | ||||
| 			"vec4 samples[3] = vec4[](" | ||||
| 				"vec4(" | ||||
| 					"texture(texID, inputPositionsVarying[0]).r," | ||||
| 					"texture(texID, inputPositionsVarying[1]).r," | ||||
| 					"texture(texID, inputPositionsVarying[2]).r," | ||||
| 					"texture(texID, inputPositionsVarying[3]).r" | ||||
| 				")," | ||||
| 				"vec4(" | ||||
| 			"vec4 samples = vec4(" | ||||
| 				"texture(texID, inputPositionsVarying[3]).r," | ||||
| 				"texture(texID, inputPositionsVarying[4]).r," | ||||
| 				"texture(texID, inputPositionsVarying[5]).r," | ||||
| 					"texture(texID, inputPositionsVarying[6]).r," | ||||
| 					"texture(texID, inputPositionsVarying[7]).r" | ||||
| 				")," | ||||
| 				"vec4(" | ||||
| 					"texture(texID, inputPositionsVarying[8]).r," | ||||
| 					"texture(texID, inputPositionsVarying[9]).r," | ||||
| 					"texture(texID, inputPositionsVarying[10]).r," | ||||
| 					"0.0" | ||||
| 				")" | ||||
| 				"texture(texID, inputPositionsVarying[6]).r" | ||||
| 			");" | ||||
| 			"float luminance = dot(samples, vec4(0.25));" | ||||
|  | ||||
| 			"float luminance = " | ||||
| 				"dot(vec3(" | ||||
| 					"dot(samples[0], weights[0])," | ||||
| 					"dot(samples[1], weights[1])," | ||||
| 					"dot(samples[2], weights[2])" | ||||
| 				"), vec3(1.0));" | ||||
|  | ||||
| 			"float chrominance = 0.5 * (samples[1].y - luminance) / phaseAndAmplitudeVarying.y;" | ||||
| 			// define chroma to be whatever was here, minus luma | ||||
| 			"float chrominance = 0.5 * (samples.z - luminance) / phaseAndAmplitudeVarying.y;" | ||||
| 			"luminance /= (1.0 - phaseAndAmplitudeVarying.y);" | ||||
|  | ||||
| 			// split choma colours here, as the most direct place, writing out | ||||
| 			// RGB = (luma, chroma.x, chroma.y) | ||||
| 			"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));" | ||||
| 			"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" | ||||
| 		"}",false, false); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader() | ||||
| { | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader() { | ||||
| 	return make_shader( | ||||
| 		"#version 150\n" | ||||
|  | ||||
| @@ -232,41 +228,18 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade | ||||
| 		"void main(void)" | ||||
| 		"{" | ||||
| 			"vec3 samples[] = vec3[](" | ||||
| 				"texture(texID, inputPositionsVarying[0]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[1]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[2]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[3]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[4]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[5]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[6]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[7]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[8]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[9]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[10]).rgb" | ||||
| 				"texture(texID, inputPositionsVarying[6]).rgb" | ||||
| 			");" | ||||
|  | ||||
| 			"vec4 chromaChannel1[] = vec4[](" | ||||
| 				"vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," | ||||
| 				"vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," | ||||
| 				"vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" | ||||
| 			");" | ||||
| 			"vec4 chromaChannel2[] = vec4[](" | ||||
| 				"vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b)," | ||||
| 				"vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b)," | ||||
| 				"vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)" | ||||
| 			");" | ||||
| 			"vec4 chromaChannel1 = vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g);" | ||||
| 			"vec4 chromaChannel2 = vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b);" | ||||
|  | ||||
| 			"vec3 lumaChromaColour = vec3(samples[5].r," | ||||
| 				"dot(vec3(" | ||||
| 					"dot(chromaChannel1[0], weights[0])," | ||||
| 					"dot(chromaChannel1[1], weights[1])," | ||||
| 					"dot(chromaChannel1[2], weights[2])" | ||||
| 				"), vec3(1.0))," | ||||
| 				"dot(vec3(" | ||||
| 					"dot(chromaChannel2[0], weights[0])," | ||||
| 					"dot(chromaChannel2[1], weights[1])," | ||||
| 					"dot(chromaChannel2[2], weights[2])" | ||||
| 				"), vec3(1.0))" | ||||
| 			"vec3 lumaChromaColour = vec3(samples[2].r," | ||||
| 				"dot(chromaChannel1, vec4(0.25))," | ||||
| 				"dot(chromaChannel2, vec4(0.25))" | ||||
| 			");" | ||||
|  | ||||
| 			"vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" | ||||
| @@ -274,54 +247,7 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade | ||||
| 		"}", false, false); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_luma_filter_shader() | ||||
| { | ||||
| 	return make_shader( | ||||
| 		"#version 150\n" | ||||
|  | ||||
| 		"in vec2 inputPositionsVarying[11];" | ||||
| 		"uniform vec4 weights[3];" | ||||
|  | ||||
| 		"out vec3 fragColour;" | ||||
|  | ||||
| 		"uniform sampler2D texID;" | ||||
| 		"uniform mat3 lumaChromaToRGB;" | ||||
|  | ||||
| 		"void main(void)" | ||||
| 		"{" | ||||
| 			"vec3 samples[] = vec3[](" | ||||
| 				"texture(texID, inputPositionsVarying[0]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[1]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[2]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[3]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[4]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[5]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[6]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[7]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[8]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[9]).rgb," | ||||
| 				"texture(texID, inputPositionsVarying[10]).rgb" | ||||
| 			");" | ||||
|  | ||||
| 			"vec4 luminance[] = vec4[](" | ||||
| 				"vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," | ||||
| 				"vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," | ||||
| 				"vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" | ||||
| 			");" | ||||
|  | ||||
| 			"fragColour = vec3(" | ||||
| 				"dot(vec3(" | ||||
| 					"dot(luminance[0], weights[0])," | ||||
| 					"dot(luminance[1], weights[1])," | ||||
| 					"dot(luminance[2], weights[2])" | ||||
| 				"), vec3(1.0))," | ||||
| 				"samples[5].gb" | ||||
| 			");" | ||||
| 		"}", false, false); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() | ||||
| { | ||||
| std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() { | ||||
| 	return make_shader( | ||||
| 		"#version 150\n" | ||||
|  | ||||
| @@ -384,18 +310,15 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() | ||||
| 		"}", false, false); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) | ||||
| { | ||||
| void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) { | ||||
| 	set_uniform("outputTextureSize", (GLint)output_width, (GLint)output_height); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_source_texture_unit(GLenum unit) | ||||
| { | ||||
| void IntermediateShader::set_source_texture_unit(GLenum unit) { | ||||
| 	set_uniform("texID", (GLint)(unit - GL_TEXTURE0)); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency) | ||||
| { | ||||
| void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency) { | ||||
| 	// The process below: the source texture will have bilinear filtering enabled; so by | ||||
| 	// sampling at non-integral offsets from the centre the shader can get a weighted sum | ||||
| 	// of two source pixels, then scale that once, to do two taps per sample. However | ||||
| @@ -404,44 +327,45 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto | ||||
| 	// Perform a linear search for the highest number of taps we can use with 11 samples. | ||||
| 	GLfloat weights[12]; | ||||
| 	GLfloat offsets[5]; | ||||
| 	unsigned int taps = 21; | ||||
| 	while(1) | ||||
| 	{ | ||||
| 	unsigned int taps = 11; | ||||
| //	unsigned int taps = 21; | ||||
| 	while(1) { | ||||
| 		float coefficients[21]; | ||||
| 		SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); | ||||
| 		luminance_filter.get_coefficients(coefficients); | ||||
|  | ||||
| 		int sample = 0; | ||||
| 		int c = 0; | ||||
| //		int sample = 0; | ||||
| //		int c = 0; | ||||
| 		memset(weights, 0, sizeof(float)*12); | ||||
| 		memset(offsets, 0, sizeof(float)*5); | ||||
|  | ||||
| 		int halfSize = (taps >> 1); | ||||
| 		while(c < halfSize && sample < 5) | ||||
| 		{ | ||||
| 			offsets[sample] = (float)(halfSize - c); | ||||
| 			if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) | ||||
| 			{ | ||||
| 				weights[sample] = coefficients[c] + coefficients[c+1]; | ||||
| 				offsets[sample] -= (coefficients[c+1] / weights[sample]); | ||||
| 				c += 2; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				weights[sample] = coefficients[c]; | ||||
| 				c++; | ||||
| 			} | ||||
| 			sample ++; | ||||
| 		} | ||||
| 		if(c == halfSize)	// i.e. we finished combining inputs before we ran out of space | ||||
| 		{ | ||||
| 			weights[sample] = coefficients[c]; | ||||
| 			for(int c = 0; c < sample; c++) | ||||
| 			{ | ||||
| 				weights[sample+c+1] = weights[sample-c-1]; | ||||
| 		for(int c = 0; c < taps; c++) { | ||||
| 			if(c < 5) offsets[c] = (halfSize - c); | ||||
| 			weights[c] = coefficients[c]; | ||||
| 		} | ||||
| 		break; | ||||
| 		} | ||||
|  | ||||
| //		int halfSize = (taps >> 1); | ||||
| //		while(c < halfSize && sample < 5) { | ||||
| //			offsets[sample] = (float)(halfSize - c); | ||||
| //			if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) { | ||||
| //				weights[sample] = coefficients[c] + coefficients[c+1]; | ||||
| //				offsets[sample] -= (coefficients[c+1] / weights[sample]); | ||||
| //				c += 2; | ||||
| //			} else { | ||||
| //				weights[sample] = coefficients[c]; | ||||
| //				c++; | ||||
| //			} | ||||
| //			sample ++; | ||||
| //		} | ||||
| //		if(c == halfSize) {	// i.e. we finished combining inputs before we ran out of space | ||||
| //			weights[sample] = coefficients[c]; | ||||
| //			for(int c = 0; c < sample; c++) { | ||||
| //				weights[sample+c+1] = weights[sample-c-1]; | ||||
| //			} | ||||
| //			break; | ||||
| //		} | ||||
| 		taps -= 2; | ||||
| 	} | ||||
|  | ||||
| @@ -449,19 +373,25 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto | ||||
| 	set_uniform("offsets", 1, 5, offsets); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) | ||||
| { | ||||
| void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) { | ||||
| 	set_filter_coefficients(sampling_rate, colour_burst_frequency); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle) | ||||
| { | ||||
| 	set_uniform("phaseCyclesPerTick", (GLfloat)phase_cycles_per_sample); | ||||
| 	set_uniform("extension", extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f); | ||||
| void IntermediateShader::set_extension(float extension) { | ||||
| 	set_uniform("extension", extension); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) | ||||
| { | ||||
| void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) { | ||||
| 	set_uniform_matrix("lumaChromaToRGB", 3, false, toRGB); | ||||
| 	set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_width_scalers(float input_scaler, float output_scaler) { | ||||
| 	set_uniform("widthScalers", input_scaler, output_scaler); | ||||
| } | ||||
|  | ||||
| void IntermediateShader::set_is_double_height(bool is_double_height, float input_offset, float output_offset) { | ||||
| 	set_uniform("textureHeightDivisor", is_double_height ? 2.0f : 1.0f); | ||||
| 	set_uniform("inputVerticalOffset", input_offset); | ||||
| 	set_uniform("outputVerticalOffset", output_offset); | ||||
| } | ||||
|   | ||||
| @@ -25,13 +25,13 @@ public: | ||||
| 		converting them to single-channel composite values using @c composite_shader if supplied | ||||
| 		or @c rgb_shader and a reference composite conversion if @c composite_shader is @c nullptr. | ||||
| 	*/ | ||||
| 	static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const char *composite_shader, const char *rgb_shader); | ||||
| 	static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader); | ||||
|  | ||||
| 	/*! | ||||
| 		Constructs and returns an intermediate shader that will take runs from the inputPositions, | ||||
| 		converting them to RGB values using @c rgb_shader. | ||||
| 	*/ | ||||
| 	static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const char *rgb_shader); | ||||
| 	static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const std::string &rgb_shader); | ||||
|  | ||||
| 	/*! | ||||
| 		Constructs and returns an intermediate shader that will read composite samples from the R channel, | ||||
| @@ -44,11 +44,6 @@ public: | ||||
| 	*/ | ||||
| 	static std::unique_ptr<IntermediateShader> make_chroma_filter_shader(); | ||||
|  | ||||
| 	/*! | ||||
| 		Constructs and returns an intermediate shader that will filter R while passing through G and B unchanged. | ||||
| 	*/ | ||||
| 	static std::unique_ptr<IntermediateShader> make_luma_filter_shader(); | ||||
|  | ||||
| 	/*! | ||||
| 		Constructs and returns an intermediate shader that will filter R, G and B. | ||||
| 	*/ | ||||
| @@ -81,15 +76,26 @@ public: | ||||
| 		geometry should be extended so that a complete colour cycle is included at both the beginning and end, | ||||
| 		to occur upon the next `bind`. | ||||
| 	*/ | ||||
| 	void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle); | ||||
| 	void set_extension(float extension); | ||||
|  | ||||
| 	/*! | ||||
| 		Queues setting the matrices that convert between RGB and chrominance/luminance to occur on the next `bind`. | ||||
| 	*/ | ||||
| 	void set_colour_conversion_matrices(float *fromRGB, float *toRGB); | ||||
|  | ||||
| 	/*! | ||||
| 		Sets the proportions of the input and output areas that should be considered the whole width — 1.0 means use all available | ||||
| 		space, 0.5 means use half, etc. | ||||
| 	*/ | ||||
| 	void set_width_scalers(float input_scaler, float output_scaler); | ||||
|  | ||||
| 	/*! | ||||
| 		Sets source and target vertical offsets. | ||||
| 	*/ | ||||
| 	void set_is_double_height(bool is_double_height, float input_offset = 0.0f, float output_offset = 0.0f); | ||||
|  | ||||
| private: | ||||
| 	static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition); | ||||
| 	static std::unique_ptr<IntermediateShader> make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition); | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -14,16 +14,14 @@ | ||||
| using namespace OpenGL; | ||||
|  | ||||
| namespace { | ||||
| 	const OpenGL::Shader::AttributeBinding bindings[] = | ||||
| 	{ | ||||
| 	const OpenGL::Shader::AttributeBinding bindings[] = { | ||||
| 		{"position", 0}, | ||||
| 		{"srcCoordinates", 1}, | ||||
| 		{nullptr} | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler) | ||||
| { | ||||
| std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler) { | ||||
| 	const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D"; | ||||
|  | ||||
| 	char *vertex_shader; | ||||
| @@ -38,6 +36,8 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met | ||||
| 		"uniform vec2 positionConversion;" | ||||
| 		"uniform vec2 scanNormal;" | ||||
| 		"uniform %s texID;" | ||||
| 		"uniform float inputScaler;" | ||||
| 		"uniform int textureHeightDivisor;" | ||||
|  | ||||
| 		"out float lateralVarying;" | ||||
| 		"out vec2 srcCoordinatesVarying;" | ||||
| @@ -52,9 +52,10 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met | ||||
| 			"lateralVarying = lateral - 0.5;" | ||||
|  | ||||
| 			"vec2 vSrcCoordinates = vec2(x, vertical.y);" | ||||
| 			"ivec2 textureSize = textureSize(texID, 0);" | ||||
| 			"ivec2 textureSize = textureSize(texID, 0) * ivec2(1, textureHeightDivisor);" | ||||
| 			"iSrcCoordinatesVarying = vSrcCoordinates;" | ||||
| 			"srcCoordinatesVarying = vec2(vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);" | ||||
| 			"srcCoordinatesVarying = vec2(inputScaler * vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);" | ||||
| 			"srcCoordinatesVarying.x = srcCoordinatesVarying.x - mod(srcCoordinatesVarying.x, 1.0 / textureSize.x);" | ||||
|  | ||||
| 			"vec2 vPosition = vec2(x, vertical.x);" | ||||
| 			"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;" | ||||
| @@ -89,8 +90,7 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) | ||||
| { | ||||
| void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) { | ||||
| 	GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f); | ||||
|  | ||||
| 	GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width; | ||||
| @@ -101,13 +101,11 @@ void OutputShader::set_output_size(unsigned int output_width, unsigned int outpu | ||||
| 	set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height); | ||||
| } | ||||
|  | ||||
| void OutputShader::set_source_texture_unit(GLenum unit) | ||||
| { | ||||
| void OutputShader::set_source_texture_unit(GLenum unit) { | ||||
| 	set_uniform("texID", (GLint)(unit - GL_TEXTURE0)); | ||||
| } | ||||
|  | ||||
| void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) | ||||
| { | ||||
| void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) { | ||||
| 	GLfloat scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f); | ||||
| 	GLfloat scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; | ||||
| 	GLfloat multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period); | ||||
| @@ -117,3 +115,11 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle | ||||
| 	set_uniform("scanNormal", scan_normal[0], scan_normal[1]); | ||||
| 	set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider); | ||||
| } | ||||
|  | ||||
| void OutputShader::set_input_width_scaler(float input_scaler) { | ||||
| 	set_uniform("inputScaler", input_scaler); | ||||
| } | ||||
|  | ||||
| void OutputShader::set_origin_is_double_height(bool is_double_height) { | ||||
| 	set_uniform("textureHeightDivisor", is_double_height ? 2 : 1); | ||||
| } | ||||
|   | ||||
| @@ -58,6 +58,16 @@ public: | ||||
| 		to occur upon the next `bind`. | ||||
| 	*/ | ||||
| 	void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); | ||||
|  | ||||
| 	/*! | ||||
| 	*/ | ||||
| 	void set_origin_is_double_height(bool is_double_height); | ||||
|  | ||||
| 	/*! | ||||
| 		Sets the proportion of the input area that should be considered the whole width — 1.0 means use all available | ||||
| 		space, 0.5 means use half, etc. | ||||
| 	*/ | ||||
| 	void set_input_width_scaler(float input_scaler); | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,6 @@ | ||||
|  | ||||
| #include "Shader.hpp" | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
|  | ||||
| using namespace OpenGL; | ||||
| @@ -17,24 +16,23 @@ namespace { | ||||
| 	Shader *bound_shader = nullptr; | ||||
| } | ||||
|  | ||||
| GLuint Shader::compile_shader(const char *source, GLenum type) | ||||
| { | ||||
| GLuint Shader::compile_shader(const std::string &source, GLenum type) { | ||||
| 	GLuint shader = glCreateShader(type); | ||||
| 	glShaderSource(shader, 1, &source, NULL); | ||||
| 	const char *c_str = source.c_str(); | ||||
| 	glShaderSource(shader, 1, &c_str, NULL); | ||||
| 	glCompileShader(shader); | ||||
|  | ||||
| #if defined(DEBUG) | ||||
| #ifdef DEBUG | ||||
| 	GLint isCompiled = 0; | ||||
| 	glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); | ||||
| 	if(isCompiled == GL_FALSE) | ||||
| 	{ | ||||
| 	if(isCompiled == GL_FALSE) { | ||||
| 		GLint logLength; | ||||
| 		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); | ||||
| 		if(logLength > 0) { | ||||
| 			GLchar *log = (GLchar *)malloc((size_t)logLength); | ||||
| 			GLchar *log = new GLchar[logLength]; | ||||
| 			glGetShaderInfoLog(shader, logLength, &logLength, log); | ||||
| 			printf("Compile log:\n%s\n", log); | ||||
| 			free(log); | ||||
| 			delete[] log; | ||||
| 		} | ||||
|  | ||||
| 		throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError; | ||||
| @@ -44,8 +42,7 @@ GLuint Shader::compile_shader(const char *source, GLenum type) | ||||
| 	return shader; | ||||
| } | ||||
|  | ||||
| Shader::Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings) | ||||
| { | ||||
| Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings) { | ||||
| 	shader_program_ = glCreateProgram(); | ||||
| 	GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER); | ||||
| 	GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER); | ||||
| @@ -53,10 +50,8 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att | ||||
| 	glAttachShader(shader_program_, vertex); | ||||
| 	glAttachShader(shader_program_, fragment); | ||||
|  | ||||
| 	if(attribute_bindings) | ||||
| 	{ | ||||
| 		while(attribute_bindings->name) | ||||
| 		{ | ||||
| 	if(attribute_bindings) { | ||||
| 		while(attribute_bindings->name) { | ||||
| 			glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name); | ||||
| 			attribute_bindings++; | ||||
| 		} | ||||
| @@ -64,58 +59,50 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att | ||||
|  | ||||
| 	glLinkProgram(shader_program_); | ||||
|  | ||||
| #if defined(DEBUG) | ||||
| #ifdef DEBUG | ||||
| 	GLint didLink = 0; | ||||
| 	glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink); | ||||
| 	if(didLink == GL_FALSE) | ||||
| 	{ | ||||
| 	if(didLink == GL_FALSE) { | ||||
| 		GLint logLength; | ||||
| 		glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength); | ||||
| 		if(logLength > 0) { | ||||
| 			GLchar *log = (GLchar *)malloc((size_t)logLength); | ||||
| 			GLchar *log = new GLchar[logLength]; | ||||
| 			glGetProgramInfoLog(shader_program_, logLength, &logLength, log); | ||||
| 			printf("Link log:\n%s\n", log); | ||||
| 			free(log); | ||||
| 			delete[] log; | ||||
| 		} | ||||
| 		throw ProgramLinkageError; | ||||
| 	} | ||||
| #endif | ||||
| } | ||||
|  | ||||
| Shader::~Shader() | ||||
| { | ||||
| Shader::~Shader() { | ||||
| 	if(bound_shader == this) Shader::unbind(); | ||||
| 	glDeleteProgram(shader_program_); | ||||
| } | ||||
|  | ||||
| void Shader::bind() | ||||
| { | ||||
| 	if(bound_shader != this) | ||||
| 	{ | ||||
| void Shader::bind() { | ||||
| 	if(bound_shader != this) { | ||||
| 		glUseProgram(shader_program_); | ||||
| 		bound_shader = this; | ||||
| 	} | ||||
| 	flush_functions(); | ||||
| } | ||||
|  | ||||
| void Shader::unbind() | ||||
| { | ||||
| void Shader::unbind() { | ||||
| 	bound_shader = nullptr; | ||||
| 	glUseProgram(0); | ||||
| } | ||||
|  | ||||
| GLint Shader::get_attrib_location(const GLchar *name) | ||||
| { | ||||
| GLint Shader::get_attrib_location(const GLchar *name) { | ||||
| 	return glGetAttribLocation(shader_program_, name); | ||||
| } | ||||
|  | ||||
| GLint Shader::get_uniform_location(const GLchar *name) | ||||
| { | ||||
| GLint Shader::get_uniform_location(const GLchar *name) { | ||||
| 	return glGetUniformLocation(shader_program_, name); | ||||
| } | ||||
|  | ||||
| void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor) | ||||
| { | ||||
| void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor) { | ||||
| 	GLint location = get_attrib_location(name); | ||||
| 	glEnableVertexAttribArray((GLuint)location); | ||||
| 	glVertexAttribPointer((GLuint)location, size, type, normalised, stride, pointer); | ||||
| @@ -124,103 +111,87 @@ void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, | ||||
|  | ||||
| // The various set_uniforms... | ||||
| #define location() glGetUniformLocation(shader_program_, name.c_str()) | ||||
| void Shader::set_uniform(const std::string &name, GLint value) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint value) { | ||||
| 	enqueue_function([name, value, this] { | ||||
| 		glUniform1i(location(), value); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLuint value) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLuint value) { | ||||
| 	enqueue_function([name, value, this] { | ||||
| 		glUniform1ui(location(), value); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value) { | ||||
| 	enqueue_function([name, value, this] { | ||||
| 		glUniform1f(location(), value); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLint value1, GLint value2) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint value1, GLint value2) { | ||||
| 	enqueue_function([name, value1, value2, this] { | ||||
| 		glUniform2i(location(), value1, value2); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2) { | ||||
| 	enqueue_function([name, value1, value2, this] { | ||||
| 		glUniform2f(location(), value1, value2); | ||||
| 		GLint location = location(); | ||||
| 		glUniform2f(location, value1, value2); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2) { | ||||
| 	enqueue_function([name, value1, value2, this] { | ||||
| 		glUniform2ui(location(), value1, value2); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3) { | ||||
| 	enqueue_function([name, value1, value2, value3, this] { | ||||
| 		glUniform3i(location(), value1, value2, value3); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3) { | ||||
| 	enqueue_function([name, value1, value2, value3, this] { | ||||
| 		glUniform3f(location(), value1, value2, value3); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3) { | ||||
| 	enqueue_function([name, value1, value2, value3, this] { | ||||
| 		glUniform3ui(location(), value1, value2, value3); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint value1, GLint value2, GLint value3, GLint value4) { | ||||
| 	enqueue_function([name, value1, value2, value3, value4, this] { | ||||
| 		glUniform4i(location(), value1, value2, value3, value4); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2, GLfloat value3, GLfloat value4) { | ||||
| 	enqueue_function([name, value1, value2, value3, value4, this] { | ||||
| 		glUniform4f(location(), value1, value2, value3, value4); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLuint value1, GLuint value2, GLuint value3, GLuint value4) { | ||||
| 	enqueue_function([name, value1, value2, value3, value4, this] { | ||||
| 		glUniform4ui(location(), value1, value2, value3, value4); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLint *values) { | ||||
| 	size_t number_of_values = (size_t)count * (size_t)size; | ||||
| 	GLint *values_copy = new GLint[number_of_values]; | ||||
| 	memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values); | ||||
|  | ||||
| 	enqueue_function([name, size, count, values_copy, this] { | ||||
| 		switch(size) | ||||
| 		{ | ||||
| 		switch(size) { | ||||
| 			case 1: glUniform1iv(location(), count, values_copy);	break; | ||||
| 			case 2: glUniform2iv(location(), count, values_copy);	break; | ||||
| 			case 3: glUniform3iv(location(), count, values_copy);	break; | ||||
| @@ -230,15 +201,13 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLfloat *values) { | ||||
| 	size_t number_of_values = (size_t)count * (size_t)size; | ||||
| 	GLfloat *values_copy = new GLfloat[number_of_values]; | ||||
| 	memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values); | ||||
|  | ||||
| 	enqueue_function([name, size, count, values_copy, this] { | ||||
| 		switch(size) | ||||
| 		{ | ||||
| 		switch(size) { | ||||
| 			case 1: glUniform1fv(location(), count, values_copy);	break; | ||||
| 			case 2: glUniform2fv(location(), count, values_copy);	break; | ||||
| 			case 3: glUniform3fv(location(), count, values_copy);	break; | ||||
| @@ -248,15 +217,13 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values) | ||||
| { | ||||
| void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, const GLuint *values) { | ||||
| 	size_t number_of_values = (size_t)count * (size_t)size; | ||||
| 	GLuint *values_copy = new GLuint[number_of_values]; | ||||
| 	memcpy(values_copy, values, sizeof(*values) * (size_t)number_of_values); | ||||
|  | ||||
| 	enqueue_function([name, size, count, values_copy, this] { | ||||
| 		switch(size) | ||||
| 		{ | ||||
| 		switch(size) { | ||||
| 			case 1: glUniform1uiv(location(), count, values_copy);	break; | ||||
| 			case 2: glUniform2uiv(location(), count, values_copy);	break; | ||||
| 			case 3: glUniform3uiv(location(), count, values_copy);	break; | ||||
| @@ -266,21 +233,18 @@ void Shader::set_uniform(const std::string &name, GLint size, GLsizei count, con | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values) | ||||
| { | ||||
| void Shader::set_uniform_matrix(const std::string &name, GLint size, bool transpose, const GLfloat *values) { | ||||
| 	set_uniform_matrix(name, size, 1, transpose, values); | ||||
| } | ||||
|  | ||||
| void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values) | ||||
| { | ||||
| void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values) { | ||||
| 	size_t number_of_values = (size_t)count * (size_t)size * (size_t)size; | ||||
| 	GLfloat *values_copy = new GLfloat[number_of_values]; | ||||
| 	memcpy(values_copy, values, sizeof(*values) * number_of_values); | ||||
|  | ||||
| 	enqueue_function([name, size, count, transpose, values_copy, this] { | ||||
| 		GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE; | ||||
| 		switch(size) | ||||
| 		{ | ||||
| 		switch(size) { | ||||
| 			case 2: glUniformMatrix2fv(location(), count, glTranspose, values_copy);	break; | ||||
| 			case 3: glUniformMatrix3fv(location(), count, glTranspose, values_copy);	break; | ||||
| 			case 4: glUniformMatrix4fv(location(), count, glTranspose, values_copy);	break; | ||||
| @@ -289,20 +253,15 @@ void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei cou | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void Shader::enqueue_function(std::function<void(void)> function) | ||||
| { | ||||
| 	function_mutex_.lock(); | ||||
| void Shader::enqueue_function(std::function<void(void)> function) { | ||||
| 	std::lock_guard<std::mutex> function_guard(function_mutex_); | ||||
| 	enqueued_functions_.push_back(function); | ||||
| 	function_mutex_.unlock(); | ||||
| } | ||||
|  | ||||
| void Shader::flush_functions() | ||||
| { | ||||
| 	function_mutex_.lock(); | ||||
| 	for(std::function<void(void)> function : enqueued_functions_) | ||||
| 	{ | ||||
| void Shader::flush_functions() { | ||||
| 	std::lock_guard<std::mutex> function_guard(function_mutex_); | ||||
| 	for(std::function<void(void)> function : enqueued_functions_) { | ||||
| 		function(); | ||||
| 	} | ||||
| 	enqueued_functions_.clear(); | ||||
| 	function_mutex_.unlock(); | ||||
| } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ public: | ||||
| 		@param fragment_shader The fragment shader source code. | ||||
| 		@param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings. | ||||
| 	*/ | ||||
| 	Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings); | ||||
| 	Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings); | ||||
| 	~Shader(); | ||||
|  | ||||
| 	/*! | ||||
| @@ -106,7 +106,7 @@ public: | ||||
| 	void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values); | ||||
|  | ||||
| private: | ||||
| 	GLuint compile_shader(const char *source, GLenum type); | ||||
| 	GLuint compile_shader(const std::string &source, GLenum type); | ||||
| 	GLuint shader_program_; | ||||
|  | ||||
| 	void flush_functions(); | ||||
|   | ||||
| @@ -13,10 +13,8 @@ | ||||
|  | ||||
| using namespace Outputs::CRT; | ||||
|  | ||||
| static const GLint internalFormatForDepth(size_t depth) | ||||
| { | ||||
| 	switch(depth) | ||||
| 	{ | ||||
| static const GLint internalFormatForDepth(size_t depth) { | ||||
| 	switch(depth) { | ||||
| 		default: return GL_FALSE; | ||||
| 		case 1: return GL_R8UI; | ||||
| 		case 2: return GL_RG8UI; | ||||
| @@ -25,10 +23,8 @@ static const GLint internalFormatForDepth(size_t depth) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static const GLenum formatForDepth(size_t depth) | ||||
| { | ||||
| 	switch(depth) | ||||
| 	{ | ||||
| static const GLenum formatForDepth(size_t depth) { | ||||
| 	switch(depth) { | ||||
| 		default: return GL_FALSE; | ||||
| 		case 1: return GL_RED_INTEGER; | ||||
| 		case 2: return GL_RG_INTEGER; | ||||
| @@ -43,8 +39,8 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) : | ||||
| 		write_areas_start_y_(0), | ||||
| 		is_full_(false), | ||||
| 		did_submit_(false), | ||||
| 	number_of_write_areas_(0) | ||||
| { | ||||
| 		has_write_area_(false), | ||||
| 		number_of_write_areas_(0) { | ||||
| 	image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); | ||||
| 	glGenTextures(1, &texture_name_); | ||||
|  | ||||
| @@ -57,41 +53,33 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) : | ||||
| 	glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr); | ||||
| } | ||||
|  | ||||
| TextureBuilder::~TextureBuilder() | ||||
| { | ||||
| TextureBuilder::~TextureBuilder() { | ||||
| 	glDeleteTextures(1, &texture_name_); | ||||
| } | ||||
|  | ||||
| inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) | ||||
| { | ||||
| inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) { | ||||
| 	return &image_[((y * InputBufferBuilderWidth) + x) * bytes_per_pixel_]; | ||||
| } | ||||
|  | ||||
| uint8_t *TextureBuilder::allocate_write_area(size_t required_length) | ||||
| { | ||||
| uint8_t *TextureBuilder::allocate_write_area(size_t required_length) { | ||||
| 	if(is_full_) return nullptr; | ||||
|  | ||||
| 	uint16_t starting_x, starting_y; | ||||
|  | ||||
| 	if(!number_of_write_areas_) | ||||
| 	{ | ||||
| 	if(!number_of_write_areas_) { | ||||
| 		starting_x = write_areas_start_x_; | ||||
| 		starting_y = write_areas_start_y_; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		starting_x = write_areas_[number_of_write_areas_ - 1].x + write_areas_[number_of_write_areas_ - 1].length + 1; | ||||
| 		starting_y = write_areas_[number_of_write_areas_ - 1].y; | ||||
| 	} | ||||
|  | ||||
| 	WriteArea next_write_area; | ||||
| 	if(starting_x + required_length + 2 > InputBufferBuilderWidth) | ||||
| 	{ | ||||
| 	if(starting_x + required_length + 2 > InputBufferBuilderWidth) { | ||||
| 		starting_x = 0; | ||||
| 		starting_y++; | ||||
|  | ||||
| 		if(starting_y == InputBufferBuilderHeight) | ||||
| 		{ | ||||
| 		if(starting_y == InputBufferBuilderHeight) { | ||||
| 			is_full_ = true; | ||||
| 			return nullptr; | ||||
| 		} | ||||
| @@ -105,19 +93,19 @@ uint8_t *TextureBuilder::allocate_write_area(size_t required_length) | ||||
| 	else | ||||
| 		write_areas_.push_back(next_write_area); | ||||
| 	number_of_write_areas_++; | ||||
| 	has_write_area_ = true; | ||||
|  | ||||
| 	return pointer_to_location(next_write_area.x, next_write_area.y); | ||||
| } | ||||
|  | ||||
| bool TextureBuilder::is_full() | ||||
| { | ||||
| bool TextureBuilder::is_full() { | ||||
| 	return is_full_; | ||||
| } | ||||
|  | ||||
| void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) | ||||
| { | ||||
| 	if(is_full_) return; | ||||
| void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) { | ||||
| 	if(is_full_ || !has_write_area_) return; | ||||
|  | ||||
| 	has_write_area_ = false; | ||||
| 	WriteArea &write_area = write_areas_[number_of_write_areas_-1]; | ||||
| 	write_area.length = (uint16_t)actual_length; | ||||
|  | ||||
| @@ -133,8 +121,7 @@ void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) | ||||
| 			bytes_per_pixel_); | ||||
| } | ||||
|  | ||||
| void TextureBuilder::submit() | ||||
| { | ||||
| void TextureBuilder::submit() { | ||||
| 	uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0); | ||||
| 	did_submit_ = true; | ||||
|  | ||||
| @@ -145,30 +132,23 @@ void TextureBuilder::submit() | ||||
| 						image_.data()); | ||||
| } | ||||
|  | ||||
| void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function) | ||||
| { | ||||
| void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function) { | ||||
| 	bool was_full = is_full_; | ||||
| 	if(did_submit_) | ||||
| 	{ | ||||
| 	if(did_submit_) { | ||||
| 		write_areas_start_y_ = write_areas_start_x_ = 0; | ||||
| 		is_full_ = false; | ||||
| 	} | ||||
|  | ||||
| 	if(number_of_write_areas_ && !was_full) | ||||
| 	{ | ||||
| 		if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_) | ||||
| 		{ | ||||
| 			for(size_t area = 0; area < number_of_write_areas_; area++) | ||||
| 			{ | ||||
| 	if(number_of_write_areas_ && !was_full) { | ||||
| 		if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_) { | ||||
| 			for(size_t area = 0; area < number_of_write_areas_; area++) { | ||||
| 				WriteArea &write_area = write_areas_[area]; | ||||
|  | ||||
| 				if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth) | ||||
| 				{ | ||||
| 				if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth) { | ||||
| 					write_areas_start_x_ = 0; | ||||
| 					write_areas_start_y_ ++; | ||||
|  | ||||
| 					if(write_areas_start_y_ == InputBufferBuilderHeight) | ||||
| 					{ | ||||
| 					if(write_areas_start_y_ == InputBufferBuilderHeight) { | ||||
| 						is_full_ = true; | ||||
| 						break; | ||||
| 					} | ||||
| @@ -183,8 +163,7 @@ void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(!is_full_) | ||||
| 		{ | ||||
| 		if(!is_full_) { | ||||
| 			function(write_areas_, number_of_write_areas_); | ||||
|  | ||||
| 			write_areas_start_x_ = write_areas_[number_of_write_areas_-1].x + write_areas_[number_of_write_areas_-1].length + 1; | ||||
| @@ -193,5 +172,6 @@ void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> | ||||
| 	} | ||||
|  | ||||
| 	did_submit_ = false; | ||||
| 	has_write_area_ = false; | ||||
| 	number_of_write_areas_ = 0; | ||||
| } | ||||
|   | ||||
| @@ -69,6 +69,7 @@ class TextureBuilder { | ||||
| 		size_t number_of_write_areas_; | ||||
| 		bool is_full_; | ||||
| 		bool did_submit_; | ||||
| 		bool has_write_area_; | ||||
| 		inline uint8_t *pointer_to_location(uint16_t x, uint16_t y); | ||||
|  | ||||
| 		// Usually: the start position for the current batch of write areas. | ||||
|   | ||||
| @@ -12,15 +12,14 @@ | ||||
|  | ||||
| using namespace OpenGL; | ||||
|  | ||||
| TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) : | ||||
| TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter) : | ||||
| 		_width(width), | ||||
| 		_height(height), | ||||
| 		_pixel_shader(nullptr), | ||||
| 		_drawing_vertex_array(0), | ||||
| 		_drawing_array_buffer(0), | ||||
| 		_set_aspect_ratio(0.0f), | ||||
| 	_texture_unit(texture_unit) | ||||
| { | ||||
| 		_texture_unit(texture_unit) { | ||||
| 	glGenFramebuffers(1, &_framebuffer); | ||||
| 	glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); | ||||
|  | ||||
| @@ -33,8 +32,8 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) | ||||
| 	uint8_t *blank_buffer = (uint8_t *)calloc((size_t)(_expanded_width * _expanded_height), 4); | ||||
| 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, blank_buffer); | ||||
| 	free(blank_buffer); | ||||
| 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||
| 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||
| 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); | ||||
| 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|  | ||||
| 	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); | ||||
|  | ||||
| @@ -42,29 +41,24 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) | ||||
| 		throw ErrorFramebufferIncomplete; | ||||
| } | ||||
|  | ||||
| TextureTarget::~TextureTarget() | ||||
| { | ||||
| TextureTarget::~TextureTarget() { | ||||
| 	glDeleteFramebuffers(1, &_framebuffer); | ||||
| 	glDeleteTextures(1, &_texture); | ||||
| 	if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array); | ||||
| 	if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer); | ||||
| } | ||||
|  | ||||
| void TextureTarget::bind_framebuffer() | ||||
| { | ||||
| void TextureTarget::bind_framebuffer() { | ||||
| 	glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); | ||||
| 	glViewport(0, 0, _width, _height); | ||||
| } | ||||
|  | ||||
| void TextureTarget::bind_texture() | ||||
| { | ||||
| void TextureTarget::bind_texture() { | ||||
| 	glBindTexture(GL_TEXTURE_2D, _texture); | ||||
| } | ||||
|  | ||||
| void TextureTarget::draw(float aspect_ratio) | ||||
| { | ||||
| 	if(!_pixel_shader) | ||||
| 	{ | ||||
| void TextureTarget::draw(float aspect_ratio) { | ||||
| 	if(!_pixel_shader) { | ||||
| 		const char *vertex_shader = | ||||
| 			"#version 150\n" | ||||
|  | ||||
| @@ -112,8 +106,7 @@ void TextureTarget::draw(float aspect_ratio) | ||||
| 		glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0)); | ||||
| 	} | ||||
|  | ||||
| 	if(_set_aspect_ratio != aspect_ratio) | ||||
| 	{ | ||||
| 	if(_set_aspect_ratio != aspect_ratio) { | ||||
| 		_set_aspect_ratio = aspect_ratio; | ||||
| 		float buffer[4*4]; | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class TextureTarget { | ||||
| 			@param height The height of target to create. | ||||
| 			@param texture_unit A texture unit on which to bind the texture. | ||||
| 		*/ | ||||
| 		TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit); | ||||
| 		TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter); | ||||
| 		~TextureTarget(); | ||||
|  | ||||
| 		/*! | ||||
| @@ -46,16 +46,14 @@ class TextureTarget { | ||||
| 		/*! | ||||
| 			@returns the width of the texture target. | ||||
| 		*/ | ||||
| 		GLsizei get_width() | ||||
| 		{ | ||||
| 		GLsizei get_width() { | ||||
| 			return _width; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns the height of the texture target. | ||||
| 		*/ | ||||
| 		GLsizei get_height() | ||||
| 		{ | ||||
| 		GLsizei get_height() { | ||||
| 			return _height; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -1,12 +0,0 @@ | ||||
| // | ||||
| //  Speaker.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 12/01/2016. | ||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "Speaker.hpp" | ||||
|  | ||||
| using namespace Outputs; | ||||
|  | ||||
| @@ -37,8 +37,7 @@ class Speaker { | ||||
| 				virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; | ||||
| 		}; | ||||
|  | ||||
| 		float get_ideal_clock_rate_in_range(float minimum, float maximum) | ||||
| 		{ | ||||
| 		float get_ideal_clock_rate_in_range(float minimum, float maximum) { | ||||
| 			// return twice the cut off, if applicable | ||||
| 			if(high_frequency_cut_off_ > 0.0f && input_cycles_per_second_ >= high_frequency_cut_off_ * 3.0f && input_cycles_per_second_ <= high_frequency_cut_off_ * 3.0f) return high_frequency_cut_off_ * 3.0f; | ||||
|  | ||||
| @@ -52,30 +51,25 @@ class Speaker { | ||||
| 			return maximum; | ||||
| 		} | ||||
|  | ||||
| 		void set_output_rate(float cycles_per_second, int buffer_size) | ||||
| 		{ | ||||
| 		void set_output_rate(float cycles_per_second, int buffer_size) { | ||||
| 			output_cycles_per_second_ = cycles_per_second; | ||||
| 			if(buffer_size_ != buffer_size) | ||||
| 			{ | ||||
| 			if(buffer_size_ != buffer_size) { | ||||
| 				buffer_in_progress_.reset(new int16_t[buffer_size]); | ||||
| 				buffer_size_ = buffer_size; | ||||
| 			} | ||||
| 			set_needs_updated_filter_coefficients(); | ||||
| 		} | ||||
|  | ||||
| 		void set_output_quality(int number_of_taps) | ||||
| 		{ | ||||
| 		void set_output_quality(int number_of_taps) { | ||||
| 			requested_number_of_taps_ = number_of_taps; | ||||
| 			set_needs_updated_filter_coefficients(); | ||||
| 		} | ||||
|  | ||||
| 		void set_delegate(Delegate *delegate) | ||||
| 		{ | ||||
| 		void set_delegate(Delegate *delegate) { | ||||
| 			delegate_ = delegate; | ||||
| 		} | ||||
|  | ||||
| 		void set_input_rate(float cycles_per_second) | ||||
| 		{ | ||||
| 		void set_input_rate(float cycles_per_second) { | ||||
| 			input_cycles_per_second_ = cycles_per_second; | ||||
| 			set_needs_updated_filter_coefficients(); | ||||
| 		} | ||||
| @@ -83,8 +77,7 @@ class Speaker { | ||||
| 		/*! | ||||
| 			Sets the cut-off frequency for a low-pass filter attached to the output of this speaker; optional. | ||||
| 		*/ | ||||
| 		void set_high_frequency_cut_off(float high_frequency) | ||||
| 		{ | ||||
| 		void set_high_frequency_cut_off(float high_frequency) { | ||||
| 			high_frequency_cut_off_ = high_frequency; | ||||
| 			set_needs_updated_filter_coefficients(); | ||||
| 		} | ||||
| @@ -94,21 +87,18 @@ class Speaker { | ||||
| 		/*! | ||||
| 			Ensures any deferred processing occurs now. | ||||
| 		*/ | ||||
| 		void flush() | ||||
| 		{ | ||||
| 		void flush() { | ||||
| 			std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_; | ||||
| 			queued_functions_.reset(); | ||||
| 			_queue->enqueue([queued_functions] { | ||||
| 				for(auto function : *queued_functions) | ||||
| 				{ | ||||
| 				for(auto function : *queued_functions) { | ||||
| 					function(); | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 	protected: | ||||
| 		void enqueue(std::function<void(void)> function) | ||||
| 		{ | ||||
| 		void enqueue(std::function<void(void)> function) { | ||||
| 			if(!queued_functions_) queued_functions_.reset(new std::list<std::function<void(void)>>); | ||||
| 			queued_functions_->push_back(function); | ||||
| 		} | ||||
| @@ -124,14 +114,12 @@ class Speaker { | ||||
|  | ||||
| 		float input_cycles_per_second_, output_cycles_per_second_; | ||||
|  | ||||
| 		void set_needs_updated_filter_coefficients() | ||||
| 		{ | ||||
| 		void set_needs_updated_filter_coefficients() { | ||||
| 			coefficients_are_dirty_ = true; | ||||
| 		} | ||||
|  | ||||
| 		void get_samples(unsigned int quantity, int16_t *target)	{} | ||||
| 		void skip_samples(unsigned int quantity) | ||||
| 		{ | ||||
| 		void skip_samples(unsigned int quantity) { | ||||
| 			int16_t throwaway_samples[quantity]; | ||||
| 			get_samples(quantity, throwaway_samples); | ||||
| 		} | ||||
| @@ -151,22 +139,18 @@ class Speaker { | ||||
| */ | ||||
| template <class T> class Filter: public Speaker { | ||||
| 	public: | ||||
| 		~Filter() | ||||
| 		{ | ||||
| 		~Filter() { | ||||
| 			_queue->flush(); | ||||
| 		} | ||||
|  | ||||
| 		void run_for_cycles(unsigned int input_cycles) | ||||
| 		{ | ||||
| 		void run_for_cycles(unsigned int input_cycles) { | ||||
| 			enqueue([=]() { | ||||
| 				unsigned int cycles_remaining = input_cycles; | ||||
| 				if(coefficients_are_dirty_) update_filter_coefficients(); | ||||
|  | ||||
| 				// if input and output rates exactly match, just accumulate results and pass on | ||||
| 				if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) | ||||
| 				{ | ||||
| 					while(cycles_remaining) | ||||
| 					{ | ||||
| 				if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) { | ||||
| 					while(cycles_remaining) { | ||||
| 						unsigned int cycles_to_read = (unsigned int)(buffer_size_ - buffer_in_progress_pointer_); | ||||
| 						if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining; | ||||
|  | ||||
| @@ -174,11 +158,9 @@ template <class T> class Filter: public Speaker { | ||||
| 						buffer_in_progress_pointer_ += cycles_to_read; | ||||
|  | ||||
| 						// announce to delegate if full | ||||
| 						if(buffer_in_progress_pointer_ == buffer_size_) | ||||
| 						{ | ||||
| 						if(buffer_in_progress_pointer_ == buffer_size_) { | ||||
| 							buffer_in_progress_pointer_ = 0; | ||||
| 							if(delegate_) | ||||
| 							{ | ||||
| 							if(delegate_) { | ||||
| 								delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_); | ||||
| 							} | ||||
| 						} | ||||
| @@ -190,26 +172,21 @@ template <class T> class Filter: public Speaker { | ||||
| 				} | ||||
|  | ||||
| 				// if the output rate is less than the input rate, use the filter | ||||
| 				if(input_cycles_per_second_ > output_cycles_per_second_) | ||||
| 				{ | ||||
| 					while(cycles_remaining) | ||||
| 					{ | ||||
| 				if(input_cycles_per_second_ > output_cycles_per_second_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0)) { | ||||
| 					while(cycles_remaining) { | ||||
| 						unsigned int cycles_to_read = (unsigned int)std::min((int)cycles_remaining, number_of_taps_ - input_buffer_depth_); | ||||
| 						static_cast<T *>(this)->get_samples(cycles_to_read, &input_buffer_.get()[input_buffer_depth_]); | ||||
| 						cycles_remaining -= cycles_to_read; | ||||
| 						input_buffer_depth_ += cycles_to_read; | ||||
|  | ||||
| 						if(input_buffer_depth_ == number_of_taps_) | ||||
| 						{ | ||||
| 						if(input_buffer_depth_ == number_of_taps_) { | ||||
| 							buffer_in_progress_.get()[buffer_in_progress_pointer_] = filter_->apply(input_buffer_.get()); | ||||
| 							buffer_in_progress_pointer_++; | ||||
|  | ||||
| 							// announce to delegate if full | ||||
| 							if(buffer_in_progress_pointer_ == buffer_size_) | ||||
| 							{ | ||||
| 							if(buffer_in_progress_pointer_ == buffer_size_) { | ||||
| 								buffer_in_progress_pointer_ = 0; | ||||
| 								if(delegate_) | ||||
| 								{ | ||||
| 								if(delegate_) { | ||||
| 									delegate_->speaker_did_complete_samples(this, buffer_in_progress_.get(), buffer_size_); | ||||
| 								} | ||||
| 							} | ||||
| @@ -218,14 +195,11 @@ template <class T> class Filter: public Speaker { | ||||
| 							// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip | ||||
| 							// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. | ||||
| 							uint64_t steps = stepper_->step(); | ||||
| 							if(steps < number_of_taps_) | ||||
| 							{ | ||||
| 							if(steps < number_of_taps_) { | ||||
| 								int16_t *input_buffer = input_buffer_.get(); | ||||
| 								memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)number_of_taps_ - (size_t)steps)); | ||||
| 								input_buffer_depth_ -= steps; | ||||
| 							} | ||||
| 							else | ||||
| 							{ | ||||
| 							} else { | ||||
| 								if(steps > number_of_taps_) | ||||
| 									static_cast<T *>(this)->skip_samples((unsigned int)steps - (unsigned int)number_of_taps_); | ||||
| 								input_buffer_depth_ = 0; | ||||
| @@ -247,15 +221,11 @@ template <class T> class Filter: public Speaker { | ||||
| 		std::unique_ptr<int16_t> input_buffer_; | ||||
| 		int input_buffer_depth_; | ||||
|  | ||||
| 		void update_filter_coefficients() | ||||
| 		{ | ||||
| 		void update_filter_coefficients() { | ||||
| 			// make a guess at a good number of taps if this hasn't been provided explicitly | ||||
| 			if(requested_number_of_taps_) | ||||
| 			{ | ||||
| 			if(requested_number_of_taps_) { | ||||
| 				number_of_taps_ = requested_number_of_taps_; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				number_of_taps_ = (int)ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_); | ||||
| 				number_of_taps_ *= 2; | ||||
| 				number_of_taps_ |= 1; | ||||
| @@ -267,12 +237,9 @@ template <class T> class Filter: public Speaker { | ||||
| 			stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_)); | ||||
|  | ||||
| 			float high_pass_frequency; | ||||
| 			if(high_frequency_cut_off_ > 0.0) | ||||
| 			{ | ||||
| 			if(high_frequency_cut_off_ > 0.0) { | ||||
| 				high_pass_frequency = std::min((float)output_cycles_per_second_ / 2.0f, high_frequency_cut_off_); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 			} else { | ||||
| 				high_pass_frequency = (float)output_cycles_per_second_ / 2.0f; | ||||
| 			} | ||||
| 			filter_.reset(new SignalProcessing::FIRFilter((unsigned int)number_of_taps_, (float)input_cycles_per_second_, 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation)); | ||||
|   | ||||
| @@ -176,8 +176,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param program The program to schedule. | ||||
| 		*/ | ||||
| 		inline void schedule_program(const MicroOp *program) | ||||
| 		{ | ||||
| 		inline void schedule_program(const MicroOp *program) { | ||||
| 			scheduled_programs_[schedule_programs_write_pointer_] = program; | ||||
| 			schedule_programs_write_pointer_ = (schedule_programs_write_pointer_+1)&3; | ||||
| 		} | ||||
| @@ -189,8 +188,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@returns The current value of the flags register. | ||||
| 		*/ | ||||
| 		uint8_t get_flags() | ||||
| 		{ | ||||
| 		uint8_t get_flags() { | ||||
| 			return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_; | ||||
| 		} | ||||
|  | ||||
| @@ -201,8 +199,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param flags The new value of the flags register. | ||||
| 		*/ | ||||
| 		void set_flags(uint8_t flags) | ||||
| 		{ | ||||
| 		void set_flags(uint8_t flags) { | ||||
| 			carry_flag_				= flags		& Flag::Carry; | ||||
| 			negative_result_		= flags		& Flag::Sign; | ||||
| 			zero_result_			= (~flags)	& Flag::Zero; | ||||
| @@ -216,8 +213,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param operation The operation code for which to schedule a program. | ||||
| 		*/ | ||||
| 		inline void decode_operation(uint8_t operation) | ||||
| 		{ | ||||
| 		inline void decode_operation(uint8_t operation) { | ||||
| #define Program(...)						{__VA_ARGS__, OperationMoveToNextProgram} | ||||
|  | ||||
| #define Absolute							CycleLoadAddressAbsolute | ||||
| @@ -548,13 +544,13 @@ template <class T> class Processor { | ||||
| 				ready_is_active_(false), | ||||
| 				scheduled_programs_{nullptr, nullptr, nullptr, nullptr}, | ||||
| 				inverse_interrupt_flag_(0), | ||||
| 				irq_request_history_(0), | ||||
| 				s_(0), | ||||
| 				next_bus_operation_(BusOperation::None), | ||||
| 				interrupt_requests_(InterruptRequestFlags::PowerOn), | ||||
| 				irq_line_(0), | ||||
| 				nmi_line_is_enabled_(false), | ||||
| 			set_overflow_line_is_enabled_(false) | ||||
| 		{ | ||||
| 				set_overflow_line_is_enabled_(false) { | ||||
| 			// only the interrupt flag is defined upon reset but get_flags isn't going to | ||||
| 			// mask the other flags so we need to do that, at least | ||||
| 			carry_flag_ &= Flag::Carry; | ||||
| @@ -568,19 +564,18 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@discussion Subclasses must implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) . | ||||
| 			The 6502 will call that method for all bus accesses. The 6502 is guaranteed to perform one bus operation call per cycle. | ||||
| 			If it is a read operation then @c value will be seeded with the value 0xff. | ||||
|  | ||||
| 			@param number_of_cycles The number of cycles to run the 6502 for. | ||||
| 		*/ | ||||
| 		void run_for_cycles(int number_of_cycles) | ||||
| 		{ | ||||
| 		void run_for_cycles(int number_of_cycles) { | ||||
| 			static const MicroOp doBranch[] = { | ||||
| 				CycleReadFromPC, | ||||
| 				CycleAddSignedOperandToPC, | ||||
| 				OperationMoveToNextProgram | ||||
| 			}; | ||||
| 			static uint8_t throwaway_target; | ||||
| 			static const MicroOp fetch_decode_execute[] = | ||||
| 			{ | ||||
| 			static const MicroOp fetch_decode_execute[] = { | ||||
| 				CycleFetchOperation, | ||||
| 				CycleFetchOperand, | ||||
| 				OperationDecodeOperation, | ||||
| @@ -633,10 +628,8 @@ template <class T> class Processor { | ||||
| 					number_of_cycles -= static_cast<T *>(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue); | ||||
| 				} | ||||
|  | ||||
| 				if(!ready_is_active_) | ||||
| 				{ | ||||
| 					if(nextBusOperation != BusOperation::None) | ||||
| 					{ | ||||
| 				if(!ready_is_active_) { | ||||
| 					if(nextBusOperation != BusOperation::None) { | ||||
| 						bus_access(); | ||||
| 					} | ||||
|  | ||||
| @@ -645,9 +638,9 @@ template <class T> class Processor { | ||||
| 						const MicroOp cycle = program[scheduleProgramProgramCounter]; | ||||
| 						scheduleProgramProgramCounter++; | ||||
|  | ||||
| #define read_op(val, addr)		nextBusOperation = BusOperation::ReadOpcode;	busAddress = addr;		busValue = &val | ||||
| #define read_mem(val, addr)		nextBusOperation = BusOperation::Read;			busAddress = addr;		busValue = &val | ||||
| #define throwaway_read(addr)	nextBusOperation = BusOperation::Read;			busAddress = addr;		busValue = &throwaway_target | ||||
| #define read_op(val, addr)		nextBusOperation = BusOperation::ReadOpcode;	busAddress = addr;		busValue = &val;				val = 0xff | ||||
| #define read_mem(val, addr)		nextBusOperation = BusOperation::Read;			busAddress = addr;		busValue = &val;				val	= 0xff | ||||
| #define throwaway_read(addr)	nextBusOperation = BusOperation::Read;			busAddress = addr;		busValue = &throwaway_target;	throwaway_target = 0xff | ||||
| #define write_mem(val, addr)	nextBusOperation = BusOperation::Write;			busAddress = addr;		busValue = &val | ||||
|  | ||||
| 						switch(cycle) { | ||||
| @@ -656,7 +649,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 							case CycleFetchOperation: { | ||||
| 								last_operation_pc_ = pc_; | ||||
| //								printf("%04x	x:%02x\n", pc_.full, x_); | ||||
| //								printf("%04x\n", pc_.full); | ||||
| 								pc_.full++; | ||||
| 								read_op(operation_, last_operation_pc_.full); | ||||
|  | ||||
| @@ -689,8 +682,7 @@ template <class T> class Processor { | ||||
| 								program = scheduled_programs_[scheduleProgramsReadPointer]; | ||||
| 							continue; | ||||
|  | ||||
| #define push(v) \ | ||||
| 		{\ | ||||
| #define push(v) {\ | ||||
| 			uint16_t targetAddress = s_ | 0x100; s_--;\ | ||||
| 			write_mem(v, targetAddress);\ | ||||
| 		} | ||||
| @@ -700,8 +692,7 @@ template <class T> class Processor { | ||||
| 							case CyclePushPCL:					push(pc_.bytes.low);											break; | ||||
| 							case CyclePushOperand:				push(operand_);													break; | ||||
| 							case CyclePushA:					push(a_);														break; | ||||
| 							case CycleNoWritePush: | ||||
| 							{ | ||||
| 							case CycleNoWritePush: { | ||||
| 								uint16_t targetAddress = s_ | 0x100; s_--; | ||||
| 								read_mem(operand_, targetAddress); | ||||
| 							} | ||||
| @@ -1144,8 +1135,7 @@ template <class T> class Processor { | ||||
| 			@param r The register to set. | ||||
| 			@returns The value of the register. 8-bit registers will be returned as unsigned. | ||||
| 		*/ | ||||
| 		uint16_t get_value_of_register(Register r) | ||||
| 		{ | ||||
| 		uint16_t get_value_of_register(Register r) { | ||||
| 			switch (r) { | ||||
| 				case Register::ProgramCounter:			return pc_.full; | ||||
| 				case Register::LastOperationAddress:	return last_operation_pc_.full; | ||||
| @@ -1167,8 +1157,7 @@ template <class T> class Processor { | ||||
| 			@param r The register to set. | ||||
| 			@param value The value to set. If the register is only 8 bit, the value will be truncated. | ||||
| 		*/ | ||||
| 		void set_value_of_register(Register r, uint16_t value) | ||||
| 		{ | ||||
| 		void set_value_of_register(Register r, uint16_t value) { | ||||
| 			switch (r) { | ||||
| 				case Register::ProgramCounter:	pc_.full = value;			break; | ||||
| 				case Register::StackPointer:	s_ = (uint8_t)value;		break; | ||||
| @@ -1185,8 +1174,7 @@ template <class T> class Processor { | ||||
| 			Interrupts current execution flow to perform an RTS and, if the 6502 is currently jammed, | ||||
| 			to unjam it. | ||||
| 		*/ | ||||
| 		void return_from_subroutine() | ||||
| 		{ | ||||
| 		void return_from_subroutine() { | ||||
| 			s_++; | ||||
| 			static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.low); s_++; | ||||
| 			static_cast<T *>(this)->perform_bus_operation(CPU6502::BusOperation::Read, 0x100 | s_, &pc_.bytes.high); | ||||
| @@ -1202,8 +1190,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param active @c true if the line is logically active; @c false otherwise. | ||||
| 		*/ | ||||
| 		inline void set_ready_line(bool active) | ||||
| 		{ | ||||
| 		inline void set_ready_line(bool active) { | ||||
| 			if(active) { | ||||
| 				ready_line_is_enabled_ = true; | ||||
| 			} else { | ||||
| @@ -1217,8 +1204,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param active @c true if the line is logically active; @c false otherwise. | ||||
| 		*/ | ||||
| 		inline void set_reset_line(bool active) | ||||
| 		{ | ||||
| 		inline void set_reset_line(bool active) { | ||||
| 			interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::Reset) | (active ? InterruptRequestFlags::Reset : 0); | ||||
| 		} | ||||
|  | ||||
| @@ -1227,8 +1213,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@returns @c true if the line is logically active; @c false otherwise. | ||||
| 		*/ | ||||
| 		inline bool get_is_resetting() | ||||
| 		{ | ||||
| 		inline bool get_is_resetting() { | ||||
| 			return !!(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)); | ||||
| 		} | ||||
|  | ||||
| @@ -1236,8 +1221,7 @@ template <class T> class Processor { | ||||
| 			This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a | ||||
| 			reset at the first opportunity. Use @c set_power_on to disable that behaviour. | ||||
| 		*/ | ||||
| 		inline void set_power_on(bool active) | ||||
| 		{ | ||||
| 		inline void set_power_on(bool active) { | ||||
| 			interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::PowerOn) | (active ? InterruptRequestFlags::PowerOn : 0); | ||||
| 		} | ||||
|  | ||||
| @@ -1246,8 +1230,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param active @c true if the line is logically active; @c false otherwise. | ||||
| 		*/ | ||||
| 		inline void set_irq_line(bool active) | ||||
| 		{ | ||||
| 		inline void set_irq_line(bool active) { | ||||
| 			irq_line_ = active ? Flag::Interrupt : 0; | ||||
| 		} | ||||
|  | ||||
| @@ -1256,8 +1239,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param active @c true if the line is logically active; @c false otherwise. | ||||
| 		*/ | ||||
| 		inline void set_overflow_line(bool active) | ||||
| 		{ | ||||
| 		inline void set_overflow_line(bool active) { | ||||
| 			// a leading edge will set the overflow flag | ||||
| 			if(active && !set_overflow_line_is_enabled_) | ||||
| 				overflow_flag_ = Flag::Overflow; | ||||
| @@ -1269,8 +1251,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param active `true` if the line is logically active; `false` otherwise. | ||||
| 		*/ | ||||
| 		inline void set_nmi_line(bool active) | ||||
| 		{ | ||||
| 		inline void set_nmi_line(bool active) { | ||||
| 			// NMI is edge triggered, not level | ||||
| 			if(active && !nmi_line_is_enabled_) | ||||
| 				interrupt_requests_ |= InterruptRequestFlags::NMI; | ||||
| @@ -1283,8 +1264,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@returns @c true if the 6502 is jammed; @c false otherwise. | ||||
| 		*/ | ||||
| 		inline bool is_jammed() | ||||
| 		{ | ||||
| 		inline bool is_jammed() { | ||||
| 			return is_jammed_; | ||||
| 		} | ||||
|  | ||||
| @@ -1293,8 +1273,7 @@ template <class T> class Processor { | ||||
|  | ||||
| 			@param handler The class instance that will be this 6502's jam handler from now on. | ||||
| 		*/ | ||||
| 		inline void set_jam_handler(JamHandler *handler) | ||||
| 		{ | ||||
| 		inline void set_jam_handler(JamHandler *handler) { | ||||
| 			jam_handler_ = handler; | ||||
| 		} | ||||
| }; | ||||
|   | ||||
| @@ -12,13 +12,11 @@ | ||||
|  | ||||
| using namespace CPU6502; | ||||
|  | ||||
| AllRAMProcessor::AllRAMProcessor() : _timestamp(0) | ||||
| { | ||||
| AllRAMProcessor::AllRAMProcessor() : _timestamp(0) { | ||||
| 	set_power_on(false); | ||||
| } | ||||
|  | ||||
| int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) | ||||
| { | ||||
| int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||
| 	_timestamp++; | ||||
|  | ||||
| 	if(isReadOperation(operation)) { | ||||
| @@ -30,13 +28,11 @@ int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| void AllRAMProcessor::set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data) | ||||
| { | ||||
| void AllRAMProcessor::set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data) { | ||||
| 	size_t endAddress = std::min(startAddress + length, (size_t)65536); | ||||
| 	memcpy(&_memory[startAddress], data, endAddress - startAddress); | ||||
| } | ||||
|  | ||||
| uint32_t AllRAMProcessor::get_timestamp() | ||||
| { | ||||
| uint32_t AllRAMProcessor::get_timestamp() { | ||||
| 	return _timestamp; | ||||
| } | ||||
|   | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -30,6 +30,16 @@ Similar effort is put into audio generation. If the real machine normally genera | ||||
|  | ||||
| If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k rendering of a composite display and, assuming the emulated machine produces source audio at or above 96Khz, 96,000 individual distinct audio samples a second. Interlaced video also works and looks much as it always did on those machines that produce it. | ||||
|  | ||||
| ### Samples | ||||
|  | ||||
| | 1:1 Pixel Copying | Composite Decoded | | ||||
| |---|---| | ||||
| ||| | ||||
| ||| | ||||
| ||| | ||||
|  | ||||
| <img src="READMEImages/ReptonInterlaced.gif" height=600 alt="Repton title screen, interlaced"> | ||||
|  | ||||
| ## Low Latency | ||||
|  | ||||
| The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz monitor it can produce 140 distinct frames per second. Latency is dictated by the output hardware, not the emulated machine. | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								READMEImages/CompositeElectron.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/CompositeRepton3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 136 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/CompositeStormlord.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 271 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/NaiveElectron.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 309 B | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/NaiveRepton3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 65 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/NaiveStormlord.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 82 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/ReptonInterlaced.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 MiB | 
| @@ -36,34 +36,28 @@ using namespace SignalProcessing; | ||||
| #define kCSKaiserBesselFilterFixedMultiplier	32767.0f | ||||
| #define kCSKaiserBesselFilterFixedShift			15 | ||||
|  | ||||
| /* ino evaluates the 0th order Bessel function at a */ | ||||
| float FIRFilter::ino(float a) | ||||
| { | ||||
| /*! Evaluates the 0th order Bessel function at @c a. */ | ||||
| float FIRFilter::ino(float a) { | ||||
| 	float d = 0.0f; | ||||
| 	float ds = 1.0f; | ||||
| 	float s = 1.0f; | ||||
|  | ||||
| 	do | ||||
| 	{ | ||||
| 	do { | ||||
| 		d += 2.0f; | ||||
| 		ds *= (a * a) / (d * d); | ||||
| 		s += ds; | ||||
| 	} | ||||
| 	while(ds > s*1e-6f); | ||||
| 	} while(ds > s*1e-6f); | ||||
|  | ||||
| 	return s; | ||||
| } | ||||
|  | ||||
| //static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) | ||||
| void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) | ||||
| { | ||||
| void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) { | ||||
| 	/* calculate alpha, which is the Kaiser-Bessel window shape factor */ | ||||
| 	float a;	// to take the place of alpha in the normal derivation | ||||
|  | ||||
| 	if(attenuation < 21.0f) | ||||
| 	if(attenuation < 21.0f) { | ||||
| 		a = 0.0f; | ||||
| 	else | ||||
| 	{ | ||||
| 	} else { | ||||
| 		if(attenuation > 50.0f) | ||||
| 			a = 0.1102f * (attenuation - 8.7f); | ||||
| 		else | ||||
| @@ -76,8 +70,7 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici | ||||
| 	unsigned int Np = (numberOfTaps - 1) / 2; | ||||
| 	float I0 = ino(a); | ||||
| 	float NpSquared = (float)(Np * Np); | ||||
| 	for(unsigned int i = 0; i <= Np; i++) | ||||
| 	{ | ||||
| 	for(unsigned int i = 0; i <= Np; i++) { | ||||
| 		filterCoefficientsFloat[Np + i] =  | ||||
| 				A[i] *  | ||||
| 				ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) / | ||||
| @@ -85,38 +78,32 @@ void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoeffici | ||||
| 	} | ||||
|  | ||||
| 	/* coefficients are symmetrical, so copy from right hand side to left side */ | ||||
| 	for(unsigned int i = 0; i < Np; i++) | ||||
| 	{ | ||||
| 	for(unsigned int i = 0; i < Np; i++) { | ||||
| 		filterCoefficientsFloat[i] = filterCoefficientsFloat[numberOfTaps - 1 - i]; | ||||
| 	} | ||||
| 	 | ||||
| 	/* scale back up so that we retain 100% of input volume */ | ||||
| 	float coefficientTotal = 0.0f; | ||||
| 	for(unsigned int i = 0; i < numberOfTaps; i++) | ||||
| 	{ | ||||
| 	for(unsigned int i = 0; i < numberOfTaps; i++) { | ||||
| 		coefficientTotal += filterCoefficientsFloat[i]; | ||||
| 	} | ||||
|  | ||||
| 	/* we'll also need integer versions, potentially */ | ||||
| 	float coefficientMultiplier = 1.0f / coefficientTotal; | ||||
| 	for(unsigned int i = 0; i < numberOfTaps; i++) | ||||
| 	{ | ||||
| 	for(unsigned int i = 0; i < numberOfTaps; i++) { | ||||
| 		filterCoefficients[i] = (short)(filterCoefficientsFloat[i] * kCSKaiserBesselFilterFixedMultiplier * coefficientMultiplier); | ||||
| 	} | ||||
|  | ||||
| 	delete[] filterCoefficientsFloat; | ||||
| } | ||||
|  | ||||
| void FIRFilter::get_coefficients(float *coefficients) | ||||
| { | ||||
| 	for(unsigned int i = 0; i < number_of_taps_; i++) | ||||
| 	{ | ||||
| void FIRFilter::get_coefficients(float *coefficients) { | ||||
| 	for(unsigned int i = 0; i < number_of_taps_; i++) { | ||||
| 		coefficients[i] = (float)filter_coefficients_[i] / kCSKaiserBesselFilterFixedMultiplier; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation) | ||||
| { | ||||
| FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation) { | ||||
| 	// we must be asked to filter based on an odd number of | ||||
| 	// taps, and at least three | ||||
| 	if(number_of_taps < 3) number_of_taps = 3; | ||||
| @@ -135,8 +122,7 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float | ||||
|  | ||||
| 	float *A = new float[Np+1]; | ||||
| 	A[0] = 2.0f * (high_frequency - low_frequency) / input_sample_rate; | ||||
| 	for(unsigned int i = 1; i <= Np; i++) | ||||
| 	{ | ||||
| 	for(unsigned int i = 1; i <= Np; i++) { | ||||
| 		float iPi = (float)i * (float)M_PI; | ||||
| 		A[i] =  | ||||
| 			( | ||||
| @@ -151,7 +137,6 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float | ||||
| 	delete[] A; | ||||
| } | ||||
|  | ||||
| FIRFilter::~FIRFilter() | ||||
| { | ||||
| FIRFilter::~FIRFilter() { | ||||
| 	delete[] filter_coefficients_; | ||||
| } | ||||
|   | ||||
| @@ -57,24 +57,21 @@ class FIRFilter { | ||||
| 			@param src The source buffer to apply the filter to. | ||||
| 			@returns The result of applying the filter. | ||||
| 		*/ | ||||
| 		inline short apply(const short *src) | ||||
| 		{ | ||||
| 		inline short apply(const short *src) { | ||||
| 			#ifdef __APPLE__ | ||||
| 				short result; | ||||
| 				vDSP_dotpr_s1_15(filter_coefficients_, 1, src, 1, &result, number_of_taps_); | ||||
| 				return result; | ||||
| 			#else | ||||
| 				int outputValue = 0; | ||||
| 				for(unsigned int c = 0; c < number_of_taps_; c++) | ||||
| 				{ | ||||
| 				for(unsigned int c = 0; c < number_of_taps_; c++) { | ||||
| 					outputValue += filter_coefficients_[c] * src[c]; | ||||
| 				} | ||||
| 				return (short)(outputValue >> kCSKaiserBesselFilterFixedShift); | ||||
| 			#endif | ||||
| 		} | ||||
|  | ||||
| 		inline unsigned int get_number_of_taps() | ||||
| 		{ | ||||
| 		inline unsigned int get_number_of_taps() { | ||||
| 			return number_of_taps_; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -23,8 +23,7 @@ namespace SignalProcessing { | ||||
| 	Pegs the beginning of both clocks to the time at which the stepper is created. So e.g. a stepper | ||||
| 	that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600. | ||||
| */ | ||||
| class Stepper | ||||
| { | ||||
| class Stepper { | ||||
| 	public: | ||||
| 		/*! | ||||
| 			Establishes a stepper with a one-to-one conversion rate. | ||||
| @@ -48,12 +47,10 @@ class Stepper | ||||
|  | ||||
| 			@returns the number of output steps. | ||||
| 		*/ | ||||
| 		inline uint64_t step() | ||||
| 		{ | ||||
| 		inline uint64_t step() { | ||||
| 			uint64_t update = whole_step_; | ||||
| 			accumulated_error_ += adjustment_up_; | ||||
| 			if(accumulated_error_ > 0) | ||||
| 			{ | ||||
| 			if(accumulated_error_ > 0) { | ||||
| 				update++; | ||||
| 				accumulated_error_ -= adjustment_down_; | ||||
| 			} | ||||
| @@ -65,12 +62,10 @@ class Stepper | ||||
|  | ||||
| 			@returns the number of output steps. | ||||
| 		*/ | ||||
| 		inline uint64_t step(uint64_t number_of_steps) | ||||
| 		{ | ||||
| 		inline uint64_t step(uint64_t number_of_steps) { | ||||
| 			uint64_t update = whole_step_ * number_of_steps; | ||||
| 			accumulated_error_ += adjustment_up_ * (int64_t)number_of_steps; | ||||
| 			if(accumulated_error_ > 0) | ||||
| 			{ | ||||
| 			if(accumulated_error_ > 0) { | ||||
| 				update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_); | ||||
| 				accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_; | ||||
| 			} | ||||
| @@ -80,16 +75,14 @@ class Stepper | ||||
| 		/*! | ||||
| 			@returns the output rate. | ||||
| 		*/ | ||||
| 		inline uint64_t get_output_rate() | ||||
| 		{ | ||||
| 		inline uint64_t get_output_rate() { | ||||
| 			return output_rate_; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns the input rate. | ||||
| 		*/ | ||||
| 		inline uint64_t get_input_rate() | ||||
| 		{ | ||||
| 		inline uint64_t get_input_rate() { | ||||
| 			return input_rate_; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -14,8 +14,7 @@ | ||||
|  | ||||
| using namespace StaticAnalyser::Acorn; | ||||
|  | ||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) | ||||
| { | ||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||
| 	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format | ||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||
| 	Storage::Encodings::MFM::Parser parser(false, disk); | ||||
| @@ -34,8 +33,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | ||||
| 	snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]); | ||||
| 	catalogue->name = disk_name; | ||||
|  | ||||
| 	switch((details->data[6] >> 4)&3) | ||||
| 	{ | ||||
| 	switch((details->data[6] >> 4)&3) { | ||||
| 		case 0: catalogue->bootOption = Catalogue::BootOption::None;		break; | ||||
| 		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break; | ||||
| 		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break; | ||||
| @@ -44,8 +42,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | ||||
|  | ||||
| 	// DFS files are stored contiguously, and listed in descending order of distance from track 0. | ||||
| 	// So iterating backwards implies the least amount of seeking. | ||||
| 	for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) | ||||
| 	{ | ||||
| 	for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) { | ||||
| 		File new_file; | ||||
| 		char name[10]; | ||||
| 		snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]); | ||||
| @@ -59,8 +56,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | ||||
| 		new_file.data.reserve((size_t)data_length); | ||||
|  | ||||
| 		if(start_sector < 2) continue; | ||||
| 		while(data_length > 0) | ||||
| 		{ | ||||
| 		while(data_length > 0) { | ||||
| 			uint8_t sector = (uint8_t)(start_sector % 10); | ||||
| 			uint8_t track = (uint8_t)(start_sector / 10); | ||||
| 			start_sector++; | ||||
| @@ -77,8 +73,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | ||||
|  | ||||
| 	return catalogue; | ||||
| } | ||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) | ||||
| { | ||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||
|  | ||||
| @@ -87,8 +82,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh | ||||
|  | ||||
| 	std::vector<uint8_t> root_directory; | ||||
| 	root_directory.reserve(5 * 256); | ||||
| 	for(uint8_t c = 2; c < 7; c++) | ||||
| 	{ | ||||
| 	for(uint8_t c = 2; c < 7; c++) { | ||||
| 		std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c); | ||||
| 		if(!sector) return nullptr; | ||||
| 		root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end()); | ||||
| @@ -99,8 +93,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh | ||||
| 	if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr; | ||||
| 	if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr; | ||||
|  | ||||
| 	switch(free_space_map_second_half->data[0xfd]) | ||||
| 	{ | ||||
| 	switch(free_space_map_second_half->data[0xfd]) { | ||||
| 		default: catalogue->bootOption = Catalogue::BootOption::None;		break; | ||||
| 		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break; | ||||
| 		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break; | ||||
|   | ||||