mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			16 Commits
		
	
	
		
			2020-09-17
			...
			2020-10-02
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | dcf8cb14e2 | ||
|  | 38912859e1 | ||
|  | f4a23af5d6 | ||
|  | ce0536cdfa | ||
|  | 669d8e64ab | ||
|  | 9447aa38be | ||
|  | a781c3eb4d | ||
|  | c0b1308dfd | ||
|  | 2d9dd6704a | ||
|  | 94dba70bbe | ||
|  | 022ec20e75 | ||
|  | 41f69405d8 | ||
|  | 5741e22e29 | ||
|  | 8e242eea54 | ||
|  | 703065a0a5 | ||
|  | 291aa42fe1 | 
| @@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage { | ||||
|  | ||||
| 		void access(int address); | ||||
|  | ||||
| 		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 timer_mask); | ||||
| 		inline void reevaluate_interrupts(); | ||||
|  | ||||
| 		/// Sets the current intended output value for the port and line; | ||||
| 		/// if this affects the visible output, it will be passed to the handler. | ||||
| 		void set_control_line_output(Port port, Line line, LineState value); | ||||
| 		void evaluate_cb2_output(); | ||||
| 		void evaluate_port_b_output(); | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -43,7 +43,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | ||||
| 			registers_.output[1] = value; | ||||
|  | ||||
| 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||
| 			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); | ||||
| 			evaluate_port_b_output(); | ||||
|  | ||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); | ||||
| 			reevaluate_interrupts(); | ||||
| @@ -85,6 +85,12 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | ||||
| 			registers_.next_timer[0] = registers_.timer_latch[0]; | ||||
| 			timer_is_running_[0] = true; | ||||
|  | ||||
| 			// If PB7 output mode is active, set it low. | ||||
| 			if(timer1_is_controlling_pb7()) { | ||||
| 				registers_.timer_port_b_output &= 0x7f; | ||||
| 				evaluate_port_b_output(); | ||||
| 			} | ||||
|  | ||||
| 			// Clear existing interrupt flag. | ||||
| 			registers_.interrupt_flags &= ~InterruptFlag::Timer1; | ||||
| 			reevaluate_interrupts(); | ||||
| @@ -113,6 +119,13 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | ||||
| 		case 0xb:	// Auxiliary control ('ACR'). | ||||
| 			registers_.auxiliary_control = value; | ||||
| 			evaluate_cb2_output(); | ||||
|  | ||||
| 			// This is a bit of a guess: reset the timer-based PB7 output to its default high level | ||||
| 			// any timer that timer-linked PB7 output is disabled. | ||||
| 			if(!timer1_is_controlling_pb7()) { | ||||
| 				registers_.timer_port_b_output |= 0x80; | ||||
| 			} | ||||
| 			evaluate_port_b_output(); | ||||
| 		break; | ||||
| 		case 0xc: {	// Peripheral control ('PCR'). | ||||
| //			const auto old_peripheral_control = registers_.peripheral_control; | ||||
| @@ -176,12 +189,12 @@ template <typename T> uint8_t MOS6522<T>::read(int address) { | ||||
| 		case 0x0:	// Read Port B ('IRB'). | ||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); | ||||
| 			reevaluate_interrupts(); | ||||
| 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); | ||||
| 		return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80); | ||||
| 		case 0xf: | ||||
| 		case 0x1:	// Read Port A ('IRA'). | ||||
| 			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); | ||||
| 			reevaluate_interrupts(); | ||||
| 		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); | ||||
| 		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0); | ||||
|  | ||||
| 		case 0x2:	return registers_.data_direction[1];	// Port B direction ('DDRB'). | ||||
| 		case 0x3:	return registers_.data_direction[0];	// Port A direction ('DDRA'). | ||||
| @@ -218,9 +231,10 @@ template <typename T> uint8_t MOS6522<T>::read(int address) { | ||||
| 	return 0xff; | ||||
| } | ||||
|  | ||||
| template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) { | ||||
| template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) { | ||||
| 	bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||
| 	const uint8_t input = bus_handler_.get_port_input(port); | ||||
| 	output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask); | ||||
| 	return (input & ~output_mask) | (output & output_mask); | ||||
| } | ||||
|  | ||||
| @@ -294,9 +308,9 @@ template <typename T> void MOS6522<T>::do_phase2() { | ||||
| 	} | ||||
|  | ||||
| 	// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear). | ||||
| 	// TODO: implement count down on PB6 if this bit isn't set. | ||||
| 	registers_.timer[1] -= 1 ^ ((registers_.auxiliary_control >> 5)&1); | ||||
| 	registers_.timer[1] -= timer2_clock_decrement(); | ||||
|  | ||||
| 	// TODO: can eliminate conditional branches here. | ||||
| 	if(registers_.next_timer[0] >= 0) { | ||||
| 		registers_.timer[0] = uint16_t(registers_.next_timer[0]); | ||||
| 		registers_.next_timer[0] = -1; | ||||
| @@ -347,20 +361,29 @@ template <typename T> void MOS6522<T>::do_phase1() { | ||||
| 		reevaluate_interrupts(); | ||||
|  | ||||
| 		// Determine whether to reload. | ||||
| 		if(registers_.auxiliary_control&0x40) | ||||
| 		if(timer1_is_continuous()) | ||||
| 			registers_.timer_needs_reload = true; | ||||
| 		else | ||||
| 			timer_is_running_[0] = false; | ||||
|  | ||||
| 		// Determine whether to toggle PB7. | ||||
| 		if(registers_.auxiliary_control&0x80) { | ||||
| 			registers_.output[1] ^= 0x80; | ||||
| 		if(timer1_is_controlling_pb7()) { | ||||
| 			registers_.timer_port_b_output ^= 0x80; | ||||
| 			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>()); | ||||
| 			bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]); | ||||
| 			evaluate_port_b_output(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| template <typename T> void MOS6522<T>::evaluate_port_b_output() { | ||||
| 	// Apply current timer-linked PB7 output if any atop the stated output. | ||||
| 	const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80; | ||||
| 	bus_handler_.set_port_output( | ||||
| 		Port::B, | ||||
| 		(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit, | ||||
| 		registers_.data_direction[1] | timer_control_bit); | ||||
| } | ||||
|  | ||||
| /*! Runs for a specified number of half cycles. */ | ||||
| template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) { | ||||
| 	auto number_of_half_cycles = half_cycles.as_integral(); | ||||
| @@ -455,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() { | ||||
| } | ||||
|  | ||||
| template <typename T> void MOS6522<T>::shift_out() { | ||||
| 	// When shifting out, the shift register rotates rather than strictly shifts. | ||||
| 	// TODO: is that true for all modes? | ||||
| 	if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) { | ||||
| 		registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7)); | ||||
| 	const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning; | ||||
| 	if(is_free_running || shift_bits_remaining_) { | ||||
| 		// Recirculate bits only if in free-running mode (?) | ||||
| 		const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running; | ||||
| 		registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit; | ||||
| 		evaluate_cb2_output(); | ||||
|  | ||||
| 		--shift_bits_remaining_; | ||||
|   | ||||
| @@ -34,7 +34,9 @@ class MOS6522Storage { | ||||
| 			uint8_t peripheral_control = 0; | ||||
| 			uint8_t interrupt_flags = 0; | ||||
| 			uint8_t interrupt_enable = 0; | ||||
|  | ||||
| 			bool timer_needs_reload = false; | ||||
| 			uint8_t timer_port_b_output = 0xff; | ||||
| 		} registers_; | ||||
|  | ||||
| 		// Control state. | ||||
| @@ -79,12 +81,30 @@ class MOS6522Storage { | ||||
| 			OutUnderPhase2 = 6, | ||||
| 			OutUnderCB1 = 7 | ||||
| 		}; | ||||
| 		ShiftMode shift_mode() const { | ||||
| 			return ShiftMode((registers_.auxiliary_control >> 2) & 7); | ||||
| 		bool timer1_is_controlling_pb7() const { | ||||
| 			return registers_.auxiliary_control & 0x80; | ||||
| 		} | ||||
| 		bool timer1_is_continuous() const { | ||||
| 			return registers_.auxiliary_control & 0x40; | ||||
| 		} | ||||
| 		bool is_shifting_out() const { | ||||
| 			return registers_.auxiliary_control & 0x10; | ||||
| 		} | ||||
| 		int timer2_clock_decrement() const { | ||||
| 			return 1 ^ ((registers_.auxiliary_control >> 5)&1); | ||||
| 		} | ||||
| 		int timer2_pb6_decrement() const { | ||||
| 			return (registers_.auxiliary_control >> 5)&1; | ||||
| 		} | ||||
| 		ShiftMode shift_mode() const { | ||||
| 			return ShiftMode((registers_.auxiliary_control >> 2) & 7); | ||||
| 		} | ||||
| 		bool portb_is_latched() const { | ||||
| 			return registers_.auxiliary_control & 0x02; | ||||
| 		} | ||||
| 		bool port1_is_latched() const { | ||||
| 			return registers_.auxiliary_control & 0x01; | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -860,6 +860,7 @@ | ||||
| 		4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; | ||||
| 		4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | ||||
| 		4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; | ||||
| 		4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; }; | ||||
| 		4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; }; | ||||
| 		4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; | ||||
| 		4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; | ||||
| @@ -1830,6 +1831,7 @@ | ||||
| 			buildActionMask = 2147483647; | ||||
| 			files = ( | ||||
| 				4B9F11CA2272433900701480 /* libz.tbd in Frameworks */, | ||||
| 				4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */, | ||||
| 			); | ||||
| 			runOnlyForDeploymentPostprocessing = 0; | ||||
| 		}; | ||||
|   | ||||
| @@ -67,7 +67,7 @@ | ||||
|       </Testables> | ||||
|    </TestAction> | ||||
|    <LaunchAction | ||||
|       buildConfiguration = "Release" | ||||
|       buildConfiguration = "Debug" | ||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||
|       enableASanStackUseAfterReturn = "YES" | ||||
|   | ||||
| @@ -278,8 +278,9 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | ||||
| 	size_t _chromaKernelSize; | ||||
| 	std::atomic<bool> _isUsingSupersampling; | ||||
|  | ||||
| 	// The output view. | ||||
| 	// The output view and its aspect ratio. | ||||
| 	__weak MTKView *_view; | ||||
| 	CGFloat _viewAspectRatio;	// To avoid accessing .bounds away from the main thread. | ||||
| } | ||||
|  | ||||
| - (nonnull instancetype)initWithView:(nonnull MTKView *)view { | ||||
| @@ -357,6 +358,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | ||||
|  @param size New drawable size in pixels | ||||
|  */ | ||||
| - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { | ||||
| 	_viewAspectRatio = size.width / size.height; | ||||
| 	[self setAspectRatio]; | ||||
|  | ||||
| 	@synchronized(self) { | ||||
| @@ -522,7 +524,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | ||||
|  | ||||
| - (void)setAspectRatio { | ||||
| 	const auto modals = _scanTarget.modals(); | ||||
| 	const auto viewAspectRatio = (_view.bounds.size.width / _view.bounds.size.height); | ||||
| 	simd::float3x3 sourceToDisplay{1.0f}; | ||||
|  | ||||
| 	// The starting coordinate space is [0, 1]. | ||||
| @@ -550,7 +551,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; | ||||
| 	// Determine the correct zoom level. This is a combination of (i) the necessary horizontal stretch to produce a proper | ||||
| 	// aspect ratio; and (ii) the necessary zoom from there to either fit the visible area width or height as per a decision | ||||
| 	// on letterboxing or pillarboxing. | ||||
| 	const float aspectRatioStretch = float(modals.aspect_ratio / viewAspectRatio); | ||||
| 	const float aspectRatioStretch = float(modals.aspect_ratio / _viewAspectRatio); | ||||
| 	const float fitWidthZoom = 1.0f / (float(modals.visible_area.size.width) * aspectRatioStretch); | ||||
| 	const float fitHeightZoom = 1.0f / float(modals.visible_area.size.height); | ||||
| 	const float zoom = std::min(fitWidthZoom, fitHeightZoom); | ||||
|   | ||||
| @@ -200,7 +200,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { | ||||
| 	func runTest(_ code: [UInt8], expectedRunLength: UInt32) { | ||||
| 		machine.trapHandler = self | ||||
|  | ||||
| 		let immediateCode = Data(bytes: UnsafePointer<UInt8>(code), count: code.count) | ||||
| 		let immediateCode = Data(code) | ||||
| 		machine.setData(immediateCode, atAddress: 0x200) | ||||
| 		machine.addTrapAddress(UInt16(0x200 + code.count)) | ||||
| 		machine.setValue(0x00, forAddress: 0x0000) | ||||
|   | ||||
| @@ -101,6 +101,116 @@ class MOS6522Tests: XCTestCase { | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	// MARK: PB7 timer 1 tests | ||||
| 	// These follow the same logic and check for the same results as the VICE VIC-20 via_pb7 tests. | ||||
|  | ||||
| 	// Perfoms: | ||||
| 	// | ||||
| 	//	(1) establish initial ACR and port B output value, and grab port B input value. | ||||
| 	//	(2) start timer 1, grab port B input value. | ||||
| 	//	(3) set final ACR, grab port B input value. | ||||
| 	//	(4) allow timer 1 to expire, grab port B input value. | ||||
| 	private func runTest(startACR: UInt8, endACR: UInt8, portBOutput: UInt8) -> [UInt8] { | ||||
| 		var result: [UInt8] = [] | ||||
|  | ||||
| 		// Clear all register values. | ||||
| 		for n: UInt in 0...15 { | ||||
| 			m6522.setValue(0, forRegister: n) | ||||
| 		} | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
|  | ||||
| 		// Set data direction and current port B value. | ||||
| 		m6522.setValue(0xff, forRegister: 2) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
| 		m6522.setValue(portBOutput, forRegister: 0) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
|  | ||||
| 		// Set initial ACR and grab the current port B value. | ||||
| 		m6522.setValue(startACR, forRegister: 0xb) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
| 		result.append(m6522.value(forRegister: 0)) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
|  | ||||
| 		// Start timer 1 and grab the value. | ||||
| 		m6522.setValue(1, forRegister: 0x5) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
| 		result.append(m6522.value(forRegister: 0)) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
|  | ||||
| 		// Set the final ACR value and grab value. | ||||
| 		m6522.setValue(endACR, forRegister: 0xb) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
| 		result.append(m6522.value(forRegister: 0)) | ||||
| 		m6522.run(forHalfCycles: 2) | ||||
|  | ||||
| 		// Make sure timer 1 has expired. | ||||
| 		m6522.run(forHalfCycles: 512) | ||||
|  | ||||
| 		// Grab the final value. | ||||
| 		result.append(m6522.value(forRegister: 0)) | ||||
|  | ||||
| 		return result | ||||
| 	} | ||||
|  | ||||
| 	func testTimer1PB7() { | ||||
| 		// Original top row. [original Vic-20 screen output in comments on the right] | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x00, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@	(i.e. 0, 0, 0, 0) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x40, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@	(i.e. 0, 0, 0, 0) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x80, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@	(i.e. 0, 0, 1, 0) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0xc0, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@	(i.e. 0, 0, 1, 0) | ||||
|  | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x00, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc	(i.e. 1, 1, 1, 1) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x40, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc	(i.e. 1, 1, 1, 1) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0x80, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca	(i.e. 1, 1, 1, 0) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x00, endACR: 0xc0, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca	(i.e. 1, 1, 1, 0) | ||||
|  | ||||
| 		// Second row.	[same output as first row] | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x00, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@ | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x40, portBOutput: 0x00), [0x00, 0x00, 0x00, 0x00])	// @@@@ | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x80, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@ | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0xc0, portBOutput: 0x00), [0x00, 0x00, 0x80, 0x00])	// @@b@ | ||||
|  | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x00, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x40, portBOutput: 0xff), [0xff, 0xff, 0xff, 0xff])	// cccc | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0x80, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca | ||||
| 		XCTAssertEqual(runTest(startACR: 0x40, endACR: 0xc0, portBOutput: 0xff), [0xff, 0xff, 0xff, 0x7f])	// ccca | ||||
|  | ||||
| 		// Third row. | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x00, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@	(i.e. 1, 0, 0, 0) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x40, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@	(i.e. 1, 0, 0, 0) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x80, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b	(i.e. 1, 0, 0, 1) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0xc0, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b	(i.e. 1, 0, 0, 1) | ||||
|  | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x00, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc	(i.e. 1, 0, 1, 1) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x40, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc	(i.e. 1, 0, 1, 1) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0x80, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac	(i.e. 1, 0, 0, 1) | ||||
| 		XCTAssertEqual(runTest(startACR: 0x80, endACR: 0xc0, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac	(i.e. 1, 0, 0, 1) | ||||
|  | ||||
| 		// Final row.	[same output as third row] | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x00, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@ | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x40, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x00])	// b@@@ | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x80, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0xc0, portBOutput: 0x00), [0x80, 0x00, 0x00, 0x80])	// b@@b | ||||
|  | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x00, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x40, portBOutput: 0xff), [0xff, 0x7f, 0xff, 0xff])	// cacc | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0x80, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac | ||||
| 		XCTAssertEqual(runTest(startACR: 0xc0, endACR: 0xc0, portBOutput: 0xff), [0xff, 0x7f, 0x7f, 0xff])	// caac | ||||
|  | ||||
| 		// Conclusions: | ||||
| 		// | ||||
| 		//	after inital ACR and port B value:	[original data if not in PB7 output mode, otherwise 1] | ||||
| 		//	after starting timer 1: 			[original data if not in PB7 output mode, otherwise 0] | ||||
| 		//	after final ACR value:				[original data if not in PB7 output mode, 1 if has transitioned to PB7 mode, 0 if was already in PB7 mode] | ||||
| 		//	after timer 1 expiry:				[original data if not in PB7 mode, 1 if timer has expired while in PB7 mode] | ||||
| 		// | ||||
| 		//	i.e. | ||||
| 		//		(1)	there is separate storage for the programmer-set PB7 and the timer output; | ||||
| 		//		(2)	the timer output is reset upon a timer write only if PB7 output is enabled; | ||||
| 		//		(3)	expiry toggles the output. | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	// MARK: Data direction tests | ||||
| 	func testDataDirection() { | ||||
| 		// set low four bits of register B as output, the top four as input | ||||
|   | ||||
| @@ -70,12 +70,9 @@ | ||||
|  | ||||
| - (void)testSeekToSecondBit { | ||||
| 	Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; | ||||
| 	Storage::Time target_time(1, 10); | ||||
|  | ||||
| 	Storage::Time found_time = segmentSource.seek_to(target_time); | ||||
| 	found_time.simplify(); | ||||
|  | ||||
| 	XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th"); | ||||
| 	const float found_time = segmentSource.seek_to(1.0f / 10.0f); | ||||
| 	XCTAssertTrue(fabsf(found_time - 1.0f / 20.0f) < 0.01f, @"A request to seek to 1/10th should have seeked to 1/20th"); | ||||
|  | ||||
| 	Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); | ||||
| 	next_event.length.simplify(); | ||||
| @@ -85,12 +82,9 @@ | ||||
|  | ||||
| - (void)testSeekBeyondFinalBit { | ||||
| 	Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource; | ||||
| 	Storage::Time target_time(24, 10); | ||||
| 	const float found_time = segmentSource.seek_to(2.4f); | ||||
|  | ||||
| 	Storage::Time found_time = segmentSource.seek_to(target_time); | ||||
| 	found_time.simplify(); | ||||
|  | ||||
| 	XCTAssertTrue(found_time.length == 47 && found_time.clock_rate == 20, @"A request to seek to 24/10ths should have seeked to 47/20ths"); | ||||
| 	XCTAssertTrue(fabsf(found_time - 47.0f / 20.0f) < 0.01f, @"A request to seek to 24/10ths should have seeked to 47/20ths"); | ||||
|  | ||||
| 	Storage::Disk::Track::Event next_event = segmentSource.get_next_event(); | ||||
| 	next_event.length.simplify(); | ||||
|   | ||||
| @@ -73,16 +73,14 @@ | ||||
| 	} | ||||
|  | ||||
| 	Storage::Disk::PCMTrack track(segments); | ||||
| 	Storage::Time late_time(967445, 2045454); | ||||
| 	const float late_time = 967445.0f / 2045454.0f; | ||||
| 	const auto offset = track.seek_to(late_time); | ||||
| 	XCTAssert(offset <= late_time, "Found location should be at or before sought time"); | ||||
|  | ||||
| 	const auto difference = late_time - offset; | ||||
| 	const double difference_duration = difference.get<double>(); | ||||
| 	XCTAssert(difference_duration >= 0.0 && difference_duration < 0.005, "Next event should occur soon"); | ||||
| 	XCTAssert(difference >= 0.0 && difference < 0.005, "Next event should occur soon"); | ||||
|  | ||||
| 	const double offset_duration = offset.get<double>(); | ||||
| 	XCTAssert(offset_duration >= 0.0 && offset_duration < 0.5, "Next event should occur soon"); | ||||
| 	XCTAssert(offset >= 0.0 && offset < 0.5, "Next event should occur soon"); | ||||
|  | ||||
| 	auto next_event = track.get_next_event(); | ||||
| 	double next_event_duration = next_event.length.get<double>(); | ||||
|   | ||||
| @@ -812,7 +812,7 @@ void MainWindow::setWindowTitle() { | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	if(mouseIsCaptured) title += " (press control+escape to release mouse)"; | ||||
| 	if(mouseIsCaptured) title += " (press control+escape or F8+F12 to release mouse)"; | ||||
|  | ||||
| 	QMainWindow::setWindowTitle(title); | ||||
| } | ||||
|   | ||||
| @@ -142,12 +142,25 @@ void ScanTargetWidget::setMouseDelegate(MouseDelegate *delegate) { | ||||
| 	setMouseTracking(delegate); | ||||
| } | ||||
|  | ||||
| void ScanTargetWidget::keyReleaseEvent(QKeyEvent *event) { | ||||
| 	// Releasing F8 or F12 needs to be tracked but doesn't actively do anything, | ||||
| 	// so I'm counting that as a Qt ignore. | ||||
| 	if(event->key() == Qt::Key_F8) f8State = false; | ||||
| 	if(event->key() == Qt::Key_F12) f12State = false; | ||||
| 	event->ignore(); | ||||
| } | ||||
|  | ||||
| void ScanTargetWidget::keyPressEvent(QKeyEvent *event) { | ||||
| 	// Use CTRL+Escape to end mouse captured mode, if currently captured; otherwise ignore the event. | ||||
| 	// Empirical note: control actually appears to mean command on the Mac. I have no idea what the | ||||
| 	// Mac's command key would actually be as a modifier. Fingers crossed control means control | ||||
| 	// elsewhere (?). | ||||
| 	if(mouseIsCaptured && event->key() == Qt::Key_Escape && event->modifiers()&Qt::ControlModifier) { | ||||
| 	// Use either CTRL+Escape or F8+F12 to end mouse captured mode, if currently captured; | ||||
| 	// otherwise ignore the event. | ||||
|  | ||||
| 	if(event->key() == Qt::Key_F8) f8State = true; | ||||
| 	if(event->key() == Qt::Key_F12) f12State = true; | ||||
|  | ||||
| 	if(mouseIsCaptured && ( | ||||
| 		(event->key() == Qt::Key_Escape && event->modifiers()&Qt::ControlModifier) || | ||||
| 		(f8State && f12State) | ||||
| 	)) { | ||||
| 		releaseMouse(); | ||||
|  | ||||
| 		QCursor cursor; | ||||
|   | ||||
| @@ -42,6 +42,7 @@ class ScanTargetWidget : public QOpenGLWidget { | ||||
| 		void mouseReleaseEvent(QMouseEvent *) override; | ||||
| 		void mouseMoveEvent(QMouseEvent *) override; | ||||
| 		void keyPressEvent(QKeyEvent *) override; | ||||
| 		void keyReleaseEvent(QKeyEvent *) override; | ||||
|  | ||||
| 		void releaseMouse(); | ||||
| 		void setMouseButtonPressed(Qt::MouseButton, bool); | ||||
| @@ -66,6 +67,7 @@ class ScanTargetWidget : public QOpenGLWidget { | ||||
|  | ||||
| 		MouseDelegate *mouseDelegate = nullptr; | ||||
| 		bool mouseIsCaptured = false; | ||||
| 		bool f8State = false, f12State = false;	// To support F8+F12 as a mouse release combination. | ||||
|  | ||||
| 	private slots: | ||||
| 		void vsync(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user