mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	Compare commits
	
		
			1462 Commits
		
	
	
		
			2018-10-26
			...
			2020-01-01
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ea45ae78d1 | ||
| 
						 | 
					cb7d6c185c | ||
| 
						 | 
					5be30b1f7b | ||
| 
						 | 
					0bf1a87f4c | ||
| 
						 | 
					b184426f2b | ||
| 
						 | 
					2456fb120d | ||
| 
						 | 
					23ed9ad2de | ||
| 
						 | 
					017681a97c | ||
| 
						 | 
					153f60735d | ||
| 
						 | 
					90b899c00e | ||
| 
						 | 
					5ce8d7c0e5 | ||
| 
						 | 
					c11fe25537 | ||
| 
						 | 
					c4edd635c5 | ||
| 
						 | 
					0a12893d63 | ||
| 
						 | 
					8e777c299f | ||
| 
						 | 
					09513ec14c | ||
| 
						 | 
					e23d1a2958 | ||
| 
						 | 
					6449403f6a | ||
| 
						 | 
					c8fe66092b | ||
| 
						 | 
					b33218c61e | ||
| 
						 | 
					8ce26e7182 | ||
| 
						 | 
					47068ee081 | ||
| 
						 | 
					5361ee2526 | ||
| 
						 | 
					214b6a254a | ||
| 
						 | 
					93f6964d8a | ||
| 
						 | 
					13f11e071a | ||
| 
						 | 
					f7825dd2a2 | ||
| 
						 | 
					a9d1f5d925 | ||
| 
						 | 
					2757e5d600 | ||
| 
						 | 
					5026de9653 | ||
| 
						 | 
					5fa8e046d8 | ||
| 
						 | 
					ec9357e080 | ||
| 
						 | 
					f8dd33b645 | ||
| 
						 | 
					de43e86310 | ||
| 
						 | 
					314973a5ef | ||
| 
						 | 
					d26ce65236 | ||
| 
						 | 
					1de4f179c0 | ||
| 
						 | 
					3cb5684d95 | ||
| 
						 | 
					a9a92de954 | ||
| 
						 | 
					daacd6805e | ||
| 
						 | 
					54fe01b532 | ||
| 
						 | 
					42dd70dbff | ||
| 
						 | 
					e59de71d79 | ||
| 
						 | 
					a8ba3607b7 | ||
| 
						 | 
					4205e95883 | ||
| 
						 | 
					f633cf4c3f | ||
| 
						 | 
					dfa6b11737 | ||
| 
						 | 
					42926e72cc | ||
| 
						 | 
					80cb06eb33 | ||
| 
						 | 
					5068328a15 | ||
| 
						 | 
					adc2b77833 | ||
| 
						 | 
					99415217dc | ||
| 
						 | 
					48d519d475 | ||
| 
						 | 
					ed831e5912 | ||
| 
						 | 
					1db7c7989b | ||
| 
						 | 
					b2bed82da6 | ||
| 
						 | 
					afae1443b4 | ||
| 
						 | 
					0dae608da5 | ||
| 
						 | 
					8a1fe99fa4 | ||
| 
						 | 
					ac604b30f3 | ||
| 
						 | 
					b035b92f33 | ||
| 
						 | 
					d25b48878c | ||
| 
						 | 
					34a3790e11 | ||
| 
						 | 
					f3378f3e3e | ||
| 
						 | 
					78accc1db1 | ||
| 
						 | 
					a756985e18 | ||
| 
						 | 
					30e0d4aa30 | ||
| 
						 | 
					de72c66c64 | ||
| 
						 | 
					6edd3c9698 | ||
| 
						 | 
					5456a4a39d | ||
| 
						 | 
					66d9b60b98 | ||
| 
						 | 
					274867579b | ||
| 
						 | 
					a847654ef2 | ||
| 
						 | 
					05d77d3297 | ||
| 
						 | 
					e9318efeb6 | ||
| 
						 | 
					25da5ebdae | ||
| 
						 | 
					cf16f41939 | ||
| 
						 | 
					08f2877382 | ||
| 
						 | 
					6f4444d834 | ||
| 
						 | 
					993dfeae1b | ||
| 
						 | 
					b4fd506361 | ||
| 
						 | 
					e5440a4146 | ||
| 
						 | 
					57ce10418f | ||
| 
						 | 
					47508d50a7 | ||
| 
						 | 
					56cc191a8b | ||
| 
						 | 
					2a1520c04e | ||
| 
						 | 
					3d83f5ab49 | ||
| 
						 | 
					0007dc23b3 | ||
| 
						 | 
					416d68ab3a | ||
| 
						 | 
					ed7f171736 | ||
| 
						 | 
					0e066f0f70 | ||
| 
						 | 
					3e6f51f5cf | ||
| 
						 | 
					797abae4b3 | ||
| 
						 | 
					4605b1b264 | ||
| 
						 | 
					d802e8aee3 | ||
| 
						 | 
					206ab380c7 | ||
| 
						 | 
					d85ae21b2f | ||
| 
						 | 
					470cc572fd | ||
| 
						 | 
					f0d9d8542b | ||
| 
						 | 
					d2390fcb11 | ||
| 
						 | 
					5ce612cb38 | ||
| 
						 | 
					ec7aa2d355 | ||
| 
						 | 
					9464658d1e | ||
| 
						 | 
					a3e64cae41 | ||
| 
						 | 
					e969b386f1 | ||
| 
						 | 
					af9c0aca97 | ||
| 
						 | 
					8a2ac87209 | ||
| 
						 | 
					096b447b4b | ||
| 
						 | 
					0d23f141d6 | ||
| 
						 | 
					84167af54f | ||
| 
						 | 
					8be26502c4 | ||
| 
						 | 
					ba2436206f | ||
| 
						 | 
					60a9b260b1 | ||
| 
						 | 
					e603fc6aaa | ||
| 
						 | 
					81cc278b98 | ||
| 
						 | 
					4c068e9bb8 | ||
| 
						 | 
					f23c5ada31 | ||
| 
						 | 
					dc1abd874e | ||
| 
						 | 
					1bf4686c59 | ||
| 
						 | 
					a500fbcd73 | ||
| 
						 | 
					d0ef41f11e | ||
| 
						 | 
					adf6723bf6 | ||
| 
						 | 
					37e26c0c37 | ||
| 
						 | 
					ac1575be27 | ||
| 
						 | 
					923287bf01 | ||
| 
						 | 
					77fe14cdb3 | ||
| 
						 | 
					c00ae7ce6a | ||
| 
						 | 
					d5b2e6514a | ||
| 
						 | 
					fc7f46006e | ||
| 
						 | 
					41503d7253 | ||
| 
						 | 
					f88c942fef | ||
| 
						 | 
					4bcf217324 | ||
| 
						 | 
					f6f2b4b90f | ||
| 
						 | 
					95b5db4d87 | ||
| 
						 | 
					de4403e021 | ||
| 
						 | 
					0a405d1c06 | ||
| 
						 | 
					768b3709b8 | ||
| 
						 | 
					7cc5d0b209 | ||
| 
						 | 
					c2646a415f | ||
| 
						 | 
					e1c7a140d0 | ||
| 
						 | 
					7cd11ecb7f | ||
| 
						 | 
					4dd235f677 | ||
| 
						 | 
					a7cfb840ef | ||
| 
						 | 
					acfe2c63b8 | ||
| 
						 | 
					b192381928 | ||
| 
						 | 
					c785797da6 | ||
| 
						 | 
					0408592ada | ||
| 
						 | 
					407cc78c78 | ||
| 
						 | 
					4536c6a224 | ||
| 
						 | 
					0ed87c61bd | ||
| 
						 | 
					332f0d6167 | ||
| 
						 | 
					08a27bdec7 | ||
| 
						 | 
					288cabbad1 | ||
| 
						 | 
					7ff57f8cdf | ||
| 
						 | 
					06edeea866 | ||
| 
						 | 
					3c77d3bda0 | ||
| 
						 | 
					72cb3a1cf6 | ||
| 
						 | 
					e0ceab6642 | ||
| 
						 | 
					894066984c | ||
| 
						 | 
					c91495d068 | ||
| 
						 | 
					e787c03530 | ||
| 
						 | 
					b12136691a | ||
| 
						 | 
					c04d2f6c6e | ||
| 
						 | 
					6990abc0d3 | ||
| 
						 | 
					0ce5057fd9 | ||
| 
						 | 
					ade8df7217 | ||
| 
						 | 
					b98703bd5b | ||
| 
						 | 
					82c984afa4 | ||
| 
						 | 
					1202b0a65f | ||
| 
						 | 
					facc0a1976 | ||
| 
						 | 
					25da8b7787 | ||
| 
						 | 
					253dd84109 | ||
| 
						 | 
					9d07765823 | ||
| 
						 | 
					11de0e198f | ||
| 
						 | 
					f16f0897d5 | ||
| 
						 | 
					3d4d45ef62 | ||
| 
						 | 
					04c4f5f321 | ||
| 
						 | 
					fa900d22e8 | ||
| 
						 | 
					aee2890b25 | ||
| 
						 | 
					40e1ec28fb | ||
| 
						 | 
					c6e2b1237c | ||
| 
						 | 
					efdd27a435 | ||
| 
						 | 
					2c4f372872 | ||
| 
						 | 
					74be876d72 | ||
| 
						 | 
					e8e166eec5 | ||
| 
						 | 
					4ec8fa0d20 | ||
| 
						 | 
					b019c6f8dd | ||
| 
						 | 
					6ec3c47cc0 | ||
| 
						 | 
					ccce127f13 | ||
| 
						 | 
					f4cfca0451 | ||
| 
						 | 
					eb287605f7 | ||
| 
						 | 
					2026761a07 | ||
| 
						 | 
					6a82c87320 | ||
| 
						 | 
					f0478225f0 | ||
| 
						 | 
					d6edfa5c6d | ||
| 
						 | 
					e7253a8713 | ||
| 
						 | 
					c6f6bc68e1 | ||
| 
						 | 
					ab34fad8ca | ||
| 
						 | 
					e4c77614c1 | ||
| 
						 | 
					072b0266af | ||
| 
						 | 
					7ae0902103 | ||
| 
						 | 
					8e9428623e | ||
| 
						 | 
					2c25135d8a | ||
| 
						 | 
					3741cba88c | ||
| 
						 | 
					860837d894 | ||
| 
						 | 
					cef07038c1 | ||
| 
						 | 
					0bf61c9c99 | ||
| 
						 | 
					837dfd1ab8 | ||
| 
						 | 
					0204003680 | ||
| 
						 | 
					5fc4e57db7 | ||
| 
						 | 
					c4fefe1eb3 | ||
| 
						 | 
					77ef7dc8fc | ||
| 
						 | 
					e3abbc9966 | ||
| 
						 | 
					70c6010fe0 | ||
| 
						 | 
					8c736a639a | ||
| 
						 | 
					cc7ff1ec9e | ||
| 
						 | 
					9d12ca691f | ||
| 
						 | 
					db03b03276 | ||
| 
						 | 
					45375fb5d0 | ||
| 
						 | 
					d2324e413d | ||
| 
						 | 
					e0c15f43bb | ||
| 
						 | 
					8b0d550b81 | ||
| 
						 | 
					d1259f829e | ||
| 
						 | 
					7caef46c05 | ||
| 
						 | 
					6902251d8b | ||
| 
						 | 
					0b683b0360 | ||
| 
						 | 
					5d5dc79f2c | ||
| 
						 | 
					68f9084d9e | ||
| 
						 | 
					b7c407be10 | ||
| 
						 | 
					f45798faf0 | ||
| 
						 | 
					7c66d7a13c | ||
| 
						 | 
					f9a35c6636 | ||
| 
						 | 
					5e1570258d | ||
| 
						 | 
					fc8021c0b0 | ||
| 
						 | 
					c9cd56915e | ||
| 
						 | 
					1fd19c5786 | ||
| 
						 | 
					ce66b5fd9c | ||
| 
						 | 
					8aa425c9d8 | ||
| 
						 | 
					ec68bc5047 | ||
| 
						 | 
					0971bbeebe | ||
| 
						 | 
					9292f66c2d | ||
| 
						 | 
					f3e2e88986 | ||
| 
						 | 
					6afefa107e | ||
| 
						 | 
					0ce807805d | ||
| 
						 | 
					41f3c29e30 | ||
| 
						 | 
					6c75c60149 | ||
| 
						 | 
					015f2101f8 | ||
| 
						 | 
					35f1a7ab10 | ||
| 
						 | 
					8df1eea955 | ||
| 
						 | 
					eeafdf2c03 | ||
| 
						 | 
					befe2c2929 | ||
| 
						 | 
					46ec3510be | ||
| 
						 | 
					e9965c2738 | ||
| 
						 | 
					48b0d8c329 | ||
| 
						 | 
					07582cee4a | ||
| 
						 | 
					4dbd2a805a | ||
| 
						 | 
					20bf425f98 | ||
| 
						 | 
					0567410bcf | ||
| 
						 | 
					6d1e09ba55 | ||
| 
						 | 
					f40dbefa67 | ||
| 
						 | 
					f93cdd21de | ||
| 
						 | 
					e1dc3b1915 | ||
| 
						 | 
					cbf25a16dc | ||
| 
						 | 
					14e790746b | ||
| 
						 | 
					bf7e9cfd62 | ||
| 
						 | 
					a67e0014a4 | ||
| 
						 | 
					c070f2100c | ||
| 
						 | 
					75e34b4215 | ||
| 
						 | 
					a5bbf54a27 | ||
| 
						 | 
					5309ac7c30 | ||
| 
						 | 
					731dc350b4 | ||
| 
						 | 
					635e18a50d | ||
| 
						 | 
					4857ceb3eb | ||
| 
						 | 
					1c154131f9 | ||
| 
						 | 
					fd02b6fc18 | ||
| 
						 | 
					553f3b6d8b | ||
| 
						 | 
					1135576a3a | ||
| 
						 | 
					a5057e6540 | ||
| 
						 | 
					1d790ec2a9 | ||
| 
						 | 
					0f2d72c436 | ||
| 
						 | 
					aa52652027 | ||
| 
						 | 
					4a1fa8fc13 | ||
| 
						 | 
					95d3b6e79f | ||
| 
						 | 
					5f6711b72c | ||
| 
						 | 
					d44734d105 | ||
| 
						 | 
					1aaa6331a0 | ||
| 
						 | 
					de1bfb4e24 | ||
| 
						 | 
					0cb19421e8 | ||
| 
						 | 
					92847037b3 | ||
| 
						 | 
					f4556ef6b0 | ||
| 
						 | 
					4266264449 | ||
| 
						 | 
					1aba1db62c | ||
| 
						 | 
					0fc191c87d | ||
| 
						 | 
					dc4a0e4e3b | ||
| 
						 | 
					3794d94b68 | ||
| 
						 | 
					0082dc4411 | ||
| 
						 | 
					22754683f8 | ||
| 
						 | 
					909685d87d | ||
| 
						 | 
					55710ea00e | ||
| 
						 | 
					36a9a5288b | ||
| 
						 | 
					e89be6249d | ||
| 
						 | 
					ac39fd0235 | ||
| 
						 | 
					ecc0cea5a1 | ||
| 
						 | 
					eae11cbf17 | ||
| 
						 | 
					e96386f572 | ||
| 
						 | 
					a8d481a764 | ||
| 
						 | 
					2207638287 | ||
| 
						 | 
					872897029e | ||
| 
						 | 
					51b4b5551d | ||
| 
						 | 
					7a2de47f58 | ||
| 
						 | 
					f2f98ed60c | ||
| 
						 | 
					77f14fa638 | ||
| 
						 | 
					f09a240e6c | ||
| 
						 | 
					092a61f93e | ||
| 
						 | 
					e30ba58e0d | ||
| 
						 | 
					7cb82fccc0 | ||
| 
						 | 
					ed9a5b0430 | ||
| 
						 | 
					8f59a73425 | ||
| 
						 | 
					91223b9ec8 | ||
| 
						 | 
					83f5f0e2ad | ||
| 
						 | 
					cf37e9f5de | ||
| 
						 | 
					e4f7ead894 | ||
| 
						 | 
					4134463094 | ||
| 
						 | 
					83d73fb088 | ||
| 
						 | 
					75c3e2dacd | ||
| 
						 | 
					cf07982a9b | ||
| 
						 | 
					313aaa8f95 | ||
| 
						 | 
					2e86dada1d | ||
| 
						 | 
					696af5c3a6 | ||
| 
						 | 
					f08b38d0ae | ||
| 
						 | 
					9a8352282d | ||
| 
						 | 
					3d03cce6b1 | ||
| 
						 | 
					34075a7674 | ||
| 
						 | 
					f79c87659f | ||
| 
						 | 
					c10b64e1c0 | ||
| 
						 | 
					5d5fe52144 | ||
| 
						 | 
					d461331fd2 | ||
| 
						 | 
					ff62eb6dce | ||
| 
						 | 
					374439693e | ||
| 
						 | 
					c4ef33b23f | ||
| 
						 | 
					a7ed357569 | ||
| 
						 | 
					4e5b440145 | ||
| 
						 | 
					2bd7be13b5 | ||
| 
						 | 
					4b09d7c41d | ||
| 
						 | 
					97d44129cb | ||
| 
						 | 
					b0f5f7bd37 | ||
| 
						 | 
					d1dd6876b5 | ||
| 
						 | 
					a59ec9e818 | ||
| 
						 | 
					4ead905c3c | ||
| 
						 | 
					127bb043e7 | ||
| 
						 | 
					42ebe06474 | ||
| 
						 | 
					74fe32da23 | ||
| 
						 | 
					780916551f | ||
| 
						 | 
					305b1211ba | ||
| 
						 | 
					2cf52fb89c | ||
| 
						 | 
					6e1b606adf | ||
| 
						 | 
					3bb0bf9e14 | ||
| 
						 | 
					87a6d22894 | ||
| 
						 | 
					484a0ceeb8 | ||
| 
						 | 
					da1436abd2 | ||
| 
						 | 
					125f781ced | ||
| 
						 | 
					c66c484c54 | ||
| 
						 | 
					345b32d6e3 | ||
| 
						 | 
					8b397626bf | ||
| 
						 | 
					0da1881a07 | ||
| 
						 | 
					d4077afd30 | ||
| 
						 | 
					95c45b5515 | ||
| 
						 | 
					684644420a | ||
| 
						 | 
					735586f5f8 | ||
| 
						 | 
					ddae086661 | ||
| 
						 | 
					9c7aa5f3fc | ||
| 
						 | 
					418cd07e17 | ||
| 
						 | 
					2ae5739b8b | ||
| 
						 | 
					e095a622d3 | ||
| 
						 | 
					9ab49065cd | ||
| 
						 | 
					ab50f17d87 | ||
| 
						 | 
					f5a2e180f9 | ||
| 
						 | 
					f2e1584275 | ||
| 
						 | 
					0fd8813ddb | ||
| 
						 | 
					b69180ba01 | ||
| 
						 | 
					c352d8ae8c | ||
| 
						 | 
					530e831064 | ||
| 
						 | 
					3b165a78f2 | ||
| 
						 | 
					8d87e9eb1c | ||
| 
						 | 
					f86dc082bb | ||
| 
						 | 
					d7982aa84e | ||
| 
						 | 
					516d78f5a8 | ||
| 
						 | 
					8b50a7d6e3 | ||
| 
						 | 
					4bf81d3b90 | ||
| 
						 | 
					cd75978e4e | ||
| 
						 | 
					fda99d9c5f | ||
| 
						 | 
					c5ebf75351 | ||
| 
						 | 
					2581b520af | ||
| 
						 | 
					52e5296544 | ||
| 
						 | 
					d7ce2c26e8 | ||
| 
						 | 
					f88e1b1373 | ||
| 
						 | 
					021d4dbaf1 | ||
| 
						 | 
					dbde8f2ee7 | ||
| 
						 | 
					5d06930df4 | ||
| 
						 | 
					7722596a3b | ||
| 
						 | 
					1de1818ebb | ||
| 
						 | 
					885f890df1 | ||
| 
						 | 
					e195497ab7 | ||
| 
						 | 
					fcd2143697 | ||
| 
						 | 
					3f45cd2380 | ||
| 
						 | 
					a8a34497ff | ||
| 
						 | 
					953423cc02 | ||
| 
						 | 
					a2ca887b99 | ||
| 
						 | 
					3c5ae9cf8e | ||
| 
						 | 
					fe621d7e52 | ||
| 
						 | 
					814bb4ec63 | ||
| 
						 | 
					e8bc254f3f | ||
| 
						 | 
					3c146a3fb2 | ||
| 
						 | 
					b609ce6fcb | ||
| 
						 | 
					929475d31e | ||
| 
						 | 
					f14d98452e | ||
| 
						 | 
					9d17d48bca | ||
| 
						 | 
					4ac3839185 | ||
| 
						 | 
					c089d1cd09 | ||
| 
						 | 
					cb85ec25cc | ||
| 
						 | 
					fbf95ec2b8 | ||
| 
						 | 
					6adca98f34 | ||
| 
						 | 
					48f4d8b875 | ||
| 
						 | 
					7758f9d0a9 | ||
| 
						 | 
					7112f0336c | ||
| 
						 | 
					298694a881 | ||
| 
						 | 
					7ff4594f09 | ||
| 
						 | 
					e8bd538182 | ||
| 
						 | 
					8489e8650f | ||
| 
						 | 
					114f81941e | ||
| 
						 | 
					077c7d767f | ||
| 
						 | 
					8f88addf9f | ||
| 
						 | 
					f28c124039 | ||
| 
						 | 
					a416bc0058 | ||
| 
						 | 
					e78b1dcf3c | ||
| 
						 | 
					8a14f5d814 | ||
| 
						 | 
					e5f983fbac | ||
| 
						 | 
					3e639e96e7 | ||
| 
						 | 
					61993f0687 | ||
| 
						 | 
					5f16fa8c08 | ||
| 
						 | 
					dcea9c9ab2 | ||
| 
						 | 
					e7bf0799b6 | ||
| 
						 | 
					e760421f6f | ||
| 
						 | 
					8ea4c17315 | ||
| 
						 | 
					2e24da4614 | ||
| 
						 | 
					e46601872b | ||
| 
						 | 
					6d0e41b760 | ||
| 
						 | 
					5a82df837d | ||
| 
						 | 
					776b819a5a | ||
| 
						 | 
					1783f6c84b | ||
| 
						 | 
					2ef2c73efe | ||
| 
						 | 
					55e003ccc1 | ||
| 
						 | 
					3d54d55dbb | ||
| 
						 | 
					72c0a631f7 | ||
| 
						 | 
					1608a90d5d | ||
| 
						 | 
					4f8a45a6ce | ||
| 
						 | 
					4f0f1dcf18 | ||
| 
						 | 
					839e51d92d | ||
| 
						 | 
					e470cf23d8 | ||
| 
						 | 
					8d4a96683a | ||
| 
						 | 
					f53411a319 | ||
| 
						 | 
					128a1da626 | ||
| 
						 | 
					962275c22a | ||
| 
						 | 
					3002ac8a4a | ||
| 
						 | 
					ff43674638 | ||
| 
						 | 
					2f6c366668 | ||
| 
						 | 
					2ce1f0a3b1 | ||
| 
						 | 
					210129c3a1 | ||
| 
						 | 
					934901447a | ||
| 
						 | 
					960b289e70 | ||
| 
						 | 
					243e40cd79 | ||
| 
						 | 
					c849188016 | ||
| 
						 | 
					87e8dade2f | ||
| 
						 | 
					6fc5b4e825 | ||
| 
						 | 
					00ce7f8ae0 | ||
| 
						 | 
					6e0e9afe2f | ||
| 
						 | 
					cb0d994827 | ||
| 
						 | 
					bee782234a | ||
| 
						 | 
					64dad35026 | ||
| 
						 | 
					cbd1a8cf78 | ||
| 
						 | 
					a4ab0afce3 | ||
| 
						 | 
					1c7e0f3c9d | ||
| 
						 | 
					318cdb41ea | ||
| 
						 | 
					2f8e31bc8b | ||
| 
						 | 
					310c722cc0 | ||
| 
						 | 
					25956bd90f | ||
| 
						 | 
					1a60ced61b | ||
| 
						 | 
					081316c071 | ||
| 
						 | 
					eafbc12cc1 | ||
| 
						 | 
					ca08716c52 | ||
| 
						 | 
					30cef1ee22 | ||
| 
						 | 
					5598802439 | ||
| 
						 | 
					1c6720b0db | ||
| 
						 | 
					404b088199 | ||
| 
						 | 
					7d61df238a | ||
| 
						 | 
					c86db12f1c | ||
| 
						 | 
					ce2e85af8b | ||
| 
						 | 
					2d82855f26 | ||
| 
						 | 
					faec516a2c | ||
| 
						 | 
					8e274ec5d0 | ||
| 
						 | 
					bb1a0a0b76 | ||
| 
						 | 
					252650808d | ||
| 
						 | 
					e3d9254555 | ||
| 
						 | 
					90cf99b626 | ||
| 
						 | 
					955e909e61 | ||
| 
						 | 
					8339e2044c | ||
| 
						 | 
					0e0c789b02 | ||
| 
						 | 
					7e001c1d03 | ||
| 
						 | 
					9047932b81 | ||
| 
						 | 
					f668e4a54c | ||
| 
						 | 
					ce1c96d68c | ||
| 
						 | 
					0f67e490e8 | ||
| 
						 | 
					895c315fa5 | ||
| 
						 | 
					a90a74a512 | ||
| 
						 | 
					3e1286cbef | ||
| 
						 | 
					949c1e1668 | ||
| 
						 | 
					bbd4e4d3dc | ||
| 
						 | 
					4c5f596533 | ||
| 
						 | 
					4859d3781b | ||
| 
						 | 
					bac0461f7f | ||
| 
						 | 
					f26a200d78 | ||
| 
						 | 
					28ccb7b54e | ||
| 
						 | 
					b6e4c8209b | ||
| 
						 | 
					16548f0765 | ||
| 
						 | 
					6a80832140 | ||
| 
						 | 
					c6cf0e914b | ||
| 
						 | 
					35b1a55c12 | ||
| 
						 | 
					e3794c0c0e | ||
| 
						 | 
					f88dc23c71 | ||
| 
						 | 
					0e293e4983 | ||
| 
						 | 
					e334abfe20 | ||
| 
						 | 
					fd2fbe0e59 | ||
| 
						 | 
					330b27d085 | ||
| 
						 | 
					478f2533b5 | ||
| 
						 | 
					b96972a4b9 | ||
| 
						 | 
					f2b083f4de | ||
| 
						 | 
					80f6d665d9 | ||
| 
						 | 
					a07488cf1b | ||
| 
						 | 
					d67c5145c0 | ||
| 
						 | 
					5e76d593af | ||
| 
						 | 
					83393e8e91 | ||
| 
						 | 
					e08a64d455 | ||
| 
						 | 
					b93f9b3973 | ||
| 
						 | 
					9c517d07d4 | ||
| 
						 | 
					f45de5b87a | ||
| 
						 | 
					011d76175c | ||
| 
						 | 
					96005261c7 | ||
| 
						 | 
					c8177af45a | ||
| 
						 | 
					97eff5b16d | ||
| 
						 | 
					917520fb1e | ||
| 
						 | 
					335dda3d55 | ||
| 
						 | 
					0c8e313fd5 | ||
| 
						 | 
					f64ec11668 | ||
| 
						 | 
					9bbccd89d3 | ||
| 
						 | 
					1ae3799aee | ||
| 
						 | 
					260843e5b1 | ||
| 
						 | 
					e3f22e5787 | ||
| 
						 | 
					2aa308efdd | ||
| 
						 | 
					74c18d7861 | ||
| 
						 | 
					c41cccd9a6 | ||
| 
						 | 
					bba34b28b8 | ||
| 
						 | 
					d8a41575c8 | ||
| 
						 | 
					0521de668a | ||
| 
						 | 
					12441bddab | ||
| 
						 | 
					bc25c52683 | ||
| 
						 | 
					eb3fb70ea1 | ||
| 
						 | 
					2f90ed1f32 | ||
| 
						 | 
					f3dd4b028d | ||
| 
						 | 
					7dcad516bd | ||
| 
						 | 
					9859f99513 | ||
| 
						 | 
					51b7f2777d | ||
| 
						 | 
					2f2478d2d3 | ||
| 
						 | 
					a43ada82b2 | ||
| 
						 | 
					5149f290d0 | ||
| 
						 | 
					0dc6f08deb | ||
| 
						 | 
					b1f04ed96d | ||
| 
						 | 
					cd49b3c89b | ||
| 
						 | 
					f894d43111 | ||
| 
						 | 
					4033c0c754 | ||
| 
						 | 
					786b26d23e | ||
| 
						 | 
					d08d8ed22c | ||
| 
						 | 
					b7b62aa3f6 | ||
| 
						 | 
					39d7e3c62c | ||
| 
						 | 
					81b57ecf7c | ||
| 
						 | 
					572e8b52e1 | ||
| 
						 | 
					9b634472c6 | ||
| 
						 | 
					d8bc20b1ab | ||
| 
						 | 
					d2bfd59953 | ||
| 
						 | 
					3d20ae47ea | ||
| 
						 | 
					85cf8d89bc | ||
| 
						 | 
					50e954223a | ||
| 
						 | 
					109d5d16bd | ||
| 
						 | 
					1672dc5946 | ||
| 
						 | 
					5769944918 | ||
| 
						 | 
					9ef1211d53 | ||
| 
						 | 
					f2ae04597f | ||
| 
						 | 
					1327de1c82 | ||
| 
						 | 
					827c4e172a | ||
| 
						 | 
					c300bd17fa | ||
| 
						 | 
					0187fd8eae | ||
| 
						 | 
					0469f0240b | ||
| 
						 | 
					4aca6c5ef8 | ||
| 
						 | 
					d69aee4972 | ||
| 
						 | 
					3da47318b1 | ||
| 
						 | 
					ef036df2bc | ||
| 
						 | 
					579f68cf11 | ||
| 
						 | 
					90f6ca4635 | ||
| 
						 | 
					374cac0107 | ||
| 
						 | 
					4d361b1952 | ||
| 
						 | 
					fcee7779b0 | ||
| 
						 | 
					b4191b6225 | ||
| 
						 | 
					dbee37ab34 | ||
| 
						 | 
					a3ad0ab09b | ||
| 
						 | 
					ed0c4c117b | ||
| 
						 | 
					2432151bf8 | ||
| 
						 | 
					2129bfc570 | ||
| 
						 | 
					8de6cd3f44 | ||
| 
						 | 
					9b9831f28b | ||
| 
						 | 
					8a2cac0d0c | ||
| 
						 | 
					e17b105574 | ||
| 
						 | 
					67c5f6b7cb | ||
| 
						 | 
					d452d070a1 | ||
| 
						 | 
					a846c3245d | ||
| 
						 | 
					4ffa3c1b49 | ||
| 
						 | 
					b2a6682798 | ||
| 
						 | 
					f3aac603f8 | ||
| 
						 | 
					712cb473f7 | ||
| 
						 | 
					3c68a5ca65 | ||
| 
						 | 
					20670bab2f | ||
| 
						 | 
					86d709ae01 | ||
| 
						 | 
					0aba95cc9d | ||
| 
						 | 
					de3c8373fd | ||
| 
						 | 
					75ecd4e72d | ||
| 
						 | 
					56555a4d99 | ||
| 
						 | 
					cfad20bb33 | ||
| 
						 | 
					fa226bb1b9 | ||
| 
						 | 
					77333ff9f7 | ||
| 
						 | 
					b9a34bee51 | ||
| 
						 | 
					22ee51c12c | ||
| 
						 | 
					ee8d853fcb | ||
| 
						 | 
					19198ea665 | ||
| 
						 | 
					bcbda4d855 | ||
| 
						 | 
					79a624e696 | ||
| 
						 | 
					c123ca1054 | ||
| 
						 | 
					9f0f35033d | ||
| 
						 | 
					3633285aaa | ||
| 
						 | 
					cb16790330 | ||
| 
						 | 
					67055d8b56 | ||
| 
						 | 
					ca37fd8f4c | ||
| 
						 | 
					46b98dab70 | ||
| 
						 | 
					0568996264 | ||
| 
						 | 
					7baad61746 | ||
| 
						 | 
					1d1e0d74f8 | ||
| 
						 | 
					d53d1c616f | ||
| 
						 | 
					5b05a9bc61 | ||
| 
						 | 
					2c39229b13 | ||
| 
						 | 
					59b5dfddec | ||
| 
						 | 
					b730ac5d5a | ||
| 
						 | 
					4860d8a7df | ||
| 
						 | 
					9f0cde3d69 | ||
| 
						 | 
					c8917e677b | ||
| 
						 | 
					6c2cc206a6 | ||
| 
						 | 
					5a9f3cfc1e | ||
| 
						 | 
					8f28b33342 | ||
| 
						 | 
					cac97a9663 | ||
| 
						 | 
					2ccb564a7b | ||
| 
						 | 
					d1d0430fce | ||
| 
						 | 
					be251d6b03 | ||
| 
						 | 
					6cfaf920ee | ||
| 
						 | 
					1657f8768c | ||
| 
						 | 
					c4ab0bb867 | ||
| 
						 | 
					886946cc8c | ||
| 
						 | 
					ed4ddcfda8 | ||
| 
						 | 
					7886cd63bd | ||
| 
						 | 
					69b94719a1 | ||
| 
						 | 
					b4a3f66773 | ||
| 
						 | 
					ab14433151 | ||
| 
						 | 
					5078f6fb5c | ||
| 
						 | 
					fc6d62aefb | ||
| 
						 | 
					f73bccfec8 | ||
| 
						 | 
					96be1a3f62 | ||
| 
						 | 
					52e96e3d2a | ||
| 
						 | 
					33e2721eb2 | ||
| 
						 | 
					4bc44666e5 | ||
| 
						 | 
					3d8e4f96c8 | ||
| 
						 | 
					94457d81b6 | ||
| 
						 | 
					c212bf27db | ||
| 
						 | 
					59b5ee65d4 | ||
| 
						 | 
					60cedca97b | ||
| 
						 | 
					1a9aa60bf7 | ||
| 
						 | 
					6438a5ca1f | ||
| 
						 | 
					3f303511bd | ||
| 
						 | 
					fb352a8d40 | ||
| 
						 | 
					ea7899f47d | ||
| 
						 | 
					fb6da1de4a | ||
| 
						 | 
					2651b15db1 | ||
| 
						 | 
					6e7a733c3c | ||
| 
						 | 
					245e27c893 | ||
| 
						 | 
					793c2df7ee | ||
| 
						 | 
					28de629c08 | ||
| 
						 | 
					210bcaa56d | ||
| 
						 | 
					d7329c1bdd | ||
| 
						 | 
					a5f0761a43 | ||
| 
						 | 
					dd963d6161 | ||
| 
						 | 
					96c0253ee2 | ||
| 
						 | 
					191a7a9386 | ||
| 
						 | 
					387be4a0a6 | ||
| 
						 | 
					b9c2c42bc0 | ||
| 
						 | 
					fffe6ed2df | ||
| 
						 | 
					c4cbe9476c | ||
| 
						 | 
					0a67cc3dab | ||
| 
						 | 
					726e07ed5b | ||
| 
						 | 
					ebb6313eef | ||
| 
						 | 
					11d8f765b2 | ||
| 
						 | 
					514e57b3e9 | ||
| 
						 | 
					d8fb6fb951 | ||
| 
						 | 
					255f0d4b2a | ||
| 
						 | 
					d30e7504c2 | ||
| 
						 | 
					8d0cd356fd | ||
| 
						 | 
					aff40bf00a | ||
| 
						 | 
					eedf7358b4 | ||
| 
						 | 
					26aebcc167 | ||
| 
						 | 
					9d420c727e | ||
| 
						 | 
					60fe84ad16 | ||
| 
						 | 
					6a44c682ad | ||
| 
						 | 
					60df44f0ca | ||
| 
						 | 
					ac926f5070 | ||
| 
						 | 
					6e9a4a48f7 | ||
| 
						 | 
					a8894b308a | ||
| 
						 | 
					7cc91e1bc5 | ||
| 
						 | 
					9eb51f164c | ||
| 
						 | 
					a1c00e9318 | ||
| 
						 | 
					17666bc059 | ||
| 
						 | 
					241d29ff7c | ||
| 
						 | 
					c5039a4719 | ||
| 
						 | 
					fd604048db | ||
| 
						 | 
					6a77ed1e07 | ||
| 
						 | 
					9e38815ec4 | ||
| 
						 | 
					86c325c4ec | ||
| 
						 | 
					bfcc6cf12c | ||
| 
						 | 
					8ba8cf7c23 | ||
| 
						 | 
					6c588a1510 | ||
| 
						 | 
					651fd9c4a5 | ||
| 
						 | 
					5d0db2198c | ||
| 
						 | 
					d81053ea38 | ||
| 
						 | 
					8d39c3bc98 | ||
| 
						 | 
					da351a3e32 | ||
| 
						 | 
					c0591090f5 | ||
| 
						 | 
					538aecb46e | ||
| 
						 | 
					dbdbea85c2 | ||
| 
						 | 
					ba2224dd06 | ||
| 
						 | 
					44e2aa9183 | ||
| 
						 | 
					202bff70fe | ||
| 
						 | 
					26c0cd7f7c | ||
| 
						 | 
					cb76301fbe | ||
| 
						 | 
					8bfa12edf1 | ||
| 
						 | 
					7daa969a5a | ||
| 
						 | 
					4aeb60100d | ||
| 
						 | 
					e2c7aaac5a | ||
| 
						 | 
					6ff661c30d | ||
| 
						 | 
					79066f8628 | ||
| 
						 | 
					2c813a2692 | ||
| 
						 | 
					d2cb595b83 | ||
| 
						 | 
					cc4abcb00a | ||
| 
						 | 
					c1ca85987f | ||
| 
						 | 
					ecb5a0b8cc | ||
| 
						 | 
					e12e8fc616 | ||
| 
						 | 
					1fbbf32cd2 | ||
| 
						 | 
					31edb15369 | ||
| 
						 | 
					d7883d18d4 | ||
| 
						 | 
					40100773d3 | ||
| 
						 | 
					4048ed3a33 | ||
| 
						 | 
					11f2d3cea7 | ||
| 
						 | 
					aa656a39b8 | ||
| 
						 | 
					e830d23533 | ||
| 
						 | 
					9a666fb8cc | ||
| 
						 | 
					0e208ed432 | ||
| 
						 | 
					c8b769de8a | ||
| 
						 | 
					c447655047 | ||
| 
						 | 
					3ec9a1d869 | ||
| 
						 | 
					d326886852 | ||
| 
						 | 
					faef917cbd | ||
| 
						 | 
					d27ba90c07 | ||
| 
						 | 
					db4ca746e3 | ||
| 
						 | 
					d50fbfb506 | ||
| 
						 | 
					5d283a9f1f | ||
| 
						 | 
					86fdc75feb | ||
| 
						 | 
					b63231523a | ||
| 
						 | 
					70e296674d | ||
| 
						 | 
					5089fcd2f6 | ||
| 
						 | 
					df2ce8ca6f | ||
| 
						 | 
					7e209353bb | ||
| 
						 | 
					c2806a94e2 | ||
| 
						 | 
					d428120776 | ||
| 
						 | 
					8c8493bc9d | ||
| 
						 | 
					6b996ae57d | ||
| 
						 | 
					ccfe1b13cb | ||
| 
						 | 
					0c1c10bc66 | ||
| 
						 | 
					fafd1801fe | ||
| 
						 | 
					bcf6f665b8 | ||
| 
						 | 
					bd069490b5 | ||
| 
						 | 
					79d8d27b4c | ||
| 
						 | 
					624b0b6372 | ||
| 
						 | 
					7976cf5b3c | ||
| 
						 | 
					440f52c943 | ||
| 
						 | 
					47b1218a68 | ||
| 
						 | 
					91ced056d2 | ||
| 
						 | 
					8dace34e63 | ||
| 
						 | 
					8182b0363f | ||
| 
						 | 
					c5b036fedf | ||
| 
						 | 
					e26ddd0ed5 | ||
| 
						 | 
					ca83431e54 | ||
| 
						 | 
					68a3e5a739 | ||
| 
						 | 
					b98f10cb45 | ||
| 
						 | 
					9730800b6a | ||
| 
						 | 
					506276a2bd | ||
| 
						 | 
					00c32e4b59 | ||
| 
						 | 
					df56e6fe53 | ||
| 
						 | 
					756641e837 | ||
| 
						 | 
					05c2854dbc | ||
| 
						 | 
					5c8aacdc17 | ||
| 
						 | 
					745a5ab749 | ||
| 
						 | 
					fe0dc4df88 | ||
| 
						 | 
					33f2664fe9 | ||
| 
						 | 
					a17e47fa43 | ||
| 
						 | 
					877b46d2c1 | ||
| 
						 | 
					cc7226ae9f | ||
| 
						 | 
					bde975a3b9 | ||
| 
						 | 
					f6f9024631 | ||
| 
						 | 
					39aae34323 | ||
| 
						 | 
					5630141ad7 | ||
| 
						 | 
					535747e3f2 | ||
| 
						 | 
					59a94943aa | ||
| 
						 | 
					bf4889f238 | ||
| 
						 | 
					7cc5afd798 | ||
| 
						 | 
					11ab021672 | ||
| 
						 | 
					feafd4bdae | ||
| 
						 | 
					d6150645c0 | ||
| 
						 | 
					ccd2cb44a2 | ||
| 
						 | 
					ec5701459c | ||
| 
						 | 
					ad8b68c998 | ||
| 
						 | 
					c8066b01b6 | ||
| 
						 | 
					ebd59f4dd3 | ||
| 
						 | 
					109953ef49 | ||
| 
						 | 
					124c7bcbb0 | ||
| 
						 | 
					a0321aa6ff | ||
| 
						 | 
					567feaac10 | ||
| 
						 | 
					15c38e2f15 | ||
| 
						 | 
					3c075e9542 | ||
| 
						 | 
					9230969f43 | ||
| 
						 | 
					0e16c67805 | ||
| 
						 | 
					697e094a4e | ||
| 
						 | 
					50d37798a2 | ||
| 
						 | 
					e9d0676e75 | ||
| 
						 | 
					7591906777 | ||
| 
						 | 
					08671ed69c | ||
| 
						 | 
					511d292e73 | ||
| 
						 | 
					a413ae11cb | ||
| 
						 | 
					833258f3d7 | ||
| 
						 | 
					b8a1553368 | ||
| 
						 | 
					058fe3e986 | ||
| 
						 | 
					51ee83a427 | ||
| 
						 | 
					5b21da7874 | ||
| 
						 | 
					bd7f00bd9c | ||
| 
						 | 
					517cca251f | ||
| 
						 | 
					1033abd9fe | ||
| 
						 | 
					113d022741 | ||
| 
						 | 
					299a7b99ae | ||
| 
						 | 
					66540ff86f | ||
| 
						 | 
					8557558bd8 | ||
| 
						 | 
					376cf08c71 | ||
| 
						 | 
					83e5e650d2 | ||
| 
						 | 
					b860ba2ee3 | ||
| 
						 | 
					661fe1e649 | ||
| 
						 | 
					5b8375f0a0 | ||
| 
						 | 
					abe55fe950 | ||
| 
						 | 
					4d4ddded6d | ||
| 
						 | 
					1328708a70 | ||
| 
						 | 
					85298319fa | ||
| 
						 | 
					881feb1bd3 | ||
| 
						 | 
					3e9fa63799 | ||
| 
						 | 
					da2b190288 | ||
| 
						 | 
					48d837c636 | ||
| 
						 | 
					983407896c | ||
| 
						 | 
					5c08bb810e | ||
| 
						 | 
					17635da812 | ||
| 
						 | 
					6d985866ee | ||
| 
						 | 
					723137c0d4 | ||
| 
						 | 
					938928865d | ||
| 
						 | 
					d80b0cbf90 | ||
| 
						 | 
					e88ef30ce6 | ||
| 
						 | 
					4197c6f149 | ||
| 
						 | 
					035f07877c | ||
| 
						 | 
					4632be4fe5 | ||
| 
						 | 
					b3d2b4cd37 | ||
| 
						 | 
					c86fe9ada9 | ||
| 
						 | 
					ecf93b7822 | ||
| 
						 | 
					541b75ee6e | ||
| 
						 | 
					77b08febdb | ||
| 
						 | 
					fcda376f33 | ||
| 
						 | 
					0848fc7e03 | ||
| 
						 | 
					3bb8d6717f | ||
| 
						 | 
					5e2496d59c | ||
| 
						 | 
					c52da9d802 | ||
| 
						 | 
					1d3dde32f2 | ||
| 
						 | 
					0b999ce0e4 | ||
| 
						 | 
					b04bd7069d | ||
| 
						 | 
					249b0fbb32 | ||
| 
						 | 
					41740fb45e | ||
| 
						 | 
					0ad88508f7 | ||
| 
						 | 
					8293b18278 | ||
| 
						 | 
					2ba0364850 | ||
| 
						 | 
					8b72043f33 | ||
| 
						 | 
					2e7bc0b98a | ||
| 
						 | 
					f0f9722ca6 | ||
| 
						 | 
					b5ef88902b | ||
| 
						 | 
					8278809383 | ||
| 
						 | 
					4367459cf2 | ||
| 
						 | 
					254132b83d | ||
| 
						 | 
					7b466e6d0a | ||
| 
						 | 
					7e6d4f5a3e | ||
| 
						 | 
					ce099a297a | ||
| 
						 | 
					949c848815 | ||
| 
						 | 
					9bf9b9ea8c | ||
| 
						 | 
					d8ed8b66f3 | ||
| 
						 | 
					a131d39451 | ||
| 
						 | 
					b540f58457 | ||
| 
						 | 
					4f5a38b5c5 | ||
| 
						 | 
					cefc3af08b | ||
| 
						 | 
					e6ed50383c | ||
| 
						 | 
					96facc103a | ||
| 
						 | 
					407bbfb379 | ||
| 
						 | 
					a99ebda513 | ||
| 
						 | 
					537b604fc9 | ||
| 
						 | 
					98bc570bf7 | ||
| 
						 | 
					181b77c490 | ||
| 
						 | 
					bc9eb82e6f | ||
| 
						 | 
					29fc024ecd | ||
| 
						 | 
					c1695d0910 | ||
| 
						 | 
					6d6a4e79c9 | ||
| 
						 | 
					417a3e1540 | ||
| 
						 | 
					fa8c804d47 | ||
| 
						 | 
					68392ce6f5 | ||
| 
						 | 
					6873f62ad8 | ||
| 
						 | 
					5f385e15f6 | ||
| 
						 | 
					8c5d37b6ee | ||
| 
						 | 
					9c3c2192dd | ||
| 
						 | 
					4f9f73ca81 | ||
| 
						 | 
					2c9a1f7b16 | ||
| 
						 | 
					0ea4c1ac80 | ||
| 
						 | 
					a873ec97eb | ||
| 
						 | 
					cc8a65780e | ||
| 
						 | 
					c117deb43b | ||
| 
						 | 
					ae31d45c88 | ||
| 
						 | 
					a0eb20ff1f | ||
| 
						 | 
					34fe9981e4 | ||
| 
						 | 
					291e91375f | ||
| 
						 | 
					857f74b320 | ||
| 
						 | 
					1d9608efc7 | ||
| 
						 | 
					93616a4903 | ||
| 
						 | 
					bb07206c55 | ||
| 
						 | 
					2e5c0811e7 | ||
| 
						 | 
					f6ac407e4d | ||
| 
						 | 
					078c3135df | ||
| 
						 | 
					92568c90c8 | ||
| 
						 | 
					f1879c5fbc | ||
| 
						 | 
					31bb770fdd | ||
| 
						 | 
					e430f2658f | ||
| 
						 | 
					3060175ff5 | ||
| 
						 | 
					eb4233e2fd | ||
| 
						 | 
					6b4c656849 | ||
| 
						 | 
					1b8fada6aa | ||
| 
						 | 
					7332c64964 | ||
| 
						 | 
					977f9ee831 | ||
| 
						 | 
					16fb3b49a5 | ||
| 
						 | 
					3da1b3bf9b | ||
| 
						 | 
					bc00856c05 | ||
| 
						 | 
					52e3dece81 | ||
| 
						 | 
					2c1d8fa18a | ||
| 
						 | 
					3e34ae67f6 | ||
| 
						 | 
					d6e16d0042 | ||
| 
						 | 
					8e02d29ae6 | ||
| 
						 | 
					ceebecec8d | ||
| 
						 | 
					05d1eda422 | ||
| 
						 | 
					31f318ad43 | ||
| 
						 | 
					270f46e147 | ||
| 
						 | 
					c0e9c37cc7 | ||
| 
						 | 
					8564945713 | ||
| 
						 | 
					7bd7f3fb73 | ||
| 
						 | 
					5b5bfc8445 | ||
| 
						 | 
					c466b6f9e7 | ||
| 
						 | 
					407643c575 | ||
| 
						 | 
					d9071ee9f1 | ||
| 
						 | 
					97e118abfa | ||
| 
						 | 
					412f091d76 | ||
| 
						 | 
					d9278e9827 | ||
| 
						 | 
					ca1f669e64 | ||
| 
						 | 
					0298b1b3b7 | ||
| 
						 | 
					4b1324de77 | ||
| 
						 | 
					8e8dce9bec | ||
| 
						 | 
					f4350522bf | ||
| 
						 | 
					e2abb66a11 | ||
| 
						 | 
					ab5fcab9bf | ||
| 
						 | 
					cf547ef569 | ||
| 
						 | 
					e75b386f7d | ||
| 
						 | 
					796203859f | ||
| 
						 | 
					40f68b70c1 | ||
| 
						 | 
					40b2fe7339 | ||
| 
						 | 
					a3b6d2d16e | ||
| 
						 | 
					3983f8303f | ||
| 
						 | 
					7cbd5e0ef6 | ||
| 
						 | 
					dab9bb6575 | ||
| 
						 | 
					7df85ea695 | ||
| 
						 | 
					c132bda01c | ||
| 
						 | 
					4e25bcfcdc | ||
| 
						 | 
					ea463549c7 | ||
| 
						 | 
					723acb31b3 | ||
| 
						 | 
					5725db9234 | ||
| 
						 | 
					8557e563bc | ||
| 
						 | 
					d2491633ce | ||
| 
						 | 
					002796e5f5 | ||
| 
						 | 
					fa0accf251 | ||
| 
						 | 
					dcb8176d90 | ||
| 
						 | 
					be32b1a198 | ||
| 
						 | 
					582e4acc11 | ||
| 
						 | 
					10f75acf71 | ||
| 
						 | 
					b9933f512f | ||
| 
						 | 
					75a7f7ab22 | ||
| 
						 | 
					757be2906e | ||
| 
						 | 
					e214584c76 | ||
| 
						 | 
					0bb6b498ce | ||
| 
						 | 
					958d44a20d | ||
| 
						 | 
					bb9424d944 | ||
| 
						 | 
					11bf706aa2 | ||
| 
						 | 
					033b8e6b36 | ||
| 
						 | 
					7c3ea7b2ea | ||
| 
						 | 
					a08043ae88 | ||
| 
						 | 
					7c132a3ed5 | ||
| 
						 | 
					20e774be1e | ||
| 
						 | 
					6d6046757d | ||
| 
						 | 
					55073b0a52 | ||
| 
						 | 
					44eb4e51ed | ||
| 
						 | 
					3cb042a49d | ||
| 
						 | 
					b78ea7d24c | ||
| 
						 | 
					c66728dce2 | ||
| 
						 | 
					0be9a0cb88 | ||
| 
						 | 
					a90f12dab7 | ||
| 
						 | 
					ef33b004f9 | ||
| 
						 | 
					2cac4b0d74 | ||
| 
						 | 
					a49f516265 | ||
| 
						 | 
					71ac26944d | ||
| 
						 | 
					2d97fc1f59 | ||
| 
						 | 
					9ef7743205 | ||
| 
						 | 
					ee7ae11e90 | ||
| 
						 | 
					f67d7f1db5 | ||
| 
						 | 
					99981751a2 | ||
| 
						 | 
					ffdf02c5df | ||
| 
						 | 
					27c7d00a05 | ||
| 
						 | 
					64c4137e5b | ||
| 
						 | 
					8c26d0c6e6 | ||
| 
						 | 
					81dcfd9f85 | ||
| 
						 | 
					9334557fbf | ||
| 
						 | 
					b09de8efce | ||
| 
						 | 
					5a50eb56dd | ||
| 
						 | 
					e49b257e94 | ||
| 
						 | 
					b8a0f4e831 | ||
| 
						 | 
					c265ea9847 | ||
| 
						 | 
					29f8dcfb40 | ||
| 
						 | 
					0c05983617 | ||
| 
						 | 
					0bd653708c | ||
| 
						 | 
					41d800cb63 | ||
| 
						 | 
					cadc0bd509 | ||
| 
						 | 
					b64da2710a | ||
| 
						 | 
					82b08d0e3a | ||
| 
						 | 
					8f77d1831b | ||
| 
						 | 
					be722143e1 | ||
| 
						 | 
					d8d974e2d7 | ||
| 
						 | 
					9b7ca6f271 | ||
| 
						 | 
					8ce018dbab | ||
| 
						 | 
					180062c58c | ||
| 
						 | 
					6076b8df69 | ||
| 
						 | 
					5e65ee79b1 | ||
| 
						 | 
					c0861c7362 | ||
| 
						 | 
					37656f14d8 | ||
| 
						 | 
					dec5535e54 | ||
| 
						 | 
					1f0e3b157a | ||
| 
						 | 
					d802e83f49 | ||
| 
						 | 
					ebcae25762 | ||
| 
						 | 
					5330267d16 | ||
| 
						 | 
					892476973b | ||
| 
						 | 
					84f4a25bc9 | ||
| 
						 | 
					1460a88bb3 | ||
| 
						 | 
					62e4c23961 | ||
| 
						 | 
					d25ab35d58 | ||
| 
						 | 
					a223cd90a1 | ||
| 
						 | 
					aef92ba29c | ||
| 
						 | 
					328d297490 | ||
| 
						 | 
					3d240f3f18 | ||
| 
						 | 
					45f35236a7 | ||
| 
						 | 
					fba210f7ce | ||
| 
						 | 
					8a09e5fc16 | ||
| 
						 | 
					52e33e861c | ||
| 
						 | 
					75d8824e6b | ||
| 
						 | 
					325af677d3 | ||
| 
						 | 
					1003e70b5e | ||
| 
						 | 
					d70229201d | ||
| 
						 | 
					823f91605b | ||
| 
						 | 
					53f75034fc | ||
| 
						 | 
					78649a5b54 | ||
| 
						 | 
					f48db625a0 | ||
| 
						 | 
					2ba66c4457 | ||
| 
						 | 
					2c78ea1a4e | ||
| 
						 | 
					73f50ac44e | ||
| 
						 | 
					9ce48953c1 | ||
| 
						 | 
					1098cd0c6b | ||
| 
						 | 
					652ebd143c | ||
| 
						 | 
					8e9d7c0f40 | ||
| 
						 | 
					a64948a2ba | ||
| 
						 | 
					43f619a081 | ||
| 
						 | 
					a07de97df4 | ||
| 
						 | 
					85d25068a8 | ||
| 
						 | 
					7a0319cfe5 | ||
| 
						 | 
					f750671f33 | ||
| 
						 | 
					7886fe677a | ||
| 
						 | 
					73c027f8e3 | ||
| 
						 | 
					eda88cc462 | ||
| 
						 | 
					652f4ebfed | ||
| 
						 | 
					06a2f59bd0 | ||
| 
						 | 
					0af57806da | ||
| 
						 | 
					03f365e696 | ||
| 
						 | 
					49a22674ba | ||
| 
						 | 
					ec494511ec | ||
| 
						 | 
					af02ce9c6e | ||
| 
						 | 
					56e42859ab | ||
| 
						 | 
					2d153359f8 | ||
| 
						 | 
					068ce23716 | ||
| 
						 | 
					03be2e3652 | ||
| 
						 | 
					4ef2c0bed8 | ||
| 
						 | 
					bfd405613c | ||
| 
						 | 
					73e1c8c780 | ||
| 
						 | 
					689ba1d4a2 | ||
| 
						 | 
					39b9d00550 | ||
| 
						 | 
					64f99d83a4 | ||
| 
						 | 
					8f1faefa1c | ||
| 
						 | 
					2c5ff9ada0 | ||
| 
						 | 
					a9ceef5c37 | ||
| 
						 | 
					c6f977ed4b | ||
| 
						 | 
					cb240cd32a | ||
| 
						 | 
					bc6349f823 | ||
| 
						 | 
					a93a1ae40f | ||
| 
						 | 
					25254255fe | ||
| 
						 | 
					b0b2798f39 | ||
| 
						 | 
					7f5c637aeb | ||
| 
						 | 
					42634b500c | ||
| 
						 | 
					6f0eb5eccd | ||
| 
						 | 
					3d83891eb0 | ||
| 
						 | 
					69a2a133d5 | ||
| 
						 | 
					be4b38c76a | ||
| 
						 | 
					7163b1132c | ||
| 
						 | 
					3ccec1c996 | ||
| 
						 | 
					47359dc8f1 | ||
| 
						 | 
					43532c8455 | ||
| 
						 | 
					d7c3d4ce52 | ||
| 
						 | 
					ed7060a105 | ||
| 
						 | 
					db0da4b741 | ||
| 
						 | 
					c9c16968bb | ||
| 
						 | 
					87420881c8 | ||
| 
						 | 
					fdc598f2e1 | ||
| 
						 | 
					f679145bd1 | ||
| 
						 | 
					eeb161ec51 | ||
| 
						 | 
					21cb7307d0 | ||
| 
						 | 
					412a1eb7ee | ||
| 
						 | 
					1d801acf72 | ||
| 
						 | 
					0d7bbdad54 | ||
| 
						 | 
					53b3d9cf9d | ||
| 
						 | 
					c3ebbfb10e | ||
| 
						 | 
					58f035e31a | ||
| 
						 | 
					a8f1d98d40 | ||
| 
						 | 
					cf6fa98433 | ||
| 
						 | 
					937b3ca81d | ||
| 
						 | 
					d0c5cf0d2d | ||
| 
						 | 
					4cbf2bef82 | ||
| 
						 | 
					388d808536 | ||
| 
						 | 
					720aba3f2d | ||
| 
						 | 
					f9101de956 | ||
| 
						 | 
					bb04981280 | ||
| 
						 | 
					57898ed6dd | ||
| 
						 | 
					33b53e7605 | ||
| 
						 | 
					9e8928aad9 | ||
| 
						 | 
					89c71f9119 | ||
| 
						 | 
					a4f6db6719 | ||
| 
						 | 
					2d8e65ea32 | ||
| 
						 | 
					48d1d27067 | ||
| 
						 | 
					98aa597510 | ||
| 
						 | 
					de56d48b2f | ||
| 
						 | 
					4aeb9a7c56 | ||
| 
						 | 
					b9b52b7c8b | ||
| 
						 | 
					dc464a0b7b | ||
| 
						 | 
					13b6079826 | ||
| 
						 | 
					6f7dd10d95 | ||
| 
						 | 
					24fb95291a | ||
| 
						 | 
					48430bee60 | ||
| 
						 | 
					42997dcb80 | ||
| 
						 | 
					0ace189e38 | ||
| 
						 | 
					d03a7911b5 | ||
| 
						 | 
					84422676cb | ||
| 
						 | 
					7441e3f4c5 | ||
| 
						 | 
					f18132d674 | ||
| 
						 | 
					5660007221 | ||
| 
						 | 
					cfebf1dc4a | ||
| 
						 | 
					5b0111b4c8 | ||
| 
						 | 
					62a1d69cee | ||
| 
						 | 
					86a6b04d4a | ||
| 
						 | 
					8915950c7d | ||
| 
						 | 
					641e349f33 | ||
| 
						 | 
					72b4bf9c98 | ||
| 
						 | 
					ccdeb3fbc8 | ||
| 
						 | 
					34047fa60a | ||
| 
						 | 
					05d483bc5b | ||
| 
						 | 
					113efd9b16 | ||
| 
						 | 
					c11a1f9679 | ||
| 
						 | 
					2beeaa513b | ||
| 
						 | 
					5b56ad0d78 | ||
| 
						 | 
					bee0d09877 | ||
| 
						 | 
					42d8d187b3 | ||
| 
						 | 
					d97348dd38 | ||
| 
						 | 
					e1ebb7ce9c | ||
| 
						 | 
					47dd8ad069 | ||
| 
						 | 
					6a55d75b3d | ||
| 
						 | 
					d5b4ddd9e5 | ||
| 
						 | 
					9c8a2265b5 | ||
| 
						 | 
					84d7157dfb | ||
| 
						 | 
					ddce4fb46b | ||
| 
						 | 
					1ccee036c4 | ||
| 
						 | 
					ef085e3f93 | ||
| 
						 | 
					3862a93ff9 | ||
| 
						 | 
					fc21fbd1f1 | ||
| 
						 | 
					903f9b5240 | ||
| 
						 | 
					816ad0a94c | ||
| 
						 | 
					0536697d8f | ||
| 
						 | 
					0dbd8a667d | ||
| 
						 | 
					56e691f256 | ||
| 
						 | 
					b0503efa3d | ||
| 
						 | 
					b81e59fd8f | ||
| 
						 | 
					d2da55aa03 | ||
| 
						 | 
					28e69152d8 | ||
| 
						 | 
					d122535a65 | ||
| 
						 | 
					c8c24f81c8 | ||
| 
						 | 
					db078c7363 | ||
| 
						 | 
					6b4f6971de | ||
| 
						 | 
					79707a3c66 | ||
| 
						 | 
					694783efe9 | ||
| 
						 | 
					68c5474e36 | ||
| 
						 | 
					cd055a0298 | ||
| 
						 | 
					8f2abab0d9 | ||
| 
						 | 
					4c5dee866b | ||
| 
						 | 
					7030abca97 | ||
| 
						 | 
					c7c21a7e2c | ||
| 
						 | 
					b23e10e261 | ||
| 
						 | 
					16731661e8 | ||
| 
						 | 
					7bb90c78d9 | ||
| 
						 | 
					a6e61ef83b | ||
| 
						 | 
					d4134cd0d8 | ||
| 
						 | 
					c775a6c0f8 | ||
| 
						 | 
					2f9e825728 | ||
| 
						 | 
					2f491b5be1 | ||
| 
						 | 
					de7ebead23 | ||
| 
						 | 
					c0c4704419 | ||
| 
						 | 
					ec14750ff1 | ||
| 
						 | 
					e43de5f1ba | ||
| 
						 | 
					080f949f89 | ||
| 
						 | 
					9f6956bd87 | ||
| 
						 | 
					ddf5e1632d | ||
| 
						 | 
					40bfde41cb | ||
| 
						 | 
					e0751af56d | ||
| 
						 | 
					3979faf43b | ||
| 
						 | 
					878b480a44 | ||
| 
						 | 
					b35b6b2ba8 | ||
| 
						 | 
					d0b967ce53 | ||
| 
						 | 
					e5addb27ec | ||
| 
						 | 
					ac8d43cc4a | ||
| 
						 | 
					40ee215b1b | ||
| 
						 | 
					6c1d94beaa | ||
| 
						 | 
					6b2e1fe62b | ||
| 
						 | 
					8ecf885629 | ||
| 
						 | 
					6d76b7cd94 | ||
| 
						 | 
					7bd721f334 | ||
| 
						 | 
					7939897622 | ||
| 
						 | 
					77bebd4a65 | ||
| 
						 | 
					5d68a5bdd0 | ||
| 
						 | 
					3e0b5433b9 | ||
| 
						 | 
					ec8f1157c8 | ||
| 
						 | 
					037cbd534e | ||
| 
						 | 
					208ef70e31 | ||
| 
						 | 
					2fa4c59523 | ||
| 
						 | 
					cda0a2de79 | ||
| 
						 | 
					008f50832c | ||
| 
						 | 
					c94acb1ca2 | ||
| 
						 | 
					d341f98b09 | ||
| 
						 | 
					e35a3ab566 | ||
| 
						 | 
					b3b4b7cf0c | ||
| 
						 | 
					1cd6d58f17 | ||
| 
						 | 
					eecd4417e7 | ||
| 
						 | 
					21908dfcef | ||
| 
						 | 
					75987f64ec | ||
| 
						 | 
					798cc58f76 | ||
| 
						 | 
					6ba1194d74 | ||
| 
						 | 
					e5f75b5df2 | ||
| 
						 | 
					b75ad3def2 | ||
| 
						 | 
					10c98f0a15 | ||
| 
						 | 
					caf72afcb4 | ||
| 
						 | 
					687e0b376e | ||
| 
						 | 
					122857e5b5 | ||
| 
						 | 
					5002290428 | ||
| 
						 | 
					d09ac3384f | ||
| 
						 | 
					b6a4a7e0a5 | ||
| 
						 | 
					c87994336c | ||
| 
						 | 
					85ad490089 | ||
| 
						 | 
					73e32a9c76 | ||
| 
						 | 
					a321ff3037 | ||
| 
						 | 
					68d6feaa03 | ||
| 
						 | 
					74e1a9a621 | ||
| 
						 | 
					097bc7055e | ||
| 
						 | 
					6a43fc5df0 | ||
| 
						 | 
					312f38906b | ||
| 
						 | 
					f0ec9fa5d2 | ||
| 
						 | 
					20b4896940 | ||
| 
						 | 
					6a93d2d006 | ||
| 
						 | 
					ae0bc7e7aa | ||
| 
						 | 
					a8acadbe13 | ||
| 
						 | 
					727f2e2ba0 | ||
| 
						 | 
					a6683cb9b8 | ||
| 
						 | 
					5ceb711bd3 | ||
| 
						 | 
					4748b09721 | ||
| 
						 | 
					d593796dae | ||
| 
						 | 
					ef0dbc2a41 | ||
| 
						 | 
					6c49953115 | ||
| 
						 | 
					55290f4dad | ||
| 
						 | 
					f373a3fbb1 | ||
| 
						 | 
					bb03d2f2ad | ||
| 
						 | 
					82922aa2c7 | ||
| 
						 | 
					7aec5be61a | ||
| 
						 | 
					2ef6d4327c | ||
| 
						 | 
					cc95e587db | ||
| 
						 | 
					e89e55a9bb | ||
| 
						 | 
					7c2c243985 | ||
| 
						 | 
					25a1f23fc0 | ||
| 
						 | 
					27541196cc | ||
| 
						 | 
					5d9521fcb9 | ||
| 
						 | 
					ccb52fb625 | ||
| 
						 | 
					028e530232 | ||
| 
						 | 
					906a2ff6eb | ||
| 
						 | 
					248a8efd2f | ||
| 
						 | 
					c392c819c1 | ||
| 
						 | 
					e9d9ff0da0 | ||
| 
						 | 
					46d756d298 | ||
| 
						 | 
					fd0ffc7085 | ||
| 
						 | 
					601961deeb | ||
| 
						 | 
					557a2a0ddf | ||
| 
						 | 
					b723740f64 | ||
| 
						 | 
					6be46ae921 | ||
| 
						 | 
					a25470ee41 | ||
| 
						 | 
					fd579a019b | ||
| 
						 | 
					e39ecf59ef | ||
| 
						 | 
					5f90941e4e | ||
| 
						 | 
					64465f97b6 | ||
| 
						 | 
					aa22af6f05 | ||
| 
						 | 
					a6383247fc | ||
| 
						 | 
					d45c2a1f28 | ||
| 
						 | 
					61a63a673c | ||
| 
						 | 
					5618288459 | ||
| 
						 | 
					b69ac4ec2f | ||
| 
						 | 
					f3174069fa | ||
| 
						 | 
					cd1e796093 | ||
| 
						 | 
					dd4af4f0df | ||
| 
						 | 
					76656fab23 | ||
| 
						 | 
					cf49603a9e | ||
| 
						 | 
					6c92853461 | ||
| 
						 | 
					6a62cf9146 | ||
| 
						 | 
					4fa6bc0ad1 | ||
| 
						 | 
					95685749ad | ||
| 
						 | 
					d7c0f0c804 | ||
| 
						 | 
					6b42b92930 | ||
| 
						 | 
					f4764ea680 | ||
| 
						 | 
					538c57664f | ||
| 
						 | 
					a66a20f7fe | ||
| 
						 | 
					d4ac79b0af | ||
| 
						 | 
					a5a3769a0f | ||
| 
						 | 
					dc4b5cc37d | ||
| 
						 | 
					ee89be6730 | ||
| 
						 | 
					770d7e90e9 | ||
| 
						 | 
					b9aca39eb0 | ||
| 
						 | 
					c0454ff101 | ||
| 
						 | 
					a697a2e4f6 | ||
| 
						 | 
					396cf72029 | ||
| 
						 | 
					bfe9704829 | ||
| 
						 | 
					43ee540233 | ||
| 
						 | 
					817aa186c2 | ||
| 
						 | 
					38ffc4fdb3 | ||
| 
						 | 
					f12d734957 | ||
| 
						 | 
					a70991d50e | ||
| 
						 | 
					4c00456166 | ||
| 
						 | 
					26219213d7 | ||
| 
						 | 
					97c5ee6c0a | ||
| 
						 | 
					75bc0e451d | ||
| 
						 | 
					6496b6313c | ||
| 
						 | 
					c5d9bf2c12 | ||
| 
						 | 
					8f05560dd7 | ||
| 
						 | 
					06c0c64c1a | ||
| 
						 | 
					c173777d12 | ||
| 
						 | 
					16dfeb3fc8 | ||
| 
						 | 
					5a31891048 | ||
| 
						 | 
					8b37496447 | ||
| 
						 | 
					8f6664f0d7 | ||
| 
						 | 
					15b1176841 | ||
| 
						 | 
					3eab1f8f7c | ||
| 
						 | 
					9dff13cbbf | ||
| 
						 | 
					a47de9a884 | ||
| 
						 | 
					8a699b6072 | ||
| 
						 | 
					87df8b9e85 | ||
| 
						 | 
					91b19c5c70 | ||
| 
						 | 
					0487580a1a | ||
| 
						 | 
					3dca836571 | ||
| 
						 | 
					6ba02c44d0 | ||
| 
						 | 
					bf3ab4e260 | ||
| 
						 | 
					02f9cada43 | ||
| 
						 | 
					654a19ea15 | ||
| 
						 | 
					ecb5504bd1 | ||
| 
						 | 
					2adf3d353e | ||
| 
						 | 
					3045e85004 | ||
| 
						 | 
					e9d1afd515 | ||
| 
						 | 
					833ab7945b | ||
| 
						 | 
					0af1d668a6 | ||
| 
						 | 
					0ac62e3805 | ||
| 
						 | 
					938d09f34a | ||
| 
						 | 
					dce52d740d | ||
| 
						 | 
					3ae333fa84 | ||
| 
						 | 
					d5af1f3948 | ||
| 
						 | 
					0ba3ae53ab | ||
| 
						 | 
					be12d78c83 | ||
| 
						 | 
					b70227ac1b | ||
| 
						 | 
					6d277fecd5 | ||
| 
						 | 
					491817d85c | ||
| 
						 | 
					20faf4e477 | ||
| 
						 | 
					4fe5c7c24e | ||
| 
						 | 
					36bf640c6f | ||
| 
						 | 
					7881e40e0b | ||
| 
						 | 
					55da1e9c0f | ||
| 
						 | 
					9799aa0975 | ||
| 
						 | 
					1effb97b74 | ||
| 
						 | 
					eb28095041 | ||
| 
						 | 
					014da41471 | ||
| 
						 | 
					0446e350d3 | ||
| 
						 | 
					05fb7db147 | ||
| 
						 | 
					f6562de325 | ||
| 
						 | 
					b40211d2c0 | ||
| 
						 | 
					da4d883321 | ||
| 
						 | 
					373820f080 | ||
| 
						 | 
					6e517983b9 | ||
| 
						 | 
					b9a752fda1 | ||
| 
						 | 
					f65d80b7d1 | ||
| 
						 | 
					4701aa149a | 
							
								
								
									
										22
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					name: SDL/Ubuntu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches: 
 | 
				
			||||||
 | 
					      - master
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    branches: 
 | 
				
			||||||
 | 
					      - master
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - uses: actions/checkout@v1
 | 
				
			||||||
 | 
					    - name: Install dependencies
 | 
				
			||||||
 | 
					      run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
 | 
				
			||||||
 | 
					    - name: Make
 | 
				
			||||||
 | 
					      run: cd OSBindings/SDL; scons
 | 
				
			||||||
							
								
								
									
										13
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -1,13 +0,0 @@
 | 
				
			|||||||
# language: objective-c
 | 
					 | 
				
			||||||
# osx_image: xcode8.2
 | 
					 | 
				
			||||||
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
 | 
					 | 
				
			||||||
# xcode_scheme: Clock Signal
 | 
					 | 
				
			||||||
# xcode_sdk: macosx10.12
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
language: cpp
 | 
					 | 
				
			||||||
before_install:
 | 
					 | 
				
			||||||
	- sudo apt-get install libsdl2-dev
 | 
					 | 
				
			||||||
script: cd OSBindings/SDL && scons
 | 
					 | 
				
			||||||
compiler:
 | 
					 | 
				
			||||||
	- clang
 | 
					 | 
				
			||||||
	- gcc
 | 
					 | 
				
			||||||
@@ -13,7 +13,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Dynamic;
 | 
					using namespace Analyser::Dynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
 | 
					MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
 | 
				
			||||||
	machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
 | 
						machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
 | 
				
			||||||
	speaker_ = MultiSpeaker::create(machines);
 | 
						speaker_ = MultiSpeaker::create(machines);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -25,7 +25,7 @@ void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Ma
 | 
				
			|||||||
	std::condition_variable condition;
 | 
						std::condition_variable condition;
 | 
				
			||||||
	std::mutex mutex;
 | 
						std::mutex mutex;
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
							std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
 | 
				
			||||||
		std::lock_guard<std::mutex> lock(mutex);
 | 
							std::lock_guard<std::mutex> lock(mutex);
 | 
				
			||||||
		outstanding_machines = machines_.size();
 | 
							outstanding_machines = machines_.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,29 +46,18 @@ void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Ma
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
 | 
					void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
 | 
				
			||||||
	std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
						std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
 | 
				
			||||||
	for(const auto &machine: machines_) {
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
		CRTMachine::Machine *crt_machine = machine->crt_machine();
 | 
							CRTMachine::Machine *const crt_machine = machine->crt_machine();
 | 
				
			||||||
		if(crt_machine) function(crt_machine);
 | 
							if(crt_machine) function(crt_machine);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiCRTMachine::setup_output(float aspect_ratio) {
 | 
					void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			||||||
	perform_serial([=](::CRTMachine::Machine *machine) {
 | 
						scan_target_ = scan_target;
 | 
				
			||||||
		machine->setup_output(aspect_ratio);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiCRTMachine::close_output() {
 | 
						CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
 | 
				
			||||||
	perform_serial([=](::CRTMachine::Machine *machine) {
 | 
						if(crt_machine) crt_machine->set_scan_target(scan_target);
 | 
				
			||||||
		machine->close_output();
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Outputs::CRT::CRT *MultiCRTMachine::get_crt() {
 | 
					 | 
				
			||||||
	std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
					 | 
				
			||||||
	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
 | 
					 | 
				
			||||||
	return crt_machine ? crt_machine->get_crt() : nullptr;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
 | 
					Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
 | 
				
			||||||
@@ -84,6 +73,14 @@ void MultiCRTMachine::run_for(Time::Seconds duration) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiCRTMachine::did_change_machine_order() {
 | 
					void MultiCRTMachine::did_change_machine_order() {
 | 
				
			||||||
 | 
						if(scan_target_) scan_target_->will_change_owner();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						perform_serial([=](::CRTMachine::Machine *machine) {
 | 
				
			||||||
 | 
							machine->set_scan_target(nullptr);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
 | 
				
			||||||
 | 
						if(crt_machine) crt_machine->set_scan_target(scan_target_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(speaker_) {
 | 
						if(speaker_) {
 | 
				
			||||||
		speaker_->set_new_front_machine(machines_.front().get());
 | 
							speaker_->set_new_front_machine(machines_.front().get());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ namespace Dynamic {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
class MultiCRTMachine: public CRTMachine::Machine {
 | 
					class MultiCRTMachine: public CRTMachine::Machine {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
 | 
							MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
 | 
								Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
 | 
				
			||||||
@@ -53,19 +53,18 @@ class MultiCRTMachine: public CRTMachine::Machine {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Below is the standard CRTMachine::Machine interface; see there for documentation.
 | 
							// Below is the standard CRTMachine::Machine interface; see there for documentation.
 | 
				
			||||||
		void setup_output(float aspect_ratio) override;
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
 | 
				
			||||||
		void close_output() override;
 | 
					 | 
				
			||||||
		Outputs::CRT::CRT *get_crt() override;
 | 
					 | 
				
			||||||
		Outputs::Speaker::Speaker *get_speaker() override;
 | 
							Outputs::Speaker::Speaker *get_speaker() override;
 | 
				
			||||||
		void run_for(Time::Seconds duration) override;
 | 
							void run_for(Time::Seconds duration) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		void run_for(const Cycles cycles) override {}
 | 
							void run_for(const Cycles cycles) override {}
 | 
				
			||||||
		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
							const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
				
			||||||
		std::mutex &machines_mutex_;
 | 
							std::recursive_mutex &machines_mutex_;
 | 
				
			||||||
		std::vector<Concurrency::AsyncTaskQueue> queues_;
 | 
							std::vector<Concurrency::AsyncTaskQueue> queues_;
 | 
				
			||||||
		MultiSpeaker *speaker_ = nullptr;
 | 
							MultiSpeaker *speaker_ = nullptr;
 | 
				
			||||||
		Delegate *delegate_ = nullptr;
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
 | 
							Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Performs a parallel for operation across all machines, performing the supplied
 | 
								Performs a parallel for operation across all machines, performing the supplied
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -81,6 +81,6 @@ MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::M
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
 | 
					const std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
 | 
				
			||||||
	return joysticks_;
 | 
						return joysticks_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
 | 
				
			|||||||
		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
							MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Below is the standard JoystickMachine::Machine interface; see there for documentation.
 | 
							// Below is the standard JoystickMachine::Machine interface; see there for documentation.
 | 
				
			||||||
		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
 | 
							const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
							std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
using namespace Analyser::Dynamic;
 | 
					using namespace Analyser::Dynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
 | 
					MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
 | 
				
			||||||
 	keyboard_(machines_) {
 | 
						keyboard_(machines_) {
 | 
				
			||||||
	for(const auto &machine: machines) {
 | 
						for(const auto &machine: machines) {
 | 
				
			||||||
		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
 | 
							KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
 | 
				
			||||||
		if(keyboard_machine) machines_.push_back(keyboard_machine);
 | 
							if(keyboard_machine) machines_.push_back(keyboard_machine);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "MultiMachine.hpp"
 | 
					#include "MultiMachine.hpp"
 | 
				
			||||||
 | 
					#include "../../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MouseMachine::Machine *MultiMachine::mouse_machine() {
 | 
				
			||||||
 | 
						// TODO.
 | 
				
			||||||
 | 
						return nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Configurable::Device *MultiMachine::configurable_device() {
 | 
					Configurable::Device *MultiMachine::configurable_device() {
 | 
				
			||||||
	if(has_picked_) {
 | 
						if(has_picked_) {
 | 
				
			||||||
		return machines_.front()->configurable_device();
 | 
							return machines_.front()->configurable_device();
 | 
				
			||||||
@@ -73,15 +79,13 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiMachine::multi_crt_did_run_machines() {
 | 
					void MultiMachine::multi_crt_did_run_machines() {
 | 
				
			||||||
	std::lock_guard<std::mutex> machines_lock(machines_mutex_);
 | 
						std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
 | 
				
			||||||
#ifdef DEBUG
 | 
					#ifndef NDEBUG
 | 
				
			||||||
	for(const auto &machine: machines_) {
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
		CRTMachine::Machine *crt = machine->crt_machine();
 | 
							CRTMachine::Machine *crt = machine->crt_machine();
 | 
				
			||||||
		printf("%0.2f ", crt->get_confidence());
 | 
							LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
 | 
				
			||||||
		crt->print_type();
 | 
					 | 
				
			||||||
		printf("; ");
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	printf("\n");
 | 
						LOGNBR(std::endl);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DynamicMachine *front = machines_.front().get();
 | 
						DynamicMachine *front = machines_.front().get();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
 | 
				
			|||||||
		Configurable::Device *configurable_device() override;
 | 
							Configurable::Device *configurable_device() override;
 | 
				
			||||||
		CRTMachine::Machine *crt_machine() override;
 | 
							CRTMachine::Machine *crt_machine() override;
 | 
				
			||||||
		JoystickMachine::Machine *joystick_machine() override;
 | 
							JoystickMachine::Machine *joystick_machine() override;
 | 
				
			||||||
 | 
							MouseMachine::Machine *mouse_machine() override;
 | 
				
			||||||
		KeyboardMachine::Machine *keyboard_machine() override;
 | 
							KeyboardMachine::Machine *keyboard_machine() override;
 | 
				
			||||||
		MediaTarget::Machine *media_target() override;
 | 
							MediaTarget::Machine *media_target() override;
 | 
				
			||||||
		void *raw_pointer() override;
 | 
							void *raw_pointer() override;
 | 
				
			||||||
@@ -62,7 +63,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
 | 
				
			|||||||
		void multi_crt_did_run_machines() override;
 | 
							void multi_crt_did_run_machines() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
							std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
				
			||||||
		std::mutex machines_mutex_;
 | 
							std::recursive_mutex machines_mutex_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MultiConfigurable configurable_;
 | 
							MultiConfigurable configurable_;
 | 
				
			||||||
		MultiCRTMachine crt_machine_;
 | 
							MultiCRTMachine crt_machine_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,10 @@ enum class Machine {
 | 
				
			|||||||
	AmstradCPC,
 | 
						AmstradCPC,
 | 
				
			||||||
	AppleII,
 | 
						AppleII,
 | 
				
			||||||
	Atari2600,
 | 
						Atari2600,
 | 
				
			||||||
 | 
						AtariST,
 | 
				
			||||||
	ColecoVision,
 | 
						ColecoVision,
 | 
				
			||||||
	Electron,
 | 
						Electron,
 | 
				
			||||||
 | 
						Macintosh,
 | 
				
			||||||
	MasterSystem,
 | 
						MasterSystem,
 | 
				
			||||||
	MSX,
 | 
						MSX,
 | 
				
			||||||
	Oric,
 | 
						Oric,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ using namespace Analyser::Static::Acorn;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
					std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 
						// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 
				
			||||||
	std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
						auto catalogue = std::make_unique<Catalogue>();
 | 
				
			||||||
	Storage::Encodings::MFM::Parser parser(false, disk);
 | 
						Storage::Encodings::MFM::Parser parser(false, disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
 | 
						Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
 | 
				
			||||||
@@ -75,7 +75,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
				
			|||||||
	return catalogue;
 | 
						return catalogue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
					std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
	std::unique_ptr<Catalogue> catalogue(new Catalogue);
 | 
						auto catalogue = std::make_unique<Catalogue>();
 | 
				
			||||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
						Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
 | 
						Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::Electron;
 | 
						target->machine = Machine::Electron;
 | 
				
			||||||
	target->confidence = 0.5; // TODO: a proper estimation
 | 
						target->confidence = 0.5; // TODO: a proper estimation
 | 
				
			||||||
	target->has_dfs = false;
 | 
						target->has_dfs = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@
 | 
				
			|||||||
using namespace Analyser::Static::Acorn;
 | 
					using namespace Analyser::Static::Acorn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
 | 
					static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
 | 
				
			||||||
	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
 | 
						auto new_chunk = std::make_unique<File::Chunk>();
 | 
				
			||||||
	int shift_register = 0;
 | 
						int shift_register = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: move this into the parser
 | 
					// TODO: move this into the parser
 | 
				
			||||||
@@ -90,7 +90,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
 | 
				
			|||||||
	if(!chunks.size()) return nullptr;
 | 
						if(!chunks.size()) return nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
 | 
						// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
 | 
				
			||||||
	std::unique_ptr<File> file(new File);
 | 
						auto file = std::make_unique<File>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	uint16_t block_number = 0;
 | 
						uint16_t block_number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -181,7 +181,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	TargetList destination;
 | 
						TargetList destination;
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::AmstradCPC;
 | 
						target->machine = Machine::AmstradCPC;
 | 
				
			||||||
	target->confidence = 0.5;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@
 | 
				
			|||||||
#include "Target.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	auto target = std::unique_ptr<Target>(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::AppleII;
 | 
						target->machine = Machine::AppleII;
 | 
				
			||||||
	target->media = media;
 | 
						target->media = media;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,9 +12,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../Disassembler/6502.hpp"
 | 
					#include "../Disassembler/6502.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Static::Atari;
 | 
					using namespace Analyser::Static::Atari2600;
 | 
				
			||||||
 | 
					using Target = Analyser::Static::Atari2600::Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
					static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
				
			||||||
	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | 
						// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | 
				
			||||||
	uint16_t entry_address, break_address;
 | 
						uint16_t entry_address, break_address;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,10 +49,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &targe
 | 
				
			|||||||
	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | 
						// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | 
				
			||||||
	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | 
						// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | 
				
			||||||
	// attempts to modify itself but it probably doesn't
 | 
						// attempts to modify itself but it probably doesn't
 | 
				
			||||||
	if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
 | 
						if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 
						// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 
				
			||||||
	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | 
						// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | 
				
			||||||
	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | 
						// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | 
				
			||||||
@@ -60,12 +61,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
 | 
				
			|||||||
		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
							(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
				
			||||||
		segment.data[0] == 0x78
 | 
							segment.data[0] == 0x78
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
 | 
							target.paging_model = Target::PagingModel::ActivisionStack;
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// make an assumption that this is the Atari paging model
 | 
						// make an assumption that this is the Atari paging model
 | 
				
			||||||
	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
 | 
						target.paging_model = Target::PagingModel::Atari8k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::set<uint16_t> internal_accesses;
 | 
						std::set<uint16_t> internal_accesses;
 | 
				
			||||||
	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
						internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
				
			||||||
@@ -85,13 +86,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
 | 
				
			|||||||
		tigervision_access_count += masked_address == 0x3f;
 | 
							tigervision_access_count += masked_address == 0x3f;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
 | 
						if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros;
 | 
				
			||||||
	else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
 | 
						else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	// make an assumption that this is the Atari paging model
 | 
						// make an assumption that this is the Atari paging model
 | 
				
			||||||
	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
 | 
						target.paging_model = Target::PagingModel::Atari16k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::set<uint16_t> internal_accesses;
 | 
						std::set<uint16_t> internal_accesses;
 | 
				
			||||||
	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
						internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
 | 
				
			||||||
@@ -106,17 +107,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &targ
 | 
				
			|||||||
		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
 | 
							mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
 | 
						if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	// make an assumption that this is a Tigervision if there is a write to 3F
 | 
						// make an assumption that this is a Tigervision if there is a write to 3F
 | 
				
			||||||
	target.paging_model =
 | 
						target.paging_model =
 | 
				
			||||||
		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
							(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
				
			||||||
			Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
 | 
								Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
					static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
 | 
				
			||||||
	if(segment.data.size() == 2048) {
 | 
						if(segment.data.size() == 2048) {
 | 
				
			||||||
		DeterminePagingFor2kCartridge(target, segment);
 | 
							DeterminePagingFor2kCartridge(target, segment);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@@ -140,16 +141,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
 | 
				
			|||||||
			DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
								DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 10495:
 | 
							case 10495:
 | 
				
			||||||
			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
 | 
								target.paging_model = Target::PagingModel::Pitfall2;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 12288:
 | 
							case 12288:
 | 
				
			||||||
			target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
 | 
								target.paging_model = Target::PagingModel::CBSRamPlus;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 16384:
 | 
							case 16384:
 | 
				
			||||||
			DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
								DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 32768:
 | 
							case 32768:
 | 
				
			||||||
			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
 | 
								target.paging_model = Target::PagingModel::Atari32k;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 65536:
 | 
							case 65536:
 | 
				
			||||||
			DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
								DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
				
			||||||
@@ -161,8 +162,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
 | 
				
			|||||||
	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | 
						// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | 
				
			||||||
	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | 
						// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | 
				
			||||||
	// next 128 bytes. So check for that.
 | 
						// next 128 bytes. So check for that.
 | 
				
			||||||
	if(	target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
 | 
						if(	target.paging_model != Target::PagingModel::CBSRamPlus &&
 | 
				
			||||||
		target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
 | 
							target.paging_model != Target::PagingModel::MNetwork) {
 | 
				
			||||||
		bool has_superchip = true;
 | 
							bool has_superchip = true;
 | 
				
			||||||
		for(std::size_t address = 0; address < 128; address++) {
 | 
							for(std::size_t address = 0; address < 128; address++) {
 | 
				
			||||||
			if(segment.data[address] != segment.data[address+128]) {
 | 
								if(segment.data[address] != segment.data[address+128]) {
 | 
				
			||||||
@@ -174,19 +175,19 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check for a Tigervision or Tigervision-esque scheme
 | 
						// check for a Tigervision or Tigervision-esque scheme
 | 
				
			||||||
	if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
 | 
						if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
 | 
				
			||||||
		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
 | 
							bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
 | 
				
			||||||
		if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
 | 
							if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	// TODO: sanity checking; is this image really for an Atari 2600?
 | 
						// TODO: sanity checking; is this image really for an Atari 2600?
 | 
				
			||||||
	std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::Atari2600;
 | 
						target->machine = Machine::Atari2600;
 | 
				
			||||||
	target->confidence = 0.5;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
	target->media.cartridges = media.cartridges;
 | 
						target->media.cartridges = media.cartridges;
 | 
				
			||||||
	target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
 | 
						target->paging_model = Target::PagingModel::None;
 | 
				
			||||||
	target->uses_superchip = false;
 | 
						target->uses_superchip = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// try to figure out the paging scheme
 | 
						// try to figure out the paging scheme
 | 
				
			||||||
@@ -15,7 +15,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Atari {
 | 
					namespace Atari2600 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
					TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -6,14 +6,14 @@
 | 
				
			|||||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef Analyser_Static_Atari_Target_h
 | 
					#ifndef Analyser_Static_Atari2600_Target_h
 | 
				
			||||||
#define Analyser_Static_Atari_Target_h
 | 
					#define Analyser_Static_Atari2600_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Atari {
 | 
					namespace Atari2600 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
	enum class PagingModel {
 | 
						enum class PagingModel {
 | 
				
			||||||
							
								
								
									
										26
									
								
								Analyser/Static/AtariST/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/AtariST/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
 | 
						// This analyser can comprehend disks and mass-storage devices only.
 | 
				
			||||||
 | 
						if(media.disks.empty()) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// As there is at least one usable media image, wave it through.
 | 
				
			||||||
 | 
						Analyser::Static::TargetList targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						using Target = Analyser::Static::Target;
 | 
				
			||||||
 | 
						auto *target = new Target;
 | 
				
			||||||
 | 
						target->machine = Analyser::Machine::AtariST;
 | 
				
			||||||
 | 
						target->media = media;
 | 
				
			||||||
 | 
						targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return targets;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								Analyser/Static/AtariST/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Static/AtariST/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					#define Analyser_Static_AtariST_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/TargetPlatforms.hpp"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace AtariST {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */
 | 
				
			||||||
							
								
								
									
										23
									
								
								Analyser/Static/AtariST/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Analyser/Static/AtariST/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Target.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_AtariST_Target_h
 | 
				
			||||||
 | 
					#define Analyser_Static_AtariST_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace AtariST {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_AtariST_Target_h */
 | 
				
			||||||
@@ -54,7 +54,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	TargetList targets;
 | 
						TargetList targets;
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::ColecoVision;
 | 
						target->machine = Machine::ColecoVision;
 | 
				
			||||||
	target->confidence = 1.0f - 1.0f / 32768.0f;
 | 
						target->confidence = 1.0f - 1.0f / 32768.0f;
 | 
				
			||||||
	target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
 | 
						target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
		std::shared_ptr<Storage::Disk::Drive> drive;
 | 
							std::shared_ptr<Storage::Disk::Drive> drive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
							CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
				
			||||||
			drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
 | 
								drive = std::make_shared<Storage::Disk::Drive>(4000000, 300, 2);
 | 
				
			||||||
			set_drive(drive);
 | 
								set_drive(drive);
 | 
				
			||||||
			drive->set_motor_on(true);
 | 
								drive->set_motor_on(true);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -125,7 +125,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::shared_ptr<Sector> get_next_sector() {
 | 
							std::shared_ptr<Sector> get_next_sector() {
 | 
				
			||||||
			std::shared_ptr<Sector> sector(new Sector);
 | 
								auto sector = std::make_shared<Sector>();
 | 
				
			||||||
			const int max_index_count = index_count_ + 2;
 | 
								const int max_index_count = index_count_ + 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			while(index_count_ < max_index_count) {
 | 
								while(index_count_ < max_index_count) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,8 +13,10 @@
 | 
				
			|||||||
#include "Tape.hpp"
 | 
					#include "Tape.hpp"
 | 
				
			||||||
#include "Target.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
					#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
				
			||||||
 | 
					#include "../../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Static::Commodore;
 | 
					using namespace Analyser::Static::Commodore;
 | 
				
			||||||
@@ -43,7 +45,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	TargetList destination;
 | 
						TargetList destination;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::Vic20;	// TODO: machine estimation
 | 
						target->machine = Machine::Vic20;	// TODO: machine estimation
 | 
				
			||||||
	target->confidence = 0.5; // TODO: a proper estimation
 | 
						target->confidence = 0.5; // TODO: a proper estimation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -77,10 +79,10 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!files.empty()) {
 | 
						if(!files.empty()) {
 | 
				
			||||||
		target->memory_model = Target::MemoryModel::Unexpanded;
 | 
							auto memory_model = Target::MemoryModel::Unexpanded;
 | 
				
			||||||
		std::ostringstream string_stream;
 | 
							std::ostringstream string_stream;
 | 
				
			||||||
		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
							string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
				
			||||||
  		if(files.front().is_basic()) {
 | 
							if(files.front().is_basic()) {
 | 
				
			||||||
			string_stream << "0";
 | 
								string_stream << "0";
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			string_stream << "1";
 | 
								string_stream << "1";
 | 
				
			||||||
@@ -91,18 +93,20 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
 | 
				
			|||||||
		// make a first guess based on loading address
 | 
							// make a first guess based on loading address
 | 
				
			||||||
		switch(files.front().starting_address) {
 | 
							switch(files.front().starting_address) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				printf("Starting address %04x?\n", files.front().starting_address);
 | 
									LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) <<  files.front().starting_address);
 | 
				
			||||||
			case 0x1001:
 | 
								case 0x1001:
 | 
				
			||||||
				target->memory_model = Target::MemoryModel::Unexpanded;
 | 
									memory_model = Target::MemoryModel::Unexpanded;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
			case 0x1201:
 | 
								case 0x1201:
 | 
				
			||||||
				target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
									memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
			case 0x0401:
 | 
								case 0x0401:
 | 
				
			||||||
				target->memory_model = Target::MemoryModel::EightKB;
 | 
									memory_model = Target::MemoryModel::EightKB;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							target->set_memory_model(memory_model);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
							// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
				
			||||||
//		for(File &file : files) {
 | 
					//		for(File &file : files) {
 | 
				
			||||||
//			std::size_t file_size = file.data.size();
 | 
					//			std::size_t file_size = file.data.size();
 | 
				
			||||||
@@ -144,13 +148,52 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!target->media.empty()) {
 | 
						if(!target->media.empty()) {
 | 
				
			||||||
		// Inspect filename for a region hint.
 | 
							// Inspect filename for configuration hints.
 | 
				
			||||||
		std::string lowercase_name = file_name;
 | 
							std::string lowercase_name = file_name;
 | 
				
			||||||
		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
 | 
							std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Hint 1: 'ntsc' anywhere in the name implies America.
 | 
				
			||||||
		if(lowercase_name.find("ntsc") != std::string::npos) {
 | 
							if(lowercase_name.find("ntsc") != std::string::npos) {
 | 
				
			||||||
			target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
								target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Potential additional hints: check for TheC64 tags.
 | 
				
			||||||
 | 
							auto final_underscore = lowercase_name.find_last_of('_');
 | 
				
			||||||
 | 
							if(final_underscore != std::string::npos) {
 | 
				
			||||||
 | 
								auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								while(iterator != lowercase_name.end()) {
 | 
				
			||||||
 | 
									// Grab the next tag.
 | 
				
			||||||
 | 
									char next_tag[3] = {0, 0, 0};
 | 
				
			||||||
 | 
									next_tag[0] = *iterator++;
 | 
				
			||||||
 | 
									if(iterator == lowercase_name.end()) break;
 | 
				
			||||||
 | 
									next_tag[1] = *iterator++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Exit early if attempting to read another tag has run over the file extension.
 | 
				
			||||||
 | 
									if(next_tag[0] == '.' || next_tag[1] == '.') break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Check whether it's anything.
 | 
				
			||||||
 | 
									target->enabled_ram.bank0 |= !strcmp(next_tag, "b0");
 | 
				
			||||||
 | 
									target->enabled_ram.bank1 |= !strcmp(next_tag, "b1");
 | 
				
			||||||
 | 
									target->enabled_ram.bank2 |= !strcmp(next_tag, "b2");
 | 
				
			||||||
 | 
									target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
 | 
				
			||||||
 | 
									target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
 | 
				
			||||||
 | 
									if(!strcmp(next_tag, "tn")) {	// i.e. NTSC.
 | 
				
			||||||
 | 
										target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if(!strcmp(next_tag, "tp")) {	// i.e. PAL.
 | 
				
			||||||
 | 
										target->region = Analyser::Static::Commodore::Target::Region::European;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Unhandled:
 | 
				
			||||||
 | 
									//
 | 
				
			||||||
 | 
									//	M6: 	this is a C64 file.
 | 
				
			||||||
 | 
									//	MV: 	this is a Vic-20 file.
 | 
				
			||||||
 | 
									//	J1/J2:	this C64 file should have the primary joystick in slot 1/2.
 | 
				
			||||||
 | 
									//	RO:		this disk image should be treated as read-only.
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Attach a 1540 if there are any disks here.
 | 
							// Attach a 1540 if there are any disks here.
 | 
				
			||||||
		target->has_c1540 = !target->media.disks.empty();
 | 
							target->has_c1540 = !target->media.disks.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,26 @@ struct Target: public ::Analyser::Static::Target {
 | 
				
			|||||||
		Swedish
 | 
							Swedish
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	MemoryModel memory_model = MemoryModel::Unexpanded;
 | 
						/// Maps from a named memory model to a bank enabled/disabled set.
 | 
				
			||||||
 | 
						void set_memory_model(MemoryModel memory_model) {
 | 
				
			||||||
 | 
							// This is correct for unexpanded and 32kb memory models.
 | 
				
			||||||
 | 
							enabled_ram.bank0 = enabled_ram.bank1 =
 | 
				
			||||||
 | 
							enabled_ram.bank2 = enabled_ram.bank3 =
 | 
				
			||||||
 | 
							enabled_ram.bank5 = memory_model == MemoryModel::ThirtyTwoKB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Bank 0 will need to be enabled if this is an 8kb machine.
 | 
				
			||||||
 | 
							enabled_ram.bank0 |= memory_model == MemoryModel::EightKB;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						struct {
 | 
				
			||||||
 | 
							bool bank0 = false;
 | 
				
			||||||
 | 
							bool bank1 = false;
 | 
				
			||||||
 | 
							bool bank2 = false;
 | 
				
			||||||
 | 
							bool bank3 = false;
 | 
				
			||||||
 | 
							bool bank5 = false;
 | 
				
			||||||
 | 
										// Sic. There is no bank 4; this is because the area that logically would be
 | 
				
			||||||
 | 
										// bank 4 is occupied by the character ROM, colour RAM, hardware registers, etc.
 | 
				
			||||||
 | 
						} enabled_ram;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Region region = Region::European;
 | 
						Region region = Region::European;
 | 
				
			||||||
	bool has_c1540 = false;
 | 
						bool has_c1540 = false;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,9 +63,9 @@ class Accessor {
 | 
				
			|||||||
#define z(v) (v & 7)
 | 
					#define z(v) (v & 7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Instruction::Condition condition_table[] = {
 | 
					Instruction::Condition condition_table[] = {
 | 
				
			||||||
	Instruction::Condition::NZ, 	Instruction::Condition::Z,
 | 
						Instruction::Condition::NZ,		Instruction::Condition::Z,
 | 
				
			||||||
	Instruction::Condition::NC, 	Instruction::Condition::C,
 | 
						Instruction::Condition::NC,		Instruction::Condition::C,
 | 
				
			||||||
	Instruction::Condition::PO, 	Instruction::Condition::PE,
 | 
						Instruction::Condition::PO,		Instruction::Condition::PE,
 | 
				
			||||||
	Instruction::Condition::P,		Instruction::Condition::M
 | 
						Instruction::Condition::P,		Instruction::Condition::M
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
 | 
				
			|||||||
		output_segments.emplace_back(start_address, segment.data);
 | 
							output_segments.emplace_back(start_address, segment.data);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
 | 
						auto target = std::make_unique<Analyser::Static::MSX::Target>();
 | 
				
			||||||
	target->machine = Analyser::Machine::MSX;
 | 
						target->machine = Analyser::Machine::MSX;
 | 
				
			||||||
	target->confidence = confidence;
 | 
						target->confidence = confidence;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -269,7 +269,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
 | 
				
			|||||||
	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
 | 
						std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Consider building a target for disks and/or tapes.
 | 
						// Consider building a target for disks and/or tapes.
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check tapes for loadable files.
 | 
						// Check tapes for loadable files.
 | 
				
			||||||
	for(auto &tape : media.tapes) {
 | 
						for(auto &tape : media.tapes) {
 | 
				
			||||||
@@ -285,7 +285,12 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Region selection: for now, this as simple as:
 | 
				
			||||||
 | 
						// "If a tape is involved, be European. Otherwise be American (i.e. English, but 60Hz)".
 | 
				
			||||||
 | 
						target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Blindly accept disks for now.
 | 
						// Blindly accept disks for now.
 | 
				
			||||||
 | 
						// TODO: how to spot an MSX disk?
 | 
				
			||||||
	target->media.disks = media.disks;
 | 
						target->media.disks = media.disks;
 | 
				
			||||||
	target->has_disk_drive = !media.disks.empty();
 | 
						target->has_disk_drive = !media.disks.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,12 @@ namespace MSX {
 | 
				
			|||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
	bool has_disk_drive = false;
 | 
						bool has_disk_drive = false;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enum class Region {
 | 
				
			||||||
 | 
							Japan,
 | 
				
			||||||
 | 
							USA,
 | 
				
			||||||
 | 
							Europe
 | 
				
			||||||
 | 
						} region = Region::USA;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								Analyser/Static/Macintosh/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/Macintosh/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 02/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
 | 
						// This analyser can comprehend disks and mass-storage devices only.
 | 
				
			||||||
 | 
						if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// As there is at least one usable media image, wave it through.
 | 
				
			||||||
 | 
						Analyser::Static::TargetList targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						using Target = Analyser::Static::Macintosh::Target;
 | 
				
			||||||
 | 
						auto *target = new Target;
 | 
				
			||||||
 | 
						target->machine = Analyser::Machine::Macintosh;
 | 
				
			||||||
 | 
						target->media = media;
 | 
				
			||||||
 | 
						targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return targets;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								Analyser/Static/Macintosh/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Static/Macintosh/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 02/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					#define Analyser_Static_Macintosh_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/TargetPlatforms.hpp"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
 | 
				
			||||||
							
								
								
									
										31
									
								
								Analyser/Static/Macintosh/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								Analyser/Static/Macintosh/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Target.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_Macintosh_Target_h
 | 
				
			||||||
 | 
					#define Analyser_Static_Macintosh_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public ::Analyser::Static::Target {
 | 
				
			||||||
 | 
						enum class Model {
 | 
				
			||||||
 | 
							Mac128k,
 | 
				
			||||||
 | 
							Mac512k,
 | 
				
			||||||
 | 
							Mac512ke,
 | 
				
			||||||
 | 
							MacPlus
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Model model = Model::MacPlus;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_Macintosh_Target_h */
 | 
				
			||||||
@@ -101,7 +101,7 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::Oric;
 | 
						target->machine = Machine::Oric;
 | 
				
			||||||
	target->confidence = 0.5;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
 | 
				
			|||||||
		return {};
 | 
							return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TargetList targets;
 | 
						TargetList targets;
 | 
				
			||||||
	std::unique_ptr<Target> target(new Target);
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	target->machine = Machine::MasterSystem;
 | 
						target->machine = Machine::MasterSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,10 +17,12 @@
 | 
				
			|||||||
#include "Acorn/StaticAnalyser.hpp"
 | 
					#include "Acorn/StaticAnalyser.hpp"
 | 
				
			||||||
#include "AmstradCPC/StaticAnalyser.hpp"
 | 
					#include "AmstradCPC/StaticAnalyser.hpp"
 | 
				
			||||||
#include "AppleII/StaticAnalyser.hpp"
 | 
					#include "AppleII/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Atari/StaticAnalyser.hpp"
 | 
					#include "Atari2600/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "AtariST/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Coleco/StaticAnalyser.hpp"
 | 
					#include "Coleco/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Commodore/StaticAnalyser.hpp"
 | 
					#include "Commodore/StaticAnalyser.hpp"
 | 
				
			||||||
#include "DiskII/StaticAnalyser.hpp"
 | 
					#include "DiskII/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Macintosh/StaticAnalyser.hpp"
 | 
				
			||||||
#include "MSX/StaticAnalyser.hpp"
 | 
					#include "MSX/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Oric/StaticAnalyser.hpp"
 | 
					#include "Oric/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Sega/StaticAnalyser.hpp"
 | 
					#include "Sega/StaticAnalyser.hpp"
 | 
				
			||||||
@@ -35,15 +37,21 @@
 | 
				
			|||||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Mass Storage Devices (i.e. usually, hard disks)
 | 
				
			||||||
 | 
					#include "../../Storage/MassStorage/Formats/HFV.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tapes
 | 
					// Tapes
 | 
				
			||||||
#include "../../Storage/Tape/Formats/CAS.hpp"
 | 
					#include "../../Storage/Tape/Formats/CAS.hpp"
 | 
				
			||||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
 | 
					#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
 | 
				
			||||||
@@ -85,34 +93,39 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
				
			|||||||
		TryInsert(list, class, platforms)	\
 | 
							TryInsert(list, class, platforms)	\
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 80
 | 
						Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// 80
 | 
				
			||||||
	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 81
 | 
						Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// 81
 | 
				
			||||||
	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// A26
 | 
						Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)							// A26
 | 
				
			||||||
	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF
 | 
						Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)			// ADF
 | 
				
			||||||
	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)					// BIN
 | 
						Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)						// BIN (cartridge dump)
 | 
				
			||||||
	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS
 | 
						Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)													// CAS
 | 
				
			||||||
	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT
 | 
						Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)											// CDT
 | 
				
			||||||
	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL
 | 
						Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)						// COL
 | 
				
			||||||
	Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)											// CSW
 | 
						Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)												// CSW
 | 
				
			||||||
	Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)		// D64
 | 
						Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)			// D64
 | 
				
			||||||
	Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)				// DMK
 | 
						Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)					// DMK
 | 
				
			||||||
	Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// DO
 | 
						Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DO
 | 
				
			||||||
	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// DSD
 | 
						Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD
 | 
				
			||||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)	// DSK (Amstrad CPC)
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)		// DSK (Amstrad CPC)
 | 
				
			||||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// DSK (Apple)
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II)
 | 
				
			||||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)			// DSK (MSX)
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh, floppy disk)
 | 
				
			||||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)		// DSK (Oric)
 | 
						Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk)
 | 
				
			||||||
	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)		// G64
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)				// DSK (MSX)
 | 
				
			||||||
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric)
 | 
				
			||||||
 | 
						Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64
 | 
				
			||||||
	Format(	"hfe",
 | 
						Format(	"hfe",
 | 
				
			||||||
			result.disks,
 | 
								result.disks,
 | 
				
			||||||
			Disk::DiskImageHolder<Storage::Disk::HFE>,
 | 
								Disk::DiskImageHolder<Storage::Disk::HFE>,
 | 
				
			||||||
			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
 | 
								TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
 | 
				
			||||||
			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
 | 
								// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
 | 
				
			||||||
	Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)			// NIB
 | 
						Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)		// IMG (DiskCopy 4.2)
 | 
				
			||||||
	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O
 | 
						Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// IMG (DiskCopy 4.2)
 | 
				
			||||||
	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P
 | 
						Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST)				// MSA
 | 
				
			||||||
	Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// PO
 | 
						Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB
 | 
				
			||||||
	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P81
 | 
						Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O
 | 
				
			||||||
 | 
						Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P
 | 
				
			||||||
 | 
						Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// PO
 | 
				
			||||||
 | 
						Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P81
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// PRG
 | 
						// PRG
 | 
				
			||||||
	if(extension == "prg") {
 | 
						if(extension == "prg") {
 | 
				
			||||||
@@ -129,16 +142,17 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
				
			|||||||
	Format(	"rom",
 | 
						Format(	"rom",
 | 
				
			||||||
			result.cartridges,
 | 
								result.cartridges,
 | 
				
			||||||
			Cartridge::BinaryDump,
 | 
								Cartridge::BinaryDump,
 | 
				
			||||||
			TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX)				// ROM
 | 
								TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX)					// ROM
 | 
				
			||||||
	Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)							// SG
 | 
						Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)								// SG
 | 
				
			||||||
	Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)							// SMS
 | 
						Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)								// SMS
 | 
				
			||||||
	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD
 | 
						Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// SSD
 | 
				
			||||||
	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore)
 | 
						Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST)				// ST
 | 
				
			||||||
	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric)
 | 
						Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)									// TAP (Commodore)
 | 
				
			||||||
	Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)												// TSX
 | 
						Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)											// TAP (Oric)
 | 
				
			||||||
	Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)											// TZX
 | 
						Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)													// TSX
 | 
				
			||||||
	Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)											// UEF (tape)
 | 
						Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)												// TZX
 | 
				
			||||||
	Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII)			// WOZ
 | 
						Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)												// UEF (tape)
 | 
				
			||||||
 | 
						Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII)				// WOZ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#undef Format
 | 
					#undef Format
 | 
				
			||||||
#undef Insert
 | 
					#undef Insert
 | 
				
			||||||
@@ -155,7 +169,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
 | 
				
			|||||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
					TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
				
			||||||
	TargetList targets;
 | 
						TargetList targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
 | 
						// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
 | 
				
			||||||
	// union of all platforms this file might be a target for.
 | 
						// union of all platforms this file might be a target for.
 | 
				
			||||||
	TargetPlatform::IntType potential_platforms = 0;
 | 
						TargetPlatform::IntType potential_platforms = 0;
 | 
				
			||||||
	Media media = GetMediaAndPlatforms(file_name, potential_platforms);
 | 
						Media media = GetMediaAndPlatforms(file_name, potential_platforms);
 | 
				
			||||||
@@ -169,13 +183,15 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
				
			|||||||
	if(potential_platforms & TargetPlatform::Acorn)			Append(Acorn);
 | 
						if(potential_platforms & TargetPlatform::Acorn)			Append(Acorn);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::AmstradCPC)	Append(AmstradCPC);
 | 
						if(potential_platforms & TargetPlatform::AmstradCPC)	Append(AmstradCPC);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::AppleII)		Append(AppleII);
 | 
						if(potential_platforms & TargetPlatform::AppleII)		Append(AppleII);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Atari2600)		Append(Atari);
 | 
						if(potential_platforms & TargetPlatform::Atari2600)		Append(Atari2600);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::AtariST)		Append(AtariST);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco);
 | 
						if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore);
 | 
						if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII);
 | 
						if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Sega)			Append(Sega);
 | 
						if(potential_platforms & TargetPlatform::Macintosh)		Append(Macintosh);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::MSX)			Append(MSX);
 | 
						if(potential_platforms & TargetPlatform::MSX)			Append(MSX);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Oric)			Append(Oric);
 | 
						if(potential_platforms & TargetPlatform::Oric)			Append(Oric);
 | 
				
			||||||
 | 
						if(potential_platforms & TargetPlatform::Sega)			Append(Sega);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::ZX8081)		Append(ZX8081);
 | 
						if(potential_platforms & TargetPlatform::ZX8081)		Append(ZX8081);
 | 
				
			||||||
	#undef Append
 | 
						#undef Append
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../Machines.hpp"
 | 
					#include "../Machines.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Tape/Tape.hpp"
 | 
					 | 
				
			||||||
#include "../../Storage/Disk/Disk.hpp"
 | 
					 | 
				
			||||||
#include "../../Storage/Cartridge/Cartridge.hpp"
 | 
					#include "../../Storage/Cartridge/Cartridge.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/Disk.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/MassStorage/MassStorageDevice.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Tape/Tape.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
@@ -29,9 +30,10 @@ struct Media {
 | 
				
			|||||||
	std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
 | 
						std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
 | 
				
			||||||
	std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
 | 
						std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
 | 
				
			||||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
 | 
						std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
 | 
				
			||||||
 | 
						std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool empty() const {
 | 
						bool empty() const {
 | 
				
			||||||
		return disks.empty() && tapes.empty() && cartridges.empty();
 | 
							return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,9 @@
 | 
				
			|||||||
#ifndef ClockReceiver_hpp
 | 
					#ifndef ClockReceiver_hpp
 | 
				
			||||||
#define ClockReceiver_hpp
 | 
					#define ClockReceiver_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ForceInline.hpp"
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
	Informal pattern for all classes that run from a clock cycle:
 | 
						Informal pattern for all classes that run from a clock cycle:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,149 +55,193 @@
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
template <class T> class WrappedInt {
 | 
					template <class T> class WrappedInt {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		constexpr WrappedInt(int l) : length_(l) {}
 | 
							using IntType = int64_t;
 | 
				
			||||||
		constexpr WrappedInt() : length_(0) {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator =(const T &rhs) {
 | 
							forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
 | 
				
			||||||
 | 
							forceinline constexpr WrappedInt() noexcept : length_(0) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline T &operator =(const T &rhs) {
 | 
				
			||||||
			length_ = rhs.length_;
 | 
								length_ = rhs.length_;
 | 
				
			||||||
			return *this;
 | 
								return *this;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator +=(const T &rhs) {
 | 
							forceinline T &operator +=(const T &rhs) {
 | 
				
			||||||
			length_ += rhs.length_;
 | 
								length_ += rhs.length_;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator -=(const T &rhs) {
 | 
							forceinline T &operator -=(const T &rhs) {
 | 
				
			||||||
			length_ -= rhs.length_;
 | 
								length_ -= rhs.length_;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator ++() {
 | 
							forceinline T &operator ++() {
 | 
				
			||||||
			++ length_;
 | 
								++ length_;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator ++(int) {
 | 
							forceinline T &operator ++(int) {
 | 
				
			||||||
			length_ ++;
 | 
								length_ ++;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator --() {
 | 
							forceinline T &operator --() {
 | 
				
			||||||
			-- length_;
 | 
								-- length_;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator --(int) {
 | 
							forceinline T &operator --(int) {
 | 
				
			||||||
			length_ --;
 | 
								length_ --;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator %=(const T &rhs) {
 | 
							forceinline T &operator *=(const T &rhs) {
 | 
				
			||||||
 | 
								length_ *= rhs.length_;
 | 
				
			||||||
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline T &operator /=(const T &rhs) {
 | 
				
			||||||
 | 
								length_ /= rhs.length_;
 | 
				
			||||||
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline T &operator %=(const T &rhs) {
 | 
				
			||||||
			length_ %= rhs.length_;
 | 
								length_ %= rhs.length_;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &operator &=(const T &rhs) {
 | 
							forceinline T &operator &=(const T &rhs) {
 | 
				
			||||||
			length_ &= rhs.length_;
 | 
								length_ &= rhs.length_;
 | 
				
			||||||
			return *static_cast<T *>(this);
 | 
								return *static_cast<T *>(this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
 | 
							forceinline constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
 | 
				
			||||||
		constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}
 | 
							forceinline constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
 | 
							forceinline constexpr T operator *(const T &rhs) const			{	return T(length_ * rhs.length_);	}
 | 
				
			||||||
		constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}
 | 
							forceinline constexpr T operator /(const T &rhs) const			{	return T(length_ / rhs.length_);	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr T operator -() const						{	return T(- length_);				}
 | 
							forceinline constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
 | 
				
			||||||
 | 
							forceinline constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		}
 | 
							forceinline constexpr T operator -() const						{	return T(- length_);				}
 | 
				
			||||||
		constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		}
 | 
					 | 
				
			||||||
		constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		}
 | 
					 | 
				
			||||||
		constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		}
 | 
					 | 
				
			||||||
		constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		}
 | 
					 | 
				
			||||||
		constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr bool operator !() const					{	return !length_;					}
 | 
							forceinline constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		}
 | 
				
			||||||
 | 
							forceinline constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		}
 | 
				
			||||||
 | 
							forceinline constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		}
 | 
				
			||||||
 | 
							forceinline constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		}
 | 
				
			||||||
 | 
							forceinline constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		}
 | 
				
			||||||
 | 
							forceinline constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline constexpr bool operator !() const					{	return !length_;					}
 | 
				
			||||||
		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
 | 
							// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr int as_int() const { return length_; }
 | 
							/// @returns The underlying int, cast to an integral type of your choosing.
 | 
				
			||||||
 | 
							template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns The underlying int, in its native form.
 | 
				
			||||||
 | 
							forceinline constexpr IntType as_integral() const { return length_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
								Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
				
			||||||
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
								the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		T divide(const T &divisor) {
 | 
							template <typename Result = T> forceinline Result divide(const T &divisor) {
 | 
				
			||||||
			T result(length_ / divisor.length_);
 | 
								Result r;
 | 
				
			||||||
			length_ %= divisor.length_;
 | 
								static_cast<T *>(this)->fill(r, divisor);
 | 
				
			||||||
			return result;
 | 
								return r;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Flushes the value in @c this. The current value is returned, and the internal value
 | 
								Flushes the value in @c this. The current value is returned, and the internal value
 | 
				
			||||||
			is reset to zero.
 | 
								is reset to zero.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		T flush() {
 | 
							template <typename Result> Result flush() {
 | 
				
			||||||
			T result(length_);
 | 
								// Jiggery pokery here; switching to function overloading avoids
 | 
				
			||||||
			length_ = 0;
 | 
								// the namespace-level requirement for template specialisation.
 | 
				
			||||||
			return result;
 | 
								Result r;
 | 
				
			||||||
 | 
								static_cast<T *>(this)->fill(r);
 | 
				
			||||||
 | 
								return r;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// operator int() is deliberately not provided, to avoid accidental subtitution of
 | 
							// operator int() is deliberately not provided, to avoid accidental subtitution of
 | 
				
			||||||
		// classes that use this template.
 | 
							// classes that use this template.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected:
 | 
						protected:
 | 
				
			||||||
		int length_;
 | 
							IntType length_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
 | 
					/// Describes an integer number of whole cycles: pairs of clock signal transitions.
 | 
				
			||||||
class Cycles: public WrappedInt<Cycles> {
 | 
					class Cycles: public WrappedInt<Cycles> {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
 | 
							forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
 | 
				
			||||||
		constexpr Cycles() : WrappedInt<Cycles>() {}
 | 
							forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
 | 
				
			||||||
		constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
 | 
							forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							friend WrappedInt;
 | 
				
			||||||
 | 
							void fill(Cycles &result) {
 | 
				
			||||||
 | 
								result.length_ = length_;
 | 
				
			||||||
 | 
								length_ = 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void fill(Cycles &result, const Cycles &divisor) {
 | 
				
			||||||
 | 
								result.length_ = length_ / divisor.length_;
 | 
				
			||||||
 | 
								length_ %= divisor.length_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Describes an integer number of half cycles: single clock signal transitions.
 | 
					/// Describes an integer number of half cycles: single clock signal transitions.
 | 
				
			||||||
class HalfCycles: public WrappedInt<HalfCycles> {
 | 
					class HalfCycles: public WrappedInt<HalfCycles> {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
 | 
							forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
 | 
				
			||||||
		constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
 | 
							forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
 | 
							forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
 | 
				
			||||||
		constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
 | 
							forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns The number of whole cycles completely covered by this span of half cycles.
 | 
							/// @returns The number of whole cycles completely covered by this span of half cycles.
 | 
				
			||||||
		constexpr Cycles cycles() const {
 | 
							forceinline constexpr Cycles cycles() const {
 | 
				
			||||||
			return Cycles(length_ >> 1);
 | 
								return Cycles(length_ >> 1);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
 | 
					 | 
				
			||||||
		Cycles flush_cycles() {
 | 
					 | 
				
			||||||
			Cycles result(length_ >> 1);
 | 
					 | 
				
			||||||
			length_ &= 1;
 | 
					 | 
				
			||||||
			return result;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
 | 
					 | 
				
			||||||
		HalfCycles flush() {
 | 
					 | 
				
			||||||
			HalfCycles result(length_);
 | 
					 | 
				
			||||||
			length_ = 0;
 | 
					 | 
				
			||||||
			return result;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
								Severs from @c this the effect of dividing by @c divisor; @c this will end up with
 | 
				
			||||||
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
								the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		Cycles divide_cycles(const Cycles &divisor) {
 | 
							forceinline Cycles divide_cycles(const Cycles &divisor) {
 | 
				
			||||||
			HalfCycles half_divisor = HalfCycles(divisor);
 | 
								const HalfCycles half_divisor = HalfCycles(divisor);
 | 
				
			||||||
			Cycles result(length_ / half_divisor.length_);
 | 
								const Cycles result(length_ / half_divisor.length_);
 | 
				
			||||||
			length_ %= half_divisor.length_;
 | 
								length_ %= half_divisor.length_;
 | 
				
			||||||
			return result;
 | 
								return result;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							friend WrappedInt;
 | 
				
			||||||
 | 
							void fill(Cycles &result) {
 | 
				
			||||||
 | 
								result = Cycles(length_ >> 1);
 | 
				
			||||||
 | 
								length_ &= 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void fill(HalfCycles &result) {
 | 
				
			||||||
 | 
								result.length_ = length_;
 | 
				
			||||||
 | 
								length_ = 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void fill(Cycles &result, const HalfCycles &divisor) {
 | 
				
			||||||
 | 
								result = Cycles(length_ / (divisor.length_ << 1));
 | 
				
			||||||
 | 
								length_ %= (divisor.length_ << 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void fill(HalfCycles &result, const HalfCycles &divisor) {
 | 
				
			||||||
 | 
								result.length_ = length_ / divisor.length_;
 | 
				
			||||||
 | 
								length_ %= divisor.length_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
 | 
				
			||||||
 | 
					// without losing the fractional part.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
 | 
						If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
 | 
				
			||||||
	automatically to gain run_for(HalfCycles).
 | 
						automatically to gain run_for(HalfCycles).
 | 
				
			||||||
@@ -203,9 +250,9 @@ template <class T> class HalfClockReceiver: public T {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		using T::T;
 | 
							using T::T;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void run_for(const HalfCycles half_cycles) {
 | 
							forceinline void run_for(const HalfCycles half_cycles) {
 | 
				
			||||||
			half_cycles_ += half_cycles;
 | 
								half_cycles_ += half_cycles;
 | 
				
			||||||
			T::run_for(half_cycles_.flush_cycles());
 | 
								T::run_for(half_cycles_.flush<Cycles>());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,26 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
//  ClockDeferrer.hpp
 | 
					//  DeferredQueue.hpp
 | 
				
			||||||
//  Clock Signal
 | 
					//  Clock Signal
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
//  Created by Thomas Harte on 23/08/2018.
 | 
					//  Created by Thomas Harte on 23/08/2018.
 | 
				
			||||||
//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
					//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef ClockDeferrer_h
 | 
					#ifndef DeferredQueue_h
 | 
				
			||||||
#define ClockDeferrer_h
 | 
					#define DeferredQueue_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	A ClockDeferrer maintains a list of ordered actions and the times at which
 | 
						A DeferredQueue maintains a list of ordered actions and the times at which
 | 
				
			||||||
	they should happen, and divides a total execution period up into the portions
 | 
						they should happen, and divides a total execution period up into the portions
 | 
				
			||||||
	that occur between those actions, triggering each action when it is reached.
 | 
						that occur between those actions, triggering each action when it is reached.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
template <typename TimeUnit> class ClockDeferrer {
 | 
					template <typename TimeUnit> class DeferredQueue {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
 | 
							/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
 | 
				
			||||||
		ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
 | 
							DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Schedules @c action to occur in @c delay units of time.
 | 
								Schedules @c action to occur in @c delay units of time.
 | 
				
			||||||
@@ -78,4 +79,4 @@ template <typename TimeUnit> class ClockDeferrer {
 | 
				
			|||||||
		std::vector<DeferredAction> pending_actions_;
 | 
							std::vector<DeferredAction> pending_actions_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* ClockDeferrer_h */
 | 
					#endif /* DeferredQueue_h */
 | 
				
			||||||
@@ -9,9 +9,9 @@
 | 
				
			|||||||
#ifndef ForceInline_hpp
 | 
					#ifndef ForceInline_hpp
 | 
				
			||||||
#define ForceInline_hpp
 | 
					#define ForceInline_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef DEBUG
 | 
					#ifndef NDEBUG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define forceinline
 | 
					#define forceinline inline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										123
									
								
								ClockReceiver/JustInTime.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								ClockReceiver/JustInTime.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  JustInTime.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 28/07/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef JustInTime_h
 | 
				
			||||||
 | 
					#define JustInTime_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					#include "ForceInline.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
 | 
				
			||||||
 | 
						of time since run_for was last called.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Time can be added using the += operator. The -> operator can be used to access the
 | 
				
			||||||
 | 
						embedded object. All time accumulated will be pushed to object before the pointer is returned.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
 | 
				
			||||||
 | 
						separate @c TargetTimeScale at template declaration.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
 | 
				
			||||||
 | 
							template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Adds time to the actor.
 | 
				
			||||||
 | 
							forceinline void operator += (const LocalTimeScale &rhs) {
 | 
				
			||||||
 | 
								if constexpr (multiplier != 1) {
 | 
				
			||||||
 | 
									time_since_update_ += rhs * multiplier;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									time_since_update_ += rhs;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								is_flushed_ = false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Flushes all accumulated time and returns a pointer to the included object.
 | 
				
			||||||
 | 
							forceinline T *operator->() {
 | 
				
			||||||
 | 
								flush();
 | 
				
			||||||
 | 
								return &object_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Returns a pointer to the included object without flushing time.
 | 
				
			||||||
 | 
							forceinline T *last_valid() {
 | 
				
			||||||
 | 
								return &object_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Flushes all accumulated time.
 | 
				
			||||||
 | 
							forceinline void flush() {
 | 
				
			||||||
 | 
								if(!is_flushed_) {
 | 
				
			||||||
 | 
									is_flushed_ = true;
 | 
				
			||||||
 | 
									if constexpr (divider == 1) {
 | 
				
			||||||
 | 
										object_.run_for(time_since_update_.template flush<TargetTimeScale>());
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
 | 
				
			||||||
 | 
										if(duration > TargetTimeScale(0))
 | 
				
			||||||
 | 
											object_.run_for(duration);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							T object_;
 | 
				
			||||||
 | 
							LocalTimeScale time_since_update_;
 | 
				
			||||||
 | 
							bool is_flushed_ = true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
 | 
				
			||||||
 | 
						Any time the amount of accumulated time crosses a threshold provided at construction time,
 | 
				
			||||||
 | 
						the object will be updated on the AsyncTaskQueue.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
 | 
				
			||||||
 | 
							template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
 | 
				
			||||||
 | 
								object_(std::forward<Args>(args)...),
 | 
				
			||||||
 | 
							 	threshold_(threshold) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Adds time to the actor.
 | 
				
			||||||
 | 
							inline void operator += (const LocalTimeScale &rhs) {
 | 
				
			||||||
 | 
								time_since_update_ += rhs;
 | 
				
			||||||
 | 
								if(time_since_update_ >= threshold_) {
 | 
				
			||||||
 | 
									time_since_update_ -= threshold_;
 | 
				
			||||||
 | 
									task_queue_.enqueue([this] () {
 | 
				
			||||||
 | 
										object_.run_for(threshold_);
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								is_flushed_ = false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Flushes all accumulated time and returns a pointer to the included object.
 | 
				
			||||||
 | 
							inline T *operator->() {
 | 
				
			||||||
 | 
								flush();
 | 
				
			||||||
 | 
								return &object_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Returns a pointer to the included object without flushing time.
 | 
				
			||||||
 | 
							inline T *last_valid() {
 | 
				
			||||||
 | 
								return &object_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Flushes all accumulated time.
 | 
				
			||||||
 | 
							inline void flush() {
 | 
				
			||||||
 | 
								if(!is_flushed_) {
 | 
				
			||||||
 | 
									task_queue_.flush();
 | 
				
			||||||
 | 
									object_.run_for(time_since_update_.template flush<TargetTimeScale>());
 | 
				
			||||||
 | 
									is_flushed_ = true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							T object_;
 | 
				
			||||||
 | 
							LocalTimeScale time_since_update_;
 | 
				
			||||||
 | 
							TargetTimeScale threshold_;
 | 
				
			||||||
 | 
							bool is_flushed_ = true;
 | 
				
			||||||
 | 
							Concurrency::AsyncTaskQueue task_queue_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* JustInTime_h */
 | 
				
			||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#include "1770.hpp"
 | 
					#include "1770.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
 | 
					#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define LOG_PREFIX "[WD FDC] "
 | 
				
			||||||
#include "../../Outputs/Log.hpp"
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace WD;
 | 
					using namespace WD;
 | 
				
			||||||
@@ -16,9 +18,9 @@ using namespace WD;
 | 
				
			|||||||
WD1770::WD1770(Personality p) :
 | 
					WD1770::WD1770(Personality p) :
 | 
				
			||||||
		Storage::Disk::MFMController(8000000),
 | 
							Storage::Disk::MFMController(8000000),
 | 
				
			||||||
		personality_(p),
 | 
							personality_(p),
 | 
				
			||||||
		interesting_event_mask_(static_cast<int>(Event1770::Command)) {
 | 
							interesting_event_mask_(int(Event1770::Command)) {
 | 
				
			||||||
	set_is_double_density(false);
 | 
						set_is_double_density(false);
 | 
				
			||||||
	posit_event(static_cast<int>(Event1770::Command));
 | 
						posit_event(int(Event1770::Command));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void WD1770::set_register(int address, uint8_t value) {
 | 
					void WD1770::set_register(int address, uint8_t value) {
 | 
				
			||||||
@@ -28,7 +30,7 @@ void WD1770::set_register(int address, uint8_t value) {
 | 
				
			|||||||
				if(value == 0xd0) {
 | 
									if(value == 0xd0) {
 | 
				
			||||||
					// Force interrupt **immediately**.
 | 
										// Force interrupt **immediately**.
 | 
				
			||||||
					LOG("Force interrupt immediately");
 | 
										LOG("Force interrupt immediately");
 | 
				
			||||||
					posit_event(static_cast<int>(Event1770::ForceInterrupt));
 | 
										posit_event(int(Event1770::ForceInterrupt));
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					ERROR("!!!TODO: force interrupt!!!");
 | 
										ERROR("!!!TODO: force interrupt!!!");
 | 
				
			||||||
					update_status([] (Status &status) {
 | 
										update_status([] (Status &status) {
 | 
				
			||||||
@@ -37,7 +39,7 @@ void WD1770::set_register(int address, uint8_t value) {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				command_ = value;
 | 
									command_ = value;
 | 
				
			||||||
				posit_event(static_cast<int>(Event1770::Command));
 | 
									posit_event(int(Event1770::Command));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
@@ -59,20 +61,28 @@ uint8_t WD1770::get_register(int address) {
 | 
				
			|||||||
				status.interrupt_request = false;
 | 
									status.interrupt_request = false;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			uint8_t status =
 | 
								uint8_t status =
 | 
				
			||||||
					(status_.write_protect ? Flag::WriteProtect : 0) |
 | 
									(status_.crc_error ? Flag::CRCError : 0) |
 | 
				
			||||||
					(status_.crc_error ? Flag::CRCError : 0) |
 | 
									(status_.busy ? Flag::Busy : 0);
 | 
				
			||||||
					(status_.busy ? Flag::Busy : 0);
 | 
					
 | 
				
			||||||
 | 
								// Per Jean Louis-Guérin's documentation:
 | 
				
			||||||
 | 
								//
 | 
				
			||||||
 | 
								//	* 	the write-protect bit is locked into place by a type 2 or type 3 command, but is
 | 
				
			||||||
 | 
								//		read live after a type 1.
 | 
				
			||||||
 | 
								//	*	the track 0 bit is captured during a type 1 instruction and lost upon any other type,
 | 
				
			||||||
 | 
								//		it is not live sampled.
 | 
				
			||||||
			switch(status_.type) {
 | 
								switch(status_.type) {
 | 
				
			||||||
				case Status::One:
 | 
									case Status::One:
 | 
				
			||||||
					status |=
 | 
										status |=
 | 
				
			||||||
						(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
 | 
											(status_.track_zero ? Flag::TrackZero : 0) |
 | 
				
			||||||
						(status_.seek_error ? Flag::SeekError : 0);
 | 
											(status_.seek_error ? Flag::SeekError : 0) |
 | 
				
			||||||
						// TODO: index hole
 | 
											(get_drive().get_is_read_only() ? Flag::WriteProtect : 0) |
 | 
				
			||||||
 | 
											(get_drive().get_index_pulse() ? Flag::Index : 0);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case Status::Two:
 | 
									case Status::Two:
 | 
				
			||||||
				case Status::Three:
 | 
									case Status::Three:
 | 
				
			||||||
					status |=
 | 
										status |=
 | 
				
			||||||
 | 
											(status_.write_protect ? Flag::WriteProtect : 0) |
 | 
				
			||||||
						(status_.record_type ? Flag::RecordType : 0) |
 | 
											(status_.record_type ? Flag::RecordType : 0) |
 | 
				
			||||||
						(status_.lost_data ? Flag::LostData : 0) |
 | 
											(status_.lost_data ? Flag::LostData : 0) |
 | 
				
			||||||
						(status_.data_request ? Flag::DataRequest : 0) |
 | 
											(status_.data_request ? Flag::DataRequest : 0) |
 | 
				
			||||||
@@ -89,10 +99,15 @@ uint8_t WD1770::get_register(int address) {
 | 
				
			|||||||
				if(status_.type == Status::One)
 | 
									if(status_.type == Status::One)
 | 
				
			||||||
					status |= (status_.spin_up ? Flag::SpinUp : 0);
 | 
										status |= (status_.spin_up ? Flag::SpinUp : 0);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					//			LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type));
 | 
				
			||||||
			return status;
 | 
								return status;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		case 1:		return track_;
 | 
							case 1:
 | 
				
			||||||
		case 2:		return sector_;
 | 
								LOG("Returned track " << int(track_));
 | 
				
			||||||
 | 
								return track_;
 | 
				
			||||||
 | 
							case 2:
 | 
				
			||||||
 | 
								LOG("Returned sector " << int(sector_));
 | 
				
			||||||
 | 
								return sector_;
 | 
				
			||||||
		case 3:
 | 
							case 3:
 | 
				
			||||||
			update_status([] (Status &status) {
 | 
								update_status([] (Status &status) {
 | 
				
			||||||
				status.data_request = false;
 | 
									status.data_request = false;
 | 
				
			||||||
@@ -105,28 +120,30 @@ void WD1770::run_for(const Cycles cycles) {
 | 
				
			|||||||
	Storage::Disk::Controller::run_for(cycles);
 | 
						Storage::Disk::Controller::run_for(cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(delay_time_) {
 | 
						if(delay_time_) {
 | 
				
			||||||
		unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
 | 
							const auto number_of_cycles = cycles.as_integral();
 | 
				
			||||||
		if(delay_time_ <= number_of_cycles) {
 | 
							if(delay_time_ <= number_of_cycles) {
 | 
				
			||||||
			delay_time_ = 0;
 | 
								delay_time_ = 0;
 | 
				
			||||||
			posit_event(static_cast<int>(Event1770::Timer));
 | 
								posit_event(int(Event1770::Timer));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			delay_time_ -= number_of_cycles;
 | 
								delay_time_ -= number_of_cycles;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
 | 
					#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
 | 
				
			||||||
#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
 | 
					#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
 | 
				
			||||||
#define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
 | 
					#define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = int(Event::Token); return; }
 | 
				
			||||||
#define BEGIN_SECTION()	switch(resume_point_) { default:
 | 
					#define BEGIN_SECTION()	switch(resume_point_) { default:
 | 
				
			||||||
#define END_SECTION()	(void)0; }
 | 
					#define END_SECTION()	(void)0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define READ_ID()	\
 | 
					#define READ_ID()	\
 | 
				
			||||||
		if(new_event_type == static_cast<int>(Event::Token)) {	\
 | 
							if(new_event_type == int(Event::Token)) {	\
 | 
				
			||||||
			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }	\
 | 
								if(!distance_into_section_ && get_latest_token().type == Token::ID) {\
 | 
				
			||||||
			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\
 | 
									set_data_mode(DataMode::Reading);	\
 | 
				
			||||||
 | 
									++distance_into_section_;	\
 | 
				
			||||||
 | 
								} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\
 | 
				
			||||||
				header_[distance_into_section_ - 1] = get_latest_token().byte_value;	\
 | 
									header_[distance_into_section_ - 1] = get_latest_token().byte_value;	\
 | 
				
			||||||
				distance_into_section_++;	\
 | 
									++distance_into_section_;	\
 | 
				
			||||||
			}	\
 | 
								}	\
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -159,10 +176,10 @@ void WD1770::run_for(const Cycles cycles) {
 | 
				
			|||||||
// +--------+----------+-------------------------+
 | 
					// +--------+----------+-------------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void WD1770::posit_event(int new_event_type) {
 | 
					void WD1770::posit_event(int new_event_type) {
 | 
				
			||||||
	if(new_event_type == static_cast<int>(Event::IndexHole)) {
 | 
						if(new_event_type == int(Event::IndexHole)) {
 | 
				
			||||||
		index_hole_count_++;
 | 
							index_hole_count_++;
 | 
				
			||||||
		if(index_hole_count_target_ == index_hole_count_) {
 | 
							if(index_hole_count_target_ == index_hole_count_) {
 | 
				
			||||||
			posit_event(static_cast<int>(Event1770::IndexHoleTarget));
 | 
								posit_event(int(Event1770::IndexHoleTarget));
 | 
				
			||||||
			index_hole_count_target_ = -1;
 | 
								index_hole_count_target_ = -1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -177,19 +194,19 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
 | 
						if(new_event_type == int(Event1770::ForceInterrupt)) {
 | 
				
			||||||
		interesting_event_mask_ = 0;
 | 
							interesting_event_mask_ = 0;
 | 
				
			||||||
		resume_point_ = 0;
 | 
							resume_point_ = 0;
 | 
				
			||||||
		update_status([] (Status &status) {
 | 
							update_status([] (Status &status) {
 | 
				
			||||||
			status.type = Status::One;
 | 
								status.type = Status::One;
 | 
				
			||||||
			status.data_request = false;
 | 
								status.data_request = false;
 | 
				
			||||||
 | 
								status.spin_up = false;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
 | 
							if(!(interesting_event_mask_ & int(new_event_type))) return;
 | 
				
			||||||
		interesting_event_mask_ &= ~new_event_type;
 | 
							interesting_event_mask_ &= ~new_event_type;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Status new_status;
 | 
					 | 
				
			||||||
	BEGIN_SECTION()
 | 
						BEGIN_SECTION()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Wait for a new command, branch to the appropriate handler.
 | 
						// Wait for a new command, branch to the appropriate handler.
 | 
				
			||||||
@@ -209,9 +226,10 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		update_status([] (Status &status) {
 | 
							update_status([] (Status &status) {
 | 
				
			||||||
			status.busy = true;
 | 
								status.busy = true;
 | 
				
			||||||
			status.interrupt_request = false;
 | 
								status.interrupt_request = false;
 | 
				
			||||||
 | 
								status.track_zero = false;	// Always reset by a non-type 1; so reset regardless and set properly later.
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		LOG("Starting " << std::hex << command_ << std::endl);
 | 
							LOG("Starting " << PADHEX(2) << int(command_));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(!(command_ & 0x80)) goto begin_type_1;
 | 
							if(!(command_ & 0x80)) goto begin_type_1;
 | 
				
			||||||
		if(!(command_ & 0x40)) goto begin_type_2;
 | 
							if(!(command_ & 0x40)) goto begin_type_2;
 | 
				
			||||||
@@ -241,6 +259,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			status.data_request = false;
 | 
								status.data_request = false;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							LOG("Step/Seek/Restore with track " << int(track_) << " data " << int(data_));
 | 
				
			||||||
		if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
 | 
							if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(has_motor_on_line()) goto begin_type1_spin_up;
 | 
							if(has_motor_on_line()) goto begin_type1_spin_up;
 | 
				
			||||||
@@ -273,19 +292,19 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	perform_seek_or_restore_command:
 | 
						perform_seek_or_restore_command:
 | 
				
			||||||
		if(track_ == data_) goto verify;
 | 
							if(track_ == data_) goto verify_seek;
 | 
				
			||||||
		step_direction_ = (data_ > track_);
 | 
							step_direction_ = (data_ > track_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	adjust_track:
 | 
						adjust_track:
 | 
				
			||||||
		if(step_direction_) track_++; else track_--;
 | 
							if(step_direction_) ++track_; else --track_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	perform_step:
 | 
						perform_step:
 | 
				
			||||||
		if(!step_direction_ && get_drive().get_is_track_zero()) {
 | 
							if(!step_direction_ && get_drive().get_is_track_zero()) {
 | 
				
			||||||
			track_ = 0;
 | 
								track_ = 0;
 | 
				
			||||||
			goto verify;
 | 
								goto verify_seek;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
 | 
							get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
 | 
				
			||||||
		unsigned int time_to_wait;
 | 
							Cycles::IntType time_to_wait;
 | 
				
			||||||
		switch(command_ & 3) {
 | 
							switch(command_ & 3) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			case 0: time_to_wait = 6;	break;
 | 
								case 0: time_to_wait = 6;	break;
 | 
				
			||||||
@@ -294,14 +313,17 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			case 3: time_to_wait = (personality_ == P1772) ? 3 : 30;	break;
 | 
								case 3: time_to_wait = (personality_ == P1772) ? 3 : 30;	break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		WAIT_FOR_TIME(time_to_wait);
 | 
							WAIT_FOR_TIME(time_to_wait);
 | 
				
			||||||
		if(command_ >> 5) goto verify;
 | 
							if(command_ >> 5) goto verify_seek;
 | 
				
			||||||
		goto perform_seek_or_restore_command;
 | 
							goto perform_seek_or_restore_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	perform_step_command:
 | 
						perform_step_command:
 | 
				
			||||||
		if(command_ & 0x10) goto adjust_track;
 | 
							if(command_ & 0x10) goto adjust_track;
 | 
				
			||||||
		goto perform_step;
 | 
							goto perform_step;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	verify:
 | 
						verify_seek:
 | 
				
			||||||
 | 
							update_status([this] (Status &status) {
 | 
				
			||||||
 | 
								status.track_zero = get_drive().get_is_track_zero();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		if(!(command_ & 0x04)) {
 | 
							if(!(command_ & 0x04)) {
 | 
				
			||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -310,7 +332,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		distance_into_section_ = 0;
 | 
							distance_into_section_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	verify_read_data:
 | 
						verify_read_data:
 | 
				
			||||||
		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
 | 
							WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
 | 
				
			||||||
		READ_ID();
 | 
							READ_ID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(index_hole_count_ == 6) {
 | 
							if(index_hole_count_ == 6) {
 | 
				
			||||||
@@ -320,7 +342,9 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(distance_into_section_ == 7) {
 | 
							if(distance_into_section_ == 7) {
 | 
				
			||||||
 | 
								distance_into_section_ = 0;
 | 
				
			||||||
			set_data_mode(DataMode::Scanning);
 | 
								set_data_mode(DataMode::Scanning);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(get_crc_generator().get_value()) {
 | 
								if(get_crc_generator().get_value()) {
 | 
				
			||||||
				update_status([] (Status &status) {
 | 
									update_status([] (Status &status) {
 | 
				
			||||||
					status.crc_error = true;
 | 
										status.crc_error = true;
 | 
				
			||||||
@@ -329,14 +353,12 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(header_[0] == track_) {
 | 
								if(header_[0] == track_) {
 | 
				
			||||||
				LOG("Reached track " << std::dec << track_);
 | 
									LOG("Reached track " << std::dec << int(track_));
 | 
				
			||||||
				update_status([] (Status &status) {
 | 
									update_status([] (Status &status) {
 | 
				
			||||||
					status.crc_error = false;
 | 
										status.crc_error = false;
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				goto wait_for_command;
 | 
									goto wait_for_command;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					 | 
				
			||||||
			distance_into_section_ = 0;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		goto verify_read_data;
 | 
							goto verify_read_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -393,23 +415,28 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							distance_into_section_ = 0;
 | 
				
			||||||
 | 
							set_data_mode(DataMode::Scanning);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	type2_get_header:
 | 
						type2_get_header:
 | 
				
			||||||
		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
 | 
							WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
 | 
				
			||||||
		READ_ID();
 | 
							READ_ID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(index_hole_count_ == 5) {
 | 
							if(index_hole_count_ == 5) {
 | 
				
			||||||
			LOG("Failed to find sector " << std::dec << sector_);
 | 
								LOG("Failed to find sector " << std::dec << int(sector_));
 | 
				
			||||||
			update_status([] (Status &status) {
 | 
								update_status([] (Status &status) {
 | 
				
			||||||
				status.record_not_found = true;
 | 
									status.record_not_found = true;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(distance_into_section_ == 7) {
 | 
							if(distance_into_section_ == 7) {
 | 
				
			||||||
			LOG("Considering " << std::dec << header_[0] << "/" << header_[2]);
 | 
								distance_into_section_ = 0;
 | 
				
			||||||
			set_data_mode(DataMode::Scanning);
 | 
								set_data_mode(DataMode::Scanning);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
 | 
				
			||||||
			if(		header_[0] == track_ && header_[2] == sector_ &&
 | 
								if(		header_[0] == track_ && header_[2] == sector_ &&
 | 
				
			||||||
					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
 | 
										(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
 | 
				
			||||||
				LOG("Found " << std::dec << header_[0] << "/" << header_[2]);
 | 
									LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
 | 
				
			||||||
				if(get_crc_generator().get_value()) {
 | 
									if(get_crc_generator().get_value()) {
 | 
				
			||||||
					LOG("CRC error; back to searching");
 | 
										LOG("CRC error; back to searching");
 | 
				
			||||||
					update_status([] (Status &status) {
 | 
										update_status([] (Status &status) {
 | 
				
			||||||
@@ -423,7 +450,6 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
				});
 | 
									});
 | 
				
			||||||
				goto type2_read_or_write_data;
 | 
									goto type2_read_or_write_data;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			distance_into_section_ = 0;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		goto type2_get_header;
 | 
							goto type2_get_header;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -466,6 +492,9 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		header_[distance_into_section_] = get_latest_token().byte_value;
 | 
							header_[distance_into_section_] = get_latest_token().byte_value;
 | 
				
			||||||
		distance_into_section_++;
 | 
							distance_into_section_++;
 | 
				
			||||||
		if(distance_into_section_ == 2) {
 | 
							if(distance_into_section_ == 2) {
 | 
				
			||||||
 | 
								distance_into_section_ = 0;
 | 
				
			||||||
 | 
								set_data_mode(DataMode::Scanning);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(get_crc_generator().get_value()) {
 | 
								if(get_crc_generator().get_value()) {
 | 
				
			||||||
				LOG("CRC error; terminating");
 | 
									LOG("CRC error; terminating");
 | 
				
			||||||
				update_status([this] (Status &status) {
 | 
									update_status([this] (Status &status) {
 | 
				
			||||||
@@ -474,11 +503,13 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
				goto wait_for_command;
 | 
									goto wait_for_command;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								LOG("Finished reading sector " << std::dec << int(sector_));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(command_ & 0x10) {
 | 
								if(command_ & 0x10) {
 | 
				
			||||||
				sector_++;
 | 
									sector_++;
 | 
				
			||||||
 | 
									LOG("Advancing to search for sector " << std::dec << int(sector_));
 | 
				
			||||||
				goto test_type2_write_protection;
 | 
									goto test_type2_write_protection;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			LOG("Finished reading sector " << std::dec << sector_);
 | 
					 | 
				
			||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		goto type2_check_crc;
 | 
							goto type2_check_crc;
 | 
				
			||||||
@@ -560,7 +591,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			sector_++;
 | 
								sector_++;
 | 
				
			||||||
			goto test_type2_write_protection;
 | 
								goto test_type2_write_protection;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		LOG("Wrote sector " << std::dec << sector_);
 | 
							LOG("Wrote sector " << std::dec << int(sector_));
 | 
				
			||||||
		goto wait_for_command;
 | 
							goto wait_for_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -611,8 +642,8 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		distance_into_section_ = 0;
 | 
							distance_into_section_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	read_address_get_header:
 | 
						read_address_get_header:
 | 
				
			||||||
		WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
 | 
							WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
 | 
				
			||||||
		if(new_event_type == static_cast<int>(Event::Token)) {
 | 
							if(new_event_type == int(Event::Token)) {
 | 
				
			||||||
			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
 | 
								if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
 | 
				
			||||||
			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
 | 
								else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
 | 
				
			||||||
				if(status_.data_request) {
 | 
									if(status_.data_request) {
 | 
				
			||||||
@@ -626,9 +657,11 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
				update_status([] (Status &status) {
 | 
									update_status([] (Status &status) {
 | 
				
			||||||
					status.data_request = true;
 | 
										status.data_request = true;
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				distance_into_section_++;
 | 
									++distance_into_section_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if(distance_into_section_ == 7) {
 | 
									if(distance_into_section_ == 7) {
 | 
				
			||||||
 | 
										distance_into_section_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(get_crc_generator().get_value()) {
 | 
										if(get_crc_generator().get_value()) {
 | 
				
			||||||
						update_status([] (Status &status) {
 | 
											update_status([] (Status &status) {
 | 
				
			||||||
							status.crc_error = true;
 | 
												status.crc_error = true;
 | 
				
			||||||
@@ -652,7 +685,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		index_hole_count_ = 0;
 | 
							index_hole_count_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	read_track_read_byte:
 | 
						read_track_read_byte:
 | 
				
			||||||
		WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
 | 
							WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
 | 
				
			||||||
		if(index_hole_count_) {
 | 
							if(index_hole_count_) {
 | 
				
			||||||
			goto wait_for_command;
 | 
								goto wait_for_command;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -719,7 +752,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
				case 0xfd: case 0xfe:
 | 
									case 0xfd: case 0xfe:
 | 
				
			||||||
					// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
 | 
										// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
 | 
				
			||||||
					write_raw_short(
 | 
										write_raw_short(
 | 
				
			||||||
						static_cast<uint16_t>(
 | 
											uint16_t(
 | 
				
			||||||
							0xa022 |
 | 
												0xa022 |
 | 
				
			||||||
							((data_ & 0x80) << 7) |
 | 
												((data_ & 0x80) << 7) |
 | 
				
			||||||
							((data_ & 0x40) << 6) |
 | 
												((data_ & 0x40) << 6) |
 | 
				
			||||||
@@ -768,15 +801,18 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void WD1770::update_status(std::function<void(Status &)> updater) {
 | 
					void WD1770::update_status(std::function<void(Status &)> updater) {
 | 
				
			||||||
 | 
						const Status old_status = status_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(delegate_) {
 | 
						if(delegate_) {
 | 
				
			||||||
		Status old_status = status_;
 | 
					 | 
				
			||||||
		updater(status_);
 | 
							updater(status_);
 | 
				
			||||||
		bool did_change =
 | 
							const bool did_change =
 | 
				
			||||||
			(status_.busy != old_status.busy) ||
 | 
								(status_.busy != old_status.busy) ||
 | 
				
			||||||
			(status_.data_request != old_status.data_request);
 | 
								(status_.data_request != old_status.data_request) ||
 | 
				
			||||||
 | 
								(status_.interrupt_request != old_status.interrupt_request);
 | 
				
			||||||
		if(did_change) delegate_->wd1770_did_change_output(this);
 | 
							if(did_change) delegate_->wd1770_did_change_output(this);
 | 
				
			||||||
	}
 | 
						} else updater(status_);
 | 
				
			||||||
	else updater(status_);
 | 
					
 | 
				
			||||||
 | 
						if(status_.busy != old_status.busy) update_clocking_observer();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void WD1770::set_head_load_request(bool head_load) {}
 | 
					void WD1770::set_head_load_request(bool head_load) {}
 | 
				
			||||||
@@ -784,5 +820,10 @@ void WD1770::set_motor_on(bool motor_on) {}
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void WD1770::set_head_loaded(bool head_loaded) {
 | 
					void WD1770::set_head_loaded(bool head_loaded) {
 | 
				
			||||||
	head_is_loaded_ = head_loaded;
 | 
						head_is_loaded_ = head_loaded;
 | 
				
			||||||
	if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
 | 
						if(head_loaded) posit_event(int(Event1770::HeadLoad));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClockingHint::Preference WD1770::preferred_clocking() {
 | 
				
			||||||
 | 
						if(status_.busy) return ClockingHint::Preference::RealTime;
 | 
				
			||||||
 | 
						return Storage::Disk::MFMController::preferred_clocking();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,6 +73,8 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
		};
 | 
							};
 | 
				
			||||||
		inline void set_delegate(Delegate *delegate)	{	delegate_ = delegate;			}
 | 
							inline void set_delegate(Delegate *delegate)	{	delegate_ = delegate;			}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ClockingHint::Preference preferred_clocking() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected:
 | 
						protected:
 | 
				
			||||||
		virtual void set_head_load_request(bool head_load);
 | 
							virtual void set_head_load_request(bool head_load);
 | 
				
			||||||
		virtual void set_motor_on(bool motor_on);
 | 
							virtual void set_motor_on(bool motor_on);
 | 
				
			||||||
@@ -94,6 +96,7 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
			bool data_request = false;
 | 
								bool data_request = false;
 | 
				
			||||||
			bool interrupt_request = false;
 | 
								bool interrupt_request = false;
 | 
				
			||||||
			bool busy = false;
 | 
								bool busy = false;
 | 
				
			||||||
 | 
								bool track_zero = false;
 | 
				
			||||||
			enum {
 | 
								enum {
 | 
				
			||||||
				One, Two, Three
 | 
									One, Two, Three
 | 
				
			||||||
			} type = One;
 | 
								} type = One;
 | 
				
			||||||
@@ -121,7 +124,7 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
		void posit_event(int type);
 | 
							void posit_event(int type);
 | 
				
			||||||
		int interesting_event_mask_;
 | 
							int interesting_event_mask_;
 | 
				
			||||||
		int resume_point_ = 0;
 | 
							int resume_point_ = 0;
 | 
				
			||||||
		unsigned int delay_time_ = 0;
 | 
							Cycles::IntType delay_time_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ID buffer
 | 
							// ID buffer
 | 
				
			||||||
		uint8_t header_[6];
 | 
							uint8_t header_[6];
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										312
									
								
								Components/5380/ncr5380.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								Components/5380/ncr5380.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,312 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  ncr5380.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 10/08/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ncr5380.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace NCR::NCR5380;
 | 
				
			||||||
 | 
					using SCSI::Line;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
 | 
				
			||||||
 | 
						bus_(bus),
 | 
				
			||||||
 | 
						clock_rate_(clock_rate) {
 | 
				
			||||||
 | 
						device_id_ = bus_.add_device();
 | 
				
			||||||
 | 
						bus_.add_observer(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
 | 
				
			||||||
 | 
						switch(address & 7) {
 | 
				
			||||||
 | 
							case 0:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								data_bus_ = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(dma_request_ && dma_operation_ == DMAOperation::Send) {
 | 
				
			||||||
 | 
					//				printf("w %02x\n", value);
 | 
				
			||||||
 | 
									dma_acknowledge_ = true;
 | 
				
			||||||
 | 
									dma_request_ = false;
 | 
				
			||||||
 | 
									update_control_output();
 | 
				
			||||||
 | 
									bus_.set_device_output(device_id_, bus_output_);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 1: {
 | 
				
			||||||
 | 
					//			LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								initiator_command_ = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
 | 
				
			||||||
 | 
								if(value & 0x80) bus_output_ |= Line::Reset;
 | 
				
			||||||
 | 
								if(value & 0x08) bus_output_ |= Line::Busy;
 | 
				
			||||||
 | 
								if(value & 0x04) bus_output_ |= Line::SelectTarget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/* bit 5 = differential enable if this were a 5381 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								test_mode_ = value & 0x40;
 | 
				
			||||||
 | 
								assert_data_bus_ = value & 0x01;
 | 
				
			||||||
 | 
								update_control_output();
 | 
				
			||||||
 | 
							} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 2:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								mode_ = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
 | 
				
			||||||
 | 
								// bit 6: 1 = be a SCSI target; 0 = be an initiator
 | 
				
			||||||
 | 
								// bit 5: 1 = check parity
 | 
				
			||||||
 | 
								// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found
 | 
				
			||||||
 | 
								// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller
 | 
				
			||||||
 | 
								// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs
 | 
				
			||||||
 | 
								// bit 1: 1 = use DMA mode
 | 
				
			||||||
 | 
								// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
 | 
				
			||||||
 | 
								arbitration_in_progress_ = false;
 | 
				
			||||||
 | 
								switch(mode_ & 0x3) {
 | 
				
			||||||
 | 
									case 0x0:
 | 
				
			||||||
 | 
										bus_output_ &= ~SCSI::Line::Busy;
 | 
				
			||||||
 | 
										dma_request_ = false;
 | 
				
			||||||
 | 
										set_execution_state(ExecutionState::None);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case 0x1:
 | 
				
			||||||
 | 
										arbitration_in_progress_ = true;
 | 
				
			||||||
 | 
										set_execution_state(ExecutionState::WaitingForBusy);
 | 
				
			||||||
 | 
										lost_arbitration_ = false;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										assert_data_bus_ = false;
 | 
				
			||||||
 | 
										set_execution_state(ExecutionState::PerformingDMA);
 | 
				
			||||||
 | 
										bus_.update_observers();
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								update_control_output();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 3: {
 | 
				
			||||||
 | 
					//			LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								target_command_ = value;
 | 
				
			||||||
 | 
								update_control_output();
 | 
				
			||||||
 | 
							} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 4:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 5:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								dma_operation_ = DMAOperation::Send;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 6:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								dma_operation_ = DMAOperation::TargetReceive;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 7:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								dma_operation_ = DMAOperation::InitiatorReceive;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Data is output only if the data bus is asserted.
 | 
				
			||||||
 | 
						if(assert_data_bus_) {
 | 
				
			||||||
 | 
							bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							bus_output_ &= ~SCSI::Line::Data;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// In test mode, still nothing is output. Otherwise throw out
 | 
				
			||||||
 | 
						// the current value of bus_output_.
 | 
				
			||||||
 | 
						if(test_mode_) {
 | 
				
			||||||
 | 
							bus_.set_device_output(device_id_, SCSI::DefaultBusState);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							bus_.set_device_output(device_id_, bus_output_);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t NCR5380::read(int address, bool dma_acknowledge) {
 | 
				
			||||||
 | 
						switch(address & 7) {
 | 
				
			||||||
 | 
							case 0:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
 | 
				
			||||||
 | 
									dma_acknowledge_ = true;
 | 
				
			||||||
 | 
									dma_request_ = false;
 | 
				
			||||||
 | 
									update_control_output();
 | 
				
			||||||
 | 
									bus_.set_device_output(device_id_, bus_output_);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							return uint8_t(bus_.get_state());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 1:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') <<  (lost_arbitration_ ? 'l' : '-'));
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
								// Bits repeated as they were set.
 | 
				
			||||||
 | 
								(initiator_command_ & ~0x60) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Arbitration in progress.
 | 
				
			||||||
 | 
								(arbitration_in_progress_ ? 0x40 : 0x00) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Lost arbitration.
 | 
				
			||||||
 | 
								(lost_arbitration_ ? 0x20 : 0x00);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 2:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 2] Get mode");
 | 
				
			||||||
 | 
							return mode_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 3:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 3] Get target command");
 | 
				
			||||||
 | 
							return target_command_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 4: {
 | 
				
			||||||
 | 
								const auto bus_state = bus_.get_state();
 | 
				
			||||||
 | 
								const uint8_t result =
 | 
				
			||||||
 | 
									((bus_state & Line::Reset)			? 0x80 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Busy)			? 0x40 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Request)		? 0x20 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Message)		? 0x10 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Control)		? 0x08 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Input)			? 0x04 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::SelectTarget)	? 0x02 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Parity)			? 0x01 : 0x00);
 | 
				
			||||||
 | 
					//			LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
 | 
				
			||||||
 | 
								return result;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 5: {
 | 
				
			||||||
 | 
								const auto bus_state = bus_.get_state();
 | 
				
			||||||
 | 
								const bool phase_matches =
 | 
				
			||||||
 | 
									(target_output() & (Line::Message | Line::Control | Line::Input)) ==
 | 
				
			||||||
 | 
									(bus_state & (Line::Message | Line::Control | Line::Input));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const uint8_t result =
 | 
				
			||||||
 | 
									/* b7 = end of DMA */
 | 
				
			||||||
 | 
									((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00)	|
 | 
				
			||||||
 | 
									/* b5 = parity error */
 | 
				
			||||||
 | 
									/* b4 = IRQ active */
 | 
				
			||||||
 | 
									(phase_matches ? 0x08 : 0x00)	|
 | 
				
			||||||
 | 
									/* b2 = busy error */
 | 
				
			||||||
 | 
									((bus_state & Line::Attention) ? 0x02 : 0x00) |
 | 
				
			||||||
 | 
									((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
 | 
				
			||||||
 | 
					//			LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
 | 
				
			||||||
 | 
								return result;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 6:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 6] Get input data");
 | 
				
			||||||
 | 
							return 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 7:
 | 
				
			||||||
 | 
					//			LOG("[SCSI 7] Reset parity/interrupt");
 | 
				
			||||||
 | 
							return 0xff;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SCSI::BusState NCR5380::target_output() {
 | 
				
			||||||
 | 
						SCSI::BusState output = SCSI::DefaultBusState;
 | 
				
			||||||
 | 
						if(target_command_ & 0x08) output |= Line::Request;
 | 
				
			||||||
 | 
						if(target_command_ & 0x04) output |= Line::Message;
 | 
				
			||||||
 | 
						if(target_command_ & 0x02) output |= Line::Control;
 | 
				
			||||||
 | 
						if(target_command_ & 0x01) output |= Line::Input;
 | 
				
			||||||
 | 
						return output;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NCR5380::update_control_output() {
 | 
				
			||||||
 | 
						bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention);
 | 
				
			||||||
 | 
						if(mode_ & 0x40) {
 | 
				
			||||||
 | 
							// This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus.
 | 
				
			||||||
 | 
							bus_output_ |= target_output();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// This is an initiator; /ATN and /ACK are signalled on the bus.
 | 
				
			||||||
 | 
							if(
 | 
				
			||||||
 | 
								(initiator_command_ & 0x10) ||
 | 
				
			||||||
 | 
								(state_ == ExecutionState::PerformingDMA && dma_acknowledge_)
 | 
				
			||||||
 | 
							) bus_output_ |= Line::Acknowledge;
 | 
				
			||||||
 | 
							if(initiator_command_ & 0x02) bus_output_ |= Line::Attention;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
 | 
				
			||||||
 | 
						switch(state_) {
 | 
				
			||||||
 | 
							default: break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								Official documentation:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Arbitration is accomplished using a bus-free filter to continuously monitor BSY.
 | 
				
			||||||
 | 
									If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free
 | 
				
			||||||
 | 
									and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive
 | 
				
			||||||
 | 
									and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun
 | 
				
			||||||
 | 
									(BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus
 | 
				
			||||||
 | 
									can be examined to deter- mine if arbitration has been won. This delay must be
 | 
				
			||||||
 | 
									implemented in the controlling software driver.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Personal notes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs
 | 
				
			||||||
 | 
									to happen is:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										(i) wait for BSY to be inactive;
 | 
				
			||||||
 | 
										(ii) count 400 nsec;
 | 
				
			||||||
 | 
										(iii) check that BSY and SEL are inactive.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case ExecutionState::WaitingForBusy:
 | 
				
			||||||
 | 
								if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
 | 
				
			||||||
 | 
								state_ = ExecutionState::WatchingBusy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case ExecutionState::WatchingBusy:
 | 
				
			||||||
 | 
								if(!(new_state & SCSI::Line::Busy)) {
 | 
				
			||||||
 | 
									lost_arbitration_ = true;
 | 
				
			||||||
 | 
									set_execution_state(ExecutionState::None);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check for having hit 400ns (more or less) since BSY was inactive.
 | 
				
			||||||
 | 
								if(time_since_change >= SCSI::BusSettleDelay) {
 | 
				
			||||||
 | 
					//				arbitration_in_progress_ = false;
 | 
				
			||||||
 | 
									if(new_state & SCSI::Line::SelectTarget) {
 | 
				
			||||||
 | 
										lost_arbitration_ = true;
 | 
				
			||||||
 | 
										set_execution_state(ExecutionState::None);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										bus_output_ &= ~SCSI::Line::Busy;
 | 
				
			||||||
 | 
										set_execution_state(ExecutionState::None);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case ExecutionState::PerformingDMA:
 | 
				
			||||||
 | 
								if(time_since_change < SCSI::DeskewDelay) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Signal a DMA request if the request line is active, i.e. meaningful data is
 | 
				
			||||||
 | 
								// on the bus, and this device hasn't yet acknowledged it.
 | 
				
			||||||
 | 
								switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) {
 | 
				
			||||||
 | 
									case 0:
 | 
				
			||||||
 | 
										dma_request_ = false;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case SCSI::Line::Request:
 | 
				
			||||||
 | 
										dma_request_ = true;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case SCSI::Line::Request | SCSI::Line::Acknowledge:
 | 
				
			||||||
 | 
										dma_request_ = false;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case SCSI::Line::Acknowledge:
 | 
				
			||||||
 | 
										dma_acknowledge_ = false;
 | 
				
			||||||
 | 
										dma_request_ = false;
 | 
				
			||||||
 | 
										update_control_output();
 | 
				
			||||||
 | 
										bus_.set_device_output(device_id_, bus_output_);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void NCR5380::set_execution_state(ExecutionState state) {
 | 
				
			||||||
 | 
						state_ = state;
 | 
				
			||||||
 | 
						if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								Components/5380/ncr5380.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Components/5380/ncr5380.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  ncr5380.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 10/08/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef ncr5380_hpp
 | 
				
			||||||
 | 
					#define ncr5380_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NCR {
 | 
				
			||||||
 | 
					namespace NCR5380 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models the NCR 5380, a SCSI interface chip.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class NCR5380 final: public SCSI::Bus::Observer {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							NCR5380(SCSI::Bus &bus, int clock_rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*! Writes @c value to @c address.  */
 | 
				
			||||||
 | 
							void write(int address, uint8_t value, bool dma_acknowledge = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*! Reads from @c address. */
 | 
				
			||||||
 | 
							uint8_t read(int address, bool dma_acknowledge = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							SCSI::Bus &bus_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const int clock_rate_;
 | 
				
			||||||
 | 
							size_t device_id_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SCSI::BusState bus_output_ = SCSI::DefaultBusState;
 | 
				
			||||||
 | 
							SCSI::BusState expected_phase_ = SCSI::DefaultBusState;
 | 
				
			||||||
 | 
							uint8_t mode_ = 0xff;
 | 
				
			||||||
 | 
							uint8_t initiator_command_ = 0xff;
 | 
				
			||||||
 | 
							uint8_t data_bus_ = 0xff;
 | 
				
			||||||
 | 
							uint8_t target_command_ = 0xff;
 | 
				
			||||||
 | 
							bool test_mode_ = false;
 | 
				
			||||||
 | 
							bool assert_data_bus_ = false;
 | 
				
			||||||
 | 
							bool dma_request_ = false;
 | 
				
			||||||
 | 
							bool dma_acknowledge_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class ExecutionState {
 | 
				
			||||||
 | 
								None,
 | 
				
			||||||
 | 
								WaitingForBusy,
 | 
				
			||||||
 | 
								WatchingBusy,
 | 
				
			||||||
 | 
								PerformingDMA,
 | 
				
			||||||
 | 
							} state_ = ExecutionState::None;
 | 
				
			||||||
 | 
							enum class DMAOperation {
 | 
				
			||||||
 | 
								Ready,
 | 
				
			||||||
 | 
								Send,
 | 
				
			||||||
 | 
								TargetReceive,
 | 
				
			||||||
 | 
								InitiatorReceive
 | 
				
			||||||
 | 
							} dma_operation_ = DMAOperation::Ready;
 | 
				
			||||||
 | 
							bool lost_arbitration_ = false, arbitration_in_progress_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_execution_state(ExecutionState state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SCSI::BusState target_output();
 | 
				
			||||||
 | 
							void update_control_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* ncr5380_hpp */
 | 
				
			||||||
@@ -47,6 +47,12 @@ class PortHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		/// Sets the current logical value of the interrupt line.
 | 
							/// Sets the current logical value of the interrupt line.
 | 
				
			||||||
		void set_interrupt_status(bool status)									{}
 | 
							void set_interrupt_status(bool status)									{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Provides a measure of time elapsed between other calls.
 | 
				
			||||||
 | 
							void run_for(HalfCycles duration)										{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Receives passed-on flush() calls from the 6522.
 | 
				
			||||||
 | 
							void flush()															{}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -71,26 +77,6 @@ class IRQDelegatePortHandler: public PortHandler {
 | 
				
			|||||||
		Delegate *delegate_ = nullptr;
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MOS6522Base: public MOS6522Storage {
 | 
					 | 
				
			||||||
	public:
 | 
					 | 
				
			||||||
		/// Sets the input value of line @c line on port @c port.
 | 
					 | 
				
			||||||
		void set_control_line_input(Port port, Line line, bool value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// Runs for a specified number of half cycles.
 | 
					 | 
				
			||||||
		void run_for(const HalfCycles half_cycles);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// Runs for a specified number of cycles.
 | 
					 | 
				
			||||||
		void run_for(const Cycles cycles);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// @returns @c true if the IRQ line is currently active; @c false otherwise.
 | 
					 | 
				
			||||||
		bool get_interrupt_line();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private:
 | 
					 | 
				
			||||||
		inline void do_phase1();
 | 
					 | 
				
			||||||
		inline void do_phase2();
 | 
					 | 
				
			||||||
		virtual void reevaluate_interrupts() = 0;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
 | 
						Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,7 +88,7 @@ class MOS6522Base: public MOS6522Storage {
 | 
				
			|||||||
	Consumers should derive their own curiously-recurring-template-pattern subclass,
 | 
						Consumers should derive their own curiously-recurring-template-pattern subclass,
 | 
				
			||||||
	implementing bus communications as required.
 | 
						implementing bus communications as required.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
template <class T> class MOS6522: public MOS6522Base {
 | 
					template <class T> class MOS6522: public MOS6522Storage {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
 | 
							MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
 | 
				
			||||||
		MOS6522(const MOS6522 &) = delete;
 | 
							MOS6522(const MOS6522 &) = delete;
 | 
				
			||||||
@@ -116,16 +102,44 @@ template <class T> class MOS6522: public MOS6522Base {
 | 
				
			|||||||
		/*! @returns the bus handler. */
 | 
							/*! @returns the bus handler. */
 | 
				
			||||||
		T &bus_handler();
 | 
							T &bus_handler();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets the input value of line @c line on port @c port.
 | 
				
			||||||
 | 
							void set_control_line_input(Port port, Line line, bool value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Runs for a specified number of half cycles.
 | 
				
			||||||
 | 
							void run_for(const HalfCycles half_cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Runs for a specified number of cycles.
 | 
				
			||||||
 | 
							void run_for(const Cycles cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns @c true if the IRQ line is currently active; @c false otherwise.
 | 
				
			||||||
 | 
							bool get_interrupt_line();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Updates the port handler to the current time and then requests that it flush.
 | 
				
			||||||
 | 
							void flush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
 | 
							void do_phase1();
 | 
				
			||||||
 | 
							void do_phase2();
 | 
				
			||||||
 | 
							void shift_in();
 | 
				
			||||||
 | 
							void shift_out();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		T &bus_handler_;
 | 
							T &bus_handler_;
 | 
				
			||||||
 | 
							HalfCycles time_since_bus_handler_call_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
		inline void reevaluate_interrupts();
 | 
							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();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Implementation/6522Implementation.hpp"
 | 
					#include "Implementation/6522Implementation.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif /* _522_hpp */
 | 
					#endif /* _522_hpp */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,116 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
//  6522Base.cpp
 | 
					 | 
				
			||||||
//  Clock Signal
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//  Created by Thomas Harte on 04/09/2017.
 | 
					 | 
				
			||||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../6522.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace MOS::MOS6522;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MOS6522Base::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();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			control_inputs_[port].line_one = value;
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		case Line::Two:
 | 
					 | 
				
			||||||
			// TODO: output modes, but probably elsewhere?
 | 
					 | 
				
			||||||
			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();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			control_inputs_[port].line_two = value;
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MOS6522Base::do_phase2() {
 | 
					 | 
				
			||||||
	registers_.last_timer[0] = registers_.timer[0];
 | 
					 | 
				
			||||||
	registers_.last_timer[1] = registers_.timer[1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(registers_.timer_needs_reload) {
 | 
					 | 
				
			||||||
		registers_.timer_needs_reload = false;
 | 
					 | 
				
			||||||
		registers_.timer[0] = registers_.timer_latch[0];
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		registers_.timer[0] --;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	registers_.timer[1] --;
 | 
					 | 
				
			||||||
	if(registers_.next_timer[0] >= 0) {
 | 
					 | 
				
			||||||
		registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
 | 
					 | 
				
			||||||
		registers_.next_timer[0] = -1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if(registers_.next_timer[1] >= 0) {
 | 
					 | 
				
			||||||
		registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
 | 
					 | 
				
			||||||
		registers_.next_timer[1] = -1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MOS6522Base::do_phase1() {
 | 
					 | 
				
			||||||
	// IRQ is raised on the half cycle after overflow
 | 
					 | 
				
			||||||
	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]) {
 | 
					 | 
				
			||||||
		registers_.interrupt_flags |= InterruptFlag::Timer1;
 | 
					 | 
				
			||||||
		reevaluate_interrupts();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if(registers_.auxiliary_control&0x40)
 | 
					 | 
				
			||||||
			registers_.timer_needs_reload = true;
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
			timer_is_running_[0] = false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*! Runs for a specified number of half cycles. */
 | 
					 | 
				
			||||||
void MOS6522Base::run_for(const HalfCycles half_cycles) {
 | 
					 | 
				
			||||||
	int number_of_half_cycles = half_cycles.as_int();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(is_phase2_) {
 | 
					 | 
				
			||||||
		do_phase2();
 | 
					 | 
				
			||||||
		number_of_half_cycles--;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	while(number_of_half_cycles >= 2) {
 | 
					 | 
				
			||||||
		do_phase1();
 | 
					 | 
				
			||||||
		do_phase2();
 | 
					 | 
				
			||||||
		number_of_half_cycles -= 2;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(number_of_half_cycles) {
 | 
					 | 
				
			||||||
		do_phase1();
 | 
					 | 
				
			||||||
		is_phase2_ = true;
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		is_phase2_ = false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*! Runs for a specified number of cycles. */
 | 
					 | 
				
			||||||
void MOS6522Base::run_for(const Cycles cycles) {
 | 
					 | 
				
			||||||
	int number_of_cycles = cycles.as_int();
 | 
					 | 
				
			||||||
	while(number_of_cycles--) {
 | 
					 | 
				
			||||||
		do_phase1();
 | 
					 | 
				
			||||||
		do_phase2();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
 | 
					 | 
				
			||||||
bool MOS6522Base::get_interrupt_line() {
 | 
					 | 
				
			||||||
	uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
 | 
					 | 
				
			||||||
	return !!interrupt_status;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -6,29 +6,63 @@
 | 
				
			|||||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
					//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
 | 
					#include "../../../Outputs/Log.hpp"
 | 
				
			||||||
	address &= 0xf;
 | 
					
 | 
				
			||||||
 | 
					namespace MOS {
 | 
				
			||||||
 | 
					namespace MOS6522 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::access(int address) {
 | 
				
			||||||
	switch(address) {
 | 
						switch(address) {
 | 
				
			||||||
		case 0x0:
 | 
							case 0x0:
 | 
				
			||||||
 | 
								// In both handshake and pulse modes, CB2 goes low on any read or write of Port B.
 | 
				
			||||||
 | 
								if(handshake_modes_[1] != HandshakeMode::None) {
 | 
				
			||||||
 | 
									set_control_line_output(Port::B, Line::Two, LineState::Off);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 0xf:
 | 
				
			||||||
 | 
							case 0x1:
 | 
				
			||||||
 | 
								// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
 | 
				
			||||||
 | 
								if(handshake_modes_[0] != HandshakeMode::None) {
 | 
				
			||||||
 | 
									set_control_line_output(Port::A, Line::Two, LineState::Off);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
 | 
				
			||||||
 | 
						address &= 0xf;
 | 
				
			||||||
 | 
						access(address);
 | 
				
			||||||
 | 
						switch(address) {
 | 
				
			||||||
 | 
							case 0x0:	// Write Port B.
 | 
				
			||||||
 | 
								// Store locally and communicate outwards.
 | 
				
			||||||
			registers_.output[1] = value;
 | 
								registers_.output[1] = value;
 | 
				
			||||||
			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);	// TODO: handshake
 | 
					
 | 
				
			||||||
 | 
								bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
 | 
								bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
								registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0xf:
 | 
							case 0xf:
 | 
				
			||||||
		case 0x1:
 | 
							case 0x1:	// Write Port A.
 | 
				
			||||||
			registers_.output[0] = value;
 | 
								registers_.output[0] = value;
 | 
				
			||||||
			bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);	// TODO: handshake
 | 
					
 | 
				
			||||||
 | 
								bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
 | 
								bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(handshake_modes_[1] != HandshakeMode::None) {
 | 
				
			||||||
 | 
									set_control_line_output(Port::A, Line::Two, LineState::Off);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
								registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0x2:
 | 
							case 0x2:	// Port B direction.
 | 
				
			||||||
			registers_.data_direction[1] = value;
 | 
								registers_.data_direction[1] = value;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0x3:
 | 
							case 0x3:	// Port A direction.
 | 
				
			||||||
			registers_.data_direction[0] = value;
 | 
								registers_.data_direction[0] = value;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,32 +88,57 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
 | 
				
			|||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Shift
 | 
							// Shift
 | 
				
			||||||
		case 0xa:	registers_.shift = value;				break;
 | 
							case 0xa:
 | 
				
			||||||
 | 
								registers_.shift = value;
 | 
				
			||||||
 | 
								shift_bits_remaining_ = 8;
 | 
				
			||||||
 | 
								registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
 | 
				
			||||||
 | 
								reevaluate_interrupts();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Control
 | 
							// Control
 | 
				
			||||||
		case 0xb:
 | 
							case 0xb:
 | 
				
			||||||
			registers_.auxiliary_control = value;
 | 
								registers_.auxiliary_control = value;
 | 
				
			||||||
 | 
								evaluate_cb2_output();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0xc:
 | 
							case 0xc: {
 | 
				
			||||||
//			printf("Peripheral control %02x\n", value);
 | 
					//			const auto old_peripheral_control = registers_.peripheral_control;
 | 
				
			||||||
			registers_.peripheral_control = value;
 | 
								registers_.peripheral_control = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
 | 
								int shift = 0;
 | 
				
			||||||
			if(value & 0x08) {
 | 
								for(int port = 0; port < 2; ++port) {
 | 
				
			||||||
				switch(value & 0x0e) {
 | 
									handshake_modes_[port] = HandshakeMode::None;
 | 
				
			||||||
					default: printf("Unimplemented control line mode %d\n", (value >> 1)&7);		break;
 | 
									switch((value >> shift) & 0x0e) {
 | 
				
			||||||
					case 0x0c:	bus_handler_.set_control_line_output(Port::A, Line::Two, false);	break;
 | 
										default: break;
 | 
				
			||||||
					case 0x0e:	bus_handler_.set_control_line_output(Port::A, Line::Two, true);		break;
 | 
					
 | 
				
			||||||
 | 
										case 0x00:	// Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register.
 | 
				
			||||||
 | 
										case 0x02:	// Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically.
 | 
				
			||||||
 | 
										case 0x04:	// Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register.
 | 
				
			||||||
 | 
										case 0x06:	// Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically.
 | 
				
			||||||
 | 
											set_control_line_output(Port(port), Line::Two, LineState::Input);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										case 0x08:	// Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1.
 | 
				
			||||||
 | 
											handshake_modes_[port] = HandshakeMode::Handshake;
 | 
				
			||||||
 | 
											set_control_line_output(Port(port), Line::Two, LineState::Off);	// At a guess.
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										case 0x0a:	// Pulse output: Cx2 is low for one cycle following a read or write of Port x.
 | 
				
			||||||
 | 
											handshake_modes_[port] = HandshakeMode::Pulse;
 | 
				
			||||||
 | 
											set_control_line_output(Port(port), Line::Two, LineState::On);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										case 0x0c:	// Manual output: Cx2 low.
 | 
				
			||||||
 | 
											set_control_line_output(Port(port), Line::Two, LineState::Off);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										case 0x0e:	// Manual output: Cx2 high.
 | 
				
			||||||
 | 
											set_control_line_output(Port(port), Line::Two, LineState::On);
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									shift += 4;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if(value & 0x80) {
 | 
							} break;
 | 
				
			||||||
				switch(value & 0xe0) {
 | 
					 | 
				
			||||||
					default: printf("Unimplemented control line mode %d\n", (value >> 5)&7);		break;
 | 
					 | 
				
			||||||
					case 0xc0:	bus_handler_.set_control_line_output(Port::B, Line::Two, false);	break;
 | 
					 | 
				
			||||||
					case 0xe0:	bus_handler_.set_control_line_output(Port::B, Line::Two, true);		break;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Interrupt control
 | 
							// Interrupt control
 | 
				
			||||||
		case 0xd:
 | 
							case 0xd:
 | 
				
			||||||
@@ -98,12 +157,13 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
 | 
					template <typename T> uint8_t MOS6522<T>::get_register(int address) {
 | 
				
			||||||
	address &= 0xf;
 | 
						address &= 0xf;
 | 
				
			||||||
 | 
						access(address);
 | 
				
			||||||
	switch(address) {
 | 
						switch(address) {
 | 
				
			||||||
		case 0x0:
 | 
							case 0x0:
 | 
				
			||||||
			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
 | 
								registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								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]);
 | 
				
			||||||
		case 0xf:	// TODO: handshake, latching
 | 
							case 0xf:
 | 
				
			||||||
		case 0x1:
 | 
							case 0x1:
 | 
				
			||||||
			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
 | 
								registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
@@ -128,7 +188,11 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
 | 
				
			|||||||
		return registers_.timer[1] & 0x00ff;
 | 
							return registers_.timer[1] & 0x00ff;
 | 
				
			||||||
		case 0x9:	return registers_.timer[1] >> 8;
 | 
							case 0x9:	return registers_.timer[1] >> 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0xa:	return registers_.shift;
 | 
							case 0xa:
 | 
				
			||||||
 | 
								shift_bits_remaining_ = 8;
 | 
				
			||||||
 | 
								registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
 | 
				
			||||||
 | 
								reevaluate_interrupts();
 | 
				
			||||||
 | 
							return registers_.shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0xb:	return registers_.auxiliary_control;
 | 
							case 0xb:	return registers_.auxiliary_control;
 | 
				
			||||||
		case 0xc:	return registers_.peripheral_control;
 | 
							case 0xc:	return registers_.peripheral_control;
 | 
				
			||||||
@@ -141,7 +205,8 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 input = bus_handler_.get_port_input(port);
 | 
						bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
 | 
						const uint8_t input = bus_handler_.get_port_input(port);
 | 
				
			||||||
	return (input & ~output_mask) | (output & output_mask);
 | 
						return (input & ~output_mask) | (output & output_mask);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -154,6 +219,238 @@ template <typename T> void MOS6522<T>::reevaluate_interrupts() {
 | 
				
			|||||||
	bool new_interrupt_status = get_interrupt_line();
 | 
						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;
 | 
							last_posted_interrupt_status_ = new_interrupt_status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
		bus_handler_.set_interrupt_status(new_interrupt_status);
 | 
							bus_handler_.set_interrupt_status(new_interrupt_status);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) {
 | 
				
			||||||
 | 
						switch(line) {
 | 
				
			||||||
 | 
							case Line::One:
 | 
				
			||||||
 | 
								if(value != control_inputs_[port].lines[line]) {
 | 
				
			||||||
 | 
									// In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2.
 | 
				
			||||||
 | 
									if(handshake_modes_[port] == HandshakeMode::Handshake) {
 | 
				
			||||||
 | 
										set_control_line_output(port, Line::Two, LineState::On);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Set the proper transition interrupt bit if enabled.
 | 
				
			||||||
 | 
									if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) {
 | 
				
			||||||
 | 
										registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
 | 
				
			||||||
 | 
										reevaluate_interrupts();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// If this is a transition on CB1, consider updating the shift register.
 | 
				
			||||||
 | 
									// TODO: and at least one full clock since the shift register was written?
 | 
				
			||||||
 | 
									if(port == Port::B) {
 | 
				
			||||||
 | 
										switch(shift_mode()) {
 | 
				
			||||||
 | 
											default: 													break;
 | 
				
			||||||
 | 
											case ShiftMode::InUnderCB1:		if(value)	shift_in();		break;	// Shifts in are captured on a low-to-high transition.
 | 
				
			||||||
 | 
											case ShiftMode::OutUnderCB1:	if(!value)	shift_out();	break;	// Shifts out are updated on a high-to-low transition.
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								control_inputs_[port].lines[line] = value;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case Line::Two:
 | 
				
			||||||
 | 
								if(	value != control_inputs_[port].lines[line] &&						// 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();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								control_inputs_[port].lines[line] = value;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::do_phase2() {
 | 
				
			||||||
 | 
						++ time_since_bus_handler_call_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						registers_.last_timer[0] = registers_.timer[0];
 | 
				
			||||||
 | 
						registers_.last_timer[1] = registers_.timer[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(registers_.timer_needs_reload) {
 | 
				
			||||||
 | 
							registers_.timer_needs_reload = false;
 | 
				
			||||||
 | 
							registers_.timer[0] = registers_.timer_latch[0];
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							registers_.timer[0] --;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						registers_.timer[1] --;
 | 
				
			||||||
 | 
						if(registers_.next_timer[0] >= 0) {
 | 
				
			||||||
 | 
							registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
 | 
				
			||||||
 | 
							registers_.next_timer[0] = -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(registers_.next_timer[1] >= 0) {
 | 
				
			||||||
 | 
							registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
 | 
				
			||||||
 | 
							registers_.next_timer[1] = -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// In pulse modes, CA2 and CB2 go high again on the next clock edge.
 | 
				
			||||||
 | 
						if(handshake_modes_[1] == HandshakeMode::Pulse) {
 | 
				
			||||||
 | 
							set_control_line_output(Port::B, Line::Two, LineState::On);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(handshake_modes_[0] == HandshakeMode::Pulse) {
 | 
				
			||||||
 | 
							set_control_line_output(Port::A, Line::Two, LineState::On);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the shift register is shifting according to the input clock, do a shift.
 | 
				
			||||||
 | 
						switch(shift_mode()) {
 | 
				
			||||||
 | 
							default: 											break;
 | 
				
			||||||
 | 
							case ShiftMode::InUnderPhase2:		shift_in();		break;
 | 
				
			||||||
 | 
							case ShiftMode::OutUnderPhase2:		shift_out();	break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::do_phase1() {
 | 
				
			||||||
 | 
						++ time_since_bus_handler_call_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// IRQ is raised on the half cycle after overflow
 | 
				
			||||||
 | 
						if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
 | 
				
			||||||
 | 
							timer_is_running_[1] = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If the shift register is shifting according to this timer, do a shift.
 | 
				
			||||||
 | 
							// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
 | 
				
			||||||
 | 
							switch(shift_mode()) {
 | 
				
			||||||
 | 
								default: 												break;
 | 
				
			||||||
 | 
								case ShiftMode::InUnderT2:				shift_in();		break;
 | 
				
			||||||
 | 
								case ShiftMode::OutUnderT2FreeRunning: 	shift_out();	break;
 | 
				
			||||||
 | 
								case ShiftMode::OutUnderT2:				shift_out();	break;	// TODO: present a clock on CB1.
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							registers_.interrupt_flags |= InterruptFlag::Timer2;
 | 
				
			||||||
 | 
							reevaluate_interrupts();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
 | 
				
			||||||
 | 
							registers_.interrupt_flags |= InterruptFlag::Timer1;
 | 
				
			||||||
 | 
							reevaluate_interrupts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Determine whether to reload.
 | 
				
			||||||
 | 
							if(registers_.auxiliary_control&0x40)
 | 
				
			||||||
 | 
								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;
 | 
				
			||||||
 | 
								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]);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*! 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();
 | 
				
			||||||
 | 
						if(!number_of_half_cycles) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(is_phase2_) {
 | 
				
			||||||
 | 
							do_phase2();
 | 
				
			||||||
 | 
							number_of_half_cycles--;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while(number_of_half_cycles >= 2) {
 | 
				
			||||||
 | 
							do_phase1();
 | 
				
			||||||
 | 
							do_phase2();
 | 
				
			||||||
 | 
							number_of_half_cycles -= 2;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(number_of_half_cycles) {
 | 
				
			||||||
 | 
							do_phase1();
 | 
				
			||||||
 | 
							is_phase2_ = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							is_phase2_ = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::flush() {
 | 
				
			||||||
 | 
						bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
 | 
						bus_handler_.flush();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*! Runs for a specified number of cycles. */
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
 | 
				
			||||||
 | 
						auto number_of_cycles = cycles.as_integral();
 | 
				
			||||||
 | 
						while(number_of_cycles--) {
 | 
				
			||||||
 | 
							do_phase1();
 | 
				
			||||||
 | 
							do_phase2();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
 | 
				
			||||||
 | 
					template <typename T> bool MOS6522<T>::get_interrupt_line() {
 | 
				
			||||||
 | 
						uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
 | 
				
			||||||
 | 
						return !!interrupt_status;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::evaluate_cb2_output() {
 | 
				
			||||||
 | 
						// CB2 is a special case, being both the line the shift register can output to,
 | 
				
			||||||
 | 
						// and one that can be used as an input or handshaking output according to the
 | 
				
			||||||
 | 
						// peripheral control register.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// My guess: other CB2 functions work only if the shift register is disabled (?).
 | 
				
			||||||
 | 
						if(shift_mode() != ShiftMode::Disabled) {
 | 
				
			||||||
 | 
							// Shift register is enabled, one way or the other; but announce only output.
 | 
				
			||||||
 | 
							if(is_shifting_out()) {
 | 
				
			||||||
 | 
								// Output mode; set the level according to the current top of the shift register.
 | 
				
			||||||
 | 
								bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80));
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Input mode.
 | 
				
			||||||
 | 
								bus_handler_.set_control_line_output(Port::B, Line::Two, true);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Shift register is disabled.
 | 
				
			||||||
 | 
							bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) {
 | 
				
			||||||
 | 
						if(port == Port::B && line == Line::Two) {
 | 
				
			||||||
 | 
							control_outputs_[port].lines[line] = value;
 | 
				
			||||||
 | 
							evaluate_cb2_output();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Do nothing if unchanged.
 | 
				
			||||||
 | 
							if(value == control_outputs_[port].lines[line]) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							control_outputs_[port].lines[line] = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(value != LineState::Input) {
 | 
				
			||||||
 | 
								bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
 | 
								bus_handler_.set_control_line_output(port, line, value != LineState::Off);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::shift_in() {
 | 
				
			||||||
 | 
						registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0));
 | 
				
			||||||
 | 
						--shift_bits_remaining_;
 | 
				
			||||||
 | 
						if(!shift_bits_remaining_) {
 | 
				
			||||||
 | 
							registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
 | 
				
			||||||
 | 
							reevaluate_interrupts();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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));
 | 
				
			||||||
 | 
							evaluate_cb2_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							--shift_bits_remaining_;
 | 
				
			||||||
 | 
							if(!shift_bits_remaining_) {
 | 
				
			||||||
 | 
								registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
 | 
				
			||||||
 | 
								reevaluate_interrupts();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ class MOS6522Storage {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// The registers
 | 
							// The registers
 | 
				
			||||||
		struct Registers {
 | 
							struct Registers {
 | 
				
			||||||
			// "A  low  reset  (RES)  input  clears  all  R6522  internal registers to logic 0"
 | 
								// "A low reset (RES) input clears all R6522 internal registers to logic 0"
 | 
				
			||||||
			uint8_t output[2] = {0, 0};
 | 
								uint8_t output[2] = {0, 0};
 | 
				
			||||||
			uint8_t input[2] = {0, 0};
 | 
								uint8_t input[2] = {0, 0};
 | 
				
			||||||
			uint8_t data_direction[2] = {0, 0};
 | 
								uint8_t data_direction[2] = {0, 0};
 | 
				
			||||||
@@ -37,14 +37,27 @@ class MOS6522Storage {
 | 
				
			|||||||
			bool timer_needs_reload = false;
 | 
								bool timer_needs_reload = false;
 | 
				
			||||||
		} registers_;
 | 
							} registers_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// control state
 | 
							// Control state.
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
			bool line_one = false;
 | 
								bool lines[2] = {false, false};
 | 
				
			||||||
			bool line_two = false;
 | 
					 | 
				
			||||||
		} control_inputs_[2];
 | 
							} control_inputs_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class LineState {
 | 
				
			||||||
 | 
								On, Off, Input
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							struct {
 | 
				
			||||||
 | 
								LineState lines[2] = {LineState::Input, LineState::Input};
 | 
				
			||||||
 | 
							} control_outputs_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class HandshakeMode {
 | 
				
			||||||
 | 
								None,
 | 
				
			||||||
 | 
								Handshake,
 | 
				
			||||||
 | 
								Pulse
 | 
				
			||||||
 | 
							} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool timer_is_running_[2] = {false, false};
 | 
							bool timer_is_running_[2] = {false, false};
 | 
				
			||||||
		bool last_posted_interrupt_status_ = false;
 | 
							bool last_posted_interrupt_status_ = false;
 | 
				
			||||||
 | 
							int shift_bits_remaining_ = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		enum InterruptFlag: uint8_t {
 | 
							enum InterruptFlag: uint8_t {
 | 
				
			||||||
			CA2ActiveEdge	= 1 << 0,
 | 
								CA2ActiveEdge	= 1 << 0,
 | 
				
			||||||
@@ -55,6 +68,23 @@ class MOS6522Storage {
 | 
				
			|||||||
			Timer2			= 1 << 5,
 | 
								Timer2			= 1 << 5,
 | 
				
			||||||
			Timer1			= 1 << 6,
 | 
								Timer1			= 1 << 6,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class ShiftMode {
 | 
				
			||||||
 | 
								Disabled = 0,
 | 
				
			||||||
 | 
								InUnderT2 = 1,
 | 
				
			||||||
 | 
								InUnderPhase2 = 2,
 | 
				
			||||||
 | 
								InUnderCB1 = 3,
 | 
				
			||||||
 | 
								OutUnderT2FreeRunning = 4,
 | 
				
			||||||
 | 
								OutUnderT2 = 5,
 | 
				
			||||||
 | 
								OutUnderPhase2 = 6,
 | 
				
			||||||
 | 
								OutUnderCB1 = 7
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							ShiftMode shift_mode() const {
 | 
				
			||||||
 | 
								return ShiftMode((registers_.auxiliary_control >> 2) & 7);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bool is_shifting_out() const {
 | 
				
			||||||
 | 
								return registers_.auxiliary_control & 0x10;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void run_for(const Cycles cycles) {
 | 
							inline void run_for(const Cycles cycles) {
 | 
				
			||||||
			unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
 | 
								unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
 | 
								// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
 | 
				
			||||||
			if(timer_.value >= number_of_cycles) {
 | 
								if(timer_.value >= number_of_cycles) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
	private:
 | 
						private:
 | 
				
			||||||
		Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 | 
							Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels
 | 
							unsigned int counters_[4] = {2, 1, 0, 0};	// create a slight phase offset for the three channels
 | 
				
			||||||
		unsigned int shift_registers_[4] = {0, 0, 0, 0};
 | 
							unsigned int shift_registers_[4] = {0, 0, 0, 0};
 | 
				
			||||||
		uint8_t control_registers_[4] = {0, 0, 0, 0};
 | 
							uint8_t control_registers_[4] = {0, 0, 0, 0};
 | 
				
			||||||
		int16_t volume_ = 0;
 | 
							int16_t volume_ = 0;
 | 
				
			||||||
@@ -64,23 +64,12 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		MOS6560(BusHandler &bus_handler) :
 | 
							MOS6560(BusHandler &bus_handler) :
 | 
				
			||||||
				bus_handler_(bus_handler),
 | 
									bus_handler_(bus_handler),
 | 
				
			||||||
				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
 | 
									crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
 | 
				
			||||||
				audio_generator_(audio_queue_),
 | 
									audio_generator_(audio_queue_),
 | 
				
			||||||
				speaker_(audio_generator_)
 | 
									speaker_(audio_generator_)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			crt_->set_svideo_sampling_function(
 | 
					 | 
				
			||||||
				"vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)"
 | 
					 | 
				
			||||||
				"{"
 | 
					 | 
				
			||||||
					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
 | 
					 | 
				
			||||||
					"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					"return vec2(yc.x, chroma);"
 | 
					 | 
				
			||||||
				"}");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// default to s-video output
 | 
								// default to s-video output
 | 
				
			||||||
			crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
 | 
								crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// default to NTSC
 | 
								// default to NTSC
 | 
				
			||||||
			set_output_mode(OutputMode::NTSC);
 | 
								set_output_mode(OutputMode::NTSC);
 | 
				
			||||||
@@ -94,7 +83,8 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
 | 
								speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Outputs::CRT::CRT *get_crt() { return crt_.get(); }
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target)		{ crt_.set_scan_target(scan_target); 	}
 | 
				
			||||||
 | 
							void set_display_type(Outputs::Display::DisplayType display_type)	{ crt_.set_display_type(display_type); 	}
 | 
				
			||||||
		Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
 | 
							Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_high_frequency_cutoff(float cutoff) {
 | 
							void set_high_frequency_cutoff(float cutoff) {
 | 
				
			||||||
@@ -117,12 +107,12 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			// Chrominances are encoded such that 0-128 is a complete revolution of phase;
 | 
								// Chrominances are encoded such that 0-128 is a complete revolution of phase;
 | 
				
			||||||
			// anything above 191 disables the colour subcarrier. Phase is relative to the
 | 
								// anything above 191 disables the colour subcarrier. Phase is relative to the
 | 
				
			||||||
			// colour burst, so 0 is green.
 | 
								// colour burst, so 0 is green (NTSC) or blue/violet (PAL).
 | 
				
			||||||
			const uint8_t pal_chrominances[16] = {
 | 
								const uint8_t pal_chrominances[16] = {
 | 
				
			||||||
				255,	255,	37,		101,
 | 
									255,	255,	90,		20,
 | 
				
			||||||
				19,		86,		123,	59,
 | 
									96,		42,		8,		72,
 | 
				
			||||||
				46,		53,		37,		101,
 | 
									84,		90,		90,		20,
 | 
				
			||||||
				19,		86,		123,	59,
 | 
									96,		42,		8,		72,
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			const uint8_t ntsc_chrominances[16] = {
 | 
								const uint8_t ntsc_chrominances[16] = {
 | 
				
			||||||
				255,	255,	121,	57,
 | 
									255,	255,	121,	57,
 | 
				
			||||||
@@ -131,12 +121,12 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
				103,	42,		80,		16,
 | 
									103,	42,		80,		16,
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			const uint8_t *chrominances;
 | 
								const uint8_t *chrominances;
 | 
				
			||||||
			Outputs::CRT::DisplayType display_type;
 | 
								Outputs::Display::Type display_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch(output_mode) {
 | 
								switch(output_mode) {
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
					chrominances = pal_chrominances;
 | 
										chrominances = pal_chrominances;
 | 
				
			||||||
					display_type = Outputs::CRT::DisplayType::PAL50;
 | 
										display_type = Outputs::Display::Type::PAL50;
 | 
				
			||||||
					timing_.cycles_per_line = 71;
 | 
										timing_.cycles_per_line = 71;
 | 
				
			||||||
					timing_.line_counter_increment_offset = 4;
 | 
										timing_.line_counter_increment_offset = 4;
 | 
				
			||||||
					timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
 | 
										timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
 | 
				
			||||||
@@ -146,7 +136,7 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				case OutputMode::NTSC:
 | 
									case OutputMode::NTSC:
 | 
				
			||||||
					chrominances = ntsc_chrominances;
 | 
										chrominances = ntsc_chrominances;
 | 
				
			||||||
					display_type = Outputs::CRT::DisplayType::NTSC60;
 | 
										display_type = Outputs::Display::Type::NTSC60;
 | 
				
			||||||
					timing_.cycles_per_line = 65;
 | 
										timing_.cycles_per_line = 65;
 | 
				
			||||||
					timing_.line_counter_increment_offset = 40;
 | 
										timing_.line_counter_increment_offset = 40;
 | 
				
			||||||
					timing_.final_line_increment_position = 58;
 | 
										timing_.final_line_increment_position = 58;
 | 
				
			||||||
@@ -155,14 +145,14 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
 | 
								crt_.set_new_display_type(timing_.cycles_per_line*4, display_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch(output_mode) {
 | 
								switch(output_mode) {
 | 
				
			||||||
				case OutputMode::PAL:
 | 
									case OutputMode::PAL:
 | 
				
			||||||
					crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f));
 | 
										crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case OutputMode::NTSC:
 | 
									case OutputMode::NTSC:
 | 
				
			||||||
					crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
 | 
										crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -180,7 +170,7 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
			// keep track of the amount of time since the speaker was updated; lazy updates are applied
 | 
								// keep track of the amount of time since the speaker was updated; lazy updates are applied
 | 
				
			||||||
			cycles_since_speaker_update_ += cycles;
 | 
								cycles_since_speaker_update_ += cycles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			int number_of_cycles = cycles.as_int();
 | 
								auto number_of_cycles = cycles.as_integral();
 | 
				
			||||||
			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
 | 
									// 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_;
 | 
									int previous_vertical_counter = vertical_counter_;
 | 
				
			||||||
@@ -284,17 +274,17 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
				// update the CRT
 | 
									// update the CRT
 | 
				
			||||||
				if(this_state_ != output_state_) {
 | 
									if(this_state_ != output_state_) {
 | 
				
			||||||
					switch(output_state_) {
 | 
										switch(output_state_) {
 | 
				
			||||||
						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break;
 | 
											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);		break;
 | 
											case State::ColourBurst:	crt_.output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0);	break;
 | 
				
			||||||
						case State::Border:			output_border(cycles_in_state_ * 4);															break;
 | 
											case State::Border:			output_border(cycles_in_state_ * 4);														break;
 | 
				
			||||||
						case State::Pixels:			crt_->output_data(cycles_in_state_ * 4);														break;
 | 
											case State::Pixels:			crt_.output_data(cycles_in_state_ * 4);														break;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					output_state_ = this_state_;
 | 
										output_state_ = this_state_;
 | 
				
			||||||
					cycles_in_state_ = 0;
 | 
										cycles_in_state_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					pixel_pointer = nullptr;
 | 
										pixel_pointer = nullptr;
 | 
				
			||||||
					if(output_state_ == State::Pixels) {
 | 
										if(output_state_ == State::Pixels) {
 | 
				
			||||||
						pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
 | 
											pixel_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(260));
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				cycles_in_state_++;
 | 
									cycles_in_state_++;
 | 
				
			||||||
@@ -438,7 +428,7 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		BusHandler &bus_handler_;
 | 
							BusHandler &bus_handler_;
 | 
				
			||||||
		std::unique_ptr<Outputs::CRT::CRT> crt_;
 | 
							Outputs::CRT::CRT crt_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Concurrency::DeferringAsyncTaskQueue audio_queue_;
 | 
							Concurrency::DeferringAsyncTaskQueue audio_queue_;
 | 
				
			||||||
		AudioGenerator audio_generator_;
 | 
							AudioGenerator audio_generator_;
 | 
				
			||||||
@@ -465,7 +455,7 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
		enum State {
 | 
							enum State {
 | 
				
			||||||
			Sync, ColourBurst, Border, Pixels
 | 
								Sync, ColourBurst, Border, Pixels
 | 
				
			||||||
		} this_state_, output_state_;
 | 
							} this_state_, output_state_;
 | 
				
			||||||
		unsigned int cycles_in_state_;
 | 
							int cycles_in_state_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// counters that cover an entire field
 | 
							// counters that cover an entire field
 | 
				
			||||||
		int horizontal_counter_ = 0, vertical_counter_ = 0;
 | 
							int horizontal_counter_ = 0, vertical_counter_ = 0;
 | 
				
			||||||
@@ -511,10 +501,10 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
		uint16_t colours_[16];
 | 
							uint16_t colours_[16];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint16_t *pixel_pointer;
 | 
							uint16_t *pixel_pointer;
 | 
				
			||||||
		void output_border(unsigned int number_of_cycles) {
 | 
							void output_border(int number_of_cycles) {
 | 
				
			||||||
			uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
 | 
								uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
 | 
				
			||||||
			if(colour_pointer) *colour_pointer = registers_.borderColour;
 | 
								if(colour_pointer) *colour_pointer = registers_.borderColour;
 | 
				
			||||||
			crt_->output_level(number_of_cycles);
 | 
								crt_.output_level(number_of_cycles);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -111,7 +111,7 @@ template <class T> class CRTC6845 {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void run_for(Cycles cycles) {
 | 
							void run_for(Cycles cycles) {
 | 
				
			||||||
			int cyles_remaining = cycles.as_int();
 | 
								auto cyles_remaining = cycles.as_integral();
 | 
				
			||||||
			while(cyles_remaining--) {
 | 
								while(cyles_remaining--) {
 | 
				
			||||||
				// check for end of visible characters
 | 
									// check for end of visible characters
 | 
				
			||||||
				if(character_counter_ == registers_[1]) {
 | 
									if(character_counter_ == registers_[1]) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										228
									
								
								Components/6850/6850.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								Components/6850/6850.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  6850.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 10/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "6850.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Motorola::ACIA;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const HalfCycles ACIA::SameAsTransmit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
 | 
				
			||||||
 | 
						transmit_clock_rate_(transmit_clock_rate),
 | 
				
			||||||
 | 
						receive_clock_rate_((receive_clock_rate != SameAsTransmit) ? receive_clock_rate : transmit_clock_rate) {
 | 
				
			||||||
 | 
						transmit.set_writer_clock_rate(transmit_clock_rate);
 | 
				
			||||||
 | 
						request_to_send.set_writer_clock_rate(transmit_clock_rate);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t ACIA::read(int address) {
 | 
				
			||||||
 | 
						if(address&1) {
 | 
				
			||||||
 | 
							overran_ = false;
 | 
				
			||||||
 | 
							received_data_ |= NoValueMask;
 | 
				
			||||||
 | 
							update_interrupt_line();
 | 
				
			||||||
 | 
							return uint8_t(received_data_);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return get_status();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ACIA::reset() {
 | 
				
			||||||
 | 
						transmit.reset_writing();
 | 
				
			||||||
 | 
						transmit.write(true);
 | 
				
			||||||
 | 
						request_to_send.reset_writing();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bits_received_ = bits_incoming_ = 0;
 | 
				
			||||||
 | 
						receive_interrupt_enabled_ = transmit_interrupt_enabled_ = false;
 | 
				
			||||||
 | 
						overran_ = false;
 | 
				
			||||||
 | 
						next_transmission_ = received_data_ = NoValueMask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						update_interrupt_line();
 | 
				
			||||||
 | 
						assert(!interrupt_line_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ACIA::write(int address, uint8_t value) {
 | 
				
			||||||
 | 
						if(address&1) {
 | 
				
			||||||
 | 
							next_transmission_ = value;
 | 
				
			||||||
 | 
							consider_transmission();
 | 
				
			||||||
 | 
							update_interrupt_line();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if((value&3) == 3) {
 | 
				
			||||||
 | 
								reset();
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								switch(value & 3) {
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									case 0: divider_ = 1;	break;
 | 
				
			||||||
 | 
									case 1: divider_ = 16;	break;
 | 
				
			||||||
 | 
									case 2: divider_ = 64;	break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								switch((value >> 2) & 7) {
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									case 0:	data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even;	break;
 | 
				
			||||||
 | 
									case 1:	data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd;	break;
 | 
				
			||||||
 | 
									case 2:	data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even;	break;
 | 
				
			||||||
 | 
									case 3:	data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd;	break;
 | 
				
			||||||
 | 
									case 4:	data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None;	break;
 | 
				
			||||||
 | 
									case 5:	data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None;	break;
 | 
				
			||||||
 | 
									case 6:	data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even;	break;
 | 
				
			||||||
 | 
									case 7:	data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd;	break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								switch((value >> 5) & 3) {
 | 
				
			||||||
 | 
									case 0:	request_to_send.write(false); transmit_interrupt_enabled_ = false;	break;
 | 
				
			||||||
 | 
									case 1:	request_to_send.write(false); transmit_interrupt_enabled_ = true;	break;
 | 
				
			||||||
 | 
									case 2:	request_to_send.write(true); transmit_interrupt_enabled_ = false;	break;
 | 
				
			||||||
 | 
									case 3:
 | 
				
			||||||
 | 
										request_to_send.write(false);
 | 
				
			||||||
 | 
										transmit_interrupt_enabled_ = false;
 | 
				
			||||||
 | 
										transmit.reset_writing();
 | 
				
			||||||
 | 
										transmit.write(false);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								receive.set_read_delegate(this, Storage::Time(divider_ * 2, int(receive_clock_rate_.as_integral())));
 | 
				
			||||||
 | 
								receive_interrupt_enabled_ = value & 0x80;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								update_interrupt_line();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						update_clocking_observer();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ACIA::consider_transmission() {
 | 
				
			||||||
 | 
						if(next_transmission_ != NoValueMask && !transmit.write_data_time_remaining()) {
 | 
				
			||||||
 | 
							// Establish start bit and [7 or 8] data bits.
 | 
				
			||||||
 | 
							if(data_bits_ == 7) next_transmission_ &= 0x7f;
 | 
				
			||||||
 | 
							int transmission = next_transmission_ << 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Add a parity bit, if any.
 | 
				
			||||||
 | 
							int mask = 0x2 << data_bits_;
 | 
				
			||||||
 | 
							if(parity_ != Parity::None) {
 | 
				
			||||||
 | 
								transmission |= parity(uint8_t(next_transmission_)) ? mask : 0;
 | 
				
			||||||
 | 
								mask <<= 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Add stop bits.
 | 
				
			||||||
 | 
							for(int c = 0; c < stop_bits_; ++c) {
 | 
				
			||||||
 | 
								transmission |= mask;
 | 
				
			||||||
 | 
								mask <<= 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Output all that.
 | 
				
			||||||
 | 
							const int total_bits = expected_bits();
 | 
				
			||||||
 | 
							transmit.write(divider_ * 2, total_bits, transmission);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Mark the transmit register as empty again.
 | 
				
			||||||
 | 
							next_transmission_ = NoValueMask;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClockingHint::Preference ACIA::preferred_clocking() {
 | 
				
			||||||
 | 
						// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
 | 
				
			||||||
 | 
						// is on the receiving end.
 | 
				
			||||||
 | 
						if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If a bit reception is ongoing that might lead to an interrupt, ask for real-time clocking
 | 
				
			||||||
 | 
						// because it's unclear when the interrupt might come.
 | 
				
			||||||
 | 
						if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// No clocking required then.
 | 
				
			||||||
 | 
						return ClockingHint::Preference::None;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ACIA::get_interrupt_line() const {
 | 
				
			||||||
 | 
						return interrupt_line_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ACIA::expected_bits() {
 | 
				
			||||||
 | 
						return 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t ACIA::parity(uint8_t value) {
 | 
				
			||||||
 | 
						value ^= value >> 4;
 | 
				
			||||||
 | 
						value ^= value >> 2;
 | 
				
			||||||
 | 
						value ^= value >> 1;
 | 
				
			||||||
 | 
						return value ^ (parity_ == Parity::Even);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
 | 
				
			||||||
 | 
						// Shift this bit into the 11-bit input register; this is big enough to hold
 | 
				
			||||||
 | 
						// the largest transmission symbol.
 | 
				
			||||||
 | 
						++bits_received_;
 | 
				
			||||||
 | 
						bits_incoming_ = (bits_incoming_ >> 1) | (bit << 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If that's the now-expected number of bits, update.
 | 
				
			||||||
 | 
						const int bit_target = expected_bits();
 | 
				
			||||||
 | 
						if(bits_received_ >= bit_target) {
 | 
				
			||||||
 | 
							bits_received_ = 0;
 | 
				
			||||||
 | 
							overran_ |= get_status() & 1;
 | 
				
			||||||
 | 
							received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target));
 | 
				
			||||||
 | 
							update_interrupt_line();
 | 
				
			||||||
 | 
							update_clocking_observer();
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: overrun, and parity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Keep receiving, and consider a potential clocking change.
 | 
				
			||||||
 | 
						if(bits_received_ == 1) update_clocking_observer();
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ACIA::set_interrupt_delegate(InterruptDelegate *delegate) {
 | 
				
			||||||
 | 
						interrupt_delegate_ = delegate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ACIA::update_interrupt_line() {
 | 
				
			||||||
 | 
						const bool old_line = interrupt_line_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
							"Bit 7 of the control register is the rie bit. When the rie bit is high, the rdrf, ndcd,
 | 
				
			||||||
 | 
							and ovr bits will assert the nirq output. When the rie bit is low, nirq generation is disabled."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rie = read interrupt enable
 | 
				
			||||||
 | 
							rdrf = receive data register full (status word bit 0)
 | 
				
			||||||
 | 
							ndcd = data carrier detect (status word bit 2)
 | 
				
			||||||
 | 
							over = receiver overrun (status word bit 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							"Bit 1 of the status register is the tdre bit. When high, the tdre bit indicates that data has been
 | 
				
			||||||
 | 
							transferred from the transmitter data register to the output shift register. At this point, the a6850
 | 
				
			||||||
 | 
							is ready to accept a new transmit data byte. However, if the ncts signal is high, the tdre bit remains
 | 
				
			||||||
 | 
							low regardless of the status of the transmitter data register. Also, if transmit interrupt is enabled,
 | 
				
			||||||
 | 
							the nirq output is asserted."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tdre = transmitter data register empty
 | 
				
			||||||
 | 
							ncts = clear to send
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						const auto status = get_status();
 | 
				
			||||||
 | 
						interrupt_line_ =
 | 
				
			||||||
 | 
							(receive_interrupt_enabled_ && (status & 0x25)) ||
 | 
				
			||||||
 | 
							(transmit_interrupt_enabled_ && (status & 0x02));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(interrupt_delegate_ && old_line != interrupt_line_) {
 | 
				
			||||||
 | 
							interrupt_delegate_->acia6850_did_change_interrupt_status(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t ACIA::get_status() {
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
							((received_data_ & NoValueMask) ? 0x00 : 0x01) |
 | 
				
			||||||
 | 
							((next_transmission_ == NoValueMask) ? 0x02 : 0x00) |
 | 
				
			||||||
 | 
					//		(data_carrier_detect.read() ? 0x04 : 0x00) |
 | 
				
			||||||
 | 
					//		(clear_to_send.read() ? 0x08 : 0x00) |
 | 
				
			||||||
 | 
							(overran_ ? 0x20 : 0x00) |
 | 
				
			||||||
 | 
							(interrupt_line_ ? 0x80 : 0x00)
 | 
				
			||||||
 | 
						;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// b0: receive data full.
 | 
				
			||||||
 | 
						// b1: transmit data empty.
 | 
				
			||||||
 | 
						// b2: DCD.
 | 
				
			||||||
 | 
						// b3: CTS.
 | 
				
			||||||
 | 
						// b4: framing error (i.e. no first stop bit where expected).
 | 
				
			||||||
 | 
						// b5: receiver overran.
 | 
				
			||||||
 | 
						// b6: parity error.
 | 
				
			||||||
 | 
						// b7: IRQ state.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								Components/6850/6850.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								Components/6850/6850.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  6850.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 10/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Motorola_ACIA_6850_hpp
 | 
				
			||||||
 | 
					#define Motorola_ACIA_6850_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ForceInline.hpp"
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockingHintSource.hpp"
 | 
				
			||||||
 | 
					#include "../Serial/Line.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Motorola {
 | 
				
			||||||
 | 
					namespace ACIA {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Constructs a new instance of ACIA which will receive a transmission clock at a rate of
 | 
				
			||||||
 | 
								@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Reads from the ACIA.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Bit 0 of the address is used as the ACIA's register select line —
 | 
				
			||||||
 | 
								so even addresses select control/status registers, odd addresses
 | 
				
			||||||
 | 
								select transmit/receive data registers.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							uint8_t read(int address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Writes to the ACIA.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Bit 0 of the address is used as the ACIA's register select line —
 | 
				
			||||||
 | 
								so even addresses select control/status registers, odd addresses
 | 
				
			||||||
 | 
								select transmit/receive data registers.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void write(int address, uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Advances @c transmission_cycles in time, which should be
 | 
				
			||||||
 | 
								counted relative to the @c transmit_clock_rate.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							forceinline void run_for(HalfCycles transmission_cycles) {
 | 
				
			||||||
 | 
								if(transmit.transmission_data_time_remaining() > HalfCycles(0)) {
 | 
				
			||||||
 | 
									const auto write_data_time_remaining = transmit.write_data_time_remaining();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// There's at most one further byte available to enqueue, so a single 'if'
 | 
				
			||||||
 | 
									// rather than a 'while' is correct here. It's the responsibilit of the caller
 | 
				
			||||||
 | 
									// to ensure run_for lengths are appropriate for longer sequences.
 | 
				
			||||||
 | 
									if(transmission_cycles >= write_data_time_remaining) {
 | 
				
			||||||
 | 
										if(next_transmission_ != NoValueMask) {
 | 
				
			||||||
 | 
											transmit.advance_writer(write_data_time_remaining);
 | 
				
			||||||
 | 
											consider_transmission();
 | 
				
			||||||
 | 
											transmit.advance_writer(transmission_cycles - write_data_time_remaining);
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											transmit.advance_writer(transmission_cycles);
 | 
				
			||||||
 | 
											update_clocking_observer();
 | 
				
			||||||
 | 
											update_interrupt_line();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										transmit.advance_writer(transmission_cycles);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool get_interrupt_line() const;
 | 
				
			||||||
 | 
							void reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Input lines.
 | 
				
			||||||
 | 
							Serial::Line receive;
 | 
				
			||||||
 | 
							Serial::Line clear_to_send;
 | 
				
			||||||
 | 
							Serial::Line data_carrier_detect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Output lines.
 | 
				
			||||||
 | 
							Serial::Line transmit;
 | 
				
			||||||
 | 
							Serial::Line request_to_send;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ClockingHint::Source.
 | 
				
			||||||
 | 
							ClockingHint::Preference preferred_clocking() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct InterruptDelegate {
 | 
				
			||||||
 | 
								virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							void set_interrupt_delegate(InterruptDelegate *delegate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							int divider_ = 1;
 | 
				
			||||||
 | 
							enum class Parity {
 | 
				
			||||||
 | 
								Even, Odd, None
 | 
				
			||||||
 | 
							} parity_ = Parity::None;
 | 
				
			||||||
 | 
							int data_bits_ = 7, stop_bits_ = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static constexpr int NoValueMask = 0x100;
 | 
				
			||||||
 | 
							int next_transmission_ = NoValueMask;
 | 
				
			||||||
 | 
							int received_data_ = NoValueMask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int bits_received_ = 0;
 | 
				
			||||||
 | 
							int bits_incoming_ = 0;
 | 
				
			||||||
 | 
							bool overran_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void consider_transmission();
 | 
				
			||||||
 | 
							int expected_bits();
 | 
				
			||||||
 | 
							uint8_t parity(uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool receive_interrupt_enabled_ = false;
 | 
				
			||||||
 | 
							bool transmit_interrupt_enabled_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							HalfCycles transmit_clock_rate_;
 | 
				
			||||||
 | 
							HalfCycles receive_clock_rate_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool interrupt_line_ = false;
 | 
				
			||||||
 | 
							void update_interrupt_line();
 | 
				
			||||||
 | 
							InterruptDelegate *interrupt_delegate_ = nullptr;
 | 
				
			||||||
 | 
							uint8_t get_status();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Motorola_ACIA_6850_hpp */
 | 
				
			||||||
							
								
								
									
										374
									
								
								Components/68901/MFP68901.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								Components/68901/MFP68901.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,374 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  MFP68901.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 06/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MFP68901.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef NDEBUG
 | 
				
			||||||
 | 
					#define NDEBUG
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define LOG_PREFIX "[MFP] "
 | 
				
			||||||
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Motorola::MFP68901;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClockingHint::Preference MFP68901::preferred_clocking() {
 | 
				
			||||||
 | 
						// Rule applied: if any timer is actively running and permitted to produce an
 | 
				
			||||||
 | 
						// interrupt, request real-time running.
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
							(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
 | 
				
			||||||
 | 
							(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
 | 
				
			||||||
 | 
							(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
 | 
				
			||||||
 | 
							(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
 | 
				
			||||||
 | 
								? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t MFP68901::read(int address) {
 | 
				
			||||||
 | 
						address &= 0x1f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Interrupt block: various bits of state can be read, all passively.
 | 
				
			||||||
 | 
						if(address >= 0x03 && address <= 0x0b) {
 | 
				
			||||||
 | 
							const int shift = (address&1) << 3;
 | 
				
			||||||
 | 
							switch(address) {
 | 
				
			||||||
 | 
								case 0x03:	case 0x04:	return uint8_t(interrupt_enable_ >> shift);
 | 
				
			||||||
 | 
								case 0x05:	case 0x06:	return uint8_t(interrupt_pending_ >> shift);
 | 
				
			||||||
 | 
								case 0x07:	case 0x08:	return uint8_t(interrupt_in_service_ >> shift);
 | 
				
			||||||
 | 
								case 0x09:	case 0x0a:	return uint8_t(interrupt_mask_ >> shift);
 | 
				
			||||||
 | 
								case 0x0b:	return interrupt_vector_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								default: break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(address) {
 | 
				
			||||||
 | 
							// GPIP block: input, and configured active edge and direction values.
 | 
				
			||||||
 | 
							case 0x00:	return (gpip_input_ & ~gpip_direction_) | (gpip_output_ & gpip_direction_);
 | 
				
			||||||
 | 
							case 0x01:	return gpip_active_edge_;
 | 
				
			||||||
 | 
							case 0x02:	return gpip_direction_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Interrupt block dealt with above. */
 | 
				
			||||||
 | 
							default: break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Timer block: read back A, B and C/D control, and read current timer values.
 | 
				
			||||||
 | 
							case 0x0c:	case 0x0d:	return timer_ab_control_[address - 0xc];
 | 
				
			||||||
 | 
							case 0x0e:				return timer_cd_control_;
 | 
				
			||||||
 | 
							case 0x0f:	case 0x10:
 | 
				
			||||||
 | 
							case 0x11:	case 0x12:	return get_timer_data(address - 0xf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// USART block: TODO.
 | 
				
			||||||
 | 
							case 0x13:		LOG("Read: sync character generator");	break;
 | 
				
			||||||
 | 
							case 0x14:		LOG("Read: USART control");				break;
 | 
				
			||||||
 | 
							case 0x15:		LOG("Read: receiver status");			break;
 | 
				
			||||||
 | 
							case 0x16:		LOG("Read: transmitter status");		break;
 | 
				
			||||||
 | 
							case 0x17:		LOG("Read: USART data");				break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0x00;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::write(int address, uint8_t value) {
 | 
				
			||||||
 | 
						address &= 0x1f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
 | 
				
			||||||
 | 
						if(address >= 0x03 && address <= 0x0b) {
 | 
				
			||||||
 | 
							const int shift = (address&1) << 3;
 | 
				
			||||||
 | 
							const int preserve = 0xff00 >> shift;
 | 
				
			||||||
 | 
							const int word_value = value << shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch(address) {
 | 
				
			||||||
 | 
								default: break;
 | 
				
			||||||
 | 
								case 0x03: case 0x04:	// Adjust enabled interrupts; disabled ones also cease to be pending.
 | 
				
			||||||
 | 
									interrupt_enable_ = (interrupt_enable_ & preserve) | word_value;
 | 
				
			||||||
 | 
									interrupt_pending_ &= interrupt_enable_;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
								case 0x05: case 0x06:	// Resolve pending interrupts.
 | 
				
			||||||
 | 
									interrupt_pending_ &= (preserve | word_value);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
								case 0x07: case 0x08:	// Resolve in-service interrupts.
 | 
				
			||||||
 | 
									interrupt_in_service_ &= (preserve | word_value);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
								case 0x09: case 0x0a:	// Adjust interrupt mask.
 | 
				
			||||||
 | 
									interrupt_mask_ = (interrupt_mask_ & preserve) | word_value;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
								case 0x0b:				// Set the interrupt vector, possibly changing end-of-interrupt mode.
 | 
				
			||||||
 | 
									interrupt_vector_ = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// If automatic end-of-interrupt mode has now been enabled, clear
 | 
				
			||||||
 | 
									// the in-process mask and re-evaluate.
 | 
				
			||||||
 | 
									if(interrupt_vector_ & 0x08) return;
 | 
				
			||||||
 | 
									interrupt_in_service_ = 0;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Whatever just happened may have affected the state of the interrupt line.
 | 
				
			||||||
 | 
							update_interrupts();
 | 
				
			||||||
 | 
							update_clocking_observer();
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constexpr int timer_prescales[] = {
 | 
				
			||||||
 | 
							1, 4, 10, 16, 50, 64, 100, 200
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(address) {
 | 
				
			||||||
 | 
							// GPIP block: output and configuration of active edge and direction values.
 | 
				
			||||||
 | 
							case 0x00:
 | 
				
			||||||
 | 
								gpip_output_ = value;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
							case 0x01:
 | 
				
			||||||
 | 
								gpip_active_edge_ = value;
 | 
				
			||||||
 | 
								reevaluate_gpip_interrupts();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
							case 0x02:
 | 
				
			||||||
 | 
								gpip_direction_ = value;
 | 
				
			||||||
 | 
								reevaluate_gpip_interrupts();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Interrupt block dealt with above. */
 | 
				
			||||||
 | 
							default: break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Timer block.
 | 
				
			||||||
 | 
							case 0x0c:
 | 
				
			||||||
 | 
							case 0x0d: {
 | 
				
			||||||
 | 
								const auto timer = address - 0xc;
 | 
				
			||||||
 | 
								const bool reset = value & 0x10;
 | 
				
			||||||
 | 
								timer_ab_control_[timer] = value;
 | 
				
			||||||
 | 
								switch(value & 0xf) {
 | 
				
			||||||
 | 
									case 0x0:	set_timer_mode(timer, TimerMode::Stopped, 1, reset);		break;
 | 
				
			||||||
 | 
									case 0x1:	set_timer_mode(timer, TimerMode::Delay, 4, reset);			break;
 | 
				
			||||||
 | 
									case 0x2:	set_timer_mode(timer, TimerMode::Delay, 10, reset);			break;
 | 
				
			||||||
 | 
									case 0x3:	set_timer_mode(timer, TimerMode::Delay, 16, reset);			break;
 | 
				
			||||||
 | 
									case 0x4:	set_timer_mode(timer, TimerMode::Delay, 50, reset);			break;
 | 
				
			||||||
 | 
									case 0x5:	set_timer_mode(timer, TimerMode::Delay, 64, reset);			break;
 | 
				
			||||||
 | 
									case 0x6:	set_timer_mode(timer, TimerMode::Delay, 100, reset);		break;
 | 
				
			||||||
 | 
									case 0x7:	set_timer_mode(timer, TimerMode::Delay, 200, reset);		break;
 | 
				
			||||||
 | 
									case 0x8:	set_timer_mode(timer, TimerMode::EventCount, 1, reset);		break;
 | 
				
			||||||
 | 
									case 0x9:	set_timer_mode(timer, TimerMode::PulseWidth, 4, reset);		break;
 | 
				
			||||||
 | 
									case 0xa:	set_timer_mode(timer, TimerMode::PulseWidth, 10, reset);	break;
 | 
				
			||||||
 | 
									case 0xb:	set_timer_mode(timer, TimerMode::PulseWidth, 16, reset);	break;
 | 
				
			||||||
 | 
									case 0xc:	set_timer_mode(timer, TimerMode::PulseWidth, 50, reset);	break;
 | 
				
			||||||
 | 
									case 0xd:	set_timer_mode(timer, TimerMode::PulseWidth, 64, reset);	break;
 | 
				
			||||||
 | 
									case 0xe:	set_timer_mode(timer, TimerMode::PulseWidth, 100, reset);	break;
 | 
				
			||||||
 | 
									case 0xf:	set_timer_mode(timer, TimerMode::PulseWidth, 200, reset);	break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} break;
 | 
				
			||||||
 | 
							case 0x0e:
 | 
				
			||||||
 | 
								timer_cd_control_ = value;
 | 
				
			||||||
 | 
								set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
 | 
				
			||||||
 | 
								set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
							case 0x0f:	case 0x10:	case 0x11:	case 0x12:
 | 
				
			||||||
 | 
								set_timer_data(address - 0xf, value);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// USART block: TODO.
 | 
				
			||||||
 | 
							case 0x13:		LOG("Write: sync character generator");	break;
 | 
				
			||||||
 | 
							case 0x14:		LOG("Write: USART control");			break;
 | 
				
			||||||
 | 
							case 0x15:		LOG("Write: receiver status");			break;
 | 
				
			||||||
 | 
							case 0x16:		LOG("Write: transmitter status");		break;
 | 
				
			||||||
 | 
							case 0x17:		LOG("Write: USART data");				break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						update_clocking_observer();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::run_for(HalfCycles time) {
 | 
				
			||||||
 | 
						cycles_left_ += time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
 | 
				
			||||||
 | 
						if(!cycles) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for(int c = 0; c < 4; ++c) {
 | 
				
			||||||
 | 
							if(timers_[c].mode >= TimerMode::Delay) {
 | 
				
			||||||
 | 
								// This code applies the timer prescaling only. prescale_count is used to count
 | 
				
			||||||
 | 
								// upwards rather than downwards for simplicity, but on the real hardware it's
 | 
				
			||||||
 | 
								// pretty safe to assume it actually counted downwards. So the clamp to 0 is
 | 
				
			||||||
 | 
								// because gymnastics may need to occur when the prescale value is altered, e.g.
 | 
				
			||||||
 | 
								// if a prescale of 256 is set and the prescale_count is currently 2 then the
 | 
				
			||||||
 | 
								// counter should roll over in 254 cycles. If the user at that point changes the
 | 
				
			||||||
 | 
								// prescale_count to 1 then the counter will need to be altered to -253 and
 | 
				
			||||||
 | 
								// allowed to keep counting up until it crosses both 0 and 1.
 | 
				
			||||||
 | 
								const int dividend = timers_[c].prescale_count + cycles;
 | 
				
			||||||
 | 
								const int decrements = std::max(dividend / timers_[c].prescale, 0);
 | 
				
			||||||
 | 
								if(decrements) {
 | 
				
			||||||
 | 
									decrement_timer(c, decrements);
 | 
				
			||||||
 | 
									timers_[c].prescale_count = dividend % timers_[c].prescale;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									timers_[c].prescale_count += cycles;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HalfCycles MFP68901::get_next_sequence_point() {
 | 
				
			||||||
 | 
						return HalfCycles(-1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Timers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
 | 
				
			||||||
 | 
						LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
 | 
				
			||||||
 | 
						timers_[timer].mode = mode;
 | 
				
			||||||
 | 
						if(reset_timer) {
 | 
				
			||||||
 | 
							timers_[timer].prescale_count = 0;
 | 
				
			||||||
 | 
							timers_[timer].value = timers_[timer].reload_value;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in
 | 
				
			||||||
 | 
							// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the
 | 
				
			||||||
 | 
							// old cycle should be allowed naturally to expire.
 | 
				
			||||||
 | 
							timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timers_[timer].prescale = prescale;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::set_timer_data(int timer, uint8_t value) {
 | 
				
			||||||
 | 
						if(timers_[timer].mode == TimerMode::Stopped) {
 | 
				
			||||||
 | 
							timers_[timer].value = value;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						timers_[timer].reload_value = value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t MFP68901::get_timer_data(int timer) {
 | 
				
			||||||
 | 
						return timers_[timer].value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::set_timer_event_input(int channel, bool value) {
 | 
				
			||||||
 | 
						if(timers_[channel].event_input == value) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timers_[channel].event_input = value;
 | 
				
			||||||
 | 
						if(timers_[channel].mode == TimerMode::EventCount && (value == !!(gpip_active_edge_ & (0x10 >> channel)))) {
 | 
				
			||||||
 | 
							// "The active state of the signal on TAI or TBI is dependent upon the associated
 | 
				
			||||||
 | 
							// Interrupt Channel’s edge bit (GPIP 4 for TAI and GPIP 3 for TBI [...] ).
 | 
				
			||||||
 | 
							// If the edge bit associated with the TAI or TBI input is a one, it will be active high.
 | 
				
			||||||
 | 
							decrement_timer(channel, 1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO:
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Altering the edge bit while the timer is in the event count mode can produce a count pulse.
 | 
				
			||||||
 | 
						// The interrupt channel associated with the input (I3 for I4 for TAI) is allowed to function normally.
 | 
				
			||||||
 | 
						// To count transitions reliably, the input must remain in each state (1/O) for a length of time equal
 | 
				
			||||||
 | 
						// to four periods of the timer clock.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// (the final bit probably explains 13 cycles of the DE to interrupt latency; not sure about the other ~15)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::decrement_timer(int timer, int amount) {
 | 
				
			||||||
 | 
						while(amount--) {
 | 
				
			||||||
 | 
							--timers_[timer].value;
 | 
				
			||||||
 | 
							if(timers_[timer].value < 1) {
 | 
				
			||||||
 | 
								switch(timer) {
 | 
				
			||||||
 | 
									case 0: begin_interrupts(Interrupt::TimerA);	break;
 | 
				
			||||||
 | 
									case 1: begin_interrupts(Interrupt::TimerB);	break;
 | 
				
			||||||
 | 
									case 2: begin_interrupts(Interrupt::TimerC);	break;
 | 
				
			||||||
 | 
									case 3: begin_interrupts(Interrupt::TimerD);	break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Re: reloading when in event counting mode; I found the data sheet thoroughly unclear on
 | 
				
			||||||
 | 
								// this, but it appears empirically to be correct. See e.g. Pompey Pirates menu 27.
 | 
				
			||||||
 | 
								if(timers_[timer].mode == TimerMode::Delay || timers_[timer].mode == TimerMode::EventCount) {
 | 
				
			||||||
 | 
									timers_[timer].value += timers_[timer].reload_value;	// TODO: properly.
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - GPIP
 | 
				
			||||||
 | 
					void MFP68901::set_port_input(uint8_t input) {
 | 
				
			||||||
 | 
						gpip_input_ = input;
 | 
				
			||||||
 | 
						reevaluate_gpip_interrupts();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t MFP68901::get_port_output() {
 | 
				
			||||||
 | 
						return 0xff;	// TODO.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::reevaluate_gpip_interrupts() {
 | 
				
			||||||
 | 
						const uint8_t gpip_state = (gpip_input_ & ~gpip_direction_) ^ gpip_active_edge_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// An interrupt is detected on any falling edge.
 | 
				
			||||||
 | 
						const uint8_t new_interrupt_mask = (gpip_state ^ gpip_interrupt_state_) & gpip_interrupt_state_;
 | 
				
			||||||
 | 
						if(new_interrupt_mask) {
 | 
				
			||||||
 | 
							begin_interrupts(
 | 
				
			||||||
 | 
								(new_interrupt_mask & 0x0f) |
 | 
				
			||||||
 | 
								((new_interrupt_mask & 0x30) << 2) |
 | 
				
			||||||
 | 
								((new_interrupt_mask & 0xc0) << 8)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						gpip_interrupt_state_ = gpip_state;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Interrupts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::begin_interrupts(int interrupt) {
 | 
				
			||||||
 | 
						interrupt_pending_ |= interrupt & interrupt_enable_;
 | 
				
			||||||
 | 
						update_interrupts();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::end_interrupts(int interrupt) {
 | 
				
			||||||
 | 
						interrupt_pending_ &= ~interrupt;
 | 
				
			||||||
 | 
						update_interrupts();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::update_interrupts() {
 | 
				
			||||||
 | 
						const auto old_interrupt_line = interrupt_line_;
 | 
				
			||||||
 | 
						const auto firing_interrupts = interrupt_pending_ & interrupt_mask_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(!firing_interrupts) {
 | 
				
			||||||
 | 
							interrupt_line_ = false;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if(interrupt_vector_ & 0x8) {
 | 
				
			||||||
 | 
								// Software interrupt mode: permit only if neither this interrupt
 | 
				
			||||||
 | 
								// nor a higher interrupt is currently in service.
 | 
				
			||||||
 | 
								const int highest_bit = msb16(firing_interrupts);
 | 
				
			||||||
 | 
								interrupt_line_ = !(interrupt_in_service_ & ~(highest_bit + highest_bit - 1));
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Auto-interrupt mode; just signal.
 | 
				
			||||||
 | 
								interrupt_line_ = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the delegate if necessary.
 | 
				
			||||||
 | 
						if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) {
 | 
				
			||||||
 | 
							interrupt_delegate_->mfp68901_did_change_interrupt_status(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool MFP68901::get_interrupt_line() {
 | 
				
			||||||
 | 
						return interrupt_line_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int MFP68901::acknowledge_interrupt() {
 | 
				
			||||||
 | 
						if(!(interrupt_pending_ & interrupt_mask_)) {
 | 
				
			||||||
 | 
							return NoAcknowledgement;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const int mask = msb16(interrupt_pending_ & interrupt_mask_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Clear the pending bit regardless.
 | 
				
			||||||
 | 
						interrupt_pending_ &= ~mask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If this is software interrupt mode, set the in-service bit.
 | 
				
			||||||
 | 
						if(interrupt_vector_ & 0x8) {
 | 
				
			||||||
 | 
							interrupt_in_service_ |= mask;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						update_interrupts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int selected = 0;
 | 
				
			||||||
 | 
						while((1 << selected) != mask) ++selected;
 | 
				
			||||||
 | 
					//	LOG("Interrupt acknowledged: " << selected);
 | 
				
			||||||
 | 
						return (interrupt_vector_ & 0xf0) | uint8_t(selected);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
 | 
				
			||||||
 | 
						interrupt_delegate_ = delegate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										187
									
								
								Components/68901/MFP68901.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								Components/68901/MFP68901.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  MFP68901.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 06/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef MFP68901_hpp
 | 
				
			||||||
 | 
					#define MFP68901_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockingHintSource.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Motorola {
 | 
				
			||||||
 | 
					namespace MFP68901 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PortHandler {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							// TODO: announce changes in output.
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models the Motorola 68901 Multi-Function Peripheral ('MFP').
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class MFP68901: public ClockingHint::Source {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/// @returns the result of a read from @c address.
 | 
				
			||||||
 | 
							uint8_t read(int address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Performs a write of @c value to @c address.
 | 
				
			||||||
 | 
							void write(int address, uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Advances the MFP by the supplied number of HalfCycles.
 | 
				
			||||||
 | 
							void run_for(HalfCycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the number of cycles until the next possible sequence point — the next time
 | 
				
			||||||
 | 
							/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
 | 
				
			||||||
 | 
							/// so that mechanism can also be used to reduce the quantity of calls into this class.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// @discussion TODO, alas.
 | 
				
			||||||
 | 
							HalfCycles get_next_sequence_point();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
 | 
				
			||||||
 | 
							void set_timer_event_input(int channel, bool value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// @discussion TODO.
 | 
				
			||||||
 | 
							void set_port_handler(PortHandler *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets the current input GPIP values.
 | 
				
			||||||
 | 
							void set_port_input(uint8_t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the current GPIP output values.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// @discussion TODO.
 | 
				
			||||||
 | 
							uint8_t get_port_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
 | 
				
			||||||
 | 
							bool get_interrupt_line();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static constexpr int NoAcknowledgement = 0x100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Communicates an interrupt acknowledge cycle.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
 | 
				
			||||||
 | 
							int acknowledge_interrupt();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct InterruptDelegate {
 | 
				
			||||||
 | 
								/// Informs the delegate of a change in the interrupt line of the nominated MFP.
 | 
				
			||||||
 | 
								virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							/// Sets a delegate that will be notified upon any change in the interrupt line.
 | 
				
			||||||
 | 
							void set_interrupt_delegate(InterruptDelegate *delegate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ClockingHint::Source.
 | 
				
			||||||
 | 
							ClockingHint::Preference preferred_clocking() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							// MARK: - Timers
 | 
				
			||||||
 | 
							enum class TimerMode {
 | 
				
			||||||
 | 
								Stopped, EventCount, Delay, PulseWidth
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
 | 
				
			||||||
 | 
							void set_timer_data(int timer, uint8_t);
 | 
				
			||||||
 | 
							uint8_t get_timer_data(int timer);
 | 
				
			||||||
 | 
							void decrement_timer(int timer, int amount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct Timer {
 | 
				
			||||||
 | 
								TimerMode mode = TimerMode::Stopped;
 | 
				
			||||||
 | 
								uint8_t value = 0;
 | 
				
			||||||
 | 
								uint8_t reload_value = 0;
 | 
				
			||||||
 | 
								int prescale = 1;
 | 
				
			||||||
 | 
								int prescale_count = 1;
 | 
				
			||||||
 | 
								bool event_input = false;
 | 
				
			||||||
 | 
							} timers_[4];
 | 
				
			||||||
 | 
							uint8_t timer_ab_control_[2] = { 0, 0 };
 | 
				
			||||||
 | 
							uint8_t timer_cd_control_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							HalfCycles cycles_left_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK: - GPIP
 | 
				
			||||||
 | 
							uint8_t gpip_input_ = 0;
 | 
				
			||||||
 | 
							uint8_t gpip_output_ = 0;
 | 
				
			||||||
 | 
							uint8_t gpip_active_edge_ = 0;
 | 
				
			||||||
 | 
							uint8_t gpip_direction_ = 0;
 | 
				
			||||||
 | 
							uint8_t gpip_interrupt_state_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void reevaluate_gpip_interrupts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK: - Interrupts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							InterruptDelegate *interrupt_delegate_ = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Ad hoc documentation:
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// An interrupt becomes pending if it is enabled at the time it occurs.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// If a pending interrupt is enabled in the interrupt mask, a processor
 | 
				
			||||||
 | 
							// interrupt is generated. Otherwise no processor interrupt is generated.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// (Disabling a bit in the enabled mask also instantaneously clears anything
 | 
				
			||||||
 | 
							// in the pending mask.)
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// The user can write to the pending interrupt register; a write
 | 
				
			||||||
 | 
							// masks whatever is there — so you can disable bits but you cannot set them.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
 | 
				
			||||||
 | 
							// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
 | 
				
			||||||
 | 
							// its in-service bit. That bit will remain set until the user writes a zero to its position.
 | 
				
			||||||
 | 
							// If any bits are set in the in-service register, then they will prevent lower-priority
 | 
				
			||||||
 | 
							// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
 | 
				
			||||||
 | 
							// priority may occur.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
 | 
				
			||||||
 | 
							// Acknowledgement of an interrupt will automatically clear the corresponding
 | 
				
			||||||
 | 
							// pending bit.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							int interrupt_enable_ = 0;
 | 
				
			||||||
 | 
							int interrupt_pending_ = 0;
 | 
				
			||||||
 | 
							int interrupt_mask_ = 0;
 | 
				
			||||||
 | 
							int interrupt_in_service_ = 0;
 | 
				
			||||||
 | 
							bool interrupt_line_ = false;
 | 
				
			||||||
 | 
							uint8_t interrupt_vector_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum Interrupt {
 | 
				
			||||||
 | 
								GPIP0				= (1 << 0),
 | 
				
			||||||
 | 
								GPIP1				= (1 << 1),
 | 
				
			||||||
 | 
								GPIP2				= (1 << 2),
 | 
				
			||||||
 | 
								GPIP3				= (1 << 3),
 | 
				
			||||||
 | 
								TimerD				= (1 << 4),
 | 
				
			||||||
 | 
								TimerC				= (1 << 5),
 | 
				
			||||||
 | 
								GPIP4				= (1 << 6),
 | 
				
			||||||
 | 
								GPIP5				= (1 << 7),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								TimerB				= (1 << 8),
 | 
				
			||||||
 | 
								TransmitError		= (1 << 9),
 | 
				
			||||||
 | 
								TransmitBufferEmpty	= (1 << 10),
 | 
				
			||||||
 | 
								ReceiveError		= (1 << 11),
 | 
				
			||||||
 | 
								ReceiveBufferFull	= (1 << 12),
 | 
				
			||||||
 | 
								TimerA				= (1 << 13),
 | 
				
			||||||
 | 
								GPIP6				= (1 << 14),
 | 
				
			||||||
 | 
								GPIP7				= (1 << 15),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							void begin_interrupts(int interrupt);
 | 
				
			||||||
 | 
							void end_interrupts(int interrupt);
 | 
				
			||||||
 | 
							void update_interrupts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
 | 
				
			||||||
 | 
							inline static int msb16(int v) {
 | 
				
			||||||
 | 
								// Saturate all bits below the MSB.
 | 
				
			||||||
 | 
								v |= v >> 1;
 | 
				
			||||||
 | 
								v |= v >> 2;
 | 
				
			||||||
 | 
								v |= v >> 4;
 | 
				
			||||||
 | 
								v |= v >> 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Throw away lesser bits.
 | 
				
			||||||
 | 
								return (v+1) >> 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* MFP68901_hpp */
 | 
				
			||||||
@@ -95,11 +95,11 @@ void i8272::run_for(Cycles cycles) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// check for an expired timer
 | 
						// check for an expired timer
 | 
				
			||||||
	if(delay_time_ > 0) {
 | 
						if(delay_time_ > 0) {
 | 
				
			||||||
		if(cycles.as_int() >= delay_time_) {
 | 
							if(cycles.as_integral() >= delay_time_) {
 | 
				
			||||||
			delay_time_ = 0;
 | 
								delay_time_ = 0;
 | 
				
			||||||
			posit_event(static_cast<int>(Event8272::Timer));
 | 
								posit_event(static_cast<int>(Event8272::Timer));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			delay_time_ -= cycles.as_int();
 | 
								delay_time_ -= cycles.as_integral();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,8 +108,8 @@ void i8272::run_for(Cycles cycles) {
 | 
				
			|||||||
		int drives_left = drives_seeking_;
 | 
							int drives_left = drives_seeking_;
 | 
				
			||||||
		for(int c = 0; c < 4; c++) {
 | 
							for(int c = 0; c < 4; c++) {
 | 
				
			||||||
			if(drives_[c].phase == Drive::Seeking) {
 | 
								if(drives_[c].phase == Drive::Seeking) {
 | 
				
			||||||
				drives_[c].step_rate_counter += cycles.as_int();
 | 
									drives_[c].step_rate_counter += cycles.as_integral();
 | 
				
			||||||
				int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
 | 
									auto steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
 | 
				
			||||||
				drives_[c].step_rate_counter %= (8000 * step_rate_time_);
 | 
									drives_[c].step_rate_counter %= (8000 * step_rate_time_);
 | 
				
			||||||
				while(steps--) {
 | 
									while(steps--) {
 | 
				
			||||||
					// Perform a step.
 | 
										// Perform a step.
 | 
				
			||||||
@@ -141,12 +141,12 @@ void i8272::run_for(Cycles cycles) {
 | 
				
			|||||||
			int head = c&1;
 | 
								int head = c&1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(drives_[drive].head_unload_delay[head] > 0) {
 | 
								if(drives_[drive].head_unload_delay[head] > 0) {
 | 
				
			||||||
				if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
 | 
									if(cycles.as_integral() >= drives_[drive].head_unload_delay[head]) {
 | 
				
			||||||
					drives_[drive].head_unload_delay[head] = 0;
 | 
										drives_[drive].head_unload_delay[head] = 0;
 | 
				
			||||||
					drives_[drive].head_is_loaded[head] = false;
 | 
										drives_[drive].head_is_loaded[head] = false;
 | 
				
			||||||
					head_timers_running_--;
 | 
										head_timers_running_--;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					drives_[drive].head_unload_delay[head] -= cycles.as_int();
 | 
										drives_[drive].head_unload_delay[head] -= cycles.as_integral();
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				timers_left--;
 | 
									timers_left--;
 | 
				
			||||||
				if(!timers_left) break;
 | 
									if(!timers_left) break;
 | 
				
			||||||
@@ -292,7 +292,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
			WAIT_FOR_EVENT(Event8272::CommandByte)
 | 
								WAIT_FOR_EVENT(Event8272::CommandByte)
 | 
				
			||||||
			SetBusy();
 | 
								SetBusy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			static const std::size_t required_lengths[32] = {
 | 
								static constexpr std::size_t required_lengths[32] = {
 | 
				
			||||||
				0,	0,	9,	3,	2,	9,	9,	2,
 | 
									0,	0,	9,	3,	2,	9,	9,	2,
 | 
				
			||||||
				1,	9,	2,	0,	9,	6,	0,	3,
 | 
									1,	9,	2,	0,	9,	6,	0,	3,
 | 
				
			||||||
				0,	9,	0,	0,	0,	0,	0,	0,
 | 
									0,	9,	0,	0,	0,	0,	0,	0,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ class BusHandler {
 | 
				
			|||||||
		virtual void set_interrupt(bool irq) {}
 | 
							virtual void set_interrupt(bool irq) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class i8272: public Storage::Disk::MFMController {
 | 
					class i8272 : public Storage::Disk::MFMController {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		i8272(BusHandler &bus_handler, Cycles clock_rate);
 | 
							i8272(BusHandler &bus_handler, Cycles clock_rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
 | 
				
			|||||||
		void set_dma_acknowledge(bool dack);
 | 
							void set_dma_acknowledge(bool dack);
 | 
				
			||||||
		void set_terminal_count(bool tc);
 | 
							void set_terminal_count(bool tc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ClockingHint::Preference preferred_clocking() override;
 | 
							ClockingHint::Preference preferred_clocking() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected:
 | 
						protected:
 | 
				
			||||||
		virtual void select_drive(int number) = 0;
 | 
							virtual void select_drive(int number) = 0;
 | 
				
			||||||
@@ -73,7 +73,7 @@ class i8272: public Storage::Disk::MFMController {
 | 
				
			|||||||
		bool is_access_command_ = false;
 | 
							bool is_access_command_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// The counter used for ::Timer events.
 | 
							// The counter used for ::Timer events.
 | 
				
			||||||
		int delay_time_ = 0;
 | 
							Cycles::IntType delay_time_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// The connected drives.
 | 
							// The connected drives.
 | 
				
			||||||
		struct Drive {
 | 
							struct Drive {
 | 
				
			||||||
@@ -89,12 +89,12 @@ class i8272: public Storage::Disk::MFMController {
 | 
				
			|||||||
			bool seek_failed = false;
 | 
								bool seek_failed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Seeking: transient state.
 | 
								// Seeking: transient state.
 | 
				
			||||||
			int step_rate_counter = 0;
 | 
								Cycles::IntType step_rate_counter = 0;
 | 
				
			||||||
			int steps_taken = 0;
 | 
								int steps_taken = 0;
 | 
				
			||||||
			int target_head_position = 0;	// either an actual number, or -1 to indicate to step until track zero
 | 
								int target_head_position = 0;	// either an actual number, or -1 to indicate to step until track zero
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Head state.
 | 
								// Head state.
 | 
				
			||||||
			int head_unload_delay[2] = {0, 0};
 | 
								Cycles::IntType head_unload_delay[2] = {0, 0};
 | 
				
			||||||
			bool head_is_loaded[2] = {false, false};
 | 
								bool head_is_loaded[2] = {false, false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		} drives_[4];
 | 
							} drives_[4];
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										268
									
								
								Components/8530/z8530.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								Components/8530/z8530.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,268 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  8530.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 07/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "z8530.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Zilog::SCC;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void z8530::reset() {
 | 
				
			||||||
 | 
						// TODO.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool z8530::get_interrupt_line() {
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
							(master_interrupt_control_ & 0x8) &&
 | 
				
			||||||
 | 
							(
 | 
				
			||||||
 | 
								channels_[0].get_interrupt_line() ||
 | 
				
			||||||
 | 
								channels_[1].get_interrupt_line()
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::uint8_t z8530::read(int address) {
 | 
				
			||||||
 | 
						if(address & 2) {
 | 
				
			||||||
 | 
							// Read data register for channel
 | 
				
			||||||
 | 
							return 0x00;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Read control register for channel.
 | 
				
			||||||
 | 
							uint8_t result = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch(pointer_) {
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									result = channels_[address & 1].read(address & 2, pointer_);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 2:		// Handled non-symmetrically between channels.
 | 
				
			||||||
 | 
									if(address & 1) {
 | 
				
			||||||
 | 
										LOG("[SCC] Unimplemented: register 2 status bits");
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										result = interrupt_vector_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Modify the vector if permitted.
 | 
				
			||||||
 | 
					//					if(master_interrupt_control_ & 1) {
 | 
				
			||||||
 | 
											for(int port = 0; port < 2; ++port) {
 | 
				
			||||||
 | 
												// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix.
 | 
				
			||||||
 | 
												if(channels_[port].get_interrupt_line()) {
 | 
				
			||||||
 | 
													const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4);
 | 
				
			||||||
 | 
													const uint8_t mask = uint8_t(~(7 << shift));
 | 
				
			||||||
 | 
													result = uint8_t(
 | 
				
			||||||
 | 
														(result & mask) |
 | 
				
			||||||
 | 
														((1 | ((port == 1) ? 4 : 0)) << shift)
 | 
				
			||||||
 | 
													);
 | 
				
			||||||
 | 
													break;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					//					}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pointer_ = 0;
 | 
				
			||||||
 | 
							update_delegate();
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0x00;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void z8530::write(int address, std::uint8_t value) {
 | 
				
			||||||
 | 
						if(address & 2) {
 | 
				
			||||||
 | 
							// Write data register for channel.
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Write control register for channel.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Most registers are per channel, but a couple are shared; sever
 | 
				
			||||||
 | 
							// them here.
 | 
				
			||||||
 | 
							switch(pointer_) {
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									channels_[address & 1].write(address & 2, pointer_, value);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 2:	// Interrupt vector register; shared between both channels.
 | 
				
			||||||
 | 
									interrupt_vector_ = value;
 | 
				
			||||||
 | 
									LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 9:	// Master interrupt and reset register; also shared between both channels.
 | 
				
			||||||
 | 
									LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
 | 
				
			||||||
 | 
									master_interrupt_control_ = value;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The pointer number resets to 0 after every access, but if it is zero
 | 
				
			||||||
 | 
							// then crib at least the next set of pointer bits (which, similarly, are shared
 | 
				
			||||||
 | 
							// between the two channels).
 | 
				
			||||||
 | 
							if(pointer_) {
 | 
				
			||||||
 | 
								pointer_ = 0;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// The lowest three bits are the lowest three bits of the pointer.
 | 
				
			||||||
 | 
								pointer_ = value & 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If the command part of the byte is a 'point high', also set the
 | 
				
			||||||
 | 
								// top bit of the pointer.
 | 
				
			||||||
 | 
								if(((value >> 3)&7) == 1) {
 | 
				
			||||||
 | 
									pointer_ |= 8;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						update_delegate();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void z8530::set_dcd(int port, bool level) {
 | 
				
			||||||
 | 
						channels_[port].set_dcd(level);
 | 
				
			||||||
 | 
						update_delegate();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Channel implementations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
 | 
				
			||||||
 | 
						// If this is a data read, just return it.
 | 
				
			||||||
 | 
						if(data) {
 | 
				
			||||||
 | 
							return data_;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Otherwise, this is a control read...
 | 
				
			||||||
 | 
							switch(pointer) {
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									LOG("[SCC] Unrecognised control read from register " << int(pointer));
 | 
				
			||||||
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
								return dcd_ ? 0x8 : 0x0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0xf:
 | 
				
			||||||
 | 
								return external_interrupt_status_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0x00;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
 | 
				
			||||||
 | 
						if(data) {
 | 
				
			||||||
 | 
							data_ = value;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							switch(pointer) {
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x0:	// Write register 0 — CRC reset and other functions.
 | 
				
			||||||
 | 
									// Decode CRC reset instructions.
 | 
				
			||||||
 | 
									switch(value >> 6) {
 | 
				
			||||||
 | 
										default:	/* Do nothing. */		break;
 | 
				
			||||||
 | 
										case 1:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: reset Rx CRC checker.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 2:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: reset Tx CRC checker.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 3:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Decode command code.
 | 
				
			||||||
 | 
									switch((value >> 3)&7) {
 | 
				
			||||||
 | 
										default:	/* Do nothing. */		break;
 | 
				
			||||||
 | 
										case 2:
 | 
				
			||||||
 | 
					//						LOG("[SCC] reset ext/status interrupts.");
 | 
				
			||||||
 | 
											external_status_interrupt_ = false;
 | 
				
			||||||
 | 
											external_interrupt_status_ = 0;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 3:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: send abort (SDLC).");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 4:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: enable interrupt on next Rx character.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 5:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: reset Tx interrupt pending.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 6:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: reset error.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
										case 7:
 | 
				
			||||||
 | 
											LOG("[SCC] TODO: reset highest IUS.");
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x1:	// Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
 | 
				
			||||||
 | 
									interrupt_mask_ = value;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x4:	// Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
 | 
				
			||||||
 | 
									// Bits 0 and 1 select parity mode.
 | 
				
			||||||
 | 
									if(!(value&1)) {
 | 
				
			||||||
 | 
										parity_ = Parity::Off;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										parity_ = (value&2) ? Parity::Even : Parity::Odd;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Bits 2 and 3 select stop bits.
 | 
				
			||||||
 | 
									switch((value >> 2)&3) {
 | 
				
			||||||
 | 
										default:	stop_bits_ = StopBits::Synchronous;			break;
 | 
				
			||||||
 | 
										case 1:		stop_bits_ = StopBits::OneBit;				break;
 | 
				
			||||||
 | 
										case 2:		stop_bits_ = StopBits::OneAndAHalfBits;		break;
 | 
				
			||||||
 | 
										case 3:		stop_bits_ = StopBits::TwoBits;				break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Bits 4 and 5 pick a sync mode.
 | 
				
			||||||
 | 
									switch((value >> 4)&3) {
 | 
				
			||||||
 | 
										default:	sync_mode_ = Sync::Monosync;	break;
 | 
				
			||||||
 | 
										case 1:		sync_mode_ = Sync::Bisync;		break;
 | 
				
			||||||
 | 
										case 2:		sync_mode_ = Sync::SDLC;		break;
 | 
				
			||||||
 | 
										case 3:		sync_mode_ = Sync::External;	break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Bits 6 and 7 select a clock rate multiplier, unless synchronous
 | 
				
			||||||
 | 
									// mode is enabled (and this is ignored if sync mode is external).
 | 
				
			||||||
 | 
									if(stop_bits_ == StopBits::Synchronous) {
 | 
				
			||||||
 | 
										clock_rate_multiplier_ = 1;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										switch((value >> 6)&3) {
 | 
				
			||||||
 | 
											default:	clock_rate_multiplier_ = 1;		break;
 | 
				
			||||||
 | 
											case 1:		clock_rate_multiplier_ = 16;	break;
 | 
				
			||||||
 | 
											case 2:		clock_rate_multiplier_ = 32;	break;
 | 
				
			||||||
 | 
											case 3:		clock_rate_multiplier_ = 64;	break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0xf:	// Write register 15 — External/Status Interrupt Control.
 | 
				
			||||||
 | 
									external_interrupt_mask_ = value;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void z8530::Channel::set_dcd(bool level) {
 | 
				
			||||||
 | 
						if(dcd_ == level) return;
 | 
				
			||||||
 | 
						dcd_ = level;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(external_interrupt_mask_ & 0x8) {
 | 
				
			||||||
 | 
							external_status_interrupt_ = true;
 | 
				
			||||||
 | 
							external_interrupt_status_ |= 0x8;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool z8530::Channel::get_interrupt_line() {
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
							(interrupt_mask_ & 1) && external_status_interrupt_;
 | 
				
			||||||
 | 
						// TODO: other potential causes of an interrupt.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void z8530::update_delegate() {
 | 
				
			||||||
 | 
						const bool interrupt_line = get_interrupt_line();
 | 
				
			||||||
 | 
						if(interrupt_line != previous_interrupt_line_) {
 | 
				
			||||||
 | 
							previous_interrupt_line_ = interrupt_line;
 | 
				
			||||||
 | 
							if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								Components/8530/z8530.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								Components/8530/z8530.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  z8530.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 07/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef z8530_hpp
 | 
				
			||||||
 | 
					#define z8530_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Zilog {
 | 
				
			||||||
 | 
					namespace SCC {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models the Zilog 8530 SCC, a serial adaptor.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class z8530 {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								**Interface for emulated machine.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Notes on addressing below:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								There's no inherent ordering of the two 'address' lines,
 | 
				
			||||||
 | 
								A/B and C/D, but the methods below assume:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									A/B = A0
 | 
				
			||||||
 | 
									C/D = A1
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							std::uint8_t read(int address);
 | 
				
			||||||
 | 
							void write(int address, std::uint8_t value);
 | 
				
			||||||
 | 
							void reset();
 | 
				
			||||||
 | 
							bool get_interrupt_line();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct Delegate {
 | 
				
			||||||
 | 
								virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							void set_delegate(Delegate *delegate) {
 | 
				
			||||||
 | 
								delegate_ = delegate;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								**Interface for serial port input.**
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_dcd(int port, bool level);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							class Channel {
 | 
				
			||||||
 | 
								public:
 | 
				
			||||||
 | 
									uint8_t read(bool data, uint8_t pointer);
 | 
				
			||||||
 | 
									void write(bool data, uint8_t pointer, uint8_t value);
 | 
				
			||||||
 | 
									void set_dcd(bool level);
 | 
				
			||||||
 | 
									bool get_interrupt_line();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								private:
 | 
				
			||||||
 | 
									uint8_t data_ = 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									enum class Parity {
 | 
				
			||||||
 | 
										Even, Odd, Off
 | 
				
			||||||
 | 
									} parity_ = Parity::Off;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									enum class StopBits {
 | 
				
			||||||
 | 
										Synchronous, OneBit, OneAndAHalfBits, TwoBits
 | 
				
			||||||
 | 
									} stop_bits_ = StopBits::Synchronous;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									enum class Sync {
 | 
				
			||||||
 | 
										Monosync, Bisync, SDLC, External
 | 
				
			||||||
 | 
									} sync_mode_ = Sync::Monosync;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									int clock_rate_multiplier_ = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									uint8_t interrupt_mask_ = 0;			// i.e. Write Register 0x1.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									uint8_t external_interrupt_mask_ = 0;	// i.e. Write Register 0xf.
 | 
				
			||||||
 | 
									bool external_status_interrupt_ = false;
 | 
				
			||||||
 | 
									uint8_t external_interrupt_status_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									bool dcd_ = false;
 | 
				
			||||||
 | 
							} channels_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t pointer_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t interrupt_vector_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t master_interrupt_control_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool previous_interrupt_line_ = false;
 | 
				
			||||||
 | 
							void update_delegate();
 | 
				
			||||||
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* z8530_hpp */
 | 
				
			||||||
@@ -11,21 +11,22 @@
 | 
				
			|||||||
#include <cassert>
 | 
					#include <cassert>
 | 
				
			||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
#include <cstdlib>
 | 
					#include <cstdlib>
 | 
				
			||||||
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace TI::TMS;
 | 
					using namespace TI::TMS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace {
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uint8_t StatusInterrupt = 0x80;
 | 
					constexpr uint8_t StatusInterrupt = 0x80;
 | 
				
			||||||
const uint8_t StatusSpriteOverflow = 0x40;
 | 
					constexpr uint8_t StatusSpriteOverflow = 0x40;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const int StatusSpriteCollisionShift = 5;
 | 
					constexpr int StatusSpriteCollisionShift = 5;
 | 
				
			||||||
const uint8_t StatusSpriteCollision = 0x20;
 | 
					constexpr uint8_t StatusSpriteCollision = 0x20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
 | 
					// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
 | 
				
			||||||
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
 | 
					// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
 | 
				
			||||||
const unsigned int CRTCyclesPerLine = 1365;
 | 
					constexpr unsigned int CRTCyclesPerLine = 1365;
 | 
				
			||||||
const unsigned int CRTCyclesDivider = 4;
 | 
					constexpr unsigned int CRTCyclesDivider = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ReverseTable {
 | 
					struct ReverseTable {
 | 
				
			||||||
	std::uint8_t map[256];
 | 
						std::uint8_t map[256];
 | 
				
			||||||
@@ -50,7 +51,9 @@ struct ReverseTable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Base::Base(Personality p) :
 | 
					Base::Base(Personality p) :
 | 
				
			||||||
	personality_(p),
 | 
						personality_(p),
 | 
				
			||||||
	crt_(new Outputs::CRT::CRT(CRTCyclesPerLine, CRTCyclesDivider, Outputs::CRT::DisplayType::NTSC60, 4)) {
 | 
						crt_(CRTCyclesPerLine, CRTCyclesDivider, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red8Green8Blue8) {
 | 
				
			||||||
 | 
						// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
 | 
				
			||||||
 | 
						// into whether there's a more natural form. It feels unlikely given the diversity of chips modelled.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch(p) {
 | 
						switch(p) {
 | 
				
			||||||
		case TI::TMS::TMS9918A:
 | 
							case TI::TMS::TMS9918A:
 | 
				
			||||||
@@ -83,22 +86,15 @@ Base::Base(Personality p) :
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TMS9918::TMS9918(Personality p):
 | 
					TMS9918::TMS9918(Personality p):
 | 
				
			||||||
 	Base(p) {
 | 
						Base(p) {
 | 
				
			||||||
	// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
 | 
						crt_.set_display_type(Outputs::Display::DisplayType::RGB);
 | 
				
			||||||
	// into whether there's a more natural form.
 | 
						crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f));
 | 
				
			||||||
	crt_->set_rgb_sampling_function(
 | 
					 | 
				
			||||||
		"vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
 | 
					 | 
				
			||||||
		"{"
 | 
					 | 
				
			||||||
			"return texture(sampler, coordinate).rgb / vec3(255.0);"
 | 
					 | 
				
			||||||
		"}");
 | 
					 | 
				
			||||||
	crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
 | 
					 | 
				
			||||||
	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
 | 
						// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
 | 
				
			||||||
	// intended to produce the correct relationship between the hard edges between pixels and
 | 
						// intended to produce the correct relationship between the hard edges between pixels and
 | 
				
			||||||
	// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
 | 
						// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
 | 
				
			||||||
	// colour burst generator because I've yet to find any.
 | 
						// colour burst generator because I've yet to find any.
 | 
				
			||||||
	crt_->set_immediate_default_phase(0.85f);
 | 
						crt_.set_immediate_default_phase(0.85f);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void TMS9918::set_tv_standard(TVStandard standard) {
 | 
					void TMS9918::set_tv_standard(TVStandard standard) {
 | 
				
			||||||
@@ -107,18 +103,22 @@ void TMS9918::set_tv_standard(TVStandard standard) {
 | 
				
			|||||||
		case TVStandard::PAL:
 | 
							case TVStandard::PAL:
 | 
				
			||||||
			mode_timing_.total_lines = 313;
 | 
								mode_timing_.total_lines = 313;
 | 
				
			||||||
			mode_timing_.first_vsync_line = 253;
 | 
								mode_timing_.first_vsync_line = 253;
 | 
				
			||||||
			crt_->set_new_display_type(CRTCyclesPerLine, Outputs::CRT::DisplayType::PAL50);
 | 
								crt_.set_new_display_type(CRTCyclesPerLine, Outputs::Display::Type::PAL50);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			mode_timing_.total_lines = 262;
 | 
								mode_timing_.total_lines = 262;
 | 
				
			||||||
			mode_timing_.first_vsync_line = 227;
 | 
								mode_timing_.first_vsync_line = 227;
 | 
				
			||||||
			crt_->set_new_display_type(CRTCyclesPerLine, Outputs::CRT::DisplayType::NTSC60);
 | 
								crt_.set_new_display_type(CRTCyclesPerLine, Outputs::Display::Type::NTSC60);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Outputs::CRT::CRT *TMS9918::get_crt() {
 | 
					void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			||||||
	return crt_.get();
 | 
						crt_.set_scan_target(scan_target);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
 | 
				
			||||||
 | 
						crt_.set_display_type(display_type);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Base::LineBuffer::reset_sprite_collection() {
 | 
					void Base::LineBuffer::reset_sprite_collection() {
 | 
				
			||||||
@@ -166,7 +166,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
	// Convert 456 clocked half cycles per line to 342 internal cycles per line;
 | 
						// Convert 456 clocked half cycles per line to 342 internal cycles per line;
 | 
				
			||||||
	// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
 | 
						// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
 | 
				
			||||||
	// for this part. So multiply by three quarters.
 | 
						// for this part. So multiply by three quarters.
 | 
				
			||||||
	int int_cycles = (cycles.as_int() * 3) + cycles_error_;
 | 
						int int_cycles = int(cycles.as_integral() * 3) + cycles_error_;
 | 
				
			||||||
	cycles_error_ = int_cycles & 3;
 | 
						cycles_error_ = int_cycles & 3;
 | 
				
			||||||
	int_cycles >>= 2;
 | 
						int_cycles >>= 2;
 | 
				
			||||||
	if(!int_cycles) return;
 | 
						if(!int_cycles) return;
 | 
				
			||||||
@@ -352,8 +352,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
				// Output video stream.
 | 
									// Output video stream.
 | 
				
			||||||
				// --------------------
 | 
									// --------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define intersect(left, right, code)	\
 | 
					#define intersect(left, right, code)	{	\
 | 
				
			||||||
	{	\
 | 
					 | 
				
			||||||
		const int start = std::max(read_pointer_.column, left);	\
 | 
							const int start = std::max(read_pointer_.column, left);	\
 | 
				
			||||||
		const int end = std::min(end_column, right);	\
 | 
							const int end = std::min(end_column, right);	\
 | 
				
			||||||
		if(end > start) {\
 | 
							if(end > start) {\
 | 
				
			||||||
@@ -367,7 +366,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
					if(read_pointer_.row >= mode_timing_.first_vsync_line && read_pointer_.row < mode_timing_.first_vsync_line+4) {
 | 
										if(read_pointer_.row >= mode_timing_.first_vsync_line && read_pointer_.row < mode_timing_.first_vsync_line+4) {
 | 
				
			||||||
						// Vertical sync.
 | 
											// Vertical sync.
 | 
				
			||||||
						if(end_column == 342) {
 | 
											if(end_column == 342) {
 | 
				
			||||||
							crt_->output_sync(342 * 4);
 | 
												crt_.output_sync(342 * 4);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						// Right border.
 | 
											// Right border.
 | 
				
			||||||
@@ -377,11 +376,11 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
						// and 58+15 = 73. So output the lot when the
 | 
											// and 58+15 = 73. So output the lot when the
 | 
				
			||||||
						// cursor passes 73.
 | 
											// cursor passes 73.
 | 
				
			||||||
						if(read_pointer_.column < 73 && end_column >= 73) {
 | 
											if(read_pointer_.column < 73 && end_column >= 73) {
 | 
				
			||||||
							crt_->output_blank(8*4);
 | 
												crt_.output_blank(8*4);
 | 
				
			||||||
							crt_->output_sync(26*4);
 | 
												crt_.output_sync(26*4);
 | 
				
			||||||
							crt_->output_blank(2*4);
 | 
												crt_.output_blank(2*4);
 | 
				
			||||||
							crt_->output_default_colour_burst(14*4);
 | 
												crt_.output_default_colour_burst(14*4);
 | 
				
			||||||
							crt_->output_blank(8*4);
 | 
												crt_.output_blank(8*4);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						// Border colour for the rest of the line.
 | 
											// Border colour for the rest of the line.
 | 
				
			||||||
@@ -393,11 +392,11 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
					// Blanking region.
 | 
										// Blanking region.
 | 
				
			||||||
					if(read_pointer_.column < 73 && end_column >= 73) {
 | 
										if(read_pointer_.column < 73 && end_column >= 73) {
 | 
				
			||||||
						crt_->output_blank(8*4);
 | 
											crt_.output_blank(8*4);
 | 
				
			||||||
						crt_->output_sync(26*4);
 | 
											crt_.output_sync(26*4);
 | 
				
			||||||
						crt_->output_blank(2*4);
 | 
											crt_.output_blank(2*4);
 | 
				
			||||||
						crt_->output_default_colour_burst(14*4);
 | 
											crt_.output_default_colour_burst(14*4);
 | 
				
			||||||
						crt_->output_blank(8*4);
 | 
											crt_.output_blank(8*4);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Left border.
 | 
										// Left border.
 | 
				
			||||||
@@ -410,7 +409,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
						if(!asked_for_write_area_) {
 | 
											if(!asked_for_write_area_) {
 | 
				
			||||||
							asked_for_write_area_ = true;
 | 
												asked_for_write_area_ = true;
 | 
				
			||||||
							pixel_origin_ = pixel_target_ = reinterpret_cast<uint32_t *>(
 | 
												pixel_origin_ = pixel_target_ = reinterpret_cast<uint32_t *>(
 | 
				
			||||||
								crt_->allocate_write_area(static_cast<unsigned int>(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
 | 
													crt_.begin_data(size_t(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
 | 
				
			||||||
							);
 | 
												);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -427,8 +426,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						if(end == line_buffer.next_border_column) {
 | 
											if(end == line_buffer.next_border_column) {
 | 
				
			||||||
							const unsigned int length = static_cast<unsigned int>(line_buffer.next_border_column - line_buffer.first_pixel_output_column);
 | 
												const int length = line_buffer.next_border_column - line_buffer.first_pixel_output_column;
 | 
				
			||||||
							crt_->output_data(length * 4, length);
 | 
												crt_.output_data(length * 4, size_t(length));
 | 
				
			||||||
							pixel_origin_ = pixel_target_ = nullptr;
 | 
												pixel_origin_ = pixel_target_ = nullptr;
 | 
				
			||||||
							asked_for_write_area_ = false;
 | 
												asked_for_write_area_ = false;
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
@@ -470,16 +469,26 @@ void Base::output_border(int cycles, uint32_t cram_dot) {
 | 
				
			|||||||
			palette[background_colour_];
 | 
								palette[background_colour_];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(cram_dot) {
 | 
						if(cram_dot) {
 | 
				
			||||||
		uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
 | 
							uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_.begin_data(1));
 | 
				
			||||||
		*pixel_target = border_colour | cram_dot;
 | 
							if(pixel_target) {
 | 
				
			||||||
		crt_->output_level(4);
 | 
								*pixel_target = border_colour | cram_dot;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							crt_.output_level(4);
 | 
				
			||||||
		cycles -= 4;
 | 
							cycles -= 4;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(cycles) {
 | 
						if(cycles) {
 | 
				
			||||||
		uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
 | 
							// If the border colour is 0, that can be communicated
 | 
				
			||||||
		*pixel_target = border_colour;
 | 
							// more efficiently as an explicit blank.
 | 
				
			||||||
		crt_->output_level(static_cast<unsigned int>(cycles));
 | 
							if(border_colour) {
 | 
				
			||||||
 | 
								uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_.begin_data(1));
 | 
				
			||||||
 | 
								if(pixel_target) {
 | 
				
			||||||
 | 
									*pixel_target = border_colour;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								crt_.output_level(cycles);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								crt_.output_blank(cycles);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -582,7 +591,6 @@ void TMS9918::set_register(int address, uint8_t value) {
 | 
				
			|||||||
			case 8:
 | 
								case 8:
 | 
				
			||||||
				if(is_sega_vdp(personality_)) {
 | 
									if(is_sega_vdp(personality_)) {
 | 
				
			||||||
					master_system_.horizontal_scroll = low_write_;
 | 
										master_system_.horizontal_scroll = low_write_;
 | 
				
			||||||
//					printf("Set to %d at %d, %d\n", low_write_, row_, column_);
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -599,7 +607,7 @@ void TMS9918::set_register(int address, uint8_t value) {
 | 
				
			|||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
//				printf("Unknown TMS write: %d to %d\n", low_write_, value);
 | 
									LOG("Unknown TMS write: " << int(low_write_) << " to " << int(value));
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -616,7 +624,7 @@ void TMS9918::set_register(int address, uint8_t value) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
uint8_t TMS9918::get_current_line() {
 | 
					uint8_t TMS9918::get_current_line() {
 | 
				
			||||||
	// Determine the row to return.
 | 
						// Determine the row to return.
 | 
				
			||||||
	static const int row_change_position = 63;	// This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
 | 
						constexpr int row_change_position = 63;	// This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
 | 
				
			||||||
	int source_row =
 | 
						int source_row =
 | 
				
			||||||
		(write_pointer_.column < row_change_position)
 | 
							(write_pointer_.column < row_change_position)
 | 
				
			||||||
			? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines
 | 
								? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines
 | 
				
			||||||
@@ -821,8 +829,8 @@ void Base::draw_tms_character(int start, int end) {
 | 
				
			|||||||
		int sprite_collision = 0;
 | 
							int sprite_collision = 0;
 | 
				
			||||||
		memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
 | 
							memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
 | 
							constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
 | 
				
			||||||
		static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
 | 
							constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Draw all sprites into the sprite buffer.
 | 
							// Draw all sprites into the sprite buffer.
 | 
				
			||||||
		const int shifter_target = sprites_16x16_ ? 32 : 16;
 | 
							const int shifter_target = sprites_16x16_ ? 32 : 16;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,8 +41,11 @@ class TMS9918: public Base {
 | 
				
			|||||||
		/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
 | 
							/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
 | 
				
			||||||
		void set_tv_standard(TVStandard standard);
 | 
							void set_tv_standard(TVStandard standard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*! Provides the CRT this TMS is connected to. */
 | 
							/*! Sets the scan target this TMS will post content to. */
 | 
				
			||||||
		Outputs::CRT::CRT *get_crt();
 | 
							void set_scan_target(Outputs::Display::ScanTarget *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*! Sets the type of display the CRT will request. */
 | 
				
			||||||
 | 
							void set_display_type(Outputs::Display::DisplayType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
 | 
								Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,8 +78,8 @@ class Base {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		Base(Personality p);
 | 
							Base(Personality p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Personality personality_;
 | 
							const Personality personality_;
 | 
				
			||||||
		std::unique_ptr<Outputs::CRT::CRT> crt_;
 | 
							Outputs::CRT::CRT crt_;
 | 
				
			||||||
		TVStandard tv_standard_ = TVStandard::NTSC;
 | 
							TVStandard tv_standard_ = TVStandard::NTSC;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Holds the contents of this VDP's connected DRAM.
 | 
							// Holds the contents of this VDP's connected DRAM.
 | 
				
			||||||
@@ -100,7 +100,7 @@ class Base {
 | 
				
			|||||||
			// (though, in practice, it won't happen until the next
 | 
								// (though, in practice, it won't happen until the next
 | 
				
			||||||
			// external slot after this number of cycles after the
 | 
								// external slot after this number of cycles after the
 | 
				
			||||||
			// device has requested the read or write).
 | 
								// device has requested the read or write).
 | 
				
			||||||
			return 7;
 | 
								return 6;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Holds the main status register.
 | 
							// Holds the main status register.
 | 
				
			||||||
@@ -211,8 +211,8 @@ class Base {
 | 
				
			|||||||
			// The names array holds pattern names, as an offset into memory, and
 | 
								// The names array holds pattern names, as an offset into memory, and
 | 
				
			||||||
			// potentially flags also.
 | 
								// potentially flags also.
 | 
				
			||||||
			struct {
 | 
								struct {
 | 
				
			||||||
				size_t offset;
 | 
									size_t offset = 0;
 | 
				
			||||||
				uint8_t flags;
 | 
									uint8_t flags = 0;
 | 
				
			||||||
			} names[40];
 | 
								} names[40];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// The patterns array holds tile patterns, corresponding 1:1 with names.
 | 
								// The patterns array holds tile patterns, corresponding 1:1 with names.
 | 
				
			||||||
@@ -391,7 +391,7 @@ class Base {
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
	Fetching routines follow below; they obey the following rules:
 | 
						Fetching routines follow below; they obey the following rules:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		1) 	input is a start position and an end position; they should perform the proper
 | 
							1)	input is a start position and an end position; they should perform the proper
 | 
				
			||||||
			operations for the period: start <= time < end.
 | 
								operations for the period: start <= time < end.
 | 
				
			||||||
		2)	times are measured relative to a 172-cycles-per-line clock (so: they directly
 | 
							2)	times are measured relative to a 172-cycles-per-line clock (so: they directly
 | 
				
			||||||
			count access windows on the TMS and Master System).
 | 
								count access windows on the TMS and Master System).
 | 
				
			||||||
@@ -411,7 +411,7 @@ class Base {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	Provided for the benefit of the methods below:
 | 
						Provided for the benefit of the methods below:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		* 	the function external_slot(), which will perform any pending VRAM read/write.
 | 
							*	the function external_slot(), which will perform any pending VRAM read/write.
 | 
				
			||||||
		*	the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
 | 
							*	the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
 | 
				
			||||||
			switch(start)-based implementation.
 | 
								switch(start)-based implementation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -752,14 +752,14 @@ class Base {
 | 
				
			|||||||
		fetch_tile_name(column+1, row_info)			\
 | 
							fetch_tile_name(column+1, row_info)			\
 | 
				
			||||||
		sprite_y_read(location+5, sprite);	\
 | 
							sprite_y_read(location+5, sprite);	\
 | 
				
			||||||
	slot(location+6):	\
 | 
						slot(location+6):	\
 | 
				
			||||||
	slot(location+7): 	\
 | 
						slot(location+7):	\
 | 
				
			||||||
	slot(location+8):	\
 | 
						slot(location+8):	\
 | 
				
			||||||
		fetch_tile(column+1)					\
 | 
							fetch_tile(column+1)					\
 | 
				
			||||||
		fetch_tile_name(column+2, row_info)				\
 | 
							fetch_tile_name(column+2, row_info)				\
 | 
				
			||||||
		sprite_y_read(location+9, sprite+2);	\
 | 
							sprite_y_read(location+9, sprite+2);	\
 | 
				
			||||||
	slot(location+10):	\
 | 
						slot(location+10):	\
 | 
				
			||||||
	slot(location+11):	\
 | 
						slot(location+11):	\
 | 
				
			||||||
	slot(location+12): 	\
 | 
						slot(location+12):	\
 | 
				
			||||||
		fetch_tile(column+2)					\
 | 
							fetch_tile(column+2)					\
 | 
				
			||||||
		fetch_tile_name(column+3, row_info)				\
 | 
							fetch_tile_name(column+3, row_info)				\
 | 
				
			||||||
		sprite_y_read(location+13, sprite+4);	\
 | 
							sprite_y_read(location+13, sprite+4);	\
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,45 +12,56 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace GI::AY38910;
 | 
					using namespace GI::AY38910;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
 | 
					AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
 | 
				
			||||||
	// set up envelope lookup tables
 | 
						// Don't use the low bit of the envelope position if this is an AY.
 | 
				
			||||||
 | 
						envelope_position_mask_ |= personality == Personality::AY38910;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set up envelope lookup tables.
 | 
				
			||||||
	for(int c = 0; c < 16; c++) {
 | 
						for(int c = 0; c < 16; c++) {
 | 
				
			||||||
		for(int p = 0; p < 32; p++) {
 | 
							for(int p = 0; p < 64; p++) {
 | 
				
			||||||
			switch(c) {
 | 
								switch(c) {
 | 
				
			||||||
				case 0: case 1: case 2: case 3: case 9:
 | 
									case 0: case 1: case 2: case 3: case 9:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
 | 
										/* Envelope: \____ */
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x1f;
 | 
										envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0;
 | 
				
			||||||
 | 
										envelope_overflow_masks_[c] = 0x3f;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case 4: case 5: case 6: case 7: case 15:
 | 
									case 4: case 5: case 6: case 7: case 15:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p < 16) ? p : 0;
 | 
										/* Envelope: /____ */
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x1f;
 | 
										envelope_shapes_[c][p] = (p < 32) ? p : 0;
 | 
				
			||||||
 | 
										envelope_overflow_masks_[c] = 0x3f;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 8:
 | 
									case 8:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p & 0xf) ^ 0xf;
 | 
										/* Envelope: \\\\\\\\ */
 | 
				
			||||||
 | 
										envelope_shapes_[c][p] = (p & 0x1f) ^ 0x1f;
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x00;
 | 
										envelope_overflow_masks_[c] = 0x00;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case 12:
 | 
									case 12:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p & 0xf);
 | 
										/* Envelope: //////// */
 | 
				
			||||||
 | 
										envelope_shapes_[c][p] = (p & 0x1f);
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x00;
 | 
										envelope_overflow_masks_[c] = 0x00;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 10:
 | 
									case 10:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
 | 
										/* Envelope: \/\/\/\/ */
 | 
				
			||||||
 | 
										envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x1f : 0x0);
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x00;
 | 
										envelope_overflow_masks_[c] = 0x00;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case 14:
 | 
									case 14:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
 | 
										/* Envelope: /\/\/\/\ */
 | 
				
			||||||
 | 
										envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x0 : 0x1f);
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x00;
 | 
										envelope_overflow_masks_[c] = 0x00;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 11:
 | 
									case 11:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf;
 | 
										/* Envelope: \------	(if - is high) */
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x1f;
 | 
										envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0x1f;
 | 
				
			||||||
 | 
										envelope_overflow_masks_[c] = 0x3f;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case 13:
 | 
									case 13:
 | 
				
			||||||
					envelope_shapes_[c][p] = (p < 16) ? p : 0xf;
 | 
										/* Envelope: /------- */
 | 
				
			||||||
					envelope_overflow_masks_[c] = 0x1f;
 | 
										envelope_shapes_[c][p] = (p < 32) ? p : 0x1f;
 | 
				
			||||||
 | 
										envelope_overflow_masks_[c] = 0x3f;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -61,18 +72,26 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void AY38910::set_sample_volume_range(std::int16_t range) {
 | 
					void AY38910::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
	// set up volume lookup table
 | 
						// set up volume lookup table
 | 
				
			||||||
	const float max_volume = static_cast<float>(range) / 3.0f;	// As there are three channels.
 | 
						const float max_volume = float(range) / 3.0f;	// As there are three channels.
 | 
				
			||||||
	const float root_two = sqrtf(2.0f);
 | 
						constexpr float root_two = 1.414213562373095f;
 | 
				
			||||||
	for(int v = 0; v < 16; v++) {
 | 
						for(int v = 0; v < 32; v++) {
 | 
				
			||||||
		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
 | 
							volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	volumes_[0] = 0;
 | 
					 | 
				
			||||||
	evaluate_output_volume();
 | 
						evaluate_output_volume();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
					void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			||||||
 | 
						// Note on structure below: the real AY has a built-in divider of 8
 | 
				
			||||||
 | 
						// prior to applying its tone and noise dividers. But the YM fills the
 | 
				
			||||||
 | 
						// same total periods for noise and tone with double-precision envelopes.
 | 
				
			||||||
 | 
						// Therefore this class implements a divider of 4 and doubles the tone
 | 
				
			||||||
 | 
						// and noise periods. The envelope ticks along at the divide-by-four rate,
 | 
				
			||||||
 | 
						// but if this is an AY rather than a YM then its lowest bit is forced to 1,
 | 
				
			||||||
 | 
						// matching the YM datasheet's depiction of envelope level 31 as equal to
 | 
				
			||||||
 | 
						// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::size_t c = 0;
 | 
						std::size_t c = 0;
 | 
				
			||||||
	while((master_divider_&7) && c < number_of_samples) {
 | 
						while((master_divider_&3) && c < number_of_samples) {
 | 
				
			||||||
		target[c] = output_volume_;
 | 
							target[c] = output_volume_;
 | 
				
			||||||
		master_divider_++;
 | 
							master_divider_++;
 | 
				
			||||||
		c++;
 | 
							c++;
 | 
				
			||||||
@@ -83,49 +102,49 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			|||||||
	if(tone_counters_[c]) tone_counters_[c]--;\
 | 
						if(tone_counters_[c]) tone_counters_[c]--;\
 | 
				
			||||||
	else {\
 | 
						else {\
 | 
				
			||||||
		tone_outputs_[c] ^= 1;\
 | 
							tone_outputs_[c] ^= 1;\
 | 
				
			||||||
		tone_counters_[c] = tone_periods_[c];\
 | 
							tone_counters_[c] = tone_periods_[c] << 1;\
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// update the tone channels
 | 
							// Update the tone channels.
 | 
				
			||||||
		step_channel(0);
 | 
							step_channel(0);
 | 
				
			||||||
		step_channel(1);
 | 
							step_channel(1);
 | 
				
			||||||
		step_channel(2);
 | 
							step_channel(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#undef step_channel
 | 
					#undef step_channel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
 | 
							// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
 | 
				
			||||||
		// it into the official 17 upon divider underflow.
 | 
							// it into the official 17 upon divider underflow.
 | 
				
			||||||
		if(noise_counter_) noise_counter_--;
 | 
							if(noise_counter_) noise_counter_--;
 | 
				
			||||||
		else {
 | 
							else {
 | 
				
			||||||
			noise_counter_ = noise_period_;
 | 
								noise_counter_ = noise_period_ << 1;	// To cover the double resolution of envelopes.
 | 
				
			||||||
			noise_output_ ^= noise_shift_register_&1;
 | 
								noise_output_ ^= noise_shift_register_&1;
 | 
				
			||||||
			noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
 | 
								noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
 | 
				
			||||||
			noise_shift_register_ >>= 1;
 | 
								noise_shift_register_ >>= 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
 | 
							// Update 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.
 | 
							// implementing non-repeating patterns by locking them to the final table position.
 | 
				
			||||||
		if(envelope_divider_) envelope_divider_--;
 | 
							if(envelope_divider_) envelope_divider_--;
 | 
				
			||||||
		else {
 | 
							else {
 | 
				
			||||||
			envelope_divider_ = envelope_period_;
 | 
								envelope_divider_ = envelope_period_;
 | 
				
			||||||
			envelope_position_ ++;
 | 
								envelope_position_ ++;
 | 
				
			||||||
			if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
 | 
								if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		evaluate_output_volume();
 | 
							evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
 | 
							for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
 | 
				
			||||||
			target[c] = output_volume_;
 | 
								target[c] = output_volume_;
 | 
				
			||||||
			c++;
 | 
								c++;
 | 
				
			||||||
			master_divider_++;
 | 
								master_divider_++;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	master_divider_ &= 7;
 | 
						master_divider_ &= 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::evaluate_output_volume() {
 | 
					void AY38910::evaluate_output_volume() {
 | 
				
			||||||
	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
 | 
						int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The output level for a channel is:
 | 
						// The output level for a channel is:
 | 
				
			||||||
	//	1 if neither tone nor noise is enabled;
 | 
						//	1 if neither tone nor noise is enabled;
 | 
				
			||||||
@@ -142,9 +161,10 @@ void AY38910::evaluate_output_volume() {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
#undef level
 | 
					#undef level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
 | 
							// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits,
 | 
				
			||||||
 | 
							// mapped to the range 1–31 in case this is a YM.
 | 
				
			||||||
#define channel_volume(c)	\
 | 
					#define channel_volume(c)	\
 | 
				
			||||||
	((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf)
 | 
						((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (((output_registers_[c]&0xf) << 1) + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const int volumes[3] = {
 | 
						const int volumes[3] = {
 | 
				
			||||||
		channel_volume(8),
 | 
							channel_volume(8),
 | 
				
			||||||
@@ -173,11 +193,15 @@ void AY38910::select_register(uint8_t r) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_register_value(uint8_t value) {
 | 
					void AY38910::set_register_value(uint8_t value) {
 | 
				
			||||||
 | 
						// There are only 16 registers.
 | 
				
			||||||
	if(selected_register_ > 15) return;
 | 
						if(selected_register_ > 15) return;
 | 
				
			||||||
	registers_[selected_register_] = value;
 | 
					
 | 
				
			||||||
 | 
						// If this is a register that affects audio output, enqueue a mutation onto the
 | 
				
			||||||
 | 
						// audio generation thread.
 | 
				
			||||||
	if(selected_register_ < 14) {
 | 
						if(selected_register_ < 14) {
 | 
				
			||||||
		int selected_register = selected_register_;
 | 
							const int selected_register = selected_register_;
 | 
				
			||||||
		task_queue_.defer([=] () {
 | 
							task_queue_.defer([=] () {
 | 
				
			||||||
 | 
								// Perform any register-specific mutation to output generation.
 | 
				
			||||||
			uint8_t masked_value = value;
 | 
								uint8_t masked_value = value;
 | 
				
			||||||
			switch(selected_register) {
 | 
								switch(selected_register) {
 | 
				
			||||||
				case 0: case 2: case 4:
 | 
									case 0: case 2: case 4:
 | 
				
			||||||
@@ -208,12 +232,34 @@ void AY38910::set_register_value(uint8_t value) {
 | 
				
			|||||||
					envelope_position_ = 0;
 | 
										envelope_position_ = 0;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Store a copy of the current register within the storage used by the audio generation
 | 
				
			||||||
 | 
								// thread, and apply any changes to output volume.
 | 
				
			||||||
			output_registers_[selected_register] = masked_value;
 | 
								output_registers_[selected_register] = masked_value;
 | 
				
			||||||
			evaluate_output_volume();
 | 
								evaluate_output_volume();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decide which outputs are going to need updating (if any).
 | 
				
			||||||
 | 
						bool update_port_a = false;
 | 
				
			||||||
 | 
						bool update_port_b = true;
 | 
				
			||||||
 | 
						if(port_handler_) {
 | 
				
			||||||
 | 
							if(selected_register_ == 7) {
 | 
				
			||||||
 | 
								const uint8_t io_change = registers_[7] ^ value;
 | 
				
			||||||
 | 
								update_port_b = !!(io_change&0x80);
 | 
				
			||||||
 | 
								update_port_a = !!(io_change&0x40);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								update_port_b = selected_register_ == 15;
 | 
				
			||||||
 | 
								update_port_a = selected_register_ != 15;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Keep a copy of the new value that is usable from the emulation thread.
 | 
				
			||||||
 | 
						registers_[selected_register_] = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update ports as required.
 | 
				
			||||||
 | 
						if(update_port_b) set_port_output(true);
 | 
				
			||||||
 | 
						if(update_port_a) set_port_output(false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AY38910::get_register_value() {
 | 
					uint8_t AY38910::get_register_value() {
 | 
				
			||||||
@@ -238,6 +284,8 @@ uint8_t AY38910::get_port_output(bool port_b) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void AY38910::set_port_handler(PortHandler *handler) {
 | 
					void AY38910::set_port_handler(PortHandler *handler) {
 | 
				
			||||||
	port_handler_ = handler;
 | 
						port_handler_ = handler;
 | 
				
			||||||
 | 
						set_port_output(true);
 | 
				
			||||||
 | 
						set_port_output(false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_data_input(uint8_t r) {
 | 
					void AY38910::set_data_input(uint8_t r) {
 | 
				
			||||||
@@ -245,6 +293,16 @@ void AY38910::set_data_input(uint8_t r) {
 | 
				
			|||||||
	update_bus();
 | 
						update_bus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AY38910::set_port_output(bool port_b) {
 | 
				
			||||||
 | 
						// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
 | 
				
			||||||
 | 
						// so that when in the "input" mode, all pins will read normally high". Therefore,
 | 
				
			||||||
 | 
						// report programmer selection of input mode as creating an output of 0xff.
 | 
				
			||||||
 | 
						if(port_handler_) {
 | 
				
			||||||
 | 
							const bool is_output = !!(registers_[7] & (port_b ? 0x80 : 0x40));
 | 
				
			||||||
 | 
							port_handler_->set_port_output(port_b, is_output ? registers_[port_b ? 15 : 14] : 0xff);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AY38910::get_data_output() {
 | 
					uint8_t AY38910::get_data_output() {
 | 
				
			||||||
	if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
 | 
						if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
 | 
				
			||||||
		// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
 | 
							// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
 | 
				
			||||||
@@ -253,7 +311,7 @@ uint8_t AY38910::get_data_output() {
 | 
				
			|||||||
		const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff;
 | 
							const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch(selected_register_) {
 | 
							switch(selected_register_) {
 | 
				
			||||||
			default: 	break;
 | 
								default:	break;
 | 
				
			||||||
			case 14:	return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff);
 | 
								case 14:	return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff);
 | 
				
			||||||
			case 15:	return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff);
 | 
								case 15:	return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -280,7 +338,7 @@ void AY38910::update_bus() {
 | 
				
			|||||||
	// Assume no output, unless this turns out to be a read.
 | 
						// Assume no output, unless this turns out to be a read.
 | 
				
			||||||
	data_output_ = 0xff;
 | 
						data_output_ = 0xff;
 | 
				
			||||||
	switch(control_state_) {
 | 
						switch(control_state_) {
 | 
				
			||||||
		default: 			break;
 | 
							default:			break;
 | 
				
			||||||
		case LatchAddress:	select_register(data_input_);			break;
 | 
							case LatchAddress:	select_register(data_input_);			break;
 | 
				
			||||||
		case Write:			set_register_value(data_input_);		break;
 | 
							case Write:			set_register_value(data_input_);		break;
 | 
				
			||||||
		case Read:			data_output_ = get_register_value();	break;
 | 
							case Read:			data_output_ = get_register_value();	break;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,9 +35,10 @@ class PortHandler {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Requests the current input on an AY port.
 | 
								Sets the current output on an AY port.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
 | 
								@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
 | 
				
			||||||
 | 
								@param value the value now being output.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		virtual void set_port_output(bool port_b, uint8_t value) {}
 | 
							virtual void set_port_output(bool port_b, uint8_t value) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -51,6 +52,13 @@ enum ControlLines {
 | 
				
			|||||||
	BDIR	= (1 << 2)
 | 
						BDIR	= (1 << 2)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class Personality {
 | 
				
			||||||
 | 
						/// Provides 16 volume levels to envelopes.
 | 
				
			||||||
 | 
						AY38910,
 | 
				
			||||||
 | 
						/// Provides 32 volume levels to envelopes.
 | 
				
			||||||
 | 
						YM2149F
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
 | 
						Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
 | 
				
			||||||
	noise generator and a volume envelope generator, which also provides two bidirectional
 | 
						noise generator and a volume envelope generator, which also provides two bidirectional
 | 
				
			||||||
@@ -59,7 +67,7 @@ enum ControlLines {
 | 
				
			|||||||
class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
					class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Creates a new AY38910.
 | 
							/// Creates a new AY38910.
 | 
				
			||||||
		AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
 | 
							AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the value the AY would read from its data lines if it were not outputting.
 | 
							/// Sets the value the AY would read from its data lines if it were not outputting.
 | 
				
			||||||
		void set_data_input(uint8_t r);
 | 
							void set_data_input(uint8_t r);
 | 
				
			||||||
@@ -83,7 +91,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
		*/
 | 
							*/
 | 
				
			||||||
		void set_port_handler(PortHandler *);
 | 
							void set_port_handler(PortHandler *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
 | 
							// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
 | 
				
			||||||
		void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
							void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
				
			||||||
		bool is_zero_level();
 | 
							bool is_zero_level();
 | 
				
			||||||
		void set_sample_volume_range(std::int16_t range);
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
@@ -108,11 +116,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		int envelope_period_ = 0;
 | 
							int envelope_period_ = 0;
 | 
				
			||||||
		int envelope_divider_ = 0;
 | 
							int envelope_divider_ = 0;
 | 
				
			||||||
		int envelope_position_ = 0;
 | 
							int envelope_position_ = 0, envelope_position_mask_ = 0;
 | 
				
			||||||
		int envelope_shapes_[16][32];
 | 
							int envelope_shapes_[16][64];
 | 
				
			||||||
		int envelope_overflow_masks_[16];
 | 
							int envelope_overflow_masks_[16];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int volumes_[16];
 | 
							int volumes_[32];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		enum ControlState {
 | 
							enum ControlState {
 | 
				
			||||||
			Inactive,
 | 
								Inactive,
 | 
				
			||||||
@@ -128,10 +136,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
		uint8_t data_input_, data_output_;
 | 
							uint8_t data_input_, data_output_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int16_t output_volume_;
 | 
							int16_t output_volume_;
 | 
				
			||||||
		inline void evaluate_output_volume();
 | 
							void evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void update_bus();
 | 
							void update_bus();
 | 
				
			||||||
		PortHandler *port_handler_ = nullptr;
 | 
							PortHandler *port_handler_ = nullptr;
 | 
				
			||||||
 | 
							void set_port_output(bool port_b);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ namespace  {
 | 
				
			|||||||
DiskII::DiskII(int clock_rate) :
 | 
					DiskII::DiskII(int clock_rate) :
 | 
				
			||||||
	clock_rate_(clock_rate),
 | 
						clock_rate_(clock_rate),
 | 
				
			||||||
	inputs_(input_command),
 | 
						inputs_(input_command),
 | 
				
			||||||
	drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
 | 
						drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	drives_[0].set_clocking_hint_observer(this);
 | 
						drives_[0].set_clocking_hint_observer(this);
 | 
				
			||||||
	drives_[1].set_clocking_hint_observer(this);
 | 
						drives_[1].set_clocking_hint_observer(this);
 | 
				
			||||||
@@ -78,7 +78,7 @@ void DiskII::select_drive(int drive) {
 | 
				
			|||||||
void DiskII::run_for(const Cycles cycles) {
 | 
					void DiskII::run_for(const Cycles cycles) {
 | 
				
			||||||
	if(preferred_clocking() == ClockingHint::Preference::None) return;
 | 
						if(preferred_clocking() == ClockingHint::Preference::None) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int integer_cycles = cycles.as_int();
 | 
						auto integer_cycles = cycles.as_integral();
 | 
				
			||||||
	while(integer_cycles--) {
 | 
						while(integer_cycles--) {
 | 
				
			||||||
		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
 | 
							const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
 | 
				
			||||||
		if(flux_duration_) {
 | 
							if(flux_duration_) {
 | 
				
			||||||
@@ -124,7 +124,7 @@ void DiskII::run_for(const Cycles cycles) {
 | 
				
			|||||||
	// motor switch being flipped and the drive motor actually switching off.
 | 
						// motor switch being flipped and the drive motor actually switching off.
 | 
				
			||||||
	// This models that, accepting overrun as a risk.
 | 
						// This models that, accepting overrun as a risk.
 | 
				
			||||||
	if(motor_off_time_ >= 0) {
 | 
						if(motor_off_time_ >= 0) {
 | 
				
			||||||
		motor_off_time_ -= cycles.as_int();
 | 
							motor_off_time_ -= cycles.as_integral();
 | 
				
			||||||
		if(motor_off_time_ < 0) {
 | 
							if(motor_off_time_ < 0) {
 | 
				
			||||||
			set_control(Control::Motor, false);
 | 
								set_control(Control::Motor, false);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -211,7 +211,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
 | 
				
			|||||||
	drives_[drive].set_disk(disk);
 | 
						drives_[drive].set_disk(disk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
 | 
					void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
 | 
				
			||||||
	if(event.type == Storage::Disk::Track::Event::FluxTransition) {
 | 
						if(event.type == Storage::Disk::Track::Event::FluxTransition) {
 | 
				
			||||||
		inputs_ &= ~input_flux;
 | 
							inputs_ &= ~input_flux;
 | 
				
			||||||
		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
 | 
							flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
 | 
				
			||||||
@@ -266,7 +266,7 @@ int DiskII::read_address(int address) {
 | 
				
			|||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0xf:
 | 
							case 0xf:
 | 
				
			||||||
			if(!(inputs_ & input_mode))
 | 
								if(!(inputs_ & input_mode))
 | 
				
			||||||
				drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
 | 
									drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
 | 
				
			||||||
			inputs_ |= input_mode;
 | 
								inputs_ |= input_mode;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@ namespace Apple {
 | 
				
			|||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Provides an emulation of the Apple Disk II.
 | 
						Provides an emulation of the Apple Disk II.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class DiskII:
 | 
					class DiskII final:
 | 
				
			||||||
	public Storage::Disk::Drive::EventDelegate,
 | 
						public Storage::Disk::Drive::EventDelegate,
 | 
				
			||||||
	public ClockingHint::Source,
 | 
						public ClockingHint::Source,
 | 
				
			||||||
	public ClockingHint::Observer {
 | 
						public ClockingHint::Observer {
 | 
				
			||||||
@@ -48,13 +48,13 @@ class DiskII:
 | 
				
			|||||||
			The value returned by @c read_address if accessing that address
 | 
								The value returned by @c read_address if accessing that address
 | 
				
			||||||
			didn't cause the disk II to place anything onto the bus.
 | 
								didn't cause the disk II to place anything onto the bus.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		const int DidNotLoad = -1;
 | 
							static constexpr int DidNotLoad = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Advances the controller by @c cycles.
 | 
							/// Advances the controller by @c cycles.
 | 
				
			||||||
		void run_for(const Cycles cycles);
 | 
							void run_for(const Cycles cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
		 	Supplies the image of the state machine (i.e. P6) ROM,
 | 
								Supplies the image of the state machine (i.e. P6) ROM,
 | 
				
			||||||
			which dictates how the Disk II will respond to input.
 | 
								which dictates how the Disk II will respond to input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			To reduce processing costs, some assumptions are made by
 | 
								To reduce processing costs, some assumptions are made by
 | 
				
			||||||
@@ -76,7 +76,7 @@ class DiskII:
 | 
				
			|||||||
		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
 | 
							void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// As per Sleeper.
 | 
							// As per Sleeper.
 | 
				
			||||||
		ClockingHint::Preference preferred_clocking() override;
 | 
							ClockingHint::Preference preferred_clocking() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// The Disk II functions as a potential target for @c Activity::Sources.
 | 
							// The Disk II functions as a potential target for @c Activity::Sources.
 | 
				
			||||||
		void set_activity_observer(Activity::Observer *observer);
 | 
							void set_activity_observer(Activity::Observer *observer);
 | 
				
			||||||
@@ -98,10 +98,10 @@ class DiskII:
 | 
				
			|||||||
		void select_drive(int drive);
 | 
							void select_drive(int drive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint8_t trigger_address(int address, uint8_t value);
 | 
							uint8_t trigger_address(int address, uint8_t value);
 | 
				
			||||||
		void process_event(const Storage::Disk::Track::Event &event) override;
 | 
							void process_event(const Storage::Disk::Drive::Event &event) override;
 | 
				
			||||||
		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
 | 
							void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const int clock_rate_ = 0;
 | 
							const Cycles::IntType clock_rate_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint8_t state_ = 0;
 | 
							uint8_t state_ = 0;
 | 
				
			||||||
		uint8_t inputs_ = 0;
 | 
							uint8_t inputs_ = 0;
 | 
				
			||||||
@@ -109,7 +109,7 @@ class DiskII:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		int stepper_mask_ = 0;
 | 
							int stepper_mask_ = 0;
 | 
				
			||||||
		int stepper_position_ = 0;
 | 
							int stepper_position_ = 0;
 | 
				
			||||||
		int motor_off_time_ = -1;
 | 
							Cycles::IntType motor_off_time_ = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool is_write_protected();
 | 
							bool is_write_protected();
 | 
				
			||||||
		std::array<uint8_t, 256> state_machine_;
 | 
							std::array<uint8_t, 256> state_machine_;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										406
									
								
								Components/DiskII/IWM.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								Components/DiskII/IWM.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,406 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  IWM.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 05/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IWM.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace  {
 | 
				
			||||||
 | 
						constexpr int CA0		= 1 << 0;
 | 
				
			||||||
 | 
						constexpr int CA1		= 1 << 1;
 | 
				
			||||||
 | 
						constexpr int CA2		= 1 << 2;
 | 
				
			||||||
 | 
						constexpr int LSTRB		= 1 << 3;
 | 
				
			||||||
 | 
						constexpr int ENABLE	= 1 << 4;
 | 
				
			||||||
 | 
						constexpr int DRIVESEL	= 1 << 5;	/* This means drive select, like on the original Disk II. */
 | 
				
			||||||
 | 
						constexpr int Q6		= 1 << 6;
 | 
				
			||||||
 | 
						constexpr int Q7		= 1 << 7;
 | 
				
			||||||
 | 
						constexpr int SEL		= 1 << 8;	/* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					IWM::IWM(int clock_rate) :
 | 
				
			||||||
 | 
						clock_rate_(clock_rate) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Bus accessors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t IWM::read(int address) {
 | 
				
			||||||
 | 
						access(address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Per Inside Macintosh:
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
 | 
				
			||||||
 | 
						// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
 | 
				
			||||||
 | 
						// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
 | 
				
			||||||
 | 
						// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// My understanding:
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						//	Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
 | 
				
			||||||
 | 
						//	the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(address&1) {
 | 
				
			||||||
 | 
							return 0xff;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(state_ & (Q6 | Q7 | ENABLE)) {
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								LOG("[IWM] Invalid read\n");
 | 
				
			||||||
 | 
							return 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// "Read all 1s".
 | 
				
			||||||
 | 
					//			printf("Reading all 1s\n");
 | 
				
			||||||
 | 
					//		return 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 0:
 | 
				
			||||||
 | 
							case ENABLE: {				/* Read data register. Zeroing afterwards is a guess. */
 | 
				
			||||||
 | 
								const auto result = data_register_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(data_register_ & 0x80) {
 | 
				
			||||||
 | 
					//				printf("\n\nIWM:%02x\n\n", data_register_);
 | 
				
			||||||
 | 
					//				printf(".");
 | 
				
			||||||
 | 
									data_register_ = 0;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					//			LOG("Reading data register: " << PADHEX(2) << int(result));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return result;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case Q6: case Q6|ENABLE: {
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									[If A = 0], Read status register:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									bits 0-4: same as mode register.
 | 
				
			||||||
 | 
									bit 5: 1 = either /ENBL1 or /ENBL2 is currently low.
 | 
				
			||||||
 | 
									bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0).
 | 
				
			||||||
 | 
									bit 7: 1 = SENSE input high; 0 = SENSE input low.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
 | 
				
			||||||
 | 
									If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return uint8_t(
 | 
				
			||||||
 | 
									(mode_&0x1f) |
 | 
				
			||||||
 | 
									((state_ & ENABLE) ? 0x20 : 0x00) |
 | 
				
			||||||
 | 
									(sense() & 0x80)
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case Q7: case Q7|ENABLE:
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									Read write-handshake register:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									bits 0-5: reserved for future use (currently read as 1).
 | 
				
			||||||
 | 
									bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
 | 
				
			||||||
 | 
									bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
					//			LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_));
 | 
				
			||||||
 | 
							return 0x3f | write_handshake_;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0xff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::write(int address, uint8_t input) {
 | 
				
			||||||
 | 
						access(address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(state_ & (Q6 | Q7 | ENABLE)) {
 | 
				
			||||||
 | 
							default: break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case Q7|Q6:
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									Write mode register:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									bit 0: 1 = latch mode (should be set in asynchronous mode).
 | 
				
			||||||
 | 
									bit 1: 0 = synchronous handshake protocol; 1 = asynchronous.
 | 
				
			||||||
 | 
									bit 2: 0 = 1-second on-board timer enable; 1 = timer disable.
 | 
				
			||||||
 | 
									bit 3: 0 = slow mode; 1 = fast mode.
 | 
				
			||||||
 | 
									bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor).
 | 
				
			||||||
 | 
									bit 5: 1 = test mode; 0 = normal operation.
 | 
				
			||||||
 | 
									bit 6: 1 = MZ-reset.
 | 
				
			||||||
 | 
									bit 7: reserved for future expansion.
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								mode_ = input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch(mode_ & 0x18) {
 | 
				
			||||||
 | 
									case 0x00:		bit_length_ = Cycles(24);		break;	// slow mode, 7Mhz
 | 
				
			||||||
 | 
									case 0x08:		bit_length_ = Cycles(12);		break;	// fast mode, 7Mhz
 | 
				
			||||||
 | 
									case 0x10:		bit_length_ = Cycles(32);		break;	// slow mode, 8Mhz
 | 
				
			||||||
 | 
									case 0x18:		bit_length_ = Cycles(16);		break;	// fast mode, 8Mhz
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								LOG("IWM mode is now " << PADHEX(2) << int(mode_));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case Q7|Q6|ENABLE:	// Write data register.
 | 
				
			||||||
 | 
								next_output_ = input;
 | 
				
			||||||
 | 
								write_handshake_ &= ~0x80;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Switch access
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::access(int address) {
 | 
				
			||||||
 | 
						// Keep a record of switch state; bits in state_
 | 
				
			||||||
 | 
						// should correlate with the anonymous namespace constants
 | 
				
			||||||
 | 
						// defined at the top of this file — CA0, CA1, etc.
 | 
				
			||||||
 | 
						address &= 0xf;
 | 
				
			||||||
 | 
						const auto mask = 1 << (address >> 1);
 | 
				
			||||||
 | 
						const auto old_state = state_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(address & 1) {
 | 
				
			||||||
 | 
							state_ |= mask;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							state_ &= ~mask;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode.
 | 
				
			||||||
 | 
						if(old_state != state_) {
 | 
				
			||||||
 | 
							push_drive_state();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch(mask) {
 | 
				
			||||||
 | 
								default: break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case ENABLE:
 | 
				
			||||||
 | 
									if(address & 1) {
 | 
				
			||||||
 | 
										if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										// If the 1-second delay is enabled, set up a timer for that.
 | 
				
			||||||
 | 
										if(!(mode_ & 4)) {
 | 
				
			||||||
 | 
											cycles_until_disable_ = Cycles(clock_rate_);
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case DRIVESEL: {
 | 
				
			||||||
 | 
									const int new_drive = address & 1;
 | 
				
			||||||
 | 
									if(new_drive != active_drive_) {
 | 
				
			||||||
 | 
										if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
 | 
				
			||||||
 | 
										active_drive_ = new_drive;
 | 
				
			||||||
 | 
										if(drives_[active_drive_]) {
 | 
				
			||||||
 | 
											drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0)));
 | 
				
			||||||
 | 
											push_drive_state();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case Q6:
 | 
				
			||||||
 | 
								case Q7:
 | 
				
			||||||
 | 
									select_shift_mode();
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::set_select(bool enabled) {
 | 
				
			||||||
 | 
						// Store SEL as an extra state bit.
 | 
				
			||||||
 | 
						if(enabled) state_ |= SEL;
 | 
				
			||||||
 | 
						else state_ &= ~SEL;
 | 
				
			||||||
 | 
						push_drive_state();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::push_drive_state() {
 | 
				
			||||||
 | 
						if(drives_[active_drive_])  {
 | 
				
			||||||
 | 
							const uint8_t drive_control_lines =
 | 
				
			||||||
 | 
								((state_ & CA0) ? IWMDrive::CA0 : 0) |
 | 
				
			||||||
 | 
								((state_ & CA1) ? IWMDrive::CA1 : 0) |
 | 
				
			||||||
 | 
								((state_ & CA2) ? IWMDrive::CA2 : 0) |
 | 
				
			||||||
 | 
								((state_ & SEL) ? IWMDrive::SEL : 0) |
 | 
				
			||||||
 | 
								((state_ & LSTRB) ? IWMDrive::LSTRB : 0);
 | 
				
			||||||
 | 
							drives_[active_drive_]->set_control_lines(drive_control_lines);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Active logic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::run_for(const Cycles cycles) {
 | 
				
			||||||
 | 
						// Check for a timeout of the motor-off timer.
 | 
				
			||||||
 | 
						if(cycles_until_disable_ > Cycles(0)) {
 | 
				
			||||||
 | 
							cycles_until_disable_ -= cycles;
 | 
				
			||||||
 | 
							if(cycles_until_disable_ <= Cycles(0)) {
 | 
				
			||||||
 | 
								cycles_until_disable_ = Cycles(0);
 | 
				
			||||||
 | 
								if(drives_[active_drive_])
 | 
				
			||||||
 | 
									drives_[active_drive_]->set_enabled(false);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Activity otherwise depends on mode and motor state.
 | 
				
			||||||
 | 
						auto integer_cycles = cycles.as_integral();
 | 
				
			||||||
 | 
						switch(shift_mode_) {
 | 
				
			||||||
 | 
							case ShiftMode::Reading: {
 | 
				
			||||||
 | 
								// Per the IWM patent, column 7, around line 35 onwards: "The expected time
 | 
				
			||||||
 | 
								// is widened by approximately one-half an interval before and after the
 | 
				
			||||||
 | 
								// expected time since the data is not precisely spaced when read due to
 | 
				
			||||||
 | 
								// variations in drive speed and other external factors". The error_margin
 | 
				
			||||||
 | 
								// here implements the 'after' part of that contract.
 | 
				
			||||||
 | 
								const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(drive_is_rotating_[active_drive_]) {
 | 
				
			||||||
 | 
									while(integer_cycles--) {
 | 
				
			||||||
 | 
										drives_[active_drive_]->run_for(Cycles(1));
 | 
				
			||||||
 | 
										++cycles_since_shift_;
 | 
				
			||||||
 | 
										if(cycles_since_shift_ == bit_length_ + error_margin) {
 | 
				
			||||||
 | 
											propose_shift(0);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) {
 | 
				
			||||||
 | 
										const auto run_length = bit_length_ + error_margin - cycles_since_shift_;
 | 
				
			||||||
 | 
										integer_cycles -= run_length.as_integral();
 | 
				
			||||||
 | 
										cycles_since_shift_ += run_length;
 | 
				
			||||||
 | 
										propose_shift(0);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									cycles_since_shift_ += Cycles(integer_cycles);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case ShiftMode::Writing:
 | 
				
			||||||
 | 
								if(drives_[active_drive_]->is_writing()) {
 | 
				
			||||||
 | 
									while(cycles_since_shift_ + integer_cycles >= bit_length_) {
 | 
				
			||||||
 | 
										const auto cycles_until_write = bit_length_ - cycles_since_shift_;
 | 
				
			||||||
 | 
										drives_[active_drive_]->run_for(cycles_until_write);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Output a flux transition if the top bit is set.
 | 
				
			||||||
 | 
										drives_[active_drive_]->write_bit(shift_register_ & 0x80);
 | 
				
			||||||
 | 
										shift_register_ <<= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										integer_cycles -= cycles_until_write.as_integral();
 | 
				
			||||||
 | 
										cycles_since_shift_ = Cycles(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										--output_bits_remaining_;
 | 
				
			||||||
 | 
										if(!output_bits_remaining_) {
 | 
				
			||||||
 | 
											if(!(write_handshake_ & 0x80)) {
 | 
				
			||||||
 | 
												write_handshake_ |= 0x80;
 | 
				
			||||||
 | 
												shift_register_ = next_output_;
 | 
				
			||||||
 | 
												output_bits_remaining_ = 8;
 | 
				
			||||||
 | 
					//							LOG("Next byte: " << PADHEX(2) << int(shift_register_));
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												write_handshake_ &= ~0x40;
 | 
				
			||||||
 | 
												drives_[active_drive_]->end_writing();
 | 
				
			||||||
 | 
					//							printf("\n");
 | 
				
			||||||
 | 
												LOG("Overrun; done.");
 | 
				
			||||||
 | 
												select_shift_mode();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									cycles_since_shift_ = integer_cycles;
 | 
				
			||||||
 | 
									if(integer_cycles) {
 | 
				
			||||||
 | 
										drives_[active_drive_]->run_for(cycles_since_shift_);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									drives_[active_drive_]->run_for(cycles);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case ShiftMode::CheckingWriteProtect:
 | 
				
			||||||
 | 
								if(integer_cycles < 8) {
 | 
				
			||||||
 | 
									shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles)));
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									shift_register_ = sense();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Deliberate fallthrough. */
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::select_shift_mode() {
 | 
				
			||||||
 | 
						// Don't allow an ongoing write to be interrupted.
 | 
				
			||||||
 | 
						if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto old_shift_mode = shift_mode_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(state_ & (Q6|Q7)) {
 | 
				
			||||||
 | 
							default:	shift_mode_ = ShiftMode::CheckingWriteProtect;		break;
 | 
				
			||||||
 | 
							case 0:		shift_mode_ = ShiftMode::Reading;					break;
 | 
				
			||||||
 | 
							case Q7:
 | 
				
			||||||
 | 
								// "The IWM is put into the write state by a transition from the write protect sense state to the
 | 
				
			||||||
 | 
								// write load state".
 | 
				
			||||||
 | 
								if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If writing mode just began, set the drive into write mode and cue up the first output byte.
 | 
				
			||||||
 | 
						if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
 | 
				
			||||||
 | 
							drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
 | 
				
			||||||
 | 
							shift_register_ = next_output_;
 | 
				
			||||||
 | 
							write_handshake_ |= 0x80 | 0x40;
 | 
				
			||||||
 | 
							output_bits_remaining_ = 8;
 | 
				
			||||||
 | 
							LOG("Seeding output with " << PADHEX(2) << shift_register_);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t IWM::sense() {
 | 
				
			||||||
 | 
						return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::process_event(const Storage::Disk::Drive::Event &event) {
 | 
				
			||||||
 | 
						if(shift_mode_ != ShiftMode::Reading) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch(event.type) {
 | 
				
			||||||
 | 
							case Storage::Disk::Track::Event::IndexHole: return;
 | 
				
			||||||
 | 
							case Storage::Disk::Track::Event::FluxTransition:
 | 
				
			||||||
 | 
								propose_shift(1);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::propose_shift(uint8_t bit) {
 | 
				
			||||||
 | 
						// TODO: synchronous mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	LOG("Shifting input");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// See above for text from the IWM patent, column 7, around line 35 onwards.
 | 
				
			||||||
 | 
						// The error_margin here implements the 'before' part of that contract.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred
 | 
				
			||||||
 | 
						// on the current expected bit delivery time as implied by cycles_since_shift_,
 | 
				
			||||||
 | 
						// shift in a 1 and start a new window wherever the first found 1 was.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// If no 1s are found, shift in a 0 and don't alter expectations as to window placement.
 | 
				
			||||||
 | 
						const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
 | 
				
			||||||
 | 
						if(bit && cycles_since_shift_ < error_margin) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						shift_register_ = uint8_t((shift_register_ << 1) | bit);
 | 
				
			||||||
 | 
						if(shift_register_ & 0x80) {
 | 
				
			||||||
 | 
							data_register_ = shift_register_;
 | 
				
			||||||
 | 
							shift_register_ = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(bit)
 | 
				
			||||||
 | 
							cycles_since_shift_ = Cycles(0);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							cycles_since_shift_ -= bit_length_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::set_drive(int slot, IWMDrive *drive) {
 | 
				
			||||||
 | 
						drives_[slot] = drive;
 | 
				
			||||||
 | 
						drive->set_event_delegate(this);
 | 
				
			||||||
 | 
						drive->set_clocking_hint_observer(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
 | 
				
			||||||
 | 
						const bool is_rotating = clocking != ClockingHint::Preference::None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(component == static_cast<ClockingHint::Source *>(drives_[0])) {
 | 
				
			||||||
 | 
							drive_is_rotating_[0] = is_rotating;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							drive_is_rotating_[1] = is_rotating;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IWM::set_activity_observer(Activity::Observer *observer) {
 | 
				
			||||||
 | 
						if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true);
 | 
				
			||||||
 | 
						if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										124
									
								
								Components/DiskII/IWM.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								Components/DiskII/IWM.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  IWM.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 05/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef IWM_hpp
 | 
				
			||||||
 | 
					#define IWM_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Activity/Observer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockingHintSource.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/Drive.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Defines the drive interface used by the IWM, derived from the external pinout as
 | 
				
			||||||
 | 
						per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
 | 
				
			||||||
 | 
						and provide the usual read/write interface for on-disk data.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					struct IWMDrive: public Storage::Disk::Drive {
 | 
				
			||||||
 | 
						IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enum Line: int {
 | 
				
			||||||
 | 
							CA0		= 1 << 0,
 | 
				
			||||||
 | 
							CA1		= 1 << 1,
 | 
				
			||||||
 | 
							CA2		= 1 << 2,
 | 
				
			||||||
 | 
							LSTRB	= 1 << 3,
 | 
				
			||||||
 | 
							SEL		= 1 << 4,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void set_enabled(bool) = 0;
 | 
				
			||||||
 | 
						virtual void set_control_lines(int) = 0;
 | 
				
			||||||
 | 
						virtual bool read() = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IWM:
 | 
				
			||||||
 | 
						public Storage::Disk::Drive::EventDelegate,
 | 
				
			||||||
 | 
						public ClockingHint::Observer {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							IWM(int clock_rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets the current external value of the data bus.
 | 
				
			||||||
 | 
							void write(int address, uint8_t value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Submits an access to address @c address.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@returns The 8-bit value loaded to the data bus by the IWM.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							uint8_t read(int address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the current input of the IWM's SEL line.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_select(bool enabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Advances the controller by @c cycles.
 | 
				
			||||||
 | 
							void run_for(const Cycles cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Connects a drive to the IWM.
 | 
				
			||||||
 | 
							void set_drive(int slot, IWMDrive *drive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Registers the currently-connected drives as @c Activity::Sources ;
 | 
				
			||||||
 | 
							/// the first will be declared 'Internal', the second 'External'.
 | 
				
			||||||
 | 
							void set_activity_observer(Activity::Observer *observer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							// Storage::Disk::Drive::EventDelegate.
 | 
				
			||||||
 | 
							void process_event(const Storage::Disk::Drive::Event &event) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const int clock_rate_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t data_register_ = 0;
 | 
				
			||||||
 | 
							uint8_t mode_ = 0;
 | 
				
			||||||
 | 
							bool read_write_ready_ = true;
 | 
				
			||||||
 | 
							bool write_overran_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int state_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int active_drive_ = 0;
 | 
				
			||||||
 | 
							IWMDrive *drives_[2] = {nullptr, nullptr};
 | 
				
			||||||
 | 
							bool drive_is_rotating_[2] = {false, false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Cycles cycles_until_disable_;
 | 
				
			||||||
 | 
							uint8_t write_handshake_ = 0x80;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void access(int address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t shift_register_ = 0;
 | 
				
			||||||
 | 
							uint8_t next_output_ = 0;
 | 
				
			||||||
 | 
							int output_bits_remaining_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void propose_shift(uint8_t bit);
 | 
				
			||||||
 | 
							Cycles cycles_since_shift_;
 | 
				
			||||||
 | 
							Cycles bit_length_ = Cycles(16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void push_drive_state();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class ShiftMode {
 | 
				
			||||||
 | 
								Reading,
 | 
				
			||||||
 | 
								Writing,
 | 
				
			||||||
 | 
								CheckingWriteProtect
 | 
				
			||||||
 | 
							} shift_mode_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t sense();
 | 
				
			||||||
 | 
							void select_shift_mode();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* IWM_hpp */
 | 
				
			||||||
							
								
								
									
										181
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,181 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  MacintoshDoubleDensityDrive.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 10/07/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MacintoshDoubleDensityDrive.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
						Sources used pervasively:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html
 | 
				
			||||||
 | 
						Apple Guide to the Macintosh Family Hardware
 | 
				
			||||||
 | 
						Inside Macintosh III
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple::Macintosh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
 | 
				
			||||||
 | 
						IWMDrive(input_clock_rate, is_800k ? 2 : 1),	// Only 800kb drives are double sided.
 | 
				
			||||||
 | 
						is_800k_(is_800k) {
 | 
				
			||||||
 | 
						// Start with a valid rotation speed.
 | 
				
			||||||
 | 
						if(is_800k) {
 | 
				
			||||||
 | 
							Drive::set_rotation_speed(393.3807f);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Speed Selection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
 | 
				
			||||||
 | 
					//	printf("At track %d\n", to_position.as_int());
 | 
				
			||||||
 | 
						// The 800kb drive automatically selects rotation speed as a function of
 | 
				
			||||||
 | 
						// head position; the 400kb drive doesn't do so.
 | 
				
			||||||
 | 
						if(is_800k_) {
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								Numbers below cribbed from the Kryoflux forums; specifically:
 | 
				
			||||||
 | 
								https://forum.kryoflux.com/viewtopic.php?t=1090
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								They can almost be worked out algorithmically, since the point is to
 | 
				
			||||||
 | 
								produce an almost-constant value for speed*(number of sectors), and:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								393.3807 * 12 = 4720.5684
 | 
				
			||||||
 | 
								429.1723 * 11 = 4720.895421
 | 
				
			||||||
 | 
								472.1435 * 10 = 4721.435
 | 
				
			||||||
 | 
								524.5672 * 9 = 4721.1048
 | 
				
			||||||
 | 
								590.1098 * 8 = 4720.8784
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								So 4721 / (number of sectors per track in zone) would give essentially
 | 
				
			||||||
 | 
								the same results.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							const int zone = to_position.as_int() >> 4;
 | 
				
			||||||
 | 
							switch(zone) {
 | 
				
			||||||
 | 
								case 0:		Drive::set_rotation_speed(393.3807f);	break;
 | 
				
			||||||
 | 
								case 1:		Drive::set_rotation_speed(429.1723f);	break;
 | 
				
			||||||
 | 
								case 2:		Drive::set_rotation_speed(472.1435f);	break;
 | 
				
			||||||
 | 
								case 3:		Drive::set_rotation_speed(524.5672f);	break;
 | 
				
			||||||
 | 
								default:	Drive::set_rotation_speed(590.1098f);	break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
 | 
				
			||||||
 | 
						if(!is_800k_) {
 | 
				
			||||||
 | 
							// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
 | 
				
			||||||
 | 
							// to sanity.
 | 
				
			||||||
 | 
							Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Control input/output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DoubleDensityDrive::set_enabled(bool enabled) {
 | 
				
			||||||
 | 
						// Disabling a drive also stops its motor.
 | 
				
			||||||
 | 
						if(!enabled) set_motor_on(false);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DoubleDensityDrive::set_control_lines(int lines) {
 | 
				
			||||||
 | 
						const auto old_state = control_state_;
 | 
				
			||||||
 | 
						control_state_ = lines;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Catch low-to-high LSTRB transitions.
 | 
				
			||||||
 | 
						if((old_state ^ control_state_) & control_state_ & Line::LSTRB) {
 | 
				
			||||||
 | 
							switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) {
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0:						// Set step direction — CA2 set => step outward.
 | 
				
			||||||
 | 
								case Line::CA2:
 | 
				
			||||||
 | 
									step_direction_ = (control_state_ & Line::CA2) ? -1 : 1;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case Line::CA1:				// Set drive motor — CA2 set => motor off.
 | 
				
			||||||
 | 
								case Line::CA1|Line::CA2:
 | 
				
			||||||
 | 
									set_motor_on(!(control_state_ & Line::CA2));
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case Line::CA0:				// Initiate a step.
 | 
				
			||||||
 | 
									step(Storage::Disk::HeadPosition(step_direction_));
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case Line::SEL|Line::CA2:	// Reset new disk flag.
 | 
				
			||||||
 | 
									has_new_disk_ = false;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case Line::CA2 | Line::CA1 | Line::CA0:	// Eject the disk.
 | 
				
			||||||
 | 
									set_disk(nullptr);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DoubleDensityDrive::read() {
 | 
				
			||||||
 | 
						switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) {
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 0:					// Head step direction.
 | 
				
			||||||
 | 
													// (0 = inward)
 | 
				
			||||||
 | 
							return step_direction_ <= 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case SEL:				// Disk in place.
 | 
				
			||||||
 | 
													// (0 = disk present)
 | 
				
			||||||
 | 
							return !has_disk();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA0:				// Disk head step completed.
 | 
				
			||||||
 | 
													// (0 = still stepping)
 | 
				
			||||||
 | 
							return true;	// TODO: stepping delay. But at the main Drive level.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA0|SEL:			// Disk locked.
 | 
				
			||||||
 | 
													// (0 = write protected)
 | 
				
			||||||
 | 
							return !get_is_read_only();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA1:				// Disk motor running.
 | 
				
			||||||
 | 
													// (0 = motor on)
 | 
				
			||||||
 | 
							return !get_motor_on();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA1|SEL:			// Head at track 0.
 | 
				
			||||||
 | 
													// (0 = at track 0)
 | 
				
			||||||
 | 
													// "This bit becomes valid beginning 12 msec after the step that places the head at track 0."
 | 
				
			||||||
 | 
							return !get_is_track_zero();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA1|CA0:			// Disk has been ejected.
 | 
				
			||||||
 | 
													// (0 = user has ejected disk)
 | 
				
			||||||
 | 
							return !has_new_disk_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA1|CA0|SEL:		// Tachometer.
 | 
				
			||||||
 | 
													// (arbitrary)
 | 
				
			||||||
 | 
							return get_tachometer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA2:				// Read data, lower head.
 | 
				
			||||||
 | 
								set_head(0);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA2|SEL:			// Read data, upper head.
 | 
				
			||||||
 | 
								set_head(1);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA2|CA1:			// Single- or double-sided drive.
 | 
				
			||||||
 | 
													// (0 = single sided)
 | 
				
			||||||
 | 
							return get_head_count() != 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA2|CA1|CA0:		// "Present/HD" (per the Mac Plus ROM)
 | 
				
			||||||
 | 
													// (0 = ??HD??)
 | 
				
			||||||
 | 
													//
 | 
				
			||||||
 | 
													// Alternative explanation: "Disk ready for reading?"
 | 
				
			||||||
 | 
													// (0 = ready)
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case CA2|CA1|CA0|SEL:	// Drive installed.
 | 
				
			||||||
 | 
													// (0 = present, 1 = missing)
 | 
				
			||||||
 | 
													//
 | 
				
			||||||
 | 
													// TODO: why do I need to return this the wrong way around for the Mac Plus?
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DoubleDensityDrive::did_set_disk() {
 | 
				
			||||||
 | 
						has_new_disk_ = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										53
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Components/DiskII/MacintoshDoubleDensityDrive.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  MacintoshDoubleDensityDrive.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 10/07/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef MacintoshDoubleDensityDrive_hpp
 | 
				
			||||||
 | 
					#define MacintoshDoubleDensityDrive_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IWM.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DoubleDensityDrive: public IWMDrive {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							DoubleDensityDrive(int input_clock_rate, bool is_800k);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns @c true if this is an 800kb drive; @c false otherwise.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							bool is_800k() {
 | 
				
			||||||
 | 
								return is_800k_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the current rotation speed of this drive only if it is a 400kb drive.
 | 
				
			||||||
 | 
								800kb drives select their own rotation speed based on head position,
 | 
				
			||||||
 | 
								and ignore this input.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_rotation_speed(float revolutions_per_minute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_enabled(bool) override;
 | 
				
			||||||
 | 
							void set_control_lines(int) override;
 | 
				
			||||||
 | 
							bool read() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							// To receive the proper notifications from Storage::Disk::Drive.
 | 
				
			||||||
 | 
							void did_step(Storage::Disk::HeadPosition to_position) override;
 | 
				
			||||||
 | 
							void did_set_disk() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const bool is_800k_;
 | 
				
			||||||
 | 
							bool has_new_disk_ = false;
 | 
				
			||||||
 | 
							int control_state_ = 0;
 | 
				
			||||||
 | 
							int step_direction_ = 1;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* MacintoshDoubleDensityDrive_hpp */
 | 
				
			||||||
							
								
								
									
										140
									
								
								Components/Serial/Line.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Components/Serial/Line.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  SerialPort.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 12/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Line.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Serial;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::set_writer_clock_rate(HalfCycles clock_rate) {
 | 
				
			||||||
 | 
						clock_rate_ = clock_rate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::advance_writer(HalfCycles cycles) {
 | 
				
			||||||
 | 
						if(cycles == HalfCycles(0)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto integral_cycles = cycles.as_integral();
 | 
				
			||||||
 | 
						remaining_delays_ = std::max(remaining_delays_ - integral_cycles, Cycles::IntType(0));
 | 
				
			||||||
 | 
						if(events_.empty()) {
 | 
				
			||||||
 | 
							write_cycles_since_delegate_call_ += integral_cycles;
 | 
				
			||||||
 | 
							if(transmission_extra_) {
 | 
				
			||||||
 | 
								transmission_extra_ -= integral_cycles;
 | 
				
			||||||
 | 
								if(transmission_extra_ <= 0) {
 | 
				
			||||||
 | 
									transmission_extra_ = 0;
 | 
				
			||||||
 | 
									update_delegate(level_);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							while(!events_.empty()) {
 | 
				
			||||||
 | 
								if(events_.front().delay <= integral_cycles) {
 | 
				
			||||||
 | 
									cycles -= events_.front().delay;
 | 
				
			||||||
 | 
									write_cycles_since_delegate_call_ += events_.front().delay;
 | 
				
			||||||
 | 
									const auto old_level = level_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									auto iterator = events_.begin() + 1;
 | 
				
			||||||
 | 
									while(iterator != events_.end() && iterator->type != Event::Delay) {
 | 
				
			||||||
 | 
										level_ = iterator->type == Event::SetHigh;
 | 
				
			||||||
 | 
										++iterator;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									events_.erase(events_.begin(), iterator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if(old_level != level_) {
 | 
				
			||||||
 | 
										update_delegate(old_level);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Book enough extra time for the read delegate to be posted
 | 
				
			||||||
 | 
									// the final bit if one is attached.
 | 
				
			||||||
 | 
									if(events_.empty()) {
 | 
				
			||||||
 | 
										transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									events_.front().delay -= integral_cycles;
 | 
				
			||||||
 | 
									write_cycles_since_delegate_call_ += integral_cycles;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::write(bool level) {
 | 
				
			||||||
 | 
						if(!events_.empty()) {
 | 
				
			||||||
 | 
							events_.emplace_back();
 | 
				
			||||||
 | 
							events_.back().type = level ? Event::SetHigh : Event::SetLow;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							level_ = level;
 | 
				
			||||||
 | 
							transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::write(HalfCycles cycles, int count, int levels) {
 | 
				
			||||||
 | 
						remaining_delays_ += count * cycles.as_integral();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto event = events_.size();
 | 
				
			||||||
 | 
						events_.resize(events_.size() + size_t(count)*2);
 | 
				
			||||||
 | 
						while(count--) {
 | 
				
			||||||
 | 
							events_[event].type = Event::Delay;
 | 
				
			||||||
 | 
							events_[event].delay = int(cycles.as_integral());
 | 
				
			||||||
 | 
							events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
 | 
				
			||||||
 | 
							levels >>= 1;
 | 
				
			||||||
 | 
							event += 2;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::reset_writing() {
 | 
				
			||||||
 | 
						remaining_delays_ = 0;
 | 
				
			||||||
 | 
						events_.clear();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Line::read() {
 | 
				
			||||||
 | 
						return level_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
 | 
				
			||||||
 | 
						read_delegate_ = delegate;
 | 
				
			||||||
 | 
						read_delegate_bit_length_ = bit_length;
 | 
				
			||||||
 | 
						read_delegate_bit_length_.simplify();
 | 
				
			||||||
 | 
						write_cycles_since_delegate_call_ = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Line::update_delegate(bool level) {
 | 
				
			||||||
 | 
						// Exit early if there's no delegate, or if the delegate is waiting for
 | 
				
			||||||
 | 
						// zero and this isn't zero.
 | 
				
			||||||
 | 
						if(!read_delegate_) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const int cycles_to_forward = write_cycles_since_delegate_call_;
 | 
				
			||||||
 | 
						write_cycles_since_delegate_call_ = 0;
 | 
				
			||||||
 | 
						if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Deal with a transition out of waiting-for-zero mode by seeding time left
 | 
				
			||||||
 | 
						// in bit at half a bit.
 | 
				
			||||||
 | 
						if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
 | 
				
			||||||
 | 
							time_left_in_bit_ = read_delegate_bit_length_;
 | 
				
			||||||
 | 
							time_left_in_bit_.clock_rate <<= 1;
 | 
				
			||||||
 | 
							read_delegate_phase_ = ReadDelegatePhase::Serialising;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Forward as many bits as occur.
 | 
				
			||||||
 | 
						Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
 | 
				
			||||||
 | 
						const int bit = level ? 1 : 0;
 | 
				
			||||||
 | 
						int bits = 0;
 | 
				
			||||||
 | 
						while(time_left >= time_left_in_bit_) {
 | 
				
			||||||
 | 
							++bits;
 | 
				
			||||||
 | 
							if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
 | 
				
			||||||
 | 
								read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
 | 
				
			||||||
 | 
								if(bit) return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							time_left -= time_left_in_bit_;
 | 
				
			||||||
 | 
							time_left_in_bit_ = read_delegate_bit_length_;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						time_left_in_bit_ -= time_left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
 | 
				
			||||||
 | 
						if(!read_delegate_) return 0;
 | 
				
			||||||
 | 
						return 1 + (read_delegate_bit_length_ * static_cast<unsigned int>(clock_rate_.as_integral())).get<int>();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								Components/Serial/Line.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Components/Serial/Line.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  SerialPort.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 12/10/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef SerialPort_hpp
 | 
				
			||||||
 | 
					#define SerialPort_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include "../../Storage/Storage.hpp"
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					#include "../../ClockReceiver/ForceInline.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Serial {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						@c Line connects a single reader and a single writer, allowing timestamped events to be
 | 
				
			||||||
 | 
						published and consumed, potentially with a clock conversion in between. It allows line
 | 
				
			||||||
 | 
						levels to be written and read in larger collections.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						It is assumed that the owner of the reader and writer will ensure that the reader will never
 | 
				
			||||||
 | 
						get ahead of the writer. If the writer posts events behind the reader they will simply be
 | 
				
			||||||
 | 
						given instanteous effect.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class Line {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							void set_writer_clock_rate(HalfCycles clock_rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Advances the read position by @c cycles relative to the writer's
 | 
				
			||||||
 | 
							/// clock rate.
 | 
				
			||||||
 | 
							void advance_writer(HalfCycles cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets the line to @c level.
 | 
				
			||||||
 | 
							void write(bool level);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Enqueues @c count level changes, the first occurring immediately
 | 
				
			||||||
 | 
							/// after the final event currently posted and each subsequent event
 | 
				
			||||||
 | 
							/// occurring @c cycles after the previous. An additional gap of @c cycles
 | 
				
			||||||
 | 
							/// is scheduled after the final output. The levels to output are
 | 
				
			||||||
 | 
							/// taken from @c levels which is read from lsb to msb. @c cycles is
 | 
				
			||||||
 | 
							/// relative to the writer's clock rate.
 | 
				
			||||||
 | 
							void write(HalfCycles cycles, int count, int levels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the number of cycles until currently enqueued write data is exhausted.
 | 
				
			||||||
 | 
							forceinline HalfCycles write_data_time_remaining() const {
 | 
				
			||||||
 | 
								return HalfCycles(remaining_delays_);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the number of cycles left until it is guaranteed that a passive reader
 | 
				
			||||||
 | 
							/// has received all currently-enqueued bits.
 | 
				
			||||||
 | 
							forceinline HalfCycles transmission_data_time_remaining() const {
 | 
				
			||||||
 | 
								return HalfCycles(remaining_delays_ + transmission_extra_);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Eliminates all future write states, leaving the output at whatever it is now.
 | 
				
			||||||
 | 
							void reset_writing();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns The instantaneous level of this line.
 | 
				
			||||||
 | 
							bool read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct ReadDelegate {
 | 
				
			||||||
 | 
								virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets a read delegate, which will receive samples of the output level every
 | 
				
			||||||
 | 
								@c bit_lengths of a second apart subject to a state machine:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									* initially no bits will be delivered;
 | 
				
			||||||
 | 
									* when a zero level is first detected, the line will wait half a bit's length, then start
 | 
				
			||||||
 | 
									sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
 | 
				
			||||||
 | 
									* as soon as the delegate returns @c false, the line will return to the initial state.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							struct Event {
 | 
				
			||||||
 | 
								enum Type {
 | 
				
			||||||
 | 
									Delay, SetHigh, SetLow
 | 
				
			||||||
 | 
								} type;
 | 
				
			||||||
 | 
								int delay;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							std::vector<Event> events_;
 | 
				
			||||||
 | 
							HalfCycles::IntType remaining_delays_ = 0;
 | 
				
			||||||
 | 
							HalfCycles::IntType transmission_extra_ = 0;
 | 
				
			||||||
 | 
							bool level_ = true;
 | 
				
			||||||
 | 
							HalfCycles clock_rate_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ReadDelegate *read_delegate_ = nullptr;
 | 
				
			||||||
 | 
							Storage::Time read_delegate_bit_length_, time_left_in_bit_;
 | 
				
			||||||
 | 
							int write_cycles_since_delegate_call_ = 0;
 | 
				
			||||||
 | 
							enum class ReadDelegatePhase {
 | 
				
			||||||
 | 
								WaitingForZero,
 | 
				
			||||||
 | 
								Serialising
 | 
				
			||||||
 | 
							} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void update_delegate(bool level);
 | 
				
			||||||
 | 
							HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Defines an RS-232-esque srial port.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class Port {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* SerialPort_hpp */
 | 
				
			||||||
@@ -18,7 +18,7 @@ AsyncTaskQueue::AsyncTaskQueue()
 | 
				
			|||||||
#ifdef __APPLE__
 | 
					#ifdef __APPLE__
 | 
				
			||||||
	serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
 | 
						serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
	thread_.reset(new std::thread([this]() {
 | 
						thread_ = std::make_unique<std::thread>([this]() {
 | 
				
			||||||
		while(!should_destruct_) {
 | 
							while(!should_destruct_) {
 | 
				
			||||||
			std::function<void(void)> next_function;
 | 
								std::function<void(void)> next_function;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,7 +39,7 @@ AsyncTaskQueue::AsyncTaskQueue()
 | 
				
			|||||||
				processing_condition_.wait(lock);
 | 
									processing_condition_.wait(lock);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}));
 | 
						});
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,8 +70,8 @@ void AsyncTaskQueue::flush() {
 | 
				
			|||||||
#ifdef __APPLE__
 | 
					#ifdef __APPLE__
 | 
				
			||||||
	dispatch_sync(serial_dispatch_queue_, ^{});
 | 
						dispatch_sync(serial_dispatch_queue_, ^{});
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
	std::shared_ptr<std::mutex> flush_mutex(new std::mutex);
 | 
						auto flush_mutex = std::make_shared<std::mutex>();
 | 
				
			||||||
	std::shared_ptr<std::condition_variable> flush_condition(new std::condition_variable);
 | 
						auto flush_condition = std::make_shared<std::condition_variable>();
 | 
				
			||||||
	std::unique_lock<std::mutex> lock(*flush_mutex);
 | 
						std::unique_lock<std::mutex> lock(*flush_mutex);
 | 
				
			||||||
	enqueue([=] () {
 | 
						enqueue([=] () {
 | 
				
			||||||
		std::unique_lock<std::mutex> inner_lock(*flush_mutex);
 | 
							std::unique_lock<std::mutex> inner_lock(*flush_mutex);
 | 
				
			||||||
@@ -88,7 +88,7 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
 | 
					void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
 | 
				
			||||||
	if(!deferred_tasks_) {
 | 
						if(!deferred_tasks_) {
 | 
				
			||||||
		deferred_tasks_.reset(new std::list<std::function<void(void)>>);
 | 
							deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	deferred_tasks_->push_back(function);
 | 
						deferred_tasks_->push_back(function);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,16 +14,16 @@ namespace {
 | 
				
			|||||||
	Appends a Boolean selection of @c selection for option @c name to @c selection_set.
 | 
						Appends a Boolean selection of @c selection for option @c name to @c selection_set.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) {
 | 
					void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) {
 | 
				
			||||||
	selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection));
 | 
						selection_set[name] = std::make_unique<Configurable::BooleanSelection>(selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
 | 
						Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
 | 
					bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
 | 
				
			||||||
	auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
 | 
						auto selection = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, name);
 | 
				
			||||||
	if(!quickload) return false;
 | 
						if(!selection) return false;
 | 
				
			||||||
	result = quickload->value;
 | 
						result = selection->value;
 | 
				
			||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,14 +33,16 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std:
 | 
				
			|||||||
std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
 | 
					std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
 | 
				
			||||||
	std::vector<std::unique_ptr<Configurable::Option>> options;
 | 
						std::vector<std::unique_ptr<Configurable::Option>> options;
 | 
				
			||||||
	if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
 | 
						if(mask & QuickLoadTape)				options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
 | 
				
			||||||
	if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) {
 | 
						if(mask & (DisplayRGB | DisplayCompositeColour | DisplayCompositeMonochrome | DisplaySVideo)) {
 | 
				
			||||||
		std::vector<std::string> display_options;
 | 
							std::vector<std::string> display_options;
 | 
				
			||||||
		if(mask & DisplayComposite)	display_options.emplace_back("composite");
 | 
							if(mask & DisplayCompositeColour)		display_options.emplace_back("composite");
 | 
				
			||||||
		if(mask & DisplaySVideo) 	display_options.emplace_back("svideo");
 | 
							if(mask & DisplayCompositeMonochrome)	display_options.emplace_back("composite-mono");
 | 
				
			||||||
		if(mask & DisplayRGB) 		display_options.emplace_back("rgb");
 | 
							if(mask & DisplaySVideo)				display_options.emplace_back("svideo");
 | 
				
			||||||
 | 
							if(mask & DisplayRGB)					display_options.emplace_back("rgb");
 | 
				
			||||||
		options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
 | 
							options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
 | 
						if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
 | 
				
			||||||
 | 
						if(mask & QuickBoot)					options.emplace_back(new Configurable::BooleanOption("Boot Quickly", "quickboot"));
 | 
				
			||||||
	return options;
 | 
						return options;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -57,11 +59,16 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio
 | 
				
			|||||||
	std::string string_selection;
 | 
						std::string string_selection;
 | 
				
			||||||
	switch(selection) {
 | 
						switch(selection) {
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
		case Display::RGB:			string_selection = "rgb";		break;
 | 
							case Display::RGB:					string_selection = "rgb";				break;
 | 
				
			||||||
		case Display::SVideo:		string_selection = "svideo";	break;
 | 
							case Display::SVideo:				string_selection = "svideo";			break;
 | 
				
			||||||
		case Display::Composite:	string_selection = "composite";	break;
 | 
							case Display::CompositeMonochrome:	string_selection = "composite-mono";	break;
 | 
				
			||||||
 | 
							case Display::CompositeColour:		string_selection = "composite";			break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
 | 
						selection_set["display"] = std::make_unique<Configurable::ListSelection>(string_selection);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Configurable::append_quick_boot_selection(Configurable::SelectionSet &selection_set, bool selection) {
 | 
				
			||||||
 | 
						append_bool(selection_set, "quickboot", selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MARK: - Selection parsers
 | 
					// MARK: - Selection parsers
 | 
				
			||||||
@@ -85,9 +92,17 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
 | 
				
			|||||||
			return true;
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(display->value == "composite") {
 | 
							if(display->value == "composite") {
 | 
				
			||||||
			result = Configurable::Display::Composite;
 | 
								result = Configurable::Display::CompositeColour;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if(display->value == "composite-mono") {
 | 
				
			||||||
 | 
								result = Configurable::Display::CompositeMonochrome;
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return false;
 | 
						return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) {
 | 
				
			||||||
 | 
						return get_bool(selections_by_option, "quickboot", result);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,15 +16,18 @@ namespace Configurable {
 | 
				
			|||||||
enum StandardOptions {
 | 
					enum StandardOptions {
 | 
				
			||||||
	DisplayRGB					= (1 << 0),
 | 
						DisplayRGB					= (1 << 0),
 | 
				
			||||||
	DisplaySVideo				= (1 << 1),
 | 
						DisplaySVideo				= (1 << 1),
 | 
				
			||||||
	DisplayComposite			= (1 << 2),
 | 
						DisplayCompositeColour		= (1 << 2),
 | 
				
			||||||
	QuickLoadTape				= (1 << 3),
 | 
						DisplayCompositeMonochrome	= (1 << 3),
 | 
				
			||||||
	AutomaticTapeMotorControl	= (1 << 4)
 | 
						QuickLoadTape				= (1 << 4),
 | 
				
			||||||
 | 
						AutomaticTapeMotorControl	= (1 << 5),
 | 
				
			||||||
 | 
						QuickBoot					= (1 << 6),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class Display {
 | 
					enum class Display {
 | 
				
			||||||
	RGB,
 | 
						RGB,
 | 
				
			||||||
	SVideo,
 | 
						SVideo,
 | 
				
			||||||
	Composite
 | 
						CompositeColour,
 | 
				
			||||||
 | 
						CompositeMonochrome
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -47,6 +50,11 @@ void append_automatic_tape_motor_control_selection(SelectionSet &selection_set,
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
void append_display_selection(SelectionSet &selection_set, Display selection);
 | 
					void append_display_selection(SelectionSet &selection_set, Display selection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Appends to @c selection_set a selection of @c selection for QuickBoot.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void append_quick_boot_selection(SelectionSet &selection_set, bool selection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Attempts to discern a QuickLoadTape selection from @c selections_by_option.
 | 
						Attempts to discern a QuickLoadTape selection from @c selections_by_option.
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
@@ -74,6 +82,15 @@ bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_b
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
bool get_display(const SelectionSet &selections_by_option, Display &result);
 | 
					bool get_display(const SelectionSet &selections_by_option, Display &result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Attempts to QuickBoot a QuickLoadTape selection from @c selections_by_option.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@param selections_by_option The user selections.
 | 
				
			||||||
 | 
						@param result The location to which the selection will be stored if found.
 | 
				
			||||||
 | 
						@returns @c true if a selection is found; @c false otherwise.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool get_quick_boot(const SelectionSet &selections_by_option, bool &result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* StandardOptions_hpp */
 | 
					#endif /* StandardOptions_hpp */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -185,7 +185,7 @@ class ConcreteJoystick: public Joystick {
 | 
				
			|||||||
			// convenient hard-coded values. TODO: make these a function of time.
 | 
								// convenient hard-coded values. TODO: make these a function of time.
 | 
				
			||||||
			using Type = Joystick::Input::Type;
 | 
								using Type = Joystick::Input::Type;
 | 
				
			||||||
			switch(input.type) {
 | 
								switch(input.type) {
 | 
				
			||||||
				default: 			did_set_input(input, is_active ? 1.0f : 0.0f);													break;
 | 
									default:			did_set_input(input, is_active ? 1.0f : 0.0f);													break;
 | 
				
			||||||
				case Type::Left:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f);		break;
 | 
									case Type::Left:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f);		break;
 | 
				
			||||||
				case Type::Right:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f);		break;
 | 
									case Type::Right:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f);		break;
 | 
				
			||||||
				case Type::Up:		did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f);		break;
 | 
									case Type::Up:		did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f);		break;
 | 
				
			||||||
@@ -203,7 +203,7 @@ class ConcreteJoystick: public Joystick {
 | 
				
			|||||||
			// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
 | 
								// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
 | 
				
			||||||
			using Type = Joystick::Input::Type;
 | 
								using Type = Joystick::Input::Type;
 | 
				
			||||||
			switch(input.type) {
 | 
								switch(input.type) {
 | 
				
			||||||
				default: 			did_set_input(input, value > 0.5f);											break;
 | 
									default:			did_set_input(input, value > 0.5f);											break;
 | 
				
			||||||
				case Type::Horizontal:
 | 
									case Type::Horizontal:
 | 
				
			||||||
					did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
 | 
										did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
 | 
				
			||||||
					did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);
 | 
										did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,13 +10,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace Inputs;
 | 
					using namespace Inputs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Keyboard::Keyboard() {
 | 
					Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
 | 
				
			||||||
	for(int k = 0; k < int(Key::Help); ++k) {
 | 
						for(int k = 0; k < int(Key::Help); ++k) {
 | 
				
			||||||
		observed_keys_.insert(Key(k));
 | 
							observed_keys_.insert(Key(k));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {}
 | 
					Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) :
 | 
				
			||||||
 | 
						observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
 | 
					void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
 | 
				
			||||||
	std::size_t key_offset = static_cast<std::size_t>(key);
 | 
						std::size_t key_offset = static_cast<std::size_t>(key);
 | 
				
			||||||
@@ -28,6 +29,10 @@ void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
 | 
				
			|||||||
	if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
 | 
						if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() {
 | 
				
			||||||
 | 
						return essential_modifiers_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Keyboard::reset_all_keys() {
 | 
					void Keyboard::reset_all_keys() {
 | 
				
			||||||
	std::fill(key_states_.begin(), key_states_.end(), false);
 | 
						std::fill(key_states_.begin(), key_states_.end(), false);
 | 
				
			||||||
	if(delegate_) delegate_->reset_all_keys(this);
 | 
						if(delegate_) delegate_->reset_all_keys(this);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,8 @@
 | 
				
			|||||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
					//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef Keyboard_hpp
 | 
					#ifndef Inputs_Keyboard_hpp
 | 
				
			||||||
#define Keyboard_hpp
 | 
					#define Inputs_Keyboard_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
#include <set>
 | 
					#include <set>
 | 
				
			||||||
@@ -23,26 +23,26 @@ class Keyboard {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		enum class Key {
 | 
							enum class Key {
 | 
				
			||||||
			Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
 | 
								Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
 | 
				
			||||||
			BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
 | 
								BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, Backspace,
 | 
				
			||||||
			Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
 | 
								Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, Backslash,
 | 
				
			||||||
			CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
 | 
								CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
 | 
				
			||||||
			LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
 | 
								LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
 | 
				
			||||||
			LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
 | 
								LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
 | 
				
			||||||
			Left, Right, Up, Down,
 | 
								Left, Right, Up, Down,
 | 
				
			||||||
			Insert, Home, PageUp, Delete, End, PageDown,
 | 
								Insert, Home, PageUp, Delete, End, PageDown,
 | 
				
			||||||
			NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete,
 | 
								NumLock, KeypadSlash, KeypadAsterisk, KeypadDelete,
 | 
				
			||||||
			KeyPad7, KeyPad8, KeyPad9, KeyPadPlus,
 | 
								Keypad7, Keypad8, Keypad9, KeypadPlus,
 | 
				
			||||||
			KeyPad4, KeyPad5, KeyPad6, KeyPadMinus,
 | 
								Keypad4, Keypad5, Keypad6, KeypadMinus,
 | 
				
			||||||
			KeyPad1, KeyPad2, KeyPad3, KeyPadEnter,
 | 
								Keypad1, Keypad2, Keypad3, KeypadEnter,
 | 
				
			||||||
			KeyPad0, KeyPadDecimalPoint, KeyPadEquals,
 | 
								Keypad0, KeypadDecimalPoint, KeypadEquals,
 | 
				
			||||||
			Help
 | 
								Help
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Constructs a Keyboard that declares itself to observe all keys.
 | 
							/// Constructs a Keyboard that declares itself to observe all keys.
 | 
				
			||||||
		Keyboard();
 | 
							Keyboard(const std::set<Key> &essential_modifiers = {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
 | 
							/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
 | 
				
			||||||
		Keyboard(const std::set<Key> &observed_keys);
 | 
							Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Host interface.
 | 
							// Host interface.
 | 
				
			||||||
		virtual void set_key_pressed(Key key, char value, bool is_pressed);
 | 
							virtual void set_key_pressed(Key key, char value, bool is_pressed);
 | 
				
			||||||
@@ -51,10 +51,18 @@ class Keyboard {
 | 
				
			|||||||
		/// @returns a set of all Keys that this keyboard responds to.
 | 
							/// @returns a set of all Keys that this keyboard responds to.
 | 
				
			||||||
		virtual const std::set<Key> &observed_keys();
 | 
							virtual const std::set<Key> &observed_keys();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*
 | 
							/// @returns the list of modifiers that this keyboard considers 'essential' (i.e. both mapped and highly used).
 | 
				
			||||||
 | 
							virtual const std::set<Inputs::Keyboard::Key> &get_essential_modifiers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
			@returns @c true if this keyboard, on its original machine, looked
 | 
								@returns @c true if this keyboard, on its original machine, looked
 | 
				
			||||||
			like a complete keyboard — i.e. if a user would expect this keyboard
 | 
								like a complete keyboard — i.e. if a user would expect this keyboard
 | 
				
			||||||
			to be the only thing a real keyboard maps to.
 | 
								to be the only thing a real keyboard maps to.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								So this would be true of something like the Amstrad CPC, which has a full
 | 
				
			||||||
 | 
								keyboard, but it would be false of something like the Sega Master System
 | 
				
			||||||
 | 
								which has some buttons that you'd expect an emulator to map to its host
 | 
				
			||||||
 | 
								keyboard but which does not offer a full keyboard.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		virtual bool is_exclusive();
 | 
							virtual bool is_exclusive();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,6 +76,7 @@ class Keyboard {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::set<Key> observed_keys_;
 | 
							std::set<Key> observed_keys_;
 | 
				
			||||||
 | 
							std::set<Key> essential_modifiers_;
 | 
				
			||||||
		std::vector<bool> key_states_;
 | 
							std::vector<bool> key_states_;
 | 
				
			||||||
		Delegate *delegate_ = nullptr;
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
		bool is_exclusive_ = true;
 | 
							bool is_exclusive_ = true;
 | 
				
			||||||
@@ -75,4 +84,4 @@ class Keyboard {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* Keyboard_hpp */
 | 
					#endif /* Inputs_Keyboard_hpp */
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										47
									
								
								Inputs/Mouse.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Inputs/Mouse.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Mouse.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 11/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Mouse_h
 | 
				
			||||||
 | 
					#define Mouse_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Inputs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models a classic-era mouse: something that provides 2d relative motion plus
 | 
				
			||||||
 | 
						some quantity of buttons.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class Mouse {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Indicates a movement of the mouse.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							virtual void move(int x, int y) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns the number of buttons on this mouse.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							virtual int get_number_of_buttons() {
 | 
				
			||||||
 | 
								return 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Indicates that button @c index is now either pressed or unpressed.
 | 
				
			||||||
 | 
								The intention is that @c index be semantic, not positional:
 | 
				
			||||||
 | 
								0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							virtual void set_button_pressed(int index, bool is_pressed) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Releases all depressed buttons.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							virtual void reset_all_buttons() {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Mouse_h */
 | 
				
			||||||
							
								
								
									
										123
									
								
								Inputs/QuadratureMouse/QuadratureMouse.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								Inputs/QuadratureMouse/QuadratureMouse.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  QuadratureMouse.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 11/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef QuadratureMouse_hpp
 | 
				
			||||||
 | 
					#define QuadratureMouse_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../Mouse.hpp"
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Inputs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Provides a simple implementation of a Mouse, designed for simple
 | 
				
			||||||
 | 
						thread-safe feeding to a machine that accepts quadrature-encoded input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TEMPORARY SIMPLIFICATION: it is assumed that the caller will be interested
 | 
				
			||||||
 | 
						in observing a signal that dictates velocity, sampling the other to
 | 
				
			||||||
 | 
						obtain direction only on transitions in the velocity signal.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Or, more concretely, of the two channels per axis, one is accurate only when
 | 
				
			||||||
 | 
						the other transitions. Hence the discussion of 'primary' and 'secondary'
 | 
				
			||||||
 | 
						channels below. This is intended to be fixed.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class QuadratureMouse: public Mouse {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							QuadratureMouse(int number_of_buttons) :
 | 
				
			||||||
 | 
								number_of_buttons_(number_of_buttons) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								Inputs, to satisfy the Mouse interface.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void move(int x, int y) override {
 | 
				
			||||||
 | 
								// Accumulate all provided motion.
 | 
				
			||||||
 | 
								axes_[0] += x;
 | 
				
			||||||
 | 
								axes_[1] += y;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int get_number_of_buttons() override {
 | 
				
			||||||
 | 
								return number_of_buttons_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_button_pressed(int index, bool is_pressed) override {
 | 
				
			||||||
 | 
								if(is_pressed)
 | 
				
			||||||
 | 
									button_flags_ |= (1 << index);
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									button_flags_ &= ~(1 << index);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void reset_all_buttons() override {
 | 
				
			||||||
 | 
								button_flags_ = 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								Outputs.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Applies a single step from the current accumulated mouse movement, which
 | 
				
			||||||
 | 
								might involve the mouse moving right, or left, or not at all.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void prepare_step() {
 | 
				
			||||||
 | 
								for(int axis = 0; axis < 2; ++axis) {
 | 
				
			||||||
 | 
									// Do nothing if there's no motion to communicate.
 | 
				
			||||||
 | 
									const int axis_value = axes_[axis];
 | 
				
			||||||
 | 
									if(!axis_value) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Toggle the primary channel and set the secondary for
 | 
				
			||||||
 | 
									// negative motion. At present the y axis signals the
 | 
				
			||||||
 | 
									// secondary channel the opposite way around from the
 | 
				
			||||||
 | 
									// primary.
 | 
				
			||||||
 | 
									primaries_[axis] ^= 1;
 | 
				
			||||||
 | 
									secondaries_[axis] = primaries_[axis] ^ axis;
 | 
				
			||||||
 | 
									if(axis_value > 0) {
 | 
				
			||||||
 | 
										-- axes_[axis];
 | 
				
			||||||
 | 
										secondaries_[axis] ^= 1;	// Switch to positive motion.
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										++ axes_[axis];
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns the two quadrature channels — bit 0 is the 'primary' channel
 | 
				
			||||||
 | 
									(i.e. the one that can be monitored to observe velocity) and
 | 
				
			||||||
 | 
									bit 1 is the 'secondary' (i.e. that which can be queried to
 | 
				
			||||||
 | 
									observe direction).
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							int get_channel(int axis) {
 | 
				
			||||||
 | 
								return primaries_[axis] | (secondaries_[axis] << 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns a bit mask of the currently pressed buttons.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							int get_button_mask() {
 | 
				
			||||||
 | 
								return button_flags_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns @c true if any mouse motion is waiting to be communicated;
 | 
				
			||||||
 | 
									@c false otherwise.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							bool has_steps() {
 | 
				
			||||||
 | 
								return axes_[0] || axes_[1];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							int number_of_buttons_ = 0;
 | 
				
			||||||
 | 
							std::atomic<int> button_flags_;
 | 
				
			||||||
 | 
							std::atomic<int> axes_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int primaries_[2] = {0, 0};
 | 
				
			||||||
 | 
							int secondaries_[2] = {0, 0};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* QuadratureMouse_hpp */
 | 
				
			||||||
@@ -30,6 +30,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../../ClockReceiver/ForceInline.hpp"
 | 
					#include "../../ClockReceiver/ForceInline.hpp"
 | 
				
			||||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
					#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
				
			||||||
 | 
					#include "../../Outputs/CRT/CRT.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
 | 
					#include "../../Analyser/Static/AmstradCPC/Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +41,7 @@ namespace AmstradCPC {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
 | 
					std::vector<std::unique_ptr<Configurable::Option>> get_options() {
 | 
				
			||||||
	return Configurable::standard_options(
 | 
						return Configurable::standard_options(
 | 
				
			||||||
		static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite)
 | 
							Configurable::StandardOptions(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,7 +124,7 @@ class InterruptTimer {
 | 
				
			|||||||
class AYDeferrer {
 | 
					class AYDeferrer {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Constructs a new AY instance and sets its clock rate.
 | 
							/// Constructs a new AY instance and sets its clock rate.
 | 
				
			||||||
		AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
 | 
							AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) {
 | 
				
			||||||
			speaker_.set_input_rate(1000000);
 | 
								speaker_.set_input_rate(1000000);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -170,11 +171,15 @@ class AYDeferrer {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
class CRTCBusHandler {
 | 
					class CRTCBusHandler {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
 | 
							CRTCBusHandler(const uint8_t *ram, InterruptTimer &interrupt_timer) :
 | 
				
			||||||
 | 
								crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2),
 | 
				
			||||||
			ram_(ram),
 | 
								ram_(ram),
 | 
				
			||||||
			interrupt_timer_(interrupt_timer) {
 | 
								interrupt_timer_(interrupt_timer) {
 | 
				
			||||||
				establish_palette_hits();
 | 
									establish_palette_hits();
 | 
				
			||||||
				build_mode_table();
 | 
									build_mode_table();
 | 
				
			||||||
 | 
									crt_.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
 | 
				
			||||||
 | 
									crt_.set_brightness(3.0f / 2.0f);	// As only the values 0, 1 and 2 will be used in each channel,
 | 
				
			||||||
 | 
																		// whereas Red2Green2Blue2 defines a range of 0-3.
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
@@ -217,12 +222,12 @@ class CRTCBusHandler {
 | 
				
			|||||||
				if(cycles_) {
 | 
									if(cycles_) {
 | 
				
			||||||
					switch(previous_output_mode_) {
 | 
										switch(previous_output_mode_) {
 | 
				
			||||||
						default:
 | 
											default:
 | 
				
			||||||
						case OutputMode::Blank:			crt_->output_blank(cycles_ * 16);					break;
 | 
											case OutputMode::Blank:			crt_.output_blank(cycles_ * 16);				break;
 | 
				
			||||||
						case OutputMode::Sync:			crt_->output_sync(cycles_ * 16);					break;
 | 
											case OutputMode::Sync:			crt_.output_sync(cycles_ * 16);					break;
 | 
				
			||||||
						case OutputMode::Border:		output_border(cycles_);								break;
 | 
											case OutputMode::Border:		output_border(cycles_);							break;
 | 
				
			||||||
						case OutputMode::ColourBurst:	crt_->output_default_colour_burst(cycles_ * 16);	break;
 | 
											case OutputMode::ColourBurst:	crt_.output_default_colour_burst(cycles_ * 16);	break;
 | 
				
			||||||
						case OutputMode::Pixels:
 | 
											case OutputMode::Pixels:
 | 
				
			||||||
							crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
 | 
												crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
 | 
				
			||||||
							pixel_pointer_ = pixel_data_ = nullptr;
 | 
												pixel_pointer_ = pixel_data_ = nullptr;
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -238,52 +243,54 @@ class CRTCBusHandler {
 | 
				
			|||||||
			// collect some more pixels if output is ongoing
 | 
								// collect some more pixels if output is ongoing
 | 
				
			||||||
			if(previous_output_mode_ == OutputMode::Pixels) {
 | 
								if(previous_output_mode_ == OutputMode::Pixels) {
 | 
				
			||||||
				if(!pixel_data_) {
 | 
									if(!pixel_data_) {
 | 
				
			||||||
					pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
 | 
										pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if(pixel_pointer_) {
 | 
									if(pixel_pointer_) {
 | 
				
			||||||
					// the CPC shuffles output lines as:
 | 
										// the CPC shuffles output lines as:
 | 
				
			||||||
					//	MA13 MA12	RA2 RA1 RA0		MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0		CCLK
 | 
										//	MA13 MA12	RA2 RA1 RA0		MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0		CCLK
 | 
				
			||||||
					// ... so form the real access address.
 | 
										// ... so form the real access address.
 | 
				
			||||||
					uint16_t address =
 | 
										const uint16_t address =
 | 
				
			||||||
						static_cast<uint16_t>(
 | 
											uint16_t(
 | 
				
			||||||
							((state.refresh_address & 0x3ff) << 1) |
 | 
												((state.refresh_address & 0x3ff) << 1) |
 | 
				
			||||||
							((state.row_address & 0x7) << 11) |
 | 
												((state.row_address & 0x7) << 11) |
 | 
				
			||||||
							((state.refresh_address & 0x3000) << 2)
 | 
												((state.refresh_address & 0x3000) << 2)
 | 
				
			||||||
						);
 | 
											);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// fetch two bytes and translate into pixels
 | 
										// Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at
 | 
				
			||||||
 | 
										// hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without
 | 
				
			||||||
 | 
										// exactly reaching 320 output pixels.
 | 
				
			||||||
					switch(mode_) {
 | 
										switch(mode_) {
 | 
				
			||||||
						case 0:
 | 
											case 0:
 | 
				
			||||||
							reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
 | 
												reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
 | 
				
			||||||
							reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
 | 
												reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
 | 
				
			||||||
							pixel_pointer_ += 4;
 | 
												pixel_pointer_ += 2 * sizeof(uint16_t);
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						case 1:
 | 
											case 1:
 | 
				
			||||||
							reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
 | 
												reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
 | 
				
			||||||
							reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
 | 
												reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
 | 
				
			||||||
							pixel_pointer_ += 8;
 | 
												pixel_pointer_ += 2 * sizeof(uint32_t);
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						case 2:
 | 
											case 2:
 | 
				
			||||||
							reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
 | 
												reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
 | 
				
			||||||
							reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
 | 
												reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
 | 
				
			||||||
							pixel_pointer_ += 16;
 | 
												pixel_pointer_ += 2 * sizeof(uint64_t);
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						case 3:
 | 
											case 3:
 | 
				
			||||||
							reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
 | 
												reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
 | 
				
			||||||
							reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
 | 
												reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
 | 
				
			||||||
							pixel_pointer_ += 4;
 | 
												pixel_pointer_ += 2 * sizeof(uint16_t);
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// flush the current buffer pixel if full; the CRTC allows many different display
 | 
										// Flush the current buffer pixel if full; the CRTC allows many different display
 | 
				
			||||||
					// widths so it's not necessarily possible to predict the correct number in advance
 | 
										// widths so it's not necessarily possible to predict the correct number in advance
 | 
				
			||||||
					// and using the upper bound could lead to inefficient behaviour
 | 
										// and using the upper bound could lead to inefficient behaviour.
 | 
				
			||||||
					if(pixel_pointer_ == pixel_data_ + 320) {
 | 
										if(pixel_pointer_ == pixel_data_ + 320) {
 | 
				
			||||||
						crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
 | 
											crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
 | 
				
			||||||
						pixel_pointer_ = pixel_data_ = nullptr;
 | 
											pixel_pointer_ = pixel_data_ = nullptr;
 | 
				
			||||||
						cycles_ = 0;
 | 
											cycles_ = 0;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -323,27 +330,14 @@ class CRTCBusHandler {
 | 
				
			|||||||
			was_hsync_ = state.hsync;
 | 
								was_hsync_ = state.hsync;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Constructs an appropriate CRT for video output.
 | 
							/// Sets the destination for output.
 | 
				
			||||||
		void setup_output(float aspect_ratio) {
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			||||||
			crt_.reset(new Outputs::CRT::CRT(1024, 16, Outputs::CRT::DisplayType::PAL50, 1));
 | 
								crt_.set_scan_target(scan_target);
 | 
				
			||||||
			crt_->set_rgb_sampling_function(
 | 
					 | 
				
			||||||
				"vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
 | 
					 | 
				
			||||||
				"{"
 | 
					 | 
				
			||||||
					"uint sample = texture(texID, coordinate).r;"
 | 
					 | 
				
			||||||
					"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
 | 
					 | 
				
			||||||
				"}");
 | 
					 | 
				
			||||||
			crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
 | 
					 | 
				
			||||||
			crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Destructs the CRT.
 | 
							/// Sets the type of display.
 | 
				
			||||||
		void close_output() {
 | 
							void set_display_type(Outputs::Display::DisplayType display_type) {
 | 
				
			||||||
			crt_.reset();
 | 
								crt_.set_display_type(display_type);
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// @returns the CRT.
 | 
					 | 
				
			||||||
		Outputs::CRT::CRT *get_crt() {
 | 
					 | 
				
			||||||
			return crt_.get();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
@@ -376,10 +370,18 @@ class CRTCBusHandler {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		void output_border(unsigned int length) {
 | 
							void output_border(int length) {
 | 
				
			||||||
			uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
 | 
								assert(length >= 0);
 | 
				
			||||||
			if(colour_pointer) *colour_pointer = border_;
 | 
					
 | 
				
			||||||
			crt_->output_level(length * 16);
 | 
								// A black border can be output via crt_.output_blank for a minor performance
 | 
				
			||||||
 | 
								// win; otherwise paint whatever the border colour really is.
 | 
				
			||||||
 | 
								if(border_) {
 | 
				
			||||||
 | 
									uint8_t *const colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
 | 
				
			||||||
 | 
									if(colour_pointer) *colour_pointer = border_;
 | 
				
			||||||
 | 
									crt_.output_level(length * 16);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									crt_.output_blank(length * 16);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
 | 
					#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
 | 
				
			||||||
@@ -395,16 +397,16 @@ class CRTCBusHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		void establish_palette_hits() {
 | 
							void establish_palette_hits() {
 | 
				
			||||||
			for(int c = 0; c < 256; c++) {
 | 
								for(int c = 0; c < 256; c++) {
 | 
				
			||||||
				mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode0_palette_hits_[Mode0Colour0(c)].push_back(uint8_t(c));
 | 
				
			||||||
				mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode0_palette_hits_[Mode0Colour1(c)].push_back(uint8_t(c));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode1_palette_hits_[Mode1Colour0(c)].push_back(uint8_t(c));
 | 
				
			||||||
				mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode1_palette_hits_[Mode1Colour1(c)].push_back(uint8_t(c));
 | 
				
			||||||
				mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode1_palette_hits_[Mode1Colour2(c)].push_back(uint8_t(c));
 | 
				
			||||||
				mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode1_palette_hits_[Mode1Colour3(c)].push_back(uint8_t(c));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode3_palette_hits_[Mode3Colour0(c)].push_back(uint8_t(c));
 | 
				
			||||||
				mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
 | 
									mode3_palette_hits_[Mode3Colour1(c)].push_back(uint8_t(c));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -414,7 +416,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
					// Mode 0: abcdefgh -> [gcea] [hdfb]
 | 
										// Mode 0: abcdefgh -> [gcea] [hdfb]
 | 
				
			||||||
					for(int c = 0; c < 256; c++) {
 | 
										for(int c = 0; c < 256; c++) {
 | 
				
			||||||
						// prepare mode 0
 | 
											// prepare mode 0
 | 
				
			||||||
						uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
 | 
											uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
 | 
				
			||||||
						mode0_pixels[0] = palette_[Mode0Colour0(c)];
 | 
											mode0_pixels[0] = palette_[Mode0Colour0(c)];
 | 
				
			||||||
						mode0_pixels[1] = palette_[Mode0Colour1(c)];
 | 
											mode0_pixels[1] = palette_[Mode0Colour1(c)];
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -423,7 +425,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
				case 1:
 | 
									case 1:
 | 
				
			||||||
					for(int c = 0; c < 256; c++) {
 | 
										for(int c = 0; c < 256; c++) {
 | 
				
			||||||
						// prepare mode 1
 | 
											// prepare mode 1
 | 
				
			||||||
						uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
 | 
											uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
 | 
				
			||||||
						mode1_pixels[0] = palette_[Mode1Colour0(c)];
 | 
											mode1_pixels[0] = palette_[Mode1Colour0(c)];
 | 
				
			||||||
						mode1_pixels[1] = palette_[Mode1Colour1(c)];
 | 
											mode1_pixels[1] = palette_[Mode1Colour1(c)];
 | 
				
			||||||
						mode1_pixels[2] = palette_[Mode1Colour2(c)];
 | 
											mode1_pixels[2] = palette_[Mode1Colour2(c)];
 | 
				
			||||||
@@ -434,7 +436,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
				case 2:
 | 
									case 2:
 | 
				
			||||||
					for(int c = 0; c < 256; c++) {
 | 
										for(int c = 0; c < 256; c++) {
 | 
				
			||||||
						// prepare mode 2
 | 
											// prepare mode 2
 | 
				
			||||||
						uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
 | 
											uint8_t *const mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
 | 
				
			||||||
						mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
 | 
											mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
 | 
				
			||||||
						mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
 | 
											mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
 | 
				
			||||||
						mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
 | 
											mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
 | 
				
			||||||
@@ -449,7 +451,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
				case 3:
 | 
									case 3:
 | 
				
			||||||
					for(int c = 0; c < 256; c++) {
 | 
										for(int c = 0; c < 256; c++) {
 | 
				
			||||||
						// prepare mode 3
 | 
											// prepare mode 3
 | 
				
			||||||
						uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
 | 
											uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
 | 
				
			||||||
						mode3_pixels[0] = palette_[Mode3Colour0(c)];
 | 
											mode3_pixels[0] = palette_[Mode3Colour0(c)];
 | 
				
			||||||
						mode3_pixels[1] = palette_[Mode3Colour1(c)];
 | 
											mode3_pixels[1] = palette_[Mode3Colour1(c)];
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -461,7 +463,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
			switch(mode_) {
 | 
								switch(mode_) {
 | 
				
			||||||
				case 0: {
 | 
									case 0: {
 | 
				
			||||||
					for(uint8_t c : mode0_palette_hits_[pen]) {
 | 
										for(uint8_t c : mode0_palette_hits_[pen]) {
 | 
				
			||||||
						uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
 | 
											uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
 | 
				
			||||||
						mode0_pixels[0] = palette_[Mode0Colour0(c)];
 | 
											mode0_pixels[0] = palette_[Mode0Colour0(c)];
 | 
				
			||||||
						mode0_pixels[1] = palette_[Mode0Colour1(c)];
 | 
											mode0_pixels[1] = palette_[Mode0Colour1(c)];
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -469,7 +471,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
				case 1:
 | 
									case 1:
 | 
				
			||||||
					if(pen > 3) return;
 | 
										if(pen > 3) return;
 | 
				
			||||||
					for(uint8_t c : mode1_palette_hits_[pen]) {
 | 
										for(uint8_t c : mode1_palette_hits_[pen]) {
 | 
				
			||||||
						uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
 | 
											uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
 | 
				
			||||||
						mode1_pixels[0] = palette_[Mode1Colour0(c)];
 | 
											mode1_pixels[0] = palette_[Mode1Colour0(c)];
 | 
				
			||||||
						mode1_pixels[1] = palette_[Mode1Colour1(c)];
 | 
											mode1_pixels[1] = palette_[Mode1Colour1(c)];
 | 
				
			||||||
						mode1_pixels[2] = palette_[Mode1Colour2(c)];
 | 
											mode1_pixels[2] = palette_[Mode1Colour2(c)];
 | 
				
			||||||
@@ -486,7 +488,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
					if(pen > 3) return;
 | 
										if(pen > 3) return;
 | 
				
			||||||
					// Same argument applies here as to case 1, as the unused bits aren't masked out.
 | 
										// Same argument applies here as to case 1, as the unused bits aren't masked out.
 | 
				
			||||||
					for(uint8_t c : mode3_palette_hits_[pen]) {
 | 
										for(uint8_t c : mode3_palette_hits_[pen]) {
 | 
				
			||||||
						uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
 | 
											uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
 | 
				
			||||||
						mode3_pixels[0] = palette_[Mode3Colour0(c)];
 | 
											mode3_pixels[0] = palette_[Mode3Colour0(c)];
 | 
				
			||||||
						mode3_pixels[1] = palette_[Mode3Colour1(c)];
 | 
											mode3_pixels[1] = palette_[Mode3Colour1(c)];
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@@ -507,7 +509,7 @@ class CRTCBusHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		uint8_t mapped_palette_value(uint8_t colour) {
 | 
							uint8_t mapped_palette_value(uint8_t colour) {
 | 
				
			||||||
#define COL(r, g, b) (r << 4) | (g << 2) | b
 | 
					#define COL(r, g, b) (r << 4) | (g << 2) | b
 | 
				
			||||||
			static const uint8_t mapping[32] = {
 | 
								constexpr uint8_t mapping[32] = {
 | 
				
			||||||
				COL(1, 1, 1),	COL(1, 1, 1),	COL(0, 2, 1),	COL(2, 2, 1),
 | 
									COL(1, 1, 1),	COL(1, 1, 1),	COL(0, 2, 1),	COL(2, 2, 1),
 | 
				
			||||||
				COL(0, 0, 1),	COL(2, 0, 1),	COL(0, 1, 1),	COL(2, 1, 1),
 | 
									COL(0, 0, 1),	COL(2, 0, 1),	COL(0, 1, 1),	COL(2, 1, 1),
 | 
				
			||||||
				COL(2, 0, 1),	COL(2, 2, 1),	COL(2, 2, 0),	COL(2, 2, 2),
 | 
									COL(2, 0, 1),	COL(2, 2, 1),	COL(2, 2, 0),	COL(2, 2, 2),
 | 
				
			||||||
@@ -528,19 +530,19 @@ class CRTCBusHandler {
 | 
				
			|||||||
			Border,
 | 
								Border,
 | 
				
			||||||
			Pixels
 | 
								Pixels
 | 
				
			||||||
		} previous_output_mode_ = OutputMode::Sync;
 | 
							} previous_output_mode_ = OutputMode::Sync;
 | 
				
			||||||
		unsigned int cycles_ = 0;
 | 
							int cycles_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool was_hsync_ = false, was_vsync_ = false;
 | 
							bool was_hsync_ = false, was_vsync_ = false;
 | 
				
			||||||
		int cycles_into_hsync_ = 0;
 | 
							int cycles_into_hsync_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::unique_ptr<Outputs::CRT::CRT> crt_;
 | 
							Outputs::CRT::CRT crt_;
 | 
				
			||||||
		uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
 | 
							uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint8_t *ram_ = nullptr;
 | 
							const uint8_t *const ram_ = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int next_mode_ = 2, mode_ = 2;
 | 
							int next_mode_ = 2, mode_ = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		unsigned int pixel_divider_ = 1;
 | 
							int pixel_divider_ = 1;
 | 
				
			||||||
		uint16_t mode0_output_[256];
 | 
							uint16_t mode0_output_[256];
 | 
				
			||||||
		uint32_t mode1_output_[256];
 | 
							uint32_t mode1_output_[256];
 | 
				
			||||||
		uint64_t mode2_output_[256];
 | 
							uint64_t mode2_output_[256];
 | 
				
			||||||
@@ -572,7 +574,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
 | 
				
			|||||||
			Sets the row currently being reported to the AY.
 | 
								Sets the row currently being reported to the AY.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		void set_row(int row) {
 | 
							void set_row(int row) {
 | 
				
			||||||
			row_ = static_cast<size_t>(row);
 | 
								row_ = size_t(row);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
@@ -591,7 +593,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
 | 
				
			|||||||
		*/
 | 
							*/
 | 
				
			||||||
		void set_is_pressed(bool is_pressed, int line, int key) {
 | 
							void set_is_pressed(bool is_pressed, int line, int key) {
 | 
				
			||||||
			int mask = 1 << key;
 | 
								int mask = 1 << key;
 | 
				
			||||||
			assert(static_cast<size_t>(line) < sizeof(rows_));
 | 
								assert(size_t(line) < sizeof(rows_));
 | 
				
			||||||
			if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask;
 | 
								if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -602,7 +604,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
 | 
				
			|||||||
			memset(rows_, 0xff, sizeof(rows_));
 | 
								memset(rows_, 0xff, sizeof(rows_));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
 | 
							const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
 | 
				
			||||||
			return joysticks_;
 | 
								return joysticks_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -789,33 +791,43 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
			ay_.ay().set_port_handler(&key_state_);
 | 
								ay_.ay().set_port_handler(&key_state_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// construct the list of necessary ROMs
 | 
								// construct the list of necessary ROMs
 | 
				
			||||||
			std::vector<std::string> required_roms = {"amsdos.rom"};
 | 
								const std::string machine_name = "AmstradCPC";
 | 
				
			||||||
 | 
								std::vector<ROMMachine::ROM> required_roms = {
 | 
				
			||||||
 | 
									ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
			std::string model_number;
 | 
								std::string model_number;
 | 
				
			||||||
 | 
								uint32_t crcs[2];
 | 
				
			||||||
			switch(target.model) {
 | 
								switch(target.model) {
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
					model_number = "6128";
 | 
										model_number = "6128";
 | 
				
			||||||
					has_128k_ = true;
 | 
										has_128k_ = true;
 | 
				
			||||||
 | 
										crcs[0] = 0x0219bb74;
 | 
				
			||||||
 | 
										crcs[1] = 0xca6af63d;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case Analyser::Static::AmstradCPC::Target::Model::CPC464:
 | 
									case Analyser::Static::AmstradCPC::Target::Model::CPC464:
 | 
				
			||||||
					model_number = "464";
 | 
										model_number = "464";
 | 
				
			||||||
					has_128k_ = false;
 | 
										has_128k_ = false;
 | 
				
			||||||
 | 
										crcs[0] = 0x815752df;
 | 
				
			||||||
 | 
										crcs[1] = 0x7d9a3bac;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case Analyser::Static::AmstradCPC::Target::Model::CPC664:
 | 
									case Analyser::Static::AmstradCPC::Target::Model::CPC664:
 | 
				
			||||||
					model_number = "664";
 | 
										model_number = "664";
 | 
				
			||||||
					has_128k_ = false;
 | 
										has_128k_ = false;
 | 
				
			||||||
 | 
										crcs[0] = 0x3f5a6dc4;
 | 
				
			||||||
 | 
										crcs[1] = 0x32fee492;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			required_roms.push_back("os" + model_number + ".rom");
 | 
								required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
 | 
				
			||||||
			required_roms.push_back("basic" + model_number + ".rom");
 | 
								required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// fetch and verify the ROMs
 | 
								// fetch and verify the ROMs
 | 
				
			||||||
			const auto roms = rom_fetcher("AmstradCPC", required_roms);
 | 
								const auto roms = rom_fetcher(required_roms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for(std::size_t index = 0; index < roms.size(); ++index) {
 | 
								for(std::size_t index = 0; index < roms.size(); ++index) {
 | 
				
			||||||
				auto &data = roms[index];
 | 
									auto &data = roms[index];
 | 
				
			||||||
				if(!data) throw ROMMachine::Error::MissingROMs;
 | 
									if(!data) throw ROMMachine::Error::MissingROMs;
 | 
				
			||||||
				roms_[static_cast<int>(index)] = std::move(*data);
 | 
									roms_[int(index)] = std::move(*data);
 | 
				
			||||||
				roms_[static_cast<int>(index)].resize(16384);
 | 
									roms_[int(index)].resize(16384);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Establish default memory map
 | 
								// Establish default memory map
 | 
				
			||||||
@@ -860,13 +872,15 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			// TODO (in the player, not here): adapt it to accept an input clock rate and
 | 
								// TODO (in the player, not here): adapt it to accept an input clock rate and
 | 
				
			||||||
			// run_for as HalfCycles
 | 
								// run_for as HalfCycles
 | 
				
			||||||
			if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int());
 | 
								if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Pump the AY
 | 
								// Pump the AY
 | 
				
			||||||
			ay_.run_for(cycle.length);
 | 
								ay_.run_for(cycle.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Clock the FDC, if connected, using a lazy scale by two
 | 
								if constexpr (has_fdc) {
 | 
				
			||||||
			time_since_fdc_update_ += cycle.length;
 | 
									// Clock the FDC, if connected, using a lazy scale by two
 | 
				
			||||||
 | 
									time_since_fdc_update_ += cycle.length;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Update typing activity
 | 
								// Update typing activity
 | 
				
			||||||
			if(typer_) typer_->run_for(cycle.length);
 | 
								if(typer_) typer_->run_for(cycle.length);
 | 
				
			||||||
@@ -892,9 +906,11 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check for an upper ROM selection
 | 
										// Check for an upper ROM selection
 | 
				
			||||||
					if(has_fdc && !(address&0x2000)) {
 | 
										if constexpr (has_fdc) {
 | 
				
			||||||
						upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
 | 
											if(!(address&0x2000)) {
 | 
				
			||||||
						if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
 | 
												upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
 | 
				
			||||||
 | 
												if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check for a CRTC access
 | 
										// Check for a CRTC access
 | 
				
			||||||
@@ -911,16 +927,18 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
						i8255_.set_register((address >> 8) & 3, *cycle.value);
 | 
											i8255_.set_register((address >> 8) & 3, *cycle.value);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check for an FDC access
 | 
										if constexpr (has_fdc) {
 | 
				
			||||||
					if(has_fdc && (address & 0x580) == 0x100) {
 | 
											// Check for an FDC access
 | 
				
			||||||
						flush_fdc();
 | 
											if((address & 0x580) == 0x100) {
 | 
				
			||||||
						fdc_.set_register(address & 1, *cycle.value);
 | 
												flush_fdc();
 | 
				
			||||||
					}
 | 
												fdc_.set_register(address & 1, *cycle.value);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check for a disk motor access
 | 
											// Check for a disk motor access
 | 
				
			||||||
					if(has_fdc && !(address & 0x580)) {
 | 
											if(!(address & 0x580)) {
 | 
				
			||||||
						flush_fdc();
 | 
												flush_fdc();
 | 
				
			||||||
						fdc_.set_motor_on(!!(*cycle.value));
 | 
												fdc_.set_motor_on(!!(*cycle.value));
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case CPU::Z80::PartialMachineCycle::Input:
 | 
									case CPU::Z80::PartialMachineCycle::Input:
 | 
				
			||||||
@@ -933,9 +951,11 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check for an FDC access
 | 
										// Check for an FDC access
 | 
				
			||||||
					if(has_fdc && (address & 0x580) == 0x100) {
 | 
										if constexpr (has_fdc) {
 | 
				
			||||||
						flush_fdc();
 | 
											if((address & 0x580) == 0x100) {
 | 
				
			||||||
						*cycle.value &= fdc_.get_register(address & 1);
 | 
												flush_fdc();
 | 
				
			||||||
 | 
												*cycle.value &= fdc_.get_register(address & 1);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Check for a CRTC access; the below is not a typo, the CRTC can be selected
 | 
										// Check for a CRTC access; the below is not a typo, the CRTC can be selected
 | 
				
			||||||
@@ -981,19 +1001,14 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
			flush_fdc();
 | 
								flush_fdc();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// A CRTMachine function; indicates that outputs should be created now.
 | 
							/// A CRTMachine function; sets the destination for video.
 | 
				
			||||||
		void setup_output(float aspect_ratio) override final {
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
 | 
				
			||||||
			crtc_bus_handler_.setup_output(aspect_ratio);
 | 
								crtc_bus_handler_.set_scan_target(scan_target);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// A CRTMachine function; indicates that outputs should be destroyed now.
 | 
							/// A CRTMachine function; sets the output display type.
 | 
				
			||||||
		void close_output() override final {
 | 
							void set_display_type(Outputs::Display::DisplayType display_type) override final {
 | 
				
			||||||
			crtc_bus_handler_.close_output();
 | 
								crtc_bus_handler_.set_display_type(display_type);
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/// @returns the CRT in use.
 | 
					 | 
				
			||||||
		Outputs::CRT::CRT *get_crt() override final {
 | 
					 | 
				
			||||||
			return crtc_bus_handler_.get_crt();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the speaker in use.
 | 
							/// @returns the speaker in use.
 | 
				
			||||||
@@ -1058,7 +1073,7 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// MARK: - Activity Source
 | 
							// MARK: - Activity Source
 | 
				
			||||||
		void set_activity_observer(Activity::Observer *observer) override {
 | 
							void set_activity_observer(Activity::Observer *observer) override {
 | 
				
			||||||
			if(has_fdc) fdc_.set_activity_observer(observer);
 | 
								if constexpr (has_fdc) fdc_.set_activity_observer(observer);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// MARK: - Configuration options.
 | 
							// MARK: - Configuration options.
 | 
				
			||||||
@@ -1086,7 +1101,7 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// MARK: - Joysticks
 | 
							// MARK: - Joysticks
 | 
				
			||||||
		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
 | 
							const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
 | 
				
			||||||
			return key_state_.get_joysticks();
 | 
								return key_state_.get_joysticks();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1148,11 +1163,13 @@ template <bool has_fdc> class ConcreteMachine:
 | 
				
			|||||||
		FDC fdc_;
 | 
							FDC fdc_;
 | 
				
			||||||
		HalfCycles time_since_fdc_update_;
 | 
							HalfCycles time_since_fdc_update_;
 | 
				
			||||||
		void flush_fdc() {
 | 
							void flush_fdc() {
 | 
				
			||||||
			// Clock the FDC, if connected, using a lazy scale by two
 | 
								if constexpr (has_fdc) {
 | 
				
			||||||
			if(has_fdc && !fdc_is_sleeping_) {
 | 
									// Clock the FDC, if connected, using a lazy scale by two
 | 
				
			||||||
				fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
 | 
									if(!fdc_is_sleeping_) {
 | 
				
			||||||
 | 
										fdc_.run_for(Cycles(time_since_fdc_update_.as_integral()));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									time_since_fdc_update_ = HalfCycles(0);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			time_since_fdc_update_ = HalfCycles(0);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		InterruptTimer interrupt_timer_;
 | 
							InterruptTimer interrupt_timer_;
 | 
				
			||||||
@@ -1192,7 +1209,7 @@ Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMa
 | 
				
			|||||||
	using Target = Analyser::Static::AmstradCPC::Target;
 | 
						using Target = Analyser::Static::AmstradCPC::Target;
 | 
				
			||||||
	const Target *const cpc_target = dynamic_cast<const Target *>(target);
 | 
						const Target *const cpc_target = dynamic_cast<const Target *>(target);
 | 
				
			||||||
	switch(cpc_target->model) {
 | 
						switch(cpc_target->model) {
 | 
				
			||||||
		default: 					return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher);
 | 
							default:					return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher);
 | 
				
			||||||
		case Target::Model::CPC464:	return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher);
 | 
							case Target::Model::CPC464:	return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,12 +31,12 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
 | 
				
			|||||||
		BIND(F11, KeyRightSquareBracket);
 | 
							BIND(F11, KeyRightSquareBracket);
 | 
				
			||||||
		BIND(F12, KeyClear);
 | 
							BIND(F12, KeyClear);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		BIND(Hyphen, KeyMinus);		BIND(Equals, KeyCaret);		BIND(BackSpace, KeyDelete);
 | 
							BIND(Hyphen, KeyMinus);		BIND(Equals, KeyCaret);		BIND(Backspace, KeyDelete);
 | 
				
			||||||
		BIND(Tab, KeyTab);
 | 
							BIND(Tab, KeyTab);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		BIND(OpenSquareBracket, KeyAt);
 | 
							BIND(OpenSquareBracket, KeyAt);
 | 
				
			||||||
		BIND(CloseSquareBracket, KeyLeftSquareBracket);
 | 
							BIND(CloseSquareBracket, KeyLeftSquareBracket);
 | 
				
			||||||
		BIND(BackSlash, KeyBackSlash);
 | 
							BIND(Backslash, KeyBackSlash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		BIND(CapsLock, KeyCapsLock);
 | 
							BIND(CapsLock, KeyCapsLock);
 | 
				
			||||||
		BIND(Semicolon, KeyColon);
 | 
							BIND(Semicolon, KeyColon);
 | 
				
			||||||
@@ -57,19 +57,19 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
 | 
				
			|||||||
		BIND(Left, KeyLeft);	BIND(Right, KeyRight);
 | 
							BIND(Left, KeyLeft);	BIND(Right, KeyRight);
 | 
				
			||||||
		BIND(Up, KeyUp);		BIND(Down, KeyDown);
 | 
							BIND(Up, KeyUp);		BIND(Down, KeyDown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		BIND(KeyPad0, KeyF0);
 | 
							BIND(Keypad0, KeyF0);
 | 
				
			||||||
		BIND(KeyPad1, KeyF1);		BIND(KeyPad2, KeyF2);		BIND(KeyPad3, KeyF3);
 | 
							BIND(Keypad1, KeyF1);		BIND(Keypad2, KeyF2);		BIND(Keypad3, KeyF3);
 | 
				
			||||||
		BIND(KeyPad4, KeyF4);		BIND(KeyPad5, KeyF5);		BIND(KeyPad6, KeyF6);
 | 
							BIND(Keypad4, KeyF4);		BIND(Keypad5, KeyF5);		BIND(Keypad6, KeyF6);
 | 
				
			||||||
		BIND(KeyPad7, KeyF7);		BIND(KeyPad8, KeyF8);		BIND(KeyPad9, KeyF9);
 | 
							BIND(Keypad7, KeyF7);		BIND(Keypad8, KeyF8);		BIND(Keypad9, KeyF9);
 | 
				
			||||||
		BIND(KeyPadPlus, KeySemicolon);
 | 
							BIND(KeypadPlus, KeySemicolon);
 | 
				
			||||||
		BIND(KeyPadMinus, KeyMinus);
 | 
							BIND(KeypadMinus, KeyMinus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		BIND(KeyPadEnter, KeyEnter);
 | 
							BIND(KeypadEnter, KeyEnter);
 | 
				
			||||||
		BIND(KeyPadDecimalPoint, KeyFullStop);
 | 
							BIND(KeypadDecimalPoint, KeyFullStop);
 | 
				
			||||||
		BIND(KeyPadEquals, KeyMinus);
 | 
							BIND(KeypadEquals, KeyMinus);
 | 
				
			||||||
		BIND(KeyPadSlash, KeyForwardSlash);
 | 
							BIND(KeypadSlash, KeyForwardSlash);
 | 
				
			||||||
		BIND(KeyPadAsterisk, KeyColon);
 | 
							BIND(KeypadAsterisk, KeyColon);
 | 
				
			||||||
		BIND(KeyPadDelete, KeyDelete);
 | 
							BIND(KeypadDelete, KeyDelete);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
#undef BIND
 | 
					#undef BIND
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,32 +8,40 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "AppleII.hpp"
 | 
					#include "AppleII.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Activity/Source.hpp"
 | 
					#include "../../../Activity/Source.hpp"
 | 
				
			||||||
#include "../MediaTarget.hpp"
 | 
					#include "../../MediaTarget.hpp"
 | 
				
			||||||
#include "../CRTMachine.hpp"
 | 
					#include "../../CRTMachine.hpp"
 | 
				
			||||||
#include "../JoystickMachine.hpp"
 | 
					#include "../../JoystickMachine.hpp"
 | 
				
			||||||
#include "../KeyboardMachine.hpp"
 | 
					#include "../../KeyboardMachine.hpp"
 | 
				
			||||||
#include "../Utility/MemoryFuzzer.hpp"
 | 
					#include "../../Utility/MemoryFuzzer.hpp"
 | 
				
			||||||
#include "../Utility/StringSerialiser.hpp"
 | 
					#include "../../Utility/StringSerialiser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Processors/6502/6502.hpp"
 | 
					#include "../../../Processors/6502/6502.hpp"
 | 
				
			||||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
 | 
					#include "../../../Components/AudioToggle/AudioToggle.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
					#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
				
			||||||
#include "../../Outputs/Log.hpp"
 | 
					#include "../../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Card.hpp"
 | 
					#include "Card.hpp"
 | 
				
			||||||
#include "DiskIICard.hpp"
 | 
					#include "DiskIICard.hpp"
 | 
				
			||||||
#include "Video.hpp"
 | 
					#include "Video.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Analyser/Static/AppleII/Target.hpp"
 | 
					#include "../../../Analyser/Static/AppleII/Target.hpp"
 | 
				
			||||||
#include "../../ClockReceiver/ForceInline.hpp"
 | 
					#include "../../../ClockReceiver/ForceInline.hpp"
 | 
				
			||||||
 | 
					#include "../../../Configurable/StandardOptions.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
#include <array>
 | 
					#include <array>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace {
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace II {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Configurable::Option>> get_options() {
 | 
				
			||||||
 | 
						return Configurable::standard_options(
 | 
				
			||||||
 | 
							static_cast<Configurable::StandardOptions>(Configurable::DisplayCompositeMonochrome | Configurable::DisplayCompositeColour)
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
 | 
					#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,12 +51,13 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
	public KeyboardMachine::MappedMachine,
 | 
						public KeyboardMachine::MappedMachine,
 | 
				
			||||||
	public CPU::MOS6502::BusHandler,
 | 
						public CPU::MOS6502::BusHandler,
 | 
				
			||||||
	public Inputs::Keyboard,
 | 
						public Inputs::Keyboard,
 | 
				
			||||||
	public AppleII::Machine,
 | 
						public Configurable::Device,
 | 
				
			||||||
 | 
						public Apple::II::Machine,
 | 
				
			||||||
	public Activity::Source,
 | 
						public Activity::Source,
 | 
				
			||||||
	public JoystickMachine::Machine,
 | 
						public JoystickMachine::Machine,
 | 
				
			||||||
	public AppleII::Card::Delegate {
 | 
						public Apple::II::Card::Delegate {
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		struct VideoBusHandler : public AppleII::Video::BusHandler {
 | 
							struct VideoBusHandler : public Apple::II::Video::BusHandler {
 | 
				
			||||||
			public:
 | 
								public:
 | 
				
			||||||
				VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
 | 
									VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,20 +72,22 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
 | 
							CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
 | 
				
			||||||
		VideoBusHandler video_bus_handler_;
 | 
							VideoBusHandler video_bus_handler_;
 | 
				
			||||||
		std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie()>> video_;
 | 
							Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
 | 
				
			||||||
		int cycles_into_current_line_ = 0;
 | 
							int cycles_into_current_line_ = 0;
 | 
				
			||||||
		Cycles cycles_since_video_update_;
 | 
							Cycles cycles_since_video_update_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void update_video() {
 | 
							void update_video() {
 | 
				
			||||||
			video_->run_for(cycles_since_video_update_.flush());
 | 
								video_.run_for(cycles_since_video_update_.flush<Cycles>());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		static const int audio_divider = 8;
 | 
							static constexpr int audio_divider = 8;
 | 
				
			||||||
		void update_audio() {
 | 
							void update_audio() {
 | 
				
			||||||
			speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
 | 
								speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		void update_just_in_time_cards() {
 | 
							void update_just_in_time_cards() {
 | 
				
			||||||
			for(const auto &card : just_in_time_cards_) {
 | 
								if(cycles_since_card_update_ > Cycles(0)) {
 | 
				
			||||||
				card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
 | 
									for(const auto &card : just_in_time_cards_) {
 | 
				
			||||||
 | 
										card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			cycles_since_card_update_ = 0;
 | 
								cycles_since_card_update_ = 0;
 | 
				
			||||||
			stretched_cycles_since_card_update_ = 0;
 | 
								stretched_cycles_since_card_update_ = 0;
 | 
				
			||||||
@@ -84,7 +95,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		uint8_t ram_[65536], aux_ram_[65536];
 | 
							uint8_t ram_[65536], aux_ram_[65536];
 | 
				
			||||||
		std::vector<uint8_t> rom_;
 | 
							std::vector<uint8_t> rom_;
 | 
				
			||||||
		std::vector<uint8_t> character_rom_;
 | 
					 | 
				
			||||||
		uint8_t keyboard_input_ = 0x00;
 | 
							uint8_t keyboard_input_ = 0x00;
 | 
				
			||||||
		bool key_is_down_ = false;
 | 
							bool key_is_down_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,41 +112,47 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
		Cycles cycles_since_audio_update_;
 | 
							Cycles cycles_since_audio_update_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// MARK: - Cards
 | 
							// MARK: - Cards
 | 
				
			||||||
		std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
 | 
							std::array<std::unique_ptr<Apple::II::Card>, 7> cards_;
 | 
				
			||||||
		Cycles cycles_since_card_update_;
 | 
							Cycles cycles_since_card_update_;
 | 
				
			||||||
		std::vector<AppleII::Card *> every_cycle_cards_;
 | 
							std::vector<Apple::II::Card *> every_cycle_cards_;
 | 
				
			||||||
		std::vector<AppleII::Card *> just_in_time_cards_;
 | 
							std::vector<Apple::II::Card *> just_in_time_cards_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int stretched_cycles_since_card_update_ = 0;
 | 
							int stretched_cycles_since_card_update_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void install_card(std::size_t slot, AppleII::Card *card) {
 | 
							void install_card(std::size_t slot, Apple::II::Card *card) {
 | 
				
			||||||
			assert(slot >= 1 && slot < 8);
 | 
								assert(slot >= 1 && slot < 8);
 | 
				
			||||||
			cards_[slot - 1].reset(card);
 | 
								cards_[slot - 1].reset(card);
 | 
				
			||||||
			card->set_delegate(this);
 | 
								card->set_delegate(this);
 | 
				
			||||||
			pick_card_messaging_group(card);
 | 
								pick_card_messaging_group(card);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool is_every_cycle_card(AppleII::Card *card) {
 | 
							bool is_every_cycle_card(const Apple::II::Card *card) {
 | 
				
			||||||
			return !card->get_select_constraints();
 | 
								return !card->get_select_constraints();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void pick_card_messaging_group(AppleII::Card *card) {
 | 
							bool card_lists_are_dirty_ = true;
 | 
				
			||||||
 | 
							bool card_became_just_in_time_ = false;
 | 
				
			||||||
 | 
							void pick_card_messaging_group(Apple::II::Card *card) {
 | 
				
			||||||
 | 
								// Simplify to a card being either just-in-time or realtime.
 | 
				
			||||||
 | 
								// Don't worry about exactly what it's watching,
 | 
				
			||||||
			const bool is_every_cycle = is_every_cycle_card(card);
 | 
								const bool is_every_cycle = is_every_cycle_card(card);
 | 
				
			||||||
			std::vector<AppleII::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
 | 
								std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
 | 
				
			||||||
		 	std::vector<AppleII::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If the card is already in the proper group, stop.
 | 
				
			||||||
			if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
 | 
								if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
 | 
				
			||||||
			auto old_membership = std::find(undesired.begin(), undesired.end(), card);
 | 
					
 | 
				
			||||||
			if(old_membership != undesired.end()) undesired.erase(old_membership);
 | 
								// Otherwise, mark the sets as dirty. It isn't safe to transition the card here,
 | 
				
			||||||
			intended.push_back(card);
 | 
								// as the main loop may be part way through iterating the two lists.
 | 
				
			||||||
 | 
								card_lists_are_dirty_ = true;
 | 
				
			||||||
 | 
								card_became_just_in_time_ |= !is_every_cycle;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void card_did_change_select_constraints(AppleII::Card *card) override {
 | 
							void card_did_change_select_constraints(Apple::II::Card *card) override {
 | 
				
			||||||
			pick_card_messaging_group(card);
 | 
								pick_card_messaging_group(card);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		AppleII::DiskIICard *diskii_card() {
 | 
							Apple::II::DiskIICard *diskii_card() {
 | 
				
			||||||
			return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
 | 
								return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// MARK: - Memory Map.
 | 
							// MARK: - Memory Map.
 | 
				
			||||||
@@ -154,7 +170,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
 | 
								On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			0000 to 0200 	:	can be paged independently of the rest of RAM, other than part of the language card area which pages with it
 | 
								0000 to 0200	:	can be paged independently of the rest of RAM, other than part of the language card area which pages with it
 | 
				
			||||||
			0400 to 0800	:	the text screen, can be configured to write to auxiliary RAM
 | 
								0400 to 0800	:	the text screen, can be configured to write to auxiliary RAM
 | 
				
			||||||
			2000 to 4000	:	the graphics screen, which can be configured to write to auxiliary RAM
 | 
								2000 to 4000	:	the graphics screen, which can be configured to write to auxiliary RAM
 | 
				
			||||||
			c100 to d000	:	can be used to page an additional 3.75kb of ROM, replacing the IO area
 | 
								c100 to d000	:	can be used to page an additional 3.75kb of ROM, replacing the IO area
 | 
				
			||||||
@@ -234,13 +250,13 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
				read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
 | 
									read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
 | 
				
			||||||
				write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
 | 
									write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(video_ && video_->get_80_store()) {
 | 
								if(video_.get_80_store()) {
 | 
				
			||||||
				bool use_aux_ram = video_->get_page2();
 | 
									bool use_aux_ram = video_.get_page2();
 | 
				
			||||||
				page(0x04, 0x08,
 | 
									page(0x04, 0x08,
 | 
				
			||||||
					use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
 | 
										use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
 | 
				
			||||||
					use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
 | 
										use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if(video_->get_high_resolution()) {
 | 
									if(video_.get_high_resolution()) {
 | 
				
			||||||
					page(0x20, 0x40,
 | 
										page(0x20, 0x40,
 | 
				
			||||||
						use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
 | 
											use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
 | 
				
			||||||
						use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
 | 
											use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
 | 
				
			||||||
@@ -308,15 +324,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
 | 
							ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
 | 
				
			||||||
			m6502_(*this),
 | 
								m6502_(*this),
 | 
				
			||||||
		 	video_bus_handler_(ram_, aux_ram_),
 | 
								video_bus_handler_(ram_, aux_ram_),
 | 
				
			||||||
		 	audio_toggle_(audio_queue_),
 | 
								video_(video_bus_handler_),
 | 
				
			||||||
		 	speaker_(audio_toggle_) {
 | 
								audio_toggle_(audio_queue_),
 | 
				
			||||||
		 	// The system's master clock rate.
 | 
								speaker_(audio_toggle_) {
 | 
				
			||||||
		 	const float master_clock = 14318180.0;
 | 
								// The system's master clock rate.
 | 
				
			||||||
 | 
								constexpr float master_clock = 14318180.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		 	// This is where things get slightly convoluted: establish the machine as having a clock rate
 | 
								// This is where things get slightly convoluted: establish the machine as having a clock rate
 | 
				
			||||||
		 	// equal to the number of cycles of work the 6502 will actually achieve. Which is less than
 | 
								// equal to the number of cycles of work the 6502 will actually achieve. Which is less than
 | 
				
			||||||
		 	// the master clock rate divided by 14 because every 65th cycle is extended by one seventh.
 | 
								// the master clock rate divided by 14 because every 65th cycle is extended by one seventh.
 | 
				
			||||||
			set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0));
 | 
								set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// The speaker, however, should think it is clocked at half the master clock, per a general
 | 
								// The speaker, however, should think it is clocked at half the master clock, per a general
 | 
				
			||||||
@@ -333,35 +350,44 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
			Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
 | 
								Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Add a couple of joysticks.
 | 
								// Add a couple of joysticks.
 | 
				
			||||||
		 	joysticks_.emplace_back(new Joystick);
 | 
								joysticks_.emplace_back(new Joystick);
 | 
				
			||||||
		 	joysticks_.emplace_back(new Joystick);
 | 
								joysticks_.emplace_back(new Joystick);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Pick the required ROMs.
 | 
								// Pick the required ROMs.
 | 
				
			||||||
			using Target = Analyser::Static::AppleII::Target;
 | 
								using Target = Analyser::Static::AppleII::Target;
 | 
				
			||||||
			std::vector<std::string> rom_names;
 | 
								const std::string machine_name = "AppleII";
 | 
				
			||||||
 | 
								std::vector<ROMMachine::ROM> rom_descriptions;
 | 
				
			||||||
			size_t rom_size = 12*1024;
 | 
								size_t rom_size = 12*1024;
 | 
				
			||||||
			switch(target.model) {
 | 
								switch(target.model) {
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
					rom_names.push_back("apple2-character.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
 | 
				
			||||||
					rom_names.push_back("apple2o.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case Target::Model::IIplus:
 | 
									case Target::Model::IIplus:
 | 
				
			||||||
					rom_names.push_back("apple2-character.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
 | 
				
			||||||
					rom_names.push_back("apple2.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case Target::Model::IIe:
 | 
									case Target::Model::IIe:
 | 
				
			||||||
					rom_size += 3840;
 | 
										rom_size += 3840;
 | 
				
			||||||
					rom_names.push_back("apple2eu-character.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
 | 
				
			||||||
					rom_names.push_back("apple2eu.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case Target::Model::EnhancedIIe:
 | 
									case Target::Model::EnhancedIIe:
 | 
				
			||||||
					rom_size += 3840;
 | 
										rom_size += 3840;
 | 
				
			||||||
					rom_names.push_back("apple2e-character.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
 | 
				
			||||||
					rom_names.push_back("apple2e.rom");
 | 
										rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			const auto roms = rom_fetcher("AppleII", rom_names);
 | 
								const auto roms = rom_fetcher(rom_descriptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Try to install a Disk II card now, before checking the ROM list,
 | 
				
			||||||
 | 
								// to make sure that Disk II dependencies have been communicated.
 | 
				
			||||||
 | 
								if(target.disk_controller != Target::DiskController::None) {
 | 
				
			||||||
 | 
									// Apple recommended slot 6 for the (first) Disk II.
 | 
				
			||||||
 | 
									install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Now, check and move the ROMs.
 | 
				
			||||||
			if(!roms[0] || !roms[1]) {
 | 
								if(!roms[0] || !roms[1]) {
 | 
				
			||||||
				throw ROMMachine::Error::MissingROMs;
 | 
									throw ROMMachine::Error::MissingROMs;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -371,12 +397,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
				rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
 | 
									rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			character_rom_ = std::move(*roms[0]);
 | 
								video_.set_character_rom(*roms[0]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
			if(target.disk_controller != Target::DiskController::None) {
 | 
					 | 
				
			||||||
				// Apple recommended slot 6 for the (first) Disk II.
 | 
					 | 
				
			||||||
				install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Set up the default memory blocks. On a II or II+ these values will never change.
 | 
								// Set up the default memory blocks. On a II or II+ these values will never change.
 | 
				
			||||||
			// On a IIe they'll be affected by selection of auxiliary RAM.
 | 
								// On a IIe they'll be affected by selection of auxiliary RAM.
 | 
				
			||||||
@@ -396,17 +417,13 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
			audio_queue_.flush();
 | 
								audio_queue_.flush();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void setup_output(float aspect_ratio) override {
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
 | 
				
			||||||
			video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie()>(video_bus_handler_));
 | 
								video_.set_scan_target(scan_target);
 | 
				
			||||||
			video_->set_character_rom(character_rom_);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void close_output() override {
 | 
							/// Sets the type of display.
 | 
				
			||||||
			video_.reset();
 | 
							void set_display_type(Outputs::Display::DisplayType display_type) override {
 | 
				
			||||||
		}
 | 
								video_.set_display_type(display_type);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		Outputs::CRT::CRT *get_crt() override {
 | 
					 | 
				
			||||||
			return video_->get_crt();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Outputs::Speaker::Speaker *get_speaker() override {
 | 
							Outputs::Speaker::Speaker *get_speaker() override {
 | 
				
			||||||
@@ -463,7 +480,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
				// actor, but this will actually be the result most of the time so it's not
 | 
									// actor, but this will actually be the result most of the time so it's not
 | 
				
			||||||
				// too terrible.
 | 
									// too terrible.
 | 
				
			||||||
				if(isReadOperation(operation) && address != 0xc000) {
 | 
									if(isReadOperation(operation) && address != 0xc000) {
 | 
				
			||||||
					*value = video_->get_last_read_value(cycles_since_video_update_);
 | 
										*value = video_.get_last_read_value(cycles_since_video_update_);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				switch(address) {
 | 
									switch(address) {
 | 
				
			||||||
@@ -523,18 +540,18 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
								case 0xc015:	IIeSwitchRead(internal_CX_rom_);											break;
 | 
													case 0xc015:	IIeSwitchRead(internal_CX_rom_);											break;
 | 
				
			||||||
								case 0xc016:	IIeSwitchRead(alternative_zero_page_);										break;
 | 
													case 0xc016:	IIeSwitchRead(alternative_zero_page_);										break;
 | 
				
			||||||
								case 0xc017:	IIeSwitchRead(slot_C3_rom_);												break;
 | 
													case 0xc017:	IIeSwitchRead(slot_C3_rom_);												break;
 | 
				
			||||||
								case 0xc018:	IIeSwitchRead(video_->get_80_store());										break;
 | 
													case 0xc018:	IIeSwitchRead(video_.get_80_store());										break;
 | 
				
			||||||
								case 0xc019:	IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_));	break;
 | 
													case 0xc019:	IIeSwitchRead(video_.get_is_vertical_blank(cycles_since_video_update_));	break;
 | 
				
			||||||
								case 0xc01a:	IIeSwitchRead(video_->get_text());											break;
 | 
													case 0xc01a:	IIeSwitchRead(video_.get_text());											break;
 | 
				
			||||||
								case 0xc01b:	IIeSwitchRead(video_->get_mixed());											break;
 | 
													case 0xc01b:	IIeSwitchRead(video_.get_mixed());											break;
 | 
				
			||||||
								case 0xc01c:	IIeSwitchRead(video_->get_page2());											break;
 | 
													case 0xc01c:	IIeSwitchRead(video_.get_page2());											break;
 | 
				
			||||||
								case 0xc01d:	IIeSwitchRead(video_->get_high_resolution());								break;
 | 
													case 0xc01d:	IIeSwitchRead(video_.get_high_resolution());								break;
 | 
				
			||||||
								case 0xc01e:	IIeSwitchRead(video_->get_alternative_character_set());						break;
 | 
													case 0xc01e:	IIeSwitchRead(video_.get_alternative_character_set());						break;
 | 
				
			||||||
								case 0xc01f:	IIeSwitchRead(video_->get_80_columns());									break;
 | 
													case 0xc01f:	IIeSwitchRead(video_.get_80_columns());										break;
 | 
				
			||||||
#undef IIeSwitchRead
 | 
					#undef IIeSwitchRead
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case 0xc07f:
 | 
													case 0xc07f:
 | 
				
			||||||
									if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00);
 | 
														if(is_iie()) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00);
 | 
				
			||||||
								break;
 | 
													break;
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						} else {
 | 
											} else {
 | 
				
			||||||
@@ -546,7 +563,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
									case 0xc000:
 | 
														case 0xc000:
 | 
				
			||||||
									case 0xc001:
 | 
														case 0xc001:
 | 
				
			||||||
										update_video();
 | 
															update_video();
 | 
				
			||||||
										video_->set_80_store(!!(address&1));
 | 
															video_.set_80_store(!!(address&1));
 | 
				
			||||||
										set_main_paging();
 | 
															set_main_paging();
 | 
				
			||||||
									break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -586,13 +603,13 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
									case 0xc00c:
 | 
														case 0xc00c:
 | 
				
			||||||
									case 0xc00d:
 | 
														case 0xc00d:
 | 
				
			||||||
										update_video();
 | 
															update_video();
 | 
				
			||||||
										video_->set_80_columns(!!(address&1));
 | 
															video_.set_80_columns(!!(address&1));
 | 
				
			||||||
									break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
									case 0xc00e:
 | 
														case 0xc00e:
 | 
				
			||||||
									case 0xc00f:
 | 
														case 0xc00f:
 | 
				
			||||||
										update_video();
 | 
															update_video();
 | 
				
			||||||
										video_->set_alternative_character_set(!!(address&1));
 | 
															video_.set_alternative_character_set(!!(address&1));
 | 
				
			||||||
									break;
 | 
														break;
 | 
				
			||||||
								}
 | 
													}
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
@@ -615,20 +632,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
					case 0xc050:
 | 
										case 0xc050:
 | 
				
			||||||
					case 0xc051:
 | 
										case 0xc051:
 | 
				
			||||||
						update_video();
 | 
											update_video();
 | 
				
			||||||
						video_->set_text(!!(address&1));
 | 
											video_.set_text(!!(address&1));
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 0xc052:	update_video();		video_->set_mixed(false);			break;
 | 
										case 0xc052:	update_video();		video_.set_mixed(false);		break;
 | 
				
			||||||
					case 0xc053:	update_video();		video_->set_mixed(true);			break;
 | 
										case 0xc053:	update_video();		video_.set_mixed(true);			break;
 | 
				
			||||||
					case 0xc054:
 | 
										case 0xc054:
 | 
				
			||||||
					case 0xc055:
 | 
										case 0xc055:
 | 
				
			||||||
						update_video();
 | 
											update_video();
 | 
				
			||||||
						video_->set_page2(!!(address&1));
 | 
											video_.set_page2(!!(address&1));
 | 
				
			||||||
						set_main_paging();
 | 
											set_main_paging();
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 0xc056:
 | 
										case 0xc056:
 | 
				
			||||||
					case 0xc057:
 | 
										case 0xc057:
 | 
				
			||||||
						update_video();
 | 
											update_video();
 | 
				
			||||||
						video_->set_high_resolution(!!(address&1));
 | 
											video_.set_high_resolution(!!(address&1));
 | 
				
			||||||
						set_main_paging();
 | 
											set_main_paging();
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -636,7 +653,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
					case 0xc05f:
 | 
										case 0xc05f:
 | 
				
			||||||
						if(is_iie()) {
 | 
											if(is_iie()) {
 | 
				
			||||||
							update_video();
 | 
												update_video();
 | 
				
			||||||
							video_->set_annunciator_3(!(address&1));
 | 
												video_.set_annunciator_3(!(address&1));
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -696,7 +713,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
					// If this is a card access, figure out which card is at play before determining
 | 
										// If this is a card access, figure out which card is at play before determining
 | 
				
			||||||
					// the totality of who needs messaging.
 | 
										// the totality of who needs messaging.
 | 
				
			||||||
					size_t card_number = 0;
 | 
										size_t card_number = 0;
 | 
				
			||||||
					AppleII::Card::Select select = AppleII::Card::None;
 | 
										Apple::II::Card::Select select = Apple::II::Card::None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(address >= 0xc100) {
 | 
										if(address >= 0xc100) {
 | 
				
			||||||
						/*
 | 
											/*
 | 
				
			||||||
@@ -704,20 +721,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
								0xCn00 to 0xCnff: card n.
 | 
													0xCn00 to 0xCnff: card n.
 | 
				
			||||||
						*/
 | 
											*/
 | 
				
			||||||
						card_number = (address - 0xc100) >> 8;
 | 
											card_number = (address - 0xc100) >> 8;
 | 
				
			||||||
						select = AppleII::Card::Device;
 | 
											select = Apple::II::Card::Device;
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						/*
 | 
											/*
 | 
				
			||||||
							Decode the area conventionally used by cards for registers:
 | 
												Decode the area conventionally used by cards for registers:
 | 
				
			||||||
								C0n0 to C0nF: card n - 8.
 | 
													C0n0 to C0nF: card n - 8.
 | 
				
			||||||
						*/
 | 
											*/
 | 
				
			||||||
						card_number = (address - 0xc090) >> 4;
 | 
											card_number = (address - 0xc090) >> 4;
 | 
				
			||||||
						select = AppleII::Card::IO;
 | 
											select = Apple::II::Card::IO;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// If the selected card is a just-in-time card, update the just-in-time cards,
 | 
										// If the selected card is a just-in-time card, update the just-in-time cards,
 | 
				
			||||||
					// and then message it specifically.
 | 
										// and then message it specifically.
 | 
				
			||||||
					const bool is_read = isReadOperation(operation);
 | 
										const bool is_read = isReadOperation(operation);
 | 
				
			||||||
					AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
 | 
										Apple::II::Card *const target = cards_[static_cast<size_t>(card_number)].get();
 | 
				
			||||||
					if(target && !is_every_cycle_card(target)) {
 | 
										if(target && !is_every_cycle_card(target)) {
 | 
				
			||||||
						update_just_in_time_cards();
 | 
											update_just_in_time_cards();
 | 
				
			||||||
						target->perform_bus_operation(select, is_read, address, value);
 | 
											target->perform_bus_operation(select, is_read, address, value);
 | 
				
			||||||
@@ -728,7 +745,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
					for(const auto &card: every_cycle_cards_) {
 | 
										for(const auto &card: every_cycle_cards_) {
 | 
				
			||||||
						card->run_for(Cycles(1), is_stretched_cycle);
 | 
											card->run_for(Cycles(1), is_stretched_cycle);
 | 
				
			||||||
						card->perform_bus_operation(
 | 
											card->perform_bus_operation(
 | 
				
			||||||
							(card == target) ? select : AppleII::Card::None,
 | 
												(card == target) ? select : Apple::II::Card::None,
 | 
				
			||||||
							is_read, address, value);
 | 
												is_read, address, value);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					has_updated_cards = true;
 | 
										has_updated_cards = true;
 | 
				
			||||||
@@ -740,7 +757,32 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
				const bool is_read = isReadOperation(operation);
 | 
									const bool is_read = isReadOperation(operation);
 | 
				
			||||||
				for(const auto &card: every_cycle_cards_) {
 | 
									for(const auto &card: every_cycle_cards_) {
 | 
				
			||||||
					card->run_for(Cycles(1), is_stretched_cycle);
 | 
										card->run_for(Cycles(1), is_stretched_cycle);
 | 
				
			||||||
					card->perform_bus_operation(AppleII::Card::None, is_read, address, value);
 | 
										card->perform_bus_operation(Apple::II::Card::None, is_read, address, value);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Update the card lists if any mutations are due.
 | 
				
			||||||
 | 
								if(card_lists_are_dirty_) {
 | 
				
			||||||
 | 
									card_lists_are_dirty_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// There's only one counter of time since update
 | 
				
			||||||
 | 
									// for just-in-time cards. If something new is
 | 
				
			||||||
 | 
									// transitioning, that needs to be zeroed.
 | 
				
			||||||
 | 
									if(card_became_just_in_time_) {
 | 
				
			||||||
 | 
										card_became_just_in_time_ = false;
 | 
				
			||||||
 | 
										update_just_in_time_cards();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Clear the two lists and repopulate.
 | 
				
			||||||
 | 
									every_cycle_cards_.clear();
 | 
				
			||||||
 | 
									just_in_time_cards_.clear();
 | 
				
			||||||
 | 
									for(const auto &card: cards_) {
 | 
				
			||||||
 | 
										if(!card) continue;
 | 
				
			||||||
 | 
										if(is_every_cycle_card(card.get())) {
 | 
				
			||||||
 | 
											every_cycle_cards_.push_back(card.get());
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											just_in_time_cards_.push_back(card.get());
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -786,7 +828,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
					case Key::Right:		value = 0x15;	break;
 | 
										case Key::Right:		value = 0x15;	break;
 | 
				
			||||||
					case Key::Down:			value = 0x0a;	break;
 | 
										case Key::Down:			value = 0x0a;	break;
 | 
				
			||||||
					case Key::Up:			value = 0x0b;	break;
 | 
										case Key::Up:			value = 0x0b;	break;
 | 
				
			||||||
					case Key::BackSpace:	value = 0x7f;	break;
 | 
										case Key::Backspace:	value = 0x7f;	break;
 | 
				
			||||||
					default: return;
 | 
										default: return;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -809,7 +851,29 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void type_string(const std::string &string) override {
 | 
							void type_string(const std::string &string) override {
 | 
				
			||||||
			string_serialiser_.reset(new Utility::StringSerialiser(string, true));
 | 
								string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK:: Configuration options.
 | 
				
			||||||
 | 
							std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
 | 
				
			||||||
 | 
								return Apple::II::get_options();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_selections(const Configurable::SelectionSet &selections_by_option) override {
 | 
				
			||||||
 | 
								Configurable::Display display;
 | 
				
			||||||
 | 
								if(Configurable::get_display(selections_by_option, display)) {
 | 
				
			||||||
 | 
									set_video_signal_configurable(display);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Configurable::SelectionSet get_accurate_selections() override {
 | 
				
			||||||
 | 
								Configurable::SelectionSet selection_set;
 | 
				
			||||||
 | 
								Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
 | 
				
			||||||
 | 
								return selection_set;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Configurable::SelectionSet get_user_friendly_selections() override {
 | 
				
			||||||
 | 
								return get_accurate_selections();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// MARK: MediaTarget
 | 
							// MARK: MediaTarget
 | 
				
			||||||
@@ -829,14 +893,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// MARK: JoystickMachine
 | 
							// MARK: JoystickMachine
 | 
				
			||||||
		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
 | 
							const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
 | 
				
			||||||
			return joysticks_;
 | 
								return joysticks_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace AppleII;
 | 
					using namespace Apple::II;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
 | 
					Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
 | 
				
			||||||
	using Target = Analyser::Static::AppleII::Target;
 | 
						using Target = Analyser::Static::AppleII::Target;
 | 
				
			||||||
@@ -9,14 +9,18 @@
 | 
				
			|||||||
#ifndef AppleII_hpp
 | 
					#ifndef AppleII_hpp
 | 
				
			||||||
#define AppleII_hpp
 | 
					#define AppleII_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Configurable/Configurable.hpp"
 | 
					#include "../../../Configurable/Configurable.hpp"
 | 
				
			||||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
 | 
					#include "../../../Analyser/Static/StaticAnalyser.hpp"
 | 
				
			||||||
#include "../ROMMachine.hpp"
 | 
					#include "../../ROMMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace AppleII {
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace II {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @returns The options available for an Apple II.
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Configurable::Option>> get_options();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Machine {
 | 
					class Machine {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
@@ -26,6 +30,7 @@ class Machine {
 | 
				
			|||||||
		static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
 | 
							static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* AppleII_hpp */
 | 
					#endif /* AppleII_hpp */
 | 
				
			||||||
@@ -9,11 +9,12 @@
 | 
				
			|||||||
#ifndef Card_h
 | 
					#ifndef Card_h
 | 
				
			||||||
#define Card_h
 | 
					#define Card_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Processors/6502/6502.hpp"
 | 
					#include "../../../Processors/6502/6502.hpp"
 | 
				
			||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
					#include "../../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
#include "../../Activity/Observer.hpp"
 | 
					#include "../../../Activity/Observer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace AppleII {
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace II {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	This provides a small subset of the interface offered to cards installed in
 | 
						This provides a small subset of the interface offered to cards installed in
 | 
				
			||||||
@@ -39,6 +40,7 @@ namespace AppleII {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
class Card {
 | 
					class Card {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
 | 
							virtual ~Card() {}
 | 
				
			||||||
		enum Select: int {
 | 
							enum Select: int {
 | 
				
			||||||
			None	= 0,		// No select line is active
 | 
								None	= 0,		// No select line is active
 | 
				
			||||||
			IO		= 1 << 0,	// IO select is active
 | 
								IO		= 1 << 0,	// IO select is active
 | 
				
			||||||
@@ -81,10 +83,8 @@ class Card {
 | 
				
			|||||||
			will receive a perform_bus_operation every cycle. To reduce the number of
 | 
								will receive a perform_bus_operation every cycle. To reduce the number of
 | 
				
			||||||
			virtual method calls, they **will not** receive run_for. run_for will propagate
 | 
								virtual method calls, they **will not** receive run_for. run_for will propagate
 | 
				
			||||||
			only to cards that register for IO and/or Device accesses only.
 | 
								only to cards that register for IO and/or Device accesses only.
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		int get_select_constraints() {
 | 
							int get_select_constraints() const {
 | 
				
			||||||
			return select_constraints_;
 | 
								return select_constraints_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,6 +108,7 @@ class Card {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* Card_h */
 | 
					#endif /* Card_h */
 | 
				
			||||||
@@ -8,15 +8,25 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "DiskIICard.hpp"
 | 
					#include "DiskIICard.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace AppleII;
 | 
					using namespace Apple::II;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
 | 
					DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
 | 
				
			||||||
	const auto roms = rom_fetcher(
 | 
						std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
 | 
				
			||||||
		"DiskII",
 | 
						if(is_16_sector) {
 | 
				
			||||||
		{
 | 
							roms = rom_fetcher({
 | 
				
			||||||
			is_16_sector ? "boot-16.rom" : "boot-13.rom",
 | 
								{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
 | 
				
			||||||
			is_16_sector ? "state-machine-16.rom" : "state-machine-13.rom"
 | 
								{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							roms = rom_fetcher({
 | 
				
			||||||
 | 
								{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
 | 
				
			||||||
 | 
								{"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(!roms[0] || !roms[1]) {
 | 
				
			||||||
 | 
							throw ROMMachine::Error::MissingROMs;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	boot_ = std::move(*roms[0]);
 | 
						boot_ = std::move(*roms[0]);
 | 
				
			||||||
	diskii_.set_state_machine(*roms[1]);
 | 
						diskii_.set_state_machine(*roms[1]);
 | 
				
			||||||
	set_select_constraints(None);
 | 
						set_select_constraints(None);
 | 
				
			||||||
@@ -42,7 +52,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void DiskIICard::run_for(Cycles cycles, int stretches) {
 | 
					void DiskIICard::run_for(Cycles cycles, int stretches) {
 | 
				
			||||||
	if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
 | 
						if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
 | 
				
			||||||
	diskii_.run_for(Cycles(cycles.as_int() * 2));
 | 
						diskii_.run_for(Cycles(cycles.as_integral() * 2));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DiskIICard::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
 | 
					void DiskIICard::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
 | 
				
			||||||
@@ -55,7 +65,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
 | 
					void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
 | 
				
			||||||
	diskii_clocking_preference_ = preference;
 | 
						diskii_clocking_preference_ = preference;
 | 
				
			||||||
	set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
 | 
						set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : None);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Storage::Disk::Drive &DiskIICard::get_drive(int drive) {
 | 
					Storage::Disk::Drive &DiskIICard::get_drive(int drive) {
 | 
				
			||||||
@@ -10,17 +10,18 @@
 | 
				
			|||||||
#define DiskIICard_hpp
 | 
					#define DiskIICard_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Card.hpp"
 | 
					#include "Card.hpp"
 | 
				
			||||||
#include "../ROMMachine.hpp"
 | 
					#include "../../ROMMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Components/DiskII/DiskII.hpp"
 | 
					#include "../../../Components/DiskII/DiskII.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/Disk.hpp"
 | 
					#include "../../../Storage/Disk/Disk.hpp"
 | 
				
			||||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
 | 
					#include "../../../ClockReceiver/ClockingHintSource.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace AppleII {
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace II {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DiskIICard: public Card, public ClockingHint::Observer {
 | 
					class DiskIICard: public Card, public ClockingHint::Observer {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
@@ -41,6 +42,7 @@ class DiskIICard: public Card, public ClockingHint::Observer {
 | 
				
			|||||||
		ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
 | 
							ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* DiskIICard_hpp */
 | 
					#endif /* DiskIICard_hpp */
 | 
				
			||||||
@@ -8,25 +8,22 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "Video.hpp"
 | 
					#include "Video.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace AppleII::Video;
 | 
					using namespace Apple::II::Video;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
 | 
					VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
 | 
				
			||||||
	crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)),
 | 
						crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
 | 
				
			||||||
	is_iie_(is_iie),
 | 
						is_iie_(is_iie),
 | 
				
			||||||
	deferrer_(std::move(target)) {
 | 
						deferrer_(std::move(target)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set a composite sampling function that assumes one byte per pixel input, and
 | 
					 | 
				
			||||||
	// accepts any non-zero value as being fully on, zero being fully off.
 | 
					 | 
				
			||||||
	crt_->set_composite_sampling_function(
 | 
					 | 
				
			||||||
		"float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
 | 
					 | 
				
			||||||
		"{"
 | 
					 | 
				
			||||||
			"return clamp(texture(sampler, coordinate).r, 0.0, 0.66);"
 | 
					 | 
				
			||||||
		"}");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Show only the centre 75% of the TV frame.
 | 
						// Show only the centre 75% of the TV frame.
 | 
				
			||||||
	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
 | 
						crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
 | 
				
			||||||
	crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f));
 | 
						crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
 | 
				
			||||||
	crt_->set_immediate_default_phase(0.0f);
 | 
					
 | 
				
			||||||
 | 
						// TODO: there seems to be some sort of bug whereby switching modes can cause
 | 
				
			||||||
 | 
						// a signal discontinuity that knocks phase out of whack. So it isn't safe to
 | 
				
			||||||
 | 
						// use default_colour_bursts elsewhere, though it otherwise should be. If/when
 | 
				
			||||||
 | 
						// it is, start doing so and return to setting the immediate phase up here.
 | 
				
			||||||
 | 
					//	crt_.set_immediate_default_phase(0.5f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	character_zones[0].xor_mask = 0;
 | 
						character_zones[0].xor_mask = 0;
 | 
				
			||||||
	character_zones[0].address_mask = 0x3f;
 | 
						character_zones[0].address_mask = 0x3f;
 | 
				
			||||||
@@ -46,8 +43,12 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Outputs::CRT::CRT *VideoBase::get_crt() {
 | 
					void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			||||||
	return crt_.get();
 | 
						crt_.set_scan_target(scan_target);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
 | 
				
			||||||
 | 
						crt_.set_display_type(display_type);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
@@ -9,14 +9,15 @@
 | 
				
			|||||||
#ifndef Video_hpp
 | 
					#ifndef Video_hpp
 | 
				
			||||||
#define Video_hpp
 | 
					#define Video_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../Outputs/CRT/CRT.hpp"
 | 
					#include "../../../Outputs/CRT/CRT.hpp"
 | 
				
			||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
 | 
					#include "../../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
#include "../../ClockReceiver/ClockDeferrer.hpp"
 | 
					#include "../../../ClockReceiver/DeferredQueue.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <array>
 | 
					#include <array>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace AppleII {
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace II {
 | 
				
			||||||
namespace Video {
 | 
					namespace Video {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BusHandler {
 | 
					class BusHandler {
 | 
				
			||||||
@@ -36,8 +37,11 @@ class VideoBase {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
 | 
							VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns The CRT this video feed is feeding.
 | 
							/// Sets the scan target.
 | 
				
			||||||
		Outputs::CRT::CRT *get_crt();
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Sets the type of output.
 | 
				
			||||||
 | 
							void set_display_type(Outputs::Display::DisplayType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*
 | 
							/*
 | 
				
			||||||
			Descriptions for the setters below are taken verbatim from
 | 
								Descriptions for the setters below are taken verbatim from
 | 
				
			||||||
@@ -146,7 +150,7 @@ class VideoBase {
 | 
				
			|||||||
		void set_character_rom(const std::vector<uint8_t> &);
 | 
							void set_character_rom(const std::vector<uint8_t> &);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected:
 | 
						protected:
 | 
				
			||||||
		std::unique_ptr<Outputs::CRT::CRT> crt_;
 | 
							Outputs::CRT::CRT crt_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// State affecting output video stream generation.
 | 
							// State affecting output video stream generation.
 | 
				
			||||||
		uint8_t *pixel_pointer_ = nullptr;
 | 
							uint8_t *pixel_pointer_ = nullptr;
 | 
				
			||||||
@@ -199,7 +203,7 @@ class VideoBase {
 | 
				
			|||||||
		std::array<uint8_t, 40> auxiliary_stream_;
 | 
							std::array<uint8_t, 40> auxiliary_stream_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool is_iie_ = false;
 | 
							bool is_iie_ = false;
 | 
				
			||||||
		static const int flash_length = 8406;
 | 
							static constexpr int flash_length = 8406;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Describes the current text mode mapping from in-memory character index
 | 
							// Describes the current text mode mapping from in-memory character index
 | 
				
			||||||
		// to output character.
 | 
							// to output character.
 | 
				
			||||||
@@ -247,8 +251,8 @@ class VideoBase {
 | 
				
			|||||||
		*/
 | 
							*/
 | 
				
			||||||
		void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
 | 
							void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Maintain a ClockDeferrer for delayed mode switches.
 | 
							// Maintain a DeferredQueue for delayed mode switches.
 | 
				
			||||||
		ClockDeferrer<Cycles> deferrer_;
 | 
							DeferredQueue<Cycles> deferrer_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
					template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			||||||
@@ -280,7 +284,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
			// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html
 | 
								// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Determine column at offset.
 | 
								// Determine column at offset.
 | 
				
			||||||
			int mapped_column = column_ + offset.as_int();
 | 
								int mapped_column = column_ + int(offset.as_integral());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Map that backwards from the internal pixels-at-start generation to pixels-at-end
 | 
								// Map that backwards from the internal pixels-at-start generation to pixels-at-end
 | 
				
			||||||
			// (so what was column 0 is now column 25).
 | 
								// (so what was column 0 is now column 25).
 | 
				
			||||||
@@ -311,7 +315,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
		bool get_is_vertical_blank(Cycles offset) {
 | 
							bool get_is_vertical_blank(Cycles offset) {
 | 
				
			||||||
			// Map that backwards from the internal pixels-at-start generation to pixels-at-end
 | 
								// Map that backwards from the internal pixels-at-start generation to pixels-at-end
 | 
				
			||||||
			// (so what was column 0 is now column 25).
 | 
								// (so what was column 0 is now column 25).
 | 
				
			||||||
			int mapped_column = column_ + offset.as_int();
 | 
								int mapped_column = column_ + int(offset.as_integral());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Map that backwards from the internal pixels-at-start generation to pixels-at-end
 | 
								// Map that backwards from the internal pixels-at-start generation to pixels-at-end
 | 
				
			||||||
			// (so what was column 0 is now column 25).
 | 
								// (so what was column 0 is now column 25).
 | 
				
			||||||
@@ -335,30 +339,31 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				A frame is oriented around 65 cycles across, 262 lines down.
 | 
									A frame is oriented around 65 cycles across, 262 lines down.
 | 
				
			||||||
			*/
 | 
								*/
 | 
				
			||||||
			static const int first_sync_line = 220;		// A complete guess. Information needed.
 | 
								constexpr int first_sync_line = 220;		// A complete guess. Information needed.
 | 
				
			||||||
			static const int first_sync_column = 49;	// Also a guess.
 | 
								constexpr int first_sync_column = 49;	// Also a guess.
 | 
				
			||||||
			static const int sync_length = 4;			// One of the two likely candidates.
 | 
								constexpr int sync_length = 4;			// One of the two likely candidates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			int int_cycles = cycles.as_int();
 | 
								int int_cycles = int(cycles.as_integral());
 | 
				
			||||||
			while(int_cycles) {
 | 
								while(int_cycles) {
 | 
				
			||||||
				const int cycles_this_line = std::min(65 - column_, int_cycles);
 | 
									const int cycles_this_line = std::min(65 - column_, int_cycles);
 | 
				
			||||||
				const int ending_column = column_ + cycles_this_line;
 | 
									const int ending_column = column_ + cycles_this_line;
 | 
				
			||||||
 | 
									const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
 | 
									if(is_vertical_sync_line) {
 | 
				
			||||||
					// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
 | 
										// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
 | 
				
			||||||
					// pulses (and hencce keep hsync approximately where it should be during vsync).
 | 
										// pulses (and hencce keep hsync approximately where it should be during vsync).
 | 
				
			||||||
					const int blank_start = std::max(first_sync_column - sync_length, column_);
 | 
										const int blank_start = std::max(first_sync_column - sync_length, column_);
 | 
				
			||||||
					const int blank_end = std::min(first_sync_column, ending_column);
 | 
										const int blank_end = std::min(first_sync_column, ending_column);
 | 
				
			||||||
					if(blank_end > blank_start) {
 | 
										if(blank_end > blank_start) {
 | 
				
			||||||
						if(blank_start > column_) {
 | 
											if(blank_start > column_) {
 | 
				
			||||||
							crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
 | 
												crt_.output_sync((blank_start - column_) * 14);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
						crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
 | 
											crt_.output_blank((blank_end - blank_start) * 14);
 | 
				
			||||||
						if(blank_end < ending_column) {
 | 
											if(blank_end < ending_column) {
 | 
				
			||||||
							crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
 | 
												crt_.output_sync((ending_column - blank_end) * 14);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
 | 
											crt_.output_sync(cycles_this_line * 14);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					const GraphicsMode line_mode = graphics_mode(row_);
 | 
										const GraphicsMode line_mode = graphics_mode(row_);
 | 
				
			||||||
@@ -394,7 +399,6 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
							static_cast<size_t>(fetch_end - column_),
 | 
												static_cast<size_t>(fetch_end - column_),
 | 
				
			||||||
							&base_stream_[static_cast<size_t>(column_)],
 | 
												&base_stream_[static_cast<size_t>(column_)],
 | 
				
			||||||
							&auxiliary_stream_[static_cast<size_t>(column_)]);
 | 
												&auxiliary_stream_[static_cast<size_t>(column_)]);
 | 
				
			||||||
						// TODO: should character modes be mapped to character pixel outputs here?
 | 
					 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(row_ < 192) {
 | 
										if(row_ < 192) {
 | 
				
			||||||
@@ -402,7 +406,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
						// remain where they would naturally be but auxiliary
 | 
											// remain where they would naturally be but auxiliary
 | 
				
			||||||
						// graphics appear to the left of that.
 | 
											// graphics appear to the left of that.
 | 
				
			||||||
						if(!column_) {
 | 
											if(!column_) {
 | 
				
			||||||
							pixel_pointer_ = crt_->allocate_write_area(568);
 | 
												pixel_pointer_ = crt_.begin_data(568);
 | 
				
			||||||
							graphics_carry_ = 0;
 | 
												graphics_carry_ = 0;
 | 
				
			||||||
							was_double_ = true;
 | 
												was_double_ = true;
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
@@ -413,7 +417,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
							const int pixel_row = row_ & 7;
 | 
												const int pixel_row = row_ & 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							const bool is_double = Video::is_double_mode(line_mode);
 | 
												const bool is_double = Video::is_double_mode(line_mode);
 | 
				
			||||||
							if(!is_double && was_double_) {
 | 
												if(!is_double && was_double_ && pixel_pointer_) {
 | 
				
			||||||
								pixel_pointer_[pixel_start*14 + 0] =
 | 
													pixel_pointer_[pixel_start*14 + 0] =
 | 
				
			||||||
								pixel_pointer_[pixel_start*14 + 1] =
 | 
													pixel_pointer_[pixel_start*14 + 1] =
 | 
				
			||||||
								pixel_pointer_[pixel_start*14 + 2] =
 | 
													pixel_pointer_[pixel_start*14 + 2] =
 | 
				
			||||||
@@ -424,88 +428,92 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
							}
 | 
												}
 | 
				
			||||||
							was_double_ = is_double;
 | 
												was_double_ = is_double;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							switch(line_mode) {
 | 
												if(pixel_pointer_) {
 | 
				
			||||||
								case GraphicsMode::Text:
 | 
													switch(line_mode) {
 | 
				
			||||||
									output_text(
 | 
														case GraphicsMode::Text:
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14 + 7],
 | 
															output_text(
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&pixel_pointer_[pixel_start * 14 + 7],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start),
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_row));
 | 
																static_cast<size_t>(pixel_end - pixel_start),
 | 
				
			||||||
								break;
 | 
																static_cast<size_t>(pixel_row));
 | 
				
			||||||
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case GraphicsMode::DoubleText:
 | 
														case GraphicsMode::DoubleText:
 | 
				
			||||||
									output_double_text(
 | 
															output_double_text(
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14],
 | 
																&pixel_pointer_[pixel_start * 14],
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										&auxiliary_stream_[static_cast<size_t>(pixel_start)],
 | 
																&auxiliary_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start),
 | 
																static_cast<size_t>(pixel_end - pixel_start),
 | 
				
			||||||
										static_cast<size_t>(pixel_row));
 | 
																static_cast<size_t>(pixel_row));
 | 
				
			||||||
								break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case GraphicsMode::LowRes:
 | 
														case GraphicsMode::LowRes:
 | 
				
			||||||
									output_low_resolution(
 | 
															output_low_resolution(
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14 + 7],
 | 
																&pixel_pointer_[pixel_start * 14 + 7],
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start),
 | 
																static_cast<size_t>(pixel_end - pixel_start),
 | 
				
			||||||
										pixel_start,
 | 
																pixel_start,
 | 
				
			||||||
										pixel_row);
 | 
																pixel_row);
 | 
				
			||||||
								break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case GraphicsMode::FatLowRes:
 | 
														case GraphicsMode::FatLowRes:
 | 
				
			||||||
									output_fat_low_resolution(
 | 
															output_fat_low_resolution(
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14 + 7],
 | 
																&pixel_pointer_[pixel_start * 14 + 7],
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start),
 | 
																static_cast<size_t>(pixel_end - pixel_start),
 | 
				
			||||||
										pixel_start,
 | 
																pixel_start,
 | 
				
			||||||
										pixel_row);
 | 
																pixel_row);
 | 
				
			||||||
								break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case GraphicsMode::DoubleLowRes:
 | 
														case GraphicsMode::DoubleLowRes:
 | 
				
			||||||
									output_double_low_resolution(
 | 
															output_double_low_resolution(
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14],
 | 
																&pixel_pointer_[pixel_start * 14],
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										&auxiliary_stream_[static_cast<size_t>(pixel_start)],
 | 
																&auxiliary_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start),
 | 
																static_cast<size_t>(pixel_end - pixel_start),
 | 
				
			||||||
										pixel_start,
 | 
																pixel_start,
 | 
				
			||||||
										pixel_row);
 | 
																pixel_row);
 | 
				
			||||||
								break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case GraphicsMode::HighRes:
 | 
														case GraphicsMode::HighRes:
 | 
				
			||||||
									output_high_resolution(
 | 
															output_high_resolution(
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14 + 7],
 | 
																&pixel_pointer_[pixel_start * 14 + 7],
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start));
 | 
																static_cast<size_t>(pixel_end - pixel_start));
 | 
				
			||||||
								break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								case GraphicsMode::DoubleHighRes:
 | 
														case GraphicsMode::DoubleHighRes:
 | 
				
			||||||
									output_double_high_resolution(
 | 
															output_double_high_resolution(
 | 
				
			||||||
										&pixel_pointer_[pixel_start * 14],
 | 
																&pixel_pointer_[pixel_start * 14],
 | 
				
			||||||
										&base_stream_[static_cast<size_t>(pixel_start)],
 | 
																&base_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										&auxiliary_stream_[static_cast<size_t>(pixel_start)],
 | 
																&auxiliary_stream_[static_cast<size_t>(pixel_start)],
 | 
				
			||||||
										static_cast<size_t>(pixel_end - pixel_start));
 | 
																static_cast<size_t>(pixel_end - pixel_start));
 | 
				
			||||||
								break;
 | 
														break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								default: break;
 | 
														default: break;
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							if(pixel_end == 40) {
 | 
												if(pixel_end == 40) {
 | 
				
			||||||
								if(was_double_) {
 | 
													if(pixel_pointer_) {
 | 
				
			||||||
									pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
 | 
														if(was_double_) {
 | 
				
			||||||
									pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
 | 
															pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
 | 
				
			||||||
								} else {
 | 
															pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
 | 
				
			||||||
									if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
 | 
														} else {
 | 
				
			||||||
										pixel_pointer_[567] = graphics_carry_;
 | 
															if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
 | 
				
			||||||
									else
 | 
																pixel_pointer_[567] = graphics_carry_;
 | 
				
			||||||
										pixel_pointer_[567] = 0;
 | 
															else
 | 
				
			||||||
 | 
																pixel_pointer_[567] = 0;
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
								}
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								crt_->output_data(568, 568);
 | 
													crt_.output_data(568, 568);
 | 
				
			||||||
								pixel_pointer_ = nullptr;
 | 
													pixel_pointer_ = nullptr;
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						if(column_ < 40 && ending_column >= 40) {
 | 
											if(column_ < 40 && ending_column >= 40) {
 | 
				
			||||||
							crt_->output_blank(568);
 | 
												crt_.output_blank(568);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -515,11 +523,11 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
					*/
 | 
										*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(column_ < first_sync_column && ending_column >= first_sync_column) {
 | 
										if(column_ < first_sync_column && ending_column >= first_sync_column) {
 | 
				
			||||||
						crt_->output_blank((first_sync_column - 41)*14 - 1);
 | 
											crt_.output_blank(first_sync_column*14 - 568);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
 | 
										if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
 | 
				
			||||||
						crt_->output_sync(sync_length*14);
 | 
											crt_.output_sync(sync_length*14);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					int second_blank_start;
 | 
										int second_blank_start;
 | 
				
			||||||
@@ -527,7 +535,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
						const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
 | 
											const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
 | 
				
			||||||
						const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
 | 
											const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
 | 
				
			||||||
						if(colour_burst_end > colour_burst_start) {
 | 
											if(colour_burst_end > colour_burst_start) {
 | 
				
			||||||
							crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192);
 | 
												crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, 0);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
 | 
											second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
 | 
				
			||||||
@@ -536,7 +544,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(ending_column > second_blank_start) {
 | 
										if(ending_column > second_blank_start) {
 | 
				
			||||||
						crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
 | 
											crt_.output_blank((ending_column - second_blank_start) * 14);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -550,8 +558,13 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Add an extra half a colour cycle of blank; this isn't counted in the run_for
 | 
										// Add an extra half a colour cycle of blank; this isn't counted in the run_for
 | 
				
			||||||
					// count explicitly but is promised.
 | 
										// count explicitly but is promised. If this is a vertical sync line, output sync
 | 
				
			||||||
					crt_->output_blank(2);
 | 
										// instead of blank, taking that to be the default level.
 | 
				
			||||||
 | 
										if(is_vertical_sync_line) {
 | 
				
			||||||
 | 
											crt_.output_sync(2);
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											crt_.output_blank(2);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -588,6 +601,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
 | 
				
			|||||||
		BusHandler &bus_handler_;
 | 
							BusHandler &bus_handler_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										97
									
								
								Machines/Apple/Macintosh/Audio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								Machines/Apple/Macintosh/Audio.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Audio.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 31/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Audio.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple::Macintosh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The sample_length is coupled with the clock rate selected within the Macintosh proper;
 | 
				
			||||||
 | 
					// as per the header-declaration a divide-by-two clock is expected to arrive here.
 | 
				
			||||||
 | 
					const std::size_t sample_length = 352 / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Inputs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Audio::post_sample(uint8_t sample) {
 | 
				
			||||||
 | 
						// Store sample directly indexed by current write pointer; this ensures that collected samples
 | 
				
			||||||
 | 
						// directly map to volume and enabled/disabled states.
 | 
				
			||||||
 | 
						sample_queue_.buffer[sample_queue_.write_pointer] = sample;
 | 
				
			||||||
 | 
						sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Audio::set_volume(int volume) {
 | 
				
			||||||
 | 
						// Do nothing if the volume hasn't changed.
 | 
				
			||||||
 | 
						if(posted_volume_ == volume) return;
 | 
				
			||||||
 | 
						posted_volume_ = volume;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Post the volume change as a deferred event.
 | 
				
			||||||
 | 
						task_queue_.defer([=] () {
 | 
				
			||||||
 | 
							volume_ = volume;
 | 
				
			||||||
 | 
							set_volume_multiplier();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Audio::set_enabled(bool on) {
 | 
				
			||||||
 | 
						// Do nothing if the mask hasn't changed.
 | 
				
			||||||
 | 
						if(posted_enable_mask_ == int(on)) return;
 | 
				
			||||||
 | 
						posted_enable_mask_ = int(on);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Post the enabled mask change as a deferred event.
 | 
				
			||||||
 | 
						task_queue_.defer([=] () {
 | 
				
			||||||
 | 
							enabled_mask_ = int(on);
 | 
				
			||||||
 | 
							set_volume_multiplier();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - Output generation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Audio::is_zero_level() {
 | 
				
			||||||
 | 
						return !volume_ || !enabled_mask_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Audio::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
 | 
						// Some underflow here doesn't really matter.
 | 
				
			||||||
 | 
						output_volume_ = range / (7 * 255);
 | 
				
			||||||
 | 
						set_volume_multiplier();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Audio::set_volume_multiplier() {
 | 
				
			||||||
 | 
						volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			||||||
 | 
						// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation;
 | 
				
			||||||
 | 
						// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
 | 
				
			||||||
 | 
						// that's something to return to.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while(number_of_samples) {
 | 
				
			||||||
 | 
							// Determine how many output samples will be at the same level.
 | 
				
			||||||
 | 
							const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Determine the output level, and output that many samples.
 | 
				
			||||||
 | 
							// (Hoping that the copiler substitutes an effective memset16-type operation here).
 | 
				
			||||||
 | 
							const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128);
 | 
				
			||||||
 | 
							for(size_t c = 0; c < cycles_left_in_sample; ++c) {
 | 
				
			||||||
 | 
								target[c] = output_level;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							target += cycles_left_in_sample;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Advance the sample pointer.
 | 
				
			||||||
 | 
							subcycle_offset_ += cycles_left_in_sample;
 | 
				
			||||||
 | 
							sample_queue_.read_pointer = (sample_queue_.read_pointer + (subcycle_offset_ / sample_length)) % sample_queue_.buffer.size();
 | 
				
			||||||
 | 
							subcycle_offset_ %= sample_length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Decreate the number of samples left to write.
 | 
				
			||||||
 | 
							number_of_samples -= cycles_left_in_sample;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								Machines/Apple/Macintosh/Audio.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Machines/Apple/Macintosh/Audio.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Audio.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 31/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Audio_hpp
 | 
				
			||||||
 | 
					#define Audio_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					#include "../../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <array>
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Implements the Macintosh's audio output hardware.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Designed to be clocked at half the rate of the real hardware — i.e.
 | 
				
			||||||
 | 
						a shade less than 4Mhz.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class Audio: public ::Outputs::Speaker::SampleSource {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							Audio(Concurrency::DeferringAsyncTaskQueue &task_queue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Macintosh audio is (partly) sourced by the same scanning
 | 
				
			||||||
 | 
								hardware as the video; each line it collects an additional
 | 
				
			||||||
 | 
								word of memory, half of which is used for audio output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Use this method to add a newly-collected sample to the queue.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void post_sample(uint8_t sample);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Macintosh audio also separately receives an output volume
 | 
				
			||||||
 | 
								level, in the range 0 to 7.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Use this method to set the current output volume.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_volume(int volume);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								A further factor in audio output is the on-off toggle.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_enabled(bool on);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
 | 
				
			||||||
 | 
							void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
				
			||||||
 | 
							bool is_zero_level();
 | 
				
			||||||
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// A queue of fetched samples; read from by one thread,
 | 
				
			||||||
 | 
							// written to by another.
 | 
				
			||||||
 | 
							struct {
 | 
				
			||||||
 | 
								std::array<uint8_t, 740> buffer;
 | 
				
			||||||
 | 
								size_t read_pointer = 0, write_pointer = 0;
 | 
				
			||||||
 | 
							} sample_queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Emulator-thread stateful variables, to avoid work posting
 | 
				
			||||||
 | 
							// deferral updates if possible.
 | 
				
			||||||
 | 
							int posted_volume_ = 0;
 | 
				
			||||||
 | 
							int posted_enable_mask_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Stateful variables, modified from the audio generation
 | 
				
			||||||
 | 
							// thread only.
 | 
				
			||||||
 | 
							int volume_ = 0;
 | 
				
			||||||
 | 
							int enabled_mask_ = 0;
 | 
				
			||||||
 | 
							std::int16_t output_volume_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::int16_t volume_multiplier_ = 0;
 | 
				
			||||||
 | 
							std::size_t subcycle_offset_ = 0;
 | 
				
			||||||
 | 
							void set_volume_multiplier();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Audio_hpp */
 | 
				
			||||||
							
								
								
									
										34
									
								
								Machines/Apple/Macintosh/DeferredAudio.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Machines/Apple/Macintosh/DeferredAudio.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  DeferredAudio.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 01/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef DeferredAudio_h
 | 
				
			||||||
 | 
					#define DeferredAudio_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Audio.hpp"
 | 
				
			||||||
 | 
					#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct DeferredAudio {
 | 
				
			||||||
 | 
						Concurrency::DeferringAsyncTaskQueue queue;
 | 
				
			||||||
 | 
						Audio audio;
 | 
				
			||||||
 | 
						Outputs::Speaker::LowpassSpeaker<Audio> speaker;
 | 
				
			||||||
 | 
						HalfCycles time_since_update;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DeferredAudio() : audio(queue), speaker(audio) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void flush() {
 | 
				
			||||||
 | 
							speaker.run_for(queue, time_since_update.flush<Cycles>());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* DeferredAudio_h */
 | 
				
			||||||
							
								
								
									
										56
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  DriveSpeedAccumulator.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 01/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "DriveSpeedAccumulator.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple::Macintosh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DriveSpeedAccumulator::post_sample(uint8_t sample) {
 | 
				
			||||||
 | 
						if(!delegate_) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// An Euler-esque approximation is used here: just collect all
 | 
				
			||||||
 | 
						// the samples until there is a certain small quantity of them,
 | 
				
			||||||
 | 
						// then produce a new estimate of rotation speed and start the
 | 
				
			||||||
 | 
						// buffer afresh.
 | 
				
			||||||
 | 
						samples_[sample_pointer_] = sample;
 | 
				
			||||||
 | 
						++sample_pointer_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(sample_pointer_ == samples_.size()) {
 | 
				
			||||||
 | 
							sample_pointer_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The below fits for a function like `a + bc`; it encapsultes the following
 | 
				
			||||||
 | 
							// beliefs:
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							//	(i) motor speed is proportional to voltage supplied;
 | 
				
			||||||
 | 
							//	(ii) with pulse-width modulation it's therefore proportional to the duty cycle;
 | 
				
			||||||
 | 
							//	(iii) the Mac pulse-width modulates whatever it reads from the disk speed buffer;
 | 
				
			||||||
 | 
							//	(iv) ... subject to software pulse-width modulation of that pulse-width modulation.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// So, I believe current motor speed is proportional to a low-pass filtering of
 | 
				
			||||||
 | 
							// the speed buffer. Which I've implemented very coarsely via 'large' bucketed-averages,
 | 
				
			||||||
 | 
							// noting also that exact disk motor speed is always a little approximate.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Sum all samples.
 | 
				
			||||||
 | 
							// TODO: if the above is the correct test, do it on sample receipt rather than
 | 
				
			||||||
 | 
							// bothering with an intermediate buffer.
 | 
				
			||||||
 | 
							int sum = 0;
 | 
				
			||||||
 | 
							for(auto s: samples_) {
 | 
				
			||||||
 | 
								sum += s;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The formula below was derived from observing values the Mac wrote into its
 | 
				
			||||||
 | 
							// disk-speed buffer. Given that it runs a calibration loop before doing so,
 | 
				
			||||||
 | 
							// I cannot guarantee the accuracy of these numbers beyond being within the
 | 
				
			||||||
 | 
							// range that the computer would accept.
 | 
				
			||||||
 | 
							const float normalised_sum = float(sum) / float(samples_.size());
 | 
				
			||||||
 | 
							const float rotation_speed = (normalised_sum * 27.08f) - 259.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										45
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  DriveSpeedAccumulator.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 01/06/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef DriveSpeedAccumulator_hpp
 | 
				
			||||||
 | 
					#define DriveSpeedAccumulator_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <array>
 | 
				
			||||||
 | 
					#include <cstddef>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriveSpeedAccumulator {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Accepts fetched motor control values.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void post_sample(uint8_t sample);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct Delegate {
 | 
				
			||||||
 | 
								virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the delegate to receive drive speed changes.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_delegate(Delegate *delegate) {
 | 
				
			||||||
 | 
								delegate_ = delegate;;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							std::array<uint8_t, 20> samples_;
 | 
				
			||||||
 | 
							std::size_t sample_pointer_ = 0;
 | 
				
			||||||
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* DriveSpeedAccumulator_hpp */
 | 
				
			||||||
							
								
								
									
										88
									
								
								Machines/Apple/Macintosh/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Machines/Apple/Macintosh/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Keyboard.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 02/08/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Keyboard.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple::Macintosh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
 | 
				
			||||||
 | 
						using Key = Inputs::Keyboard::Key;
 | 
				
			||||||
 | 
						using MacKey = Apple::Macintosh::Key;
 | 
				
			||||||
 | 
						switch(key) {
 | 
				
			||||||
 | 
							default: return KeyboardMachine::MappedMachine::KeyNotMapped;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define Bind(x, y) case Key::x: return uint16_t(y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(BackTick, MacKey::BackTick);
 | 
				
			||||||
 | 
							Bind(k1, MacKey::k1);	Bind(k2, MacKey::k2);	Bind(k3, MacKey::k3);
 | 
				
			||||||
 | 
							Bind(k4, MacKey::k4);	Bind(k5, MacKey::k5);	Bind(k6, MacKey::k6);
 | 
				
			||||||
 | 
							Bind(k7, MacKey::k7);	Bind(k8, MacKey::k8);	Bind(k9, MacKey::k9);
 | 
				
			||||||
 | 
							Bind(k0, MacKey::k0);
 | 
				
			||||||
 | 
							Bind(Hyphen, MacKey::Hyphen);
 | 
				
			||||||
 | 
							Bind(Equals, MacKey::Equals);
 | 
				
			||||||
 | 
							Bind(Backspace, MacKey::Backspace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(Tab, MacKey::Tab);
 | 
				
			||||||
 | 
							Bind(Q, MacKey::Q);		Bind(W, MacKey::W);		Bind(E, MacKey::E);		Bind(R, MacKey::R);
 | 
				
			||||||
 | 
							Bind(T, MacKey::T);		Bind(Y, MacKey::Y);		Bind(U, MacKey::U);		Bind(I, MacKey::I);
 | 
				
			||||||
 | 
							Bind(O, MacKey::O);		Bind(P, MacKey::P);
 | 
				
			||||||
 | 
							Bind(OpenSquareBracket, MacKey::OpenSquareBracket);
 | 
				
			||||||
 | 
							Bind(CloseSquareBracket, MacKey::CloseSquareBracket);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(CapsLock, MacKey::CapsLock);
 | 
				
			||||||
 | 
							Bind(A, MacKey::A);		Bind(S, MacKey::S);		Bind(D, MacKey::D);		Bind(F, MacKey::F);
 | 
				
			||||||
 | 
							Bind(G, MacKey::G);		Bind(H, MacKey::H);		Bind(J, MacKey::J);		Bind(K, MacKey::K);
 | 
				
			||||||
 | 
							Bind(L, MacKey::L);
 | 
				
			||||||
 | 
							Bind(Semicolon, MacKey::Semicolon);
 | 
				
			||||||
 | 
							Bind(Quote, MacKey::Quote);
 | 
				
			||||||
 | 
							Bind(Enter, MacKey::Return);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(LeftShift, MacKey::Shift);
 | 
				
			||||||
 | 
							Bind(Z, MacKey::Z);		Bind(X, MacKey::X);		Bind(C, MacKey::C);		Bind(V, MacKey::V);
 | 
				
			||||||
 | 
							Bind(B, MacKey::B);		Bind(N, MacKey::N);		Bind(M, MacKey::M);
 | 
				
			||||||
 | 
							Bind(Comma, MacKey::Comma);
 | 
				
			||||||
 | 
							Bind(FullStop, MacKey::FullStop);
 | 
				
			||||||
 | 
							Bind(ForwardSlash, MacKey::ForwardSlash);
 | 
				
			||||||
 | 
							Bind(RightShift, MacKey::Shift);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(Left, MacKey::Left);
 | 
				
			||||||
 | 
							Bind(Right, MacKey::Right);
 | 
				
			||||||
 | 
							Bind(Up, MacKey::Up);
 | 
				
			||||||
 | 
							Bind(Down, MacKey::Down);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(LeftOption, MacKey::Option);
 | 
				
			||||||
 | 
							Bind(RightOption, MacKey::Option);
 | 
				
			||||||
 | 
							Bind(LeftMeta, MacKey::Command);
 | 
				
			||||||
 | 
							Bind(RightMeta, MacKey::Command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(Space, MacKey::Space);
 | 
				
			||||||
 | 
							Bind(Backslash, MacKey::Backslash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(KeypadDelete, MacKey::KeypadDelete);
 | 
				
			||||||
 | 
							Bind(KeypadEquals, MacKey::KeypadEquals);
 | 
				
			||||||
 | 
							Bind(KeypadSlash, MacKey::KeypadSlash);
 | 
				
			||||||
 | 
							Bind(KeypadAsterisk, MacKey::KeypadAsterisk);
 | 
				
			||||||
 | 
							Bind(KeypadMinus, MacKey::KeypadMinus);
 | 
				
			||||||
 | 
							Bind(KeypadPlus, MacKey::KeypadPlus);
 | 
				
			||||||
 | 
							Bind(KeypadEnter, MacKey::KeypadEnter);
 | 
				
			||||||
 | 
							Bind(KeypadDecimalPoint, MacKey::KeypadDecimalPoint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Bind(Keypad9, MacKey::Keypad9);
 | 
				
			||||||
 | 
							Bind(Keypad8, MacKey::Keypad8);
 | 
				
			||||||
 | 
							Bind(Keypad7, MacKey::Keypad7);
 | 
				
			||||||
 | 
							Bind(Keypad6, MacKey::Keypad6);
 | 
				
			||||||
 | 
							Bind(Keypad5, MacKey::Keypad5);
 | 
				
			||||||
 | 
							Bind(Keypad4, MacKey::Keypad4);
 | 
				
			||||||
 | 
							Bind(Keypad3, MacKey::Keypad3);
 | 
				
			||||||
 | 
							Bind(Keypad2, MacKey::Keypad2);
 | 
				
			||||||
 | 
							Bind(Keypad1, MacKey::Keypad1);
 | 
				
			||||||
 | 
							Bind(Keypad0, MacKey::Keypad0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#undef Bind
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										300
									
								
								Machines/Apple/Macintosh/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								Machines/Apple/Macintosh/Keyboard.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,300 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Keyboard.h
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 08/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Apple_Macintosh_Keyboard_hpp
 | 
				
			||||||
 | 
					#define Apple_Macintosh_Keyboard_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../KeyboardMachine.hpp"
 | 
				
			||||||
 | 
					#include "../../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr uint16_t KeypadMask = 0x100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Defines the keycodes that could be passed directly to a Macintosh via set_key_pressed.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					enum class Key: uint16_t {
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
							See p284 of the Apple Guide to the Macintosh Family Hardware
 | 
				
			||||||
 | 
							for documentation of the mapping below.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						BackTick = 0x65,
 | 
				
			||||||
 | 
						k1 = 0x25,	k2 = 0x27,	k3 = 0x29,	k4 = 0x2b,	k5 = 0x2f,
 | 
				
			||||||
 | 
						k6 = 0x2d,	k7 = 0x35,	k8 = 0x39,	k9 = 0x33,	k0 = 0x3b,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Hyphen = 0x37,
 | 
				
			||||||
 | 
						Equals = 0x31,
 | 
				
			||||||
 | 
						Backspace = 0x67,
 | 
				
			||||||
 | 
						Tab = 0x61,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q = 0x19, W = 0x1b, E = 0x1d, R = 0x1f, T = 0x23, Y = 0x21, U = 0x41, I = 0x45, O = 0x3f, P = 0x47,
 | 
				
			||||||
 | 
						A = 0x01, S = 0x03, D = 0x05, F = 0x07, G = 0x0b, H = 0x09, J = 0x4d, K = 0x51, L = 0x4b,
 | 
				
			||||||
 | 
						Z = 0x0d, X = 0x0f, C = 0x11, V = 0x13, B = 0x17, N = 0x5b, M = 0x5d,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						OpenSquareBracket = 0x43,
 | 
				
			||||||
 | 
						CloseSquareBracket = 0x3d,
 | 
				
			||||||
 | 
						Semicolon = 0x53,
 | 
				
			||||||
 | 
						Quote = 0x4f,
 | 
				
			||||||
 | 
						Comma = 0x57,
 | 
				
			||||||
 | 
						FullStop = 0x5f,
 | 
				
			||||||
 | 
						ForwardSlash = 0x59,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CapsLock = 0x73,
 | 
				
			||||||
 | 
						Shift = 0x71,
 | 
				
			||||||
 | 
						Option = 0x75,
 | 
				
			||||||
 | 
						Command = 0x6f,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Space = 0x63,
 | 
				
			||||||
 | 
						Backslash = 0x55,
 | 
				
			||||||
 | 
						Return = 0x49,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Left = KeypadMask | 0x0d,
 | 
				
			||||||
 | 
						Right = KeypadMask | 0x05,
 | 
				
			||||||
 | 
						Up = KeypadMask | 0x1b,
 | 
				
			||||||
 | 
						Down = KeypadMask | 0x11,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						KeypadDelete = KeypadMask | 0x0f,
 | 
				
			||||||
 | 
						KeypadEquals = KeypadMask | 0x11,
 | 
				
			||||||
 | 
						KeypadSlash = KeypadMask | 0x1b,
 | 
				
			||||||
 | 
						KeypadAsterisk = KeypadMask | 0x05,
 | 
				
			||||||
 | 
						KeypadMinus = KeypadMask | 0x1d,
 | 
				
			||||||
 | 
						KeypadPlus = KeypadMask | 0x0d,
 | 
				
			||||||
 | 
						KeypadEnter = KeypadMask | 0x19,
 | 
				
			||||||
 | 
						KeypadDecimalPoint = KeypadMask | 0x03,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Keypad9 = KeypadMask | 0x39,
 | 
				
			||||||
 | 
						Keypad8 = KeypadMask | 0x37,
 | 
				
			||||||
 | 
						Keypad7 = KeypadMask | 0x33,
 | 
				
			||||||
 | 
						Keypad6 = KeypadMask | 0x31,
 | 
				
			||||||
 | 
						Keypad5 = KeypadMask | 0x2f,
 | 
				
			||||||
 | 
						Keypad4 = KeypadMask | 0x2d,
 | 
				
			||||||
 | 
						Keypad3 = KeypadMask | 0x2b,
 | 
				
			||||||
 | 
						Keypad2 = KeypadMask | 0x29,
 | 
				
			||||||
 | 
						Keypad1 = KeypadMask | 0x27,
 | 
				
			||||||
 | 
						Keypad0 = KeypadMask | 0x25
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Keyboard {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							void set_input(bool data) {
 | 
				
			||||||
 | 
								switch(mode_) {
 | 
				
			||||||
 | 
									case Mode::Waiting:
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
											"Only the computer can initiate communication over the keyboard lines. When the computer and keyboard
 | 
				
			||||||
 | 
											are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The
 | 
				
			||||||
 | 
											computer signals that it is ready to begin communication by pulling the Keyboard Data line low."
 | 
				
			||||||
 | 
										*/
 | 
				
			||||||
 | 
										if(!data) {
 | 
				
			||||||
 | 
											mode_ = Mode::AcceptingCommand;
 | 
				
			||||||
 | 
											phase_ = 0;
 | 
				
			||||||
 | 
											command_ = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Mode::AcceptingCommand:
 | 
				
			||||||
 | 
										/* Note value, so that it can be latched upon a clock transition. */
 | 
				
			||||||
 | 
										data_input_ = data;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Mode::AwaitingEndOfCommand:
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
											The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready
 | 
				
			||||||
 | 
											to receive the keyboard's response by setting the Keyboard Data line high.
 | 
				
			||||||
 | 
										*/
 | 
				
			||||||
 | 
										if(data) {
 | 
				
			||||||
 | 
											mode_ = Mode::PerformingCommand;
 | 
				
			||||||
 | 
											phase_ = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									case Mode::SendingResponse:
 | 
				
			||||||
 | 
										/* This line isn't currently an input; do nothing. */
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool get_clock() {
 | 
				
			||||||
 | 
								return clock_output_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool get_data() {
 | 
				
			||||||
 | 
								return !!(response_ & 0x80);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void run_for(HalfCycles cycle) {
 | 
				
			||||||
 | 
								switch(mode_) {
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									case Mode::Waiting: return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Mode::AcceptingCommand: {
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
											"When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low,
 | 
				
			||||||
 | 
											220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places
 | 
				
			||||||
 | 
											a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge
 | 
				
			||||||
 | 
											of the Keyboard Clock signal."
 | 
				
			||||||
 | 
										*/
 | 
				
			||||||
 | 
										const auto offset = phase_ % 40;
 | 
				
			||||||
 | 
										clock_output_ = offset >= 18;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(offset == 26) {
 | 
				
			||||||
 | 
											command_ = (command_ << 1) | (data_input_ ? 1 : 0);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										++phase_;
 | 
				
			||||||
 | 
										if(phase_ == 8*40) {
 | 
				
			||||||
 | 
											mode_ = Mode::AwaitingEndOfCommand;
 | 
				
			||||||
 | 
											phase_ = 0;
 | 
				
			||||||
 | 
											clock_output_ = false;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Mode::AwaitingEndOfCommand:
 | 
				
			||||||
 | 
										// Time out if the end-of-command seems not to be forthcoming.
 | 
				
			||||||
 | 
										// This is an elaboration on my part; a guess.
 | 
				
			||||||
 | 
										++phase_;
 | 
				
			||||||
 | 
										if(phase_ == 1000) {
 | 
				
			||||||
 | 
											clock_output_ = false;
 | 
				
			||||||
 | 
											mode_ = Mode::Waiting;
 | 
				
			||||||
 | 
											phase_ = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Mode::PerformingCommand: {
 | 
				
			||||||
 | 
										response_ = perform_command(command_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Inquiry has a 0.25-second timeout; everything else is instant.
 | 
				
			||||||
 | 
										++phase_;
 | 
				
			||||||
 | 
										if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) {
 | 
				
			||||||
 | 
											mode_ = Mode::SendingResponse;
 | 
				
			||||||
 | 
											phase_ = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Mode::SendingResponse: {
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
											"When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high)
 | 
				
			||||||
 | 
											on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each
 | 
				
			||||||
 | 
											clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the
 | 
				
			||||||
 | 
											rising edge of the Keyboard Clock signal."
 | 
				
			||||||
 | 
										*/
 | 
				
			||||||
 | 
										const auto offset = phase_ % 33;
 | 
				
			||||||
 | 
										clock_output_ = offset >= 16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(offset == 29) {
 | 
				
			||||||
 | 
											response_ <<= 1;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										++phase_;
 | 
				
			||||||
 | 
										if(phase_ == 8*33) {
 | 
				
			||||||
 | 
											clock_output_ = false;
 | 
				
			||||||
 | 
											mode_ = Mode::Waiting;
 | 
				
			||||||
 | 
											phase_ = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void enqueue_key_state(uint16_t key, bool is_pressed) {
 | 
				
			||||||
 | 
								// Front insert; messages will be pop_back'd.
 | 
				
			||||||
 | 
								std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme
 | 
				
			||||||
 | 
								// they are indicated by having bit 8 set. So add the $79 prefix if required.
 | 
				
			||||||
 | 
								if(key & KeypadMask) {
 | 
				
			||||||
 | 
									key_queue_.insert(key_queue_.begin(), 0x79);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							/// Performs the pre-ADB Apple keyboard protocol command @c command, returning
 | 
				
			||||||
 | 
							/// the proper result if the command were to terminate now. So, it treats inquiry
 | 
				
			||||||
 | 
							/// and instant as the same command.
 | 
				
			||||||
 | 
							int perform_command(int command) {
 | 
				
			||||||
 | 
								switch(command) {
 | 
				
			||||||
 | 
									case 0x10:		// Inquiry.
 | 
				
			||||||
 | 
									case 0x14: {	// Instant.
 | 
				
			||||||
 | 
										std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
 | 
				
			||||||
 | 
										if(!key_queue_.empty()) {
 | 
				
			||||||
 | 
											const auto new_message = key_queue_.back();
 | 
				
			||||||
 | 
											key_queue_.pop_back();
 | 
				
			||||||
 | 
											return new_message;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case 0x16:	// Model number.
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
										0x01 |			// b0: always 1
 | 
				
			||||||
 | 
										(1 << 1) |		// keyboard model number
 | 
				
			||||||
 | 
										(1 << 4);		// next device number
 | 
				
			||||||
 | 
														// (b7 not set => no next device)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case 0x36:	// Test
 | 
				
			||||||
 | 
									return 0x7d;		// 0x7d = ACK, 0x77 = not ACK.
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return 0x7b;	// No key transition.
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Maintains the current operating mode — a record of what the
 | 
				
			||||||
 | 
							/// keyboard is doing now.
 | 
				
			||||||
 | 
							enum class Mode {
 | 
				
			||||||
 | 
								/// The keyboard is waiting to begin a transaction.
 | 
				
			||||||
 | 
								Waiting,
 | 
				
			||||||
 | 
								/// The keyboard is currently clocking in a new command.
 | 
				
			||||||
 | 
								AcceptingCommand,
 | 
				
			||||||
 | 
								/// The keyboard is waiting for the computer to indicate that it is ready for a response.
 | 
				
			||||||
 | 
								AwaitingEndOfCommand,
 | 
				
			||||||
 | 
								/// The keyboard is in the process of performing the command it most-recently received.
 | 
				
			||||||
 | 
								/// If the command was an 'inquiry', this state may persist for a non-neglibible period of time.
 | 
				
			||||||
 | 
								PerformingCommand,
 | 
				
			||||||
 | 
								/// The keyboard is currently shifting a response back to the computer.
 | 
				
			||||||
 | 
								SendingResponse,
 | 
				
			||||||
 | 
							} mode_ = Mode::Waiting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Holds a count of progress through the current @c Mode. Exact meaning depends on mode.
 | 
				
			||||||
 | 
							int phase_ = 0;
 | 
				
			||||||
 | 
							/// Holds the most-recently-received command; the command is shifted into here as it is received
 | 
				
			||||||
 | 
							/// so this may not be valid prior to Mode::PerformingCommand.
 | 
				
			||||||
 | 
							int command_ = 0;
 | 
				
			||||||
 | 
							/// Populated during PerformingCommand as the response to the most-recently-received command, this
 | 
				
			||||||
 | 
							/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
 | 
				
			||||||
 | 
							/// but not afterwards.
 | 
				
			||||||
 | 
							int response_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// The current state of the serial connection's data input.
 | 
				
			||||||
 | 
							bool data_input_ = false;
 | 
				
			||||||
 | 
							/// The current clock output from this keyboard.
 | 
				
			||||||
 | 
							bool clock_output_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Guards multithread access to key_queue_.
 | 
				
			||||||
 | 
							std::mutex key_queue_mutex_;
 | 
				
			||||||
 | 
							/// A FIFO queue for key events, in the form they'd be communicated to the Macintosh,
 | 
				
			||||||
 | 
							/// with the newest events towards the front.
 | 
				
			||||||
 | 
							std::vector<uint8_t> key_queue_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Provides a mapping from idiomatic PC keys to Macintosh keys.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
 | 
				
			||||||
 | 
						uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Apple_Macintosh_Keyboard_hpp */
 | 
				
			||||||
							
								
								
									
										877
									
								
								Machines/Apple/Macintosh/Macintosh.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										877
									
								
								Machines/Apple/Macintosh/Macintosh.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,877 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Macintosh.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Macintosh.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <array>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "DeferredAudio.hpp"
 | 
				
			||||||
 | 
					#include "DriveSpeedAccumulator.hpp"
 | 
				
			||||||
 | 
					#include "Keyboard.hpp"
 | 
				
			||||||
 | 
					#include "RealTimeClock.hpp"
 | 
				
			||||||
 | 
					#include "Video.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Activity/Source.hpp"
 | 
				
			||||||
 | 
					#include "../../CRTMachine.hpp"
 | 
				
			||||||
 | 
					#include "../../KeyboardMachine.hpp"
 | 
				
			||||||
 | 
					#include "../../MediaTarget.hpp"
 | 
				
			||||||
 | 
					#include "../../MouseMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp"
 | 
				
			||||||
 | 
					#include "../../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../ClockReceiver/JustInTime.hpp"
 | 
				
			||||||
 | 
					#include "../../../ClockReceiver/ClockingHintSource.hpp"
 | 
				
			||||||
 | 
					#include "../../../Configurable/StandardOptions.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#define LOG_TRACE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Components/5380/ncr5380.hpp"
 | 
				
			||||||
 | 
					#include "../../../Components/6522/6522.hpp"
 | 
				
			||||||
 | 
					#include "../../../Components/8530/z8530.hpp"
 | 
				
			||||||
 | 
					#include "../../../Components/DiskII/IWM.hpp"
 | 
				
			||||||
 | 
					#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
 | 
				
			||||||
 | 
					#include "../../../Processors/68000/68000.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Analyser/Static/Macintosh/Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Utility/MemoryPacker.hpp"
 | 
				
			||||||
 | 
					#include "../../Utility/MemoryFuzzer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr int CLOCK_RATE = 7833600;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Configurable::Option>> get_options() {
 | 
				
			||||||
 | 
						return Configurable::standard_options(
 | 
				
			||||||
 | 
							static_cast<Configurable::StandardOptions>(Configurable::QuickBoot)
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
 | 
				
			||||||
 | 
						public Machine,
 | 
				
			||||||
 | 
						public CRTMachine::Machine,
 | 
				
			||||||
 | 
						public MediaTarget::Machine,
 | 
				
			||||||
 | 
						public MouseMachine::Machine,
 | 
				
			||||||
 | 
						public CPU::MC68000::BusHandler,
 | 
				
			||||||
 | 
						public KeyboardMachine::MappedMachine,
 | 
				
			||||||
 | 
						public Zilog::SCC::z8530::Delegate,
 | 
				
			||||||
 | 
						public Activity::Source,
 | 
				
			||||||
 | 
						public Configurable::Device,
 | 
				
			||||||
 | 
						public DriveSpeedAccumulator::Delegate,
 | 
				
			||||||
 | 
						public ClockingHint::Observer {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							using Target = Analyser::Static::Macintosh::Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
 | 
				
			||||||
 | 
								KeyboardMachine::MappedMachine({
 | 
				
			||||||
 | 
									Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift,
 | 
				
			||||||
 | 
									Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption,
 | 
				
			||||||
 | 
									Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta,
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							 	mc68000_(*this),
 | 
				
			||||||
 | 
							 	iwm_(CLOCK_RATE),
 | 
				
			||||||
 | 
							 	video_(audio_, drive_speed_accumulator_),
 | 
				
			||||||
 | 
							 	via_(via_port_handler_),
 | 
				
			||||||
 | 
							 	via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_),
 | 
				
			||||||
 | 
							 	scsi_bus_(CLOCK_RATE * 2),
 | 
				
			||||||
 | 
							 	scsi_(scsi_bus_, CLOCK_RATE * 2),
 | 
				
			||||||
 | 
							 	hard_drive_(scsi_bus_, 6 /* SCSI ID */),
 | 
				
			||||||
 | 
							 	drives_{
 | 
				
			||||||
 | 
							 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
 | 
				
			||||||
 | 
							 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								mouse_(1) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Select a ROM name and determine the proper ROM and RAM sizes
 | 
				
			||||||
 | 
								// based on the machine model.
 | 
				
			||||||
 | 
								using Model = Analyser::Static::Macintosh::Target::Model;
 | 
				
			||||||
 | 
								const std::string machine_name = "Macintosh";
 | 
				
			||||||
 | 
								uint32_t ram_size, rom_size;
 | 
				
			||||||
 | 
								std::vector<ROMMachine::ROM> rom_descriptions;
 | 
				
			||||||
 | 
								switch(model) {
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									case Model::Mac128k:
 | 
				
			||||||
 | 
										ram_size = 128*1024;
 | 
				
			||||||
 | 
										rom_size = 64*1024;
 | 
				
			||||||
 | 
										rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case Model::Mac512k:
 | 
				
			||||||
 | 
										ram_size = 512*1024;
 | 
				
			||||||
 | 
										rom_size = 64*1024;
 | 
				
			||||||
 | 
										rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case Model::Mac512ke:
 | 
				
			||||||
 | 
									case Model::MacPlus: {
 | 
				
			||||||
 | 
										ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
 | 
				
			||||||
 | 
										rom_size = 128*1024;
 | 
				
			||||||
 | 
										const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
 | 
				
			||||||
 | 
										rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ram_mask_ = (ram_size >> 1) - 1;
 | 
				
			||||||
 | 
								rom_mask_ = (rom_size >> 1) - 1;
 | 
				
			||||||
 | 
								ram_.resize(ram_size >> 1);
 | 
				
			||||||
 | 
								video_.set_ram(ram_.data(), ram_mask_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Grab a copy of the ROM and convert it into big-endian data.
 | 
				
			||||||
 | 
								const auto roms = rom_fetcher(rom_descriptions);
 | 
				
			||||||
 | 
								if(!roms[0]) {
 | 
				
			||||||
 | 
									throw ROMMachine::Error::MissingROMs;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								roms[0]->resize(rom_size);
 | 
				
			||||||
 | 
								Memory::PackBigEndian16(*roms[0], rom_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Randomise memory contents.
 | 
				
			||||||
 | 
								Memory::Fuzz(ram_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Attach the drives to the IWM.
 | 
				
			||||||
 | 
								iwm_->set_drive(0, &drives_[0]);
 | 
				
			||||||
 | 
								iwm_->set_drive(1, &drives_[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If they are 400kb drives, also attach them to the drive-speed accumulator.
 | 
				
			||||||
 | 
								if(!drives_[0].is_800k() || !drives_[1].is_800k()) {
 | 
				
			||||||
 | 
									drive_speed_accumulator_.set_delegate(this);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Make sure interrupt changes from the SCC are observed.
 | 
				
			||||||
 | 
								scc_.set_delegate(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Also watch for changes in clocking requirement from the SCSI chip.
 | 
				
			||||||
 | 
								if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
 | 
				
			||||||
 | 
									scsi_bus_.set_clocking_hint_observer(this);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// The Mac runs at 7.8336mHz.
 | 
				
			||||||
 | 
								set_clock_rate(double(CLOCK_RATE));
 | 
				
			||||||
 | 
								audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Insert any supplied media.
 | 
				
			||||||
 | 
								insert_media(target.media);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set the immutables of the memory map.
 | 
				
			||||||
 | 
								setup_memory_map();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							~ConcreteMachine() {
 | 
				
			||||||
 | 
								audio_.queue.flush();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
 | 
				
			||||||
 | 
								video_.set_scan_target(scan_target);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Outputs::Speaker::Speaker *get_speaker() override {
 | 
				
			||||||
 | 
								return &audio_.speaker;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void run_for(const Cycles cycles) override {
 | 
				
			||||||
 | 
								mc68000_.run_for(cycles);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							using Microcycle = CPU::MC68000::Microcycle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
 | 
				
			||||||
 | 
								// Advance time.
 | 
				
			||||||
 | 
								advance_time(cycle.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// A null cycle leaves nothing else to do.
 | 
				
			||||||
 | 
								if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Grab the value on the address bus, at word precision.
 | 
				
			||||||
 | 
								uint32_t word_address = cycle.active_operation_word_address();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Everything above E0 0000 is signalled as being on the peripheral bus.
 | 
				
			||||||
 | 
								mc68000_.set_is_peripheral_address(word_address >= 0x700000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// All code below deals only with reads and writes — cycles in which a
 | 
				
			||||||
 | 
								// data select is active. So quit now if this is not the active part of
 | 
				
			||||||
 | 
								// a read or write.
 | 
				
			||||||
 | 
								//
 | 
				
			||||||
 | 
								// The 68000 uses 6800-style autovectored interrupts, so the mere act of
 | 
				
			||||||
 | 
								// having set VPA above deals with those given that the generated address
 | 
				
			||||||
 | 
								// for interrupt acknowledge cycles always has all bits set except the
 | 
				
			||||||
 | 
								// lowest explicit address lines.
 | 
				
			||||||
 | 
								if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Grab the word-precision address being accessed.
 | 
				
			||||||
 | 
								uint16_t *memory_base = nullptr;
 | 
				
			||||||
 | 
								HalfCycles delay;
 | 
				
			||||||
 | 
								switch(memory_map_[word_address >> 16]) {
 | 
				
			||||||
 | 
									default: assert(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::Unassigned:
 | 
				
			||||||
 | 
										fill_unmapped(cycle);
 | 
				
			||||||
 | 
									return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::VIA: {
 | 
				
			||||||
 | 
										if(*cycle.address & 1) {
 | 
				
			||||||
 | 
											fill_unmapped(cycle);
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											const int register_address = word_address >> 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// VIA accesses are via address 0xefe1fe + register*512,
 | 
				
			||||||
 | 
											// which at word precision is 0x77f0ff + register*256.
 | 
				
			||||||
 | 
											if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
												cycle.value->halves.low = via_.get_register(register_address);
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												via_.set_register(register_address, cycle.value->halves.low);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::PhaseRead: {
 | 
				
			||||||
 | 
										if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
											cycle.value->halves.low = phase_ & 7;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
 | 
				
			||||||
 | 
									} return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::IWM: {
 | 
				
			||||||
 | 
										if(*cycle.address & 1) {
 | 
				
			||||||
 | 
											const int register_address = word_address >> 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// The IWM; this is a purely polled device, so can be run on demand.
 | 
				
			||||||
 | 
											if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
												cycle.value->halves.low = iwm_->read(register_address);
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												iwm_->write(register_address, cycle.value->halves.low);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											fill_unmapped(cycle);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::SCSI: {
 | 
				
			||||||
 | 
										const int register_address = word_address >> 3;
 | 
				
			||||||
 | 
										const bool dma_acknowledge = word_address & 0x100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Even accesses = read; odd = write.
 | 
				
			||||||
 | 
										if(*cycle.address & 1) {
 | 
				
			||||||
 | 
											// Odd access => this is a write. Data will be in the upper byte.
 | 
				
			||||||
 | 
											if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
												scsi_.write(register_address, 0xff, dma_acknowledge);
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												if(cycle.operation & Microcycle::SelectWord) {
 | 
				
			||||||
 | 
													scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge);
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											// Even access => this is a read.
 | 
				
			||||||
 | 
											if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
												const auto result = scsi_.read(register_address, dma_acknowledge);
 | 
				
			||||||
 | 
												if(cycle.operation & Microcycle::SelectWord) {
 | 
				
			||||||
 | 
													// Data is loaded on the top part of the bus only.
 | 
				
			||||||
 | 
													cycle.value->full = uint16_t((result << 8) | 0xff);
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													cycle.value->halves.low = result;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::SCCReadResetPhase: {
 | 
				
			||||||
 | 
										// Any word access here adjusts phase.
 | 
				
			||||||
 | 
										if(cycle.operation & Microcycle::SelectWord) {
 | 
				
			||||||
 | 
											adjust_phase();
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											// A0 = 1 => reset; A0 = 0 => read.
 | 
				
			||||||
 | 
											if(*cycle.address & 1) {
 | 
				
			||||||
 | 
												scc_.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
													cycle.value->halves.low = 0xff;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												const auto read = scc_.read(int(word_address));
 | 
				
			||||||
 | 
												if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
													cycle.value->halves.low = read;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::SCCWrite: {
 | 
				
			||||||
 | 
										// Any word access here adjusts phase.
 | 
				
			||||||
 | 
										if(cycle.operation & Microcycle::SelectWord) {
 | 
				
			||||||
 | 
											adjust_phase();
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											if(*cycle.address & 1) {
 | 
				
			||||||
 | 
												if(cycle.operation & Microcycle::Read) {
 | 
				
			||||||
 | 
													scc_.write(int(word_address), 0xff);
 | 
				
			||||||
 | 
													cycle.value->halves.low = 0xff;
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													scc_.write(int(word_address), cycle.value->halves.low);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												fill_unmapped(cycle);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} return delay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::RAM: {
 | 
				
			||||||
 | 
										// This is coupled with the Macintosh implementation of video; the magic
 | 
				
			||||||
 | 
										// constant should probably be factored into the Video class.
 | 
				
			||||||
 | 
										// It embodies knowledge of the fact that video (and audio) will always
 | 
				
			||||||
 | 
										// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
 | 
				
			||||||
 | 
										// (And that ram_mask_ = ram size - 1).
 | 
				
			||||||
 | 
										if(word_address > ram_mask_ - 0x6c80)
 | 
				
			||||||
 | 
											update_video();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										memory_base = ram_.data();
 | 
				
			||||||
 | 
										word_address &= ram_mask_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Apply a delay due to video contention if applicable; scheme applied:
 | 
				
			||||||
 | 
										// only every other access slot is available during the period of video
 | 
				
			||||||
 | 
										// output. I believe this to be correct for the 128k, 512k and Plus.
 | 
				
			||||||
 | 
										// More research to do on other models.
 | 
				
			||||||
 | 
										if(video_is_outputting() && ram_subcycle_ < 8) {
 | 
				
			||||||
 | 
											delay = HalfCycles(8 - ram_subcycle_);
 | 
				
			||||||
 | 
											advance_time(delay);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case BusDevice::ROM: {
 | 
				
			||||||
 | 
										if(!(cycle.operation & Microcycle::Read)) return delay;
 | 
				
			||||||
 | 
										memory_base = rom_;
 | 
				
			||||||
 | 
										word_address &= rom_mask_;
 | 
				
			||||||
 | 
									} break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM.
 | 
				
			||||||
 | 
								switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Microcycle::SelectWord | Microcycle::Read:
 | 
				
			||||||
 | 
										cycle.value->full = memory_base[word_address];
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case Microcycle::SelectByte | Microcycle::Read:
 | 
				
			||||||
 | 
										cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case Microcycle::SelectWord:
 | 
				
			||||||
 | 
										memory_base[word_address] = cycle.value->full;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
									case Microcycle::SelectByte:
 | 
				
			||||||
 | 
										memory_base[word_address] = uint16_t(
 | 
				
			||||||
 | 
											(cycle.value->halves.low << cycle.byte_shift()) |
 | 
				
			||||||
 | 
											(memory_base[word_address] & cycle.untouched_byte_mask())
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return delay;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void flush() {
 | 
				
			||||||
 | 
								// Flush the video before the audio queue; in a Mac the
 | 
				
			||||||
 | 
								// video is responsible for providing part of the
 | 
				
			||||||
 | 
								// audio signal, so the two aren't as distinct as in
 | 
				
			||||||
 | 
								// most machines.
 | 
				
			||||||
 | 
								update_video();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// As above: flush audio after video.
 | 
				
			||||||
 | 
								via_.flush();
 | 
				
			||||||
 | 
								audio_.queue.perform();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Experimental?
 | 
				
			||||||
 | 
								iwm_.flush();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_rom_is_overlay(bool rom_is_overlay) {
 | 
				
			||||||
 | 
								ROM_is_overlay_ = rom_is_overlay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								using Model = Analyser::Static::Macintosh::Target::Model;
 | 
				
			||||||
 | 
								switch(model) {
 | 
				
			||||||
 | 
									case Model::Mac128k:
 | 
				
			||||||
 | 
									case Model::Mac512k:
 | 
				
			||||||
 | 
									case Model::Mac512ke:
 | 
				
			||||||
 | 
										populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
 | 
				
			||||||
 | 
											// Addresses up to $80 0000 aren't affected by this bit.
 | 
				
			||||||
 | 
											if(rom_is_overlay) {
 | 
				
			||||||
 | 
												// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes.
 | 
				
			||||||
 | 
												for(int c = 0; c < 0x600000; c += 0x100000) {
 | 
				
			||||||
 | 
													map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												map_to(0x800000, BusDevice::RAM);
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												map_to(0x400000, BusDevice::RAM);
 | 
				
			||||||
 | 
												map_to(0x500000, BusDevice::ROM);
 | 
				
			||||||
 | 
												map_to(0x800000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Model::MacPlus:
 | 
				
			||||||
 | 
										populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
 | 
				
			||||||
 | 
											// Addresses up to $80 0000 aren't affected by this bit.
 | 
				
			||||||
 | 
											if(rom_is_overlay) {
 | 
				
			||||||
 | 
												for(int c = 0; c < 0x580000; c += 0x20000) {
 | 
				
			||||||
 | 
													map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												map_to(0x600000, BusDevice::SCSI);
 | 
				
			||||||
 | 
												map_to(0x800000, BusDevice::RAM);
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												map_to(0x400000, BusDevice::RAM);
 | 
				
			||||||
 | 
												for(int c = 0x400000; c < 0x580000; c += 0x20000) {
 | 
				
			||||||
 | 
													map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												map_to(0x600000, BusDevice::SCSI);
 | 
				
			||||||
 | 
												map_to(0x800000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool video_is_outputting() {
 | 
				
			||||||
 | 
								return video_.is_outputting(time_since_video_update_);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
 | 
				
			||||||
 | 
								update_video();
 | 
				
			||||||
 | 
								video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool insert_media(const Analyser::Static::Media &media) override {
 | 
				
			||||||
 | 
								if(media.disks.empty() && media.mass_storage_devices.empty())
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: shouldn't allow disks to be replaced like this, as the Mac
 | 
				
			||||||
 | 
								// uses software eject. Will need to expand messaging ability of
 | 
				
			||||||
 | 
								// insert_media.
 | 
				
			||||||
 | 
								if(!media.disks.empty()) {
 | 
				
			||||||
 | 
									if(drives_[0].has_disk())
 | 
				
			||||||
 | 
										drives_[1].set_disk(media.disks[0]);
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
										drives_[0].set_disk(media.disks[0]);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: allow this only at machine startup.
 | 
				
			||||||
 | 
								if(!media.mass_storage_devices.empty()) {
 | 
				
			||||||
 | 
									const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get());
 | 
				
			||||||
 | 
									if(volume) {
 | 
				
			||||||
 | 
										volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									hard_drive_->set_storage(media.mass_storage_devices.front());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK: Keyboard input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							KeyboardMapper *get_keyboard_mapper() override {
 | 
				
			||||||
 | 
								return &keyboard_mapper_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_key_state(uint16_t key, bool is_pressed) override {
 | 
				
			||||||
 | 
								keyboard_.enqueue_key_state(key, is_pressed);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: clear all keys.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK: Interrupt updates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
 | 
				
			||||||
 | 
								update_interrupt_input();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void update_interrupt_input() {
 | 
				
			||||||
 | 
								// Update interrupt input.
 | 
				
			||||||
 | 
								// TODO: does this really cascade like this?
 | 
				
			||||||
 | 
								if(scc_.get_interrupt_line()) {
 | 
				
			||||||
 | 
									mc68000_.set_interrupt_level(2);
 | 
				
			||||||
 | 
								} else if(via_.get_interrupt_line()) {
 | 
				
			||||||
 | 
									mc68000_.set_interrupt_level(1);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									mc68000_.set_interrupt_level(0);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK: - Activity Source
 | 
				
			||||||
 | 
							void set_activity_observer(Activity::Observer *observer) override {
 | 
				
			||||||
 | 
								iwm_->set_activity_observer(observer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
 | 
				
			||||||
 | 
									scsi_bus_.set_activity_observer(observer);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// MARK: - Configuration options.
 | 
				
			||||||
 | 
							std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
 | 
				
			||||||
 | 
								return Apple::Macintosh::get_options();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_selections(const Configurable::SelectionSet &selections_by_option) override {
 | 
				
			||||||
 | 
								bool quick_boot;
 | 
				
			||||||
 | 
								if(Configurable::get_quick_boot(selections_by_option, quick_boot)) {
 | 
				
			||||||
 | 
									if(quick_boot) {
 | 
				
			||||||
 | 
										// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
 | 
				
			||||||
 | 
										// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
 | 
				
			||||||
 | 
										ram_[0x02ae >> 1] = 0x40;
 | 
				
			||||||
 | 
										ram_[0x02b0 >> 1] = 0x00;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Configurable::SelectionSet get_accurate_selections() override {
 | 
				
			||||||
 | 
								Configurable::SelectionSet selection_set;
 | 
				
			||||||
 | 
								Configurable::append_quick_boot_selection(selection_set, false);
 | 
				
			||||||
 | 
								return selection_set;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Configurable::SelectionSet get_user_friendly_selections() override {
 | 
				
			||||||
 | 
								Configurable::SelectionSet selection_set;
 | 
				
			||||||
 | 
								Configurable::append_quick_boot_selection(selection_set, true);
 | 
				
			||||||
 | 
								return selection_set;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
 | 
				
			||||||
 | 
								scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override {
 | 
				
			||||||
 | 
								iwm_.flush();
 | 
				
			||||||
 | 
								drives_[0].set_rotation_speed(speed);
 | 
				
			||||||
 | 
								drives_[1].set_rotation_speed(speed);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline void adjust_phase() {
 | 
				
			||||||
 | 
								++phase_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline void fill_unmapped(const Microcycle &cycle) {
 | 
				
			||||||
 | 
								if(!(cycle.operation & Microcycle::Read)) return;
 | 
				
			||||||
 | 
								if(cycle.operation & Microcycle::SelectWord) {
 | 
				
			||||||
 | 
									cycle.value->full = 0xffff;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									cycle.value->halves.low = 0xff;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Advances all non-CPU components by @c duration half cycles.
 | 
				
			||||||
 | 
							forceinline void advance_time(HalfCycles duration) {
 | 
				
			||||||
 | 
								time_since_video_update_ += duration;
 | 
				
			||||||
 | 
								iwm_ += duration;
 | 
				
			||||||
 | 
								ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
 | 
				
			||||||
 | 
								// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
 | 
				
			||||||
 | 
								// may occur here in order to provide VSYNC at a proper moment.
 | 
				
			||||||
 | 
								// Possibly route vsync.
 | 
				
			||||||
 | 
								if(time_since_video_update_ < time_until_video_event_) {
 | 
				
			||||||
 | 
									via_clock_ += duration;
 | 
				
			||||||
 | 
									via_.run_for(via_clock_.divide(HalfCycles(10)));
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									auto via_time_base = time_since_video_update_ - duration;
 | 
				
			||||||
 | 
									auto via_cycles_outstanding = duration;
 | 
				
			||||||
 | 
									while(time_until_video_event_ < time_since_video_update_) {
 | 
				
			||||||
 | 
										const auto via_cycles = time_until_video_event_ - via_time_base;
 | 
				
			||||||
 | 
										via_time_base = HalfCycles(0);
 | 
				
			||||||
 | 
										via_cycles_outstanding -= via_cycles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										via_clock_ += via_cycles;
 | 
				
			||||||
 | 
										via_.run_for(via_clock_.divide(HalfCycles(10)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										video_.run_for(time_until_video_event_);
 | 
				
			||||||
 | 
										time_since_video_update_ -= time_until_video_event_;
 | 
				
			||||||
 | 
										time_until_video_event_ = video_.get_next_sequence_point();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									via_clock_ += via_cycles_outstanding;
 | 
				
			||||||
 | 
									via_.run_for(via_clock_.divide(HalfCycles(10)));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
 | 
				
			||||||
 | 
								// Its clock and data lines are connected to the VIA.
 | 
				
			||||||
 | 
								keyboard_clock_ += duration;
 | 
				
			||||||
 | 
								const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
 | 
				
			||||||
 | 
								if(keyboard_ticks > HalfCycles(0)) {
 | 
				
			||||||
 | 
									keyboard_.run_for(keyboard_ticks);
 | 
				
			||||||
 | 
									via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
 | 
				
			||||||
 | 
									via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Feed mouse inputs within at most 1250 cycles of each other.
 | 
				
			||||||
 | 
								if(mouse_.has_steps()) {
 | 
				
			||||||
 | 
									time_since_mouse_update_ += duration;
 | 
				
			||||||
 | 
									const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
 | 
				
			||||||
 | 
									if(mouse_ticks > HalfCycles(0)) {
 | 
				
			||||||
 | 
										mouse_.prepare_step();
 | 
				
			||||||
 | 
										scc_.set_dcd(0, mouse_.get_channel(1) & 1);
 | 
				
			||||||
 | 
										scc_.set_dcd(1, mouse_.get_channel(0) & 1);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
 | 
				
			||||||
 | 
								// anything connected.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Consider updating the real-time clock.
 | 
				
			||||||
 | 
								real_time_clock_ += duration;
 | 
				
			||||||
 | 
								auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral();
 | 
				
			||||||
 | 
								while(ticks--) {
 | 
				
			||||||
 | 
									clock_.update();
 | 
				
			||||||
 | 
									// TODO: leave a delay between toggling the input rather than using this coupled hack.
 | 
				
			||||||
 | 
									via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
 | 
				
			||||||
 | 
									via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Update the SCSI if currently active.
 | 
				
			||||||
 | 
								if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
 | 
				
			||||||
 | 
									if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							forceinline void update_video() {
 | 
				
			||||||
 | 
								video_.run_for(time_since_video_update_.flush<HalfCycles>());
 | 
				
			||||||
 | 
								time_until_video_event_ = video_.get_next_sequence_point();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Inputs::Mouse &get_mouse() override {
 | 
				
			||||||
 | 
								return mouse_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							using IWMActor = JustInTimeActor<IWM, 1, 1, HalfCycles, Cycles>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							class VIAPortHandler: public MOS::MOS6522::PortHandler {
 | 
				
			||||||
 | 
								public:
 | 
				
			||||||
 | 
									VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) :
 | 
				
			||||||
 | 
										machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									using Port = MOS::MOS6522::Port;
 | 
				
			||||||
 | 
									using Line = MOS::MOS6522::Line;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
											Peripheral lines: keyboard data, interrupt configuration.
 | 
				
			||||||
 | 
											(See p176 [/215])
 | 
				
			||||||
 | 
										*/
 | 
				
			||||||
 | 
										switch(port) {
 | 
				
			||||||
 | 
											case Port::A:
 | 
				
			||||||
 | 
												/*
 | 
				
			||||||
 | 
													Port A:
 | 
				
			||||||
 | 
														b7:	[input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR)
 | 
				
			||||||
 | 
														b6:	0 = alternate screen buffer, 1 = main screen buffer
 | 
				
			||||||
 | 
														b5:	floppy disk SEL state control (upper/lower head "among other things")
 | 
				
			||||||
 | 
														b4:	1 = use ROM overlay memory map, 0 = use ordinary memory map
 | 
				
			||||||
 | 
														b3:	0 = use alternate sound buffer, 1 = use ordinary sound buffer
 | 
				
			||||||
 | 
														b2–b0:	audio output volume
 | 
				
			||||||
 | 
												*/
 | 
				
			||||||
 | 
												iwm_->set_select(!!(value & 0x20));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
 | 
				
			||||||
 | 
												machine_.set_rom_is_overlay(!!(value & 0x10));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												audio_.flush();
 | 
				
			||||||
 | 
												audio_.audio.set_volume(value & 7);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											case Port::B:
 | 
				
			||||||
 | 
												/*
 | 
				
			||||||
 | 
													Port B:
 | 
				
			||||||
 | 
														b7:	0 = sound enabled, 1 = sound disabled
 | 
				
			||||||
 | 
														b6:	[input] 0 = video beam in visible portion of line, 1 = outside
 | 
				
			||||||
 | 
														b5:	[input] mouse y2
 | 
				
			||||||
 | 
														b4:	[input] mouse x2
 | 
				
			||||||
 | 
														b3:	[input] 0 = mouse button down, 1 = up
 | 
				
			||||||
 | 
														b2:	0 = real-time clock enabled, 1 = disabled
 | 
				
			||||||
 | 
														b1:	clock's data-clock line
 | 
				
			||||||
 | 
														b0:	clock's serial data line
 | 
				
			||||||
 | 
												*/
 | 
				
			||||||
 | 
												if(value & 0x4) clock_.abort();
 | 
				
			||||||
 | 
												else clock_.set_input(!!(value & 0x2), !!(value & 0x1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												audio_.flush();
 | 
				
			||||||
 | 
												audio_.audio.set_enabled(!(value & 0x80));
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									uint8_t get_port_input(Port port) {
 | 
				
			||||||
 | 
										switch(port) {
 | 
				
			||||||
 | 
											case Port::A:
 | 
				
			||||||
 | 
					//							printf("6522 r A\n");
 | 
				
			||||||
 | 
											return 0x00;	// TODO: b7 = SCC wait/request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											case Port::B:
 | 
				
			||||||
 | 
											return uint8_t(
 | 
				
			||||||
 | 
												((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) |
 | 
				
			||||||
 | 
												((mouse_.get_channel(0) & 2) << 3) |
 | 
				
			||||||
 | 
												((mouse_.get_channel(1) & 2) << 4) |
 | 
				
			||||||
 | 
												(clock_.get_data() ? 0x02 : 0x00) |
 | 
				
			||||||
 | 
												(machine_.video_is_outputting() ? 0x00 : 0x40)
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Should be unreachable.
 | 
				
			||||||
 | 
										return 0xff;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									void set_control_line_output(Port port, Line line, bool value) {
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
											Keyboard wiring (I believe):
 | 
				
			||||||
 | 
											CB2 = data		(input/output)
 | 
				
			||||||
 | 
											CB1 = clock		(input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											CA2 is used for receiving RTC interrupts.
 | 
				
			||||||
 | 
											CA1 is used for receiving vsync.
 | 
				
			||||||
 | 
										*/
 | 
				
			||||||
 | 
										if(port == Port::B && line == Line::Two) {
 | 
				
			||||||
 | 
											keyboard_.set_input(value);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else LOG("Unhandled control line output: " << (port ? 'B' : 'A') << int(line));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									void run_for(HalfCycles duration) {
 | 
				
			||||||
 | 
										// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
 | 
				
			||||||
 | 
										// divided-by-two clock the audio works on.
 | 
				
			||||||
 | 
										audio_.time_since_update += HalfCycles(duration.as_integral() * 5);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									void flush() {
 | 
				
			||||||
 | 
										audio_.flush();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									void set_interrupt_status(bool status) {
 | 
				
			||||||
 | 
										machine_.update_interrupt_input();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								private:
 | 
				
			||||||
 | 
									ConcreteMachine &machine_;
 | 
				
			||||||
 | 
									RealTimeClock &clock_;
 | 
				
			||||||
 | 
									Keyboard &keyboard_;
 | 
				
			||||||
 | 
									DeferredAudio &audio_;
 | 
				
			||||||
 | 
									IWMActor &iwm_;
 | 
				
			||||||
 | 
									Inputs::QuadratureMouse &mouse_;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							DriveSpeedAccumulator drive_speed_accumulator_;
 | 
				
			||||||
 | 
							IWMActor iwm_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							DeferredAudio audio_;
 | 
				
			||||||
 | 
							Video video_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RealTimeClock clock_;
 | 
				
			||||||
 | 
							Keyboard keyboard_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MOS::MOS6522::MOS6522<VIAPortHandler> via_;
 | 
				
			||||||
 | 
					 		VIAPortHandler via_port_handler_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 		Zilog::SCC::z8530 scc_;
 | 
				
			||||||
 | 
							SCSI::Bus scsi_bus_;
 | 
				
			||||||
 | 
					 		NCR::NCR5380::NCR5380 scsi_;
 | 
				
			||||||
 | 
							SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
 | 
				
			||||||
 | 
					 		bool scsi_bus_is_clocked_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 		HalfCycles via_clock_;
 | 
				
			||||||
 | 
					 		HalfCycles real_time_clock_;
 | 
				
			||||||
 | 
					 		HalfCycles keyboard_clock_;
 | 
				
			||||||
 | 
					 		HalfCycles time_since_video_update_;
 | 
				
			||||||
 | 
					 		HalfCycles time_until_video_event_;
 | 
				
			||||||
 | 
					 		HalfCycles time_since_mouse_update_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool ROM_is_overlay_ = true;
 | 
				
			||||||
 | 
							int phase_ = 1;
 | 
				
			||||||
 | 
							int ram_subcycle_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							DoubleDensityDrive drives_[2];
 | 
				
			||||||
 | 
							Inputs::QuadratureMouse mouse_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Apple::Macintosh::KeyboardMapper keyboard_mapper_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class BusDevice {
 | 
				
			||||||
 | 
								RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording
 | 
				
			||||||
 | 
							/// which device is current mapped in each area. Keeping it in a table is a bit faster
 | 
				
			||||||
 | 
							/// than the multi-level address inspection that is otherwise required, as well as
 | 
				
			||||||
 | 
							/// simplifying slightly the handling of different models.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// So: index with the top 7 bits of the 24-bit address.
 | 
				
			||||||
 | 
							BusDevice memory_map_[128];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void setup_memory_map() {
 | 
				
			||||||
 | 
								// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true;
 | 
				
			||||||
 | 
								// start by calling into set_rom_is_overlay to seed everything up to $800000.
 | 
				
			||||||
 | 
								set_rom_is_overlay(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) {
 | 
				
			||||||
 | 
									map_to(0x900000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
									map_to(0xa00000, BusDevice::SCCReadResetPhase);
 | 
				
			||||||
 | 
									map_to(0xb00000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
									map_to(0xc00000, BusDevice::SCCWrite);
 | 
				
			||||||
 | 
									map_to(0xd00000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
									map_to(0xe00000, BusDevice::IWM);
 | 
				
			||||||
 | 
									map_to(0xe80000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
									map_to(0xf00000, BusDevice::VIA);
 | 
				
			||||||
 | 
									map_to(0xf80000, BusDevice::PhaseRead);
 | 
				
			||||||
 | 
									map_to(0x1000000, BusDevice::Unassigned);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) {
 | 
				
			||||||
 | 
								// Define semantics for below; map_to will write from the current cursor position
 | 
				
			||||||
 | 
								// to the supplied 24-bit address, setting a particular mapped device.
 | 
				
			||||||
 | 
								int segment = start_address >> 17;
 | 
				
			||||||
 | 
								auto map_to = [&segment, this](int address, BusDevice device) {
 | 
				
			||||||
 | 
									for(; segment < address >> 17; ++segment) {
 | 
				
			||||||
 | 
										this->memory_map_[segment] = device;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								populator(map_to);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint32_t ram_mask_ = 0;
 | 
				
			||||||
 | 
							uint32_t rom_mask_ = 0;
 | 
				
			||||||
 | 
							uint16_t rom_[64*1024];	// i.e. up to 128kb in size.
 | 
				
			||||||
 | 
							std::vector<uint16_t> ram_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple::Macintosh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Machine *Machine::Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
 | 
				
			||||||
 | 
						auto *const mac_target = dynamic_cast<const Analyser::Static::Macintosh::Target *>(target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						using Model = Analyser::Static::Macintosh::Target::Model;
 | 
				
			||||||
 | 
						switch(mac_target->model) {
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							case Model::Mac128k:	return new ConcreteMachine<Model::Mac128k>(*mac_target, rom_fetcher);
 | 
				
			||||||
 | 
							case Model::Mac512k:	return new ConcreteMachine<Model::Mac512k>(*mac_target, rom_fetcher);
 | 
				
			||||||
 | 
							case Model::Mac512ke:	return new ConcreteMachine<Model::Mac512ke>(*mac_target, rom_fetcher);
 | 
				
			||||||
 | 
							case Model::MacPlus:	return new ConcreteMachine<Model::MacPlus>(*mac_target, rom_fetcher);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Machine::~Machine() {}
 | 
				
			||||||
							
								
								
									
										33
									
								
								Machines/Apple/Macintosh/Macintosh.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Machines/Apple/Macintosh/Macintosh.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Macintosh.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Macintosh_hpp
 | 
				
			||||||
 | 
					#define Macintosh_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Configurable/Configurable.hpp"
 | 
				
			||||||
 | 
					#include "../../../Analyser/Static/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "../../ROMMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<std::unique_ptr<Configurable::Option>> get_options();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Machine {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							virtual ~Machine();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Creates and returns a Macintosh.
 | 
				
			||||||
 | 
							static Machine *Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Macintosh_hpp */
 | 
				
			||||||
							
								
								
									
										173
									
								
								Machines/Apple/Macintosh/RealTimeClock.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								Machines/Apple/Macintosh/RealTimeClock.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  RealTimeClock.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 07/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef RealTimeClock_hpp
 | 
				
			||||||
 | 
					#define RealTimeClock_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../Utility/MemoryFuzzer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models the storage component of Apple's real-time clock.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Since tracking of time is pushed to this class, it is assumed
 | 
				
			||||||
 | 
						that whomever is translating real time into emulated time
 | 
				
			||||||
 | 
						will notify the VIA of a potential interrupt.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class RealTimeClock {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							RealTimeClock() {
 | 
				
			||||||
 | 
								// TODO: this should persist, if possible, rather than
 | 
				
			||||||
 | 
								// being default initialised.
 | 
				
			||||||
 | 
								const uint8_t default_data[] = {
 | 
				
			||||||
 | 
									0xa8, 0x00, 0x00, 0x00,
 | 
				
			||||||
 | 
									0xcc, 0x0a, 0xcc, 0x0a,
 | 
				
			||||||
 | 
									0x00, 0x00, 0x00, 0x00,
 | 
				
			||||||
 | 
									0x00, 0x02, 0x63, 0x00,
 | 
				
			||||||
 | 
									0x03, 0x88, 0x00, 0x4c
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								memcpy(data_, default_data, sizeof(data_));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Advances the clock by 1 second.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								The caller should also notify the VIA.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void update() {
 | 
				
			||||||
 | 
								for(int c = 0; c < 4; ++c) {
 | 
				
			||||||
 | 
									++seconds_[c];
 | 
				
			||||||
 | 
									if(seconds_[c]) break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the current clock and data inputs to the clock.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_input(bool clock, bool data) {
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									Documented commands:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										z0000001		Seconds register 0 (lowest order byte)
 | 
				
			||||||
 | 
										z0000101		Seconds register 1
 | 
				
			||||||
 | 
										z0001001		Seconds register 2
 | 
				
			||||||
 | 
										z0001101		Seconds register 3
 | 
				
			||||||
 | 
										00110001		Test register (write only)
 | 
				
			||||||
 | 
										00110101		Write-protect register (write only)
 | 
				
			||||||
 | 
										z010aa01		RAM addresses 0x10 - 0x13
 | 
				
			||||||
 | 
										z1aaaa01		RAM addresses 0x00 – 0x0f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										z = 1 => a read; z = 0 => a write.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									The top bit of the write-protect register enables (0) or disables (1)
 | 
				
			||||||
 | 
									writes to other locations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									All the documentation says about the test register is to set the top
 | 
				
			||||||
 | 
									two bits to 0 for normal operation. Abnormal operation is undefined.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									The data line is valid when the clock transitions to level 0.
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(clock && !previous_clock_) {
 | 
				
			||||||
 | 
									// Shift into the command_ register, no matter what.
 | 
				
			||||||
 | 
									command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
 | 
				
			||||||
 | 
									result_ <<= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Increment phase.
 | 
				
			||||||
 | 
									++phase_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// When phase hits 8, inspect the command.
 | 
				
			||||||
 | 
									// If it's a read, prepare a result.
 | 
				
			||||||
 | 
									if(phase_ == 8) {
 | 
				
			||||||
 | 
										if(command_ & 0x80) {
 | 
				
			||||||
 | 
											// A read.
 | 
				
			||||||
 | 
											const auto address = (command_ >> 2) & 0x1f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Begin pessimistically.
 | 
				
			||||||
 | 
											result_ = 0xff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if(address < 4) {
 | 
				
			||||||
 | 
												result_ = seconds_[address];
 | 
				
			||||||
 | 
											} else if(address >= 0x10) {
 | 
				
			||||||
 | 
												result_ = data_[address & 0xf];
 | 
				
			||||||
 | 
											} else if(address >= 0x8 && address <= 0xb) {
 | 
				
			||||||
 | 
												result_ = data_[0x10 + (address & 0x3)];
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// If phase hits 16 and this was a read command,
 | 
				
			||||||
 | 
									// just stop. If it was a write command, do the
 | 
				
			||||||
 | 
									// actual write.
 | 
				
			||||||
 | 
									if(phase_ == 16) {
 | 
				
			||||||
 | 
										if(!(command_ & 0x8000)) {
 | 
				
			||||||
 | 
											// A write.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const auto address = (command_ >> 10) & 0x1f;
 | 
				
			||||||
 | 
											const uint8_t value = uint8_t(command_ & 0xff);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// First test: is this to the write-protect register?
 | 
				
			||||||
 | 
											if(address == 0xd) {
 | 
				
			||||||
 | 
												write_protect_ = value;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// No other writing is permitted if the write protect
 | 
				
			||||||
 | 
											// register won't allow it.
 | 
				
			||||||
 | 
											if(!(write_protect_ & 0x80)) {
 | 
				
			||||||
 | 
												if(address < 4) {
 | 
				
			||||||
 | 
													seconds_[address] = value;
 | 
				
			||||||
 | 
												} else if(address >= 0x10) {
 | 
				
			||||||
 | 
													data_[address & 0xf] = value;
 | 
				
			||||||
 | 
												} else if(address >= 0x8 && address <= 0xb) {
 | 
				
			||||||
 | 
													data_[0x10 + (address & 0x3)] = value;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// A phase of 16 always ends the command, so reset here.
 | 
				
			||||||
 | 
										abort();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								previous_clock_ = clock;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Reads the current data output level from the clock.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							bool get_data() {
 | 
				
			||||||
 | 
								return !!(result_ & 0x80);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Announces that a serial command has been aborted.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void abort() {
 | 
				
			||||||
 | 
								result_ = 0;
 | 
				
			||||||
 | 
								phase_ = 0;
 | 
				
			||||||
 | 
								command_ = 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							uint8_t data_[0x14];
 | 
				
			||||||
 | 
							uint8_t seconds_[4];
 | 
				
			||||||
 | 
							uint8_t write_protect_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int phase_ = 0;
 | 
				
			||||||
 | 
							uint16_t command_;
 | 
				
			||||||
 | 
							uint8_t result_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool previous_clock_ = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* RealTimeClock_hpp */
 | 
				
			||||||
							
								
								
									
										184
									
								
								Machines/Apple/Macintosh/Video.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								Machines/Apple/Macintosh/Video.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Video.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Video.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Apple::Macintosh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
 | 
				
			||||||
 | 
					// bottom of page 400:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	"For each scan line, 512 pixels are drawn on the screen ...
 | 
				
			||||||
 | 
					//	The horizontal blanking interval takes the time of an additional 192 pixels"
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// And, at the top of 401:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	"The visible portion of a full-screen display consists of 342 horizontal scan lines...
 | 
				
			||||||
 | 
					//	During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
 | 
				
			||||||
 | 
						audio_(audio),
 | 
				
			||||||
 | 
						drive_speed_accumulator_(drive_speed_accumulator),
 | 
				
			||||||
 | 
					 	crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 	crt_.set_display_type(Outputs::Display::DisplayType::RGB);
 | 
				
			||||||
 | 
						crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
 | 
				
			||||||
 | 
						crt_.set_aspect_ratio(1.73f);	// The Mac uses a non-standard scanning area.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			||||||
 | 
						crt_.set_scan_target(scan_target);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Video::run_for(HalfCycles duration) {
 | 
				
			||||||
 | 
						// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately.
 | 
				
			||||||
 | 
						const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;
 | 
				
			||||||
 | 
						const size_t audio_base = (use_alternate_audio_buffer_ ? (0xffffa100 >> 1) : (0xfffffd00 >> 1)) & ram_mask_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The number of HalfCycles is literally the number of pixel clocks to move through,
 | 
				
			||||||
 | 
						// since pixel output occurs at twice the processor clock. So divide by 16 to get
 | 
				
			||||||
 | 
						// the number of fetches.
 | 
				
			||||||
 | 
						while(duration > HalfCycles(0)) {
 | 
				
			||||||
 | 
							const auto pixel_start = frame_position_ % line_length;
 | 
				
			||||||
 | 
							const int line = int((frame_position_ / line_length).as_integral());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto cycles_left_in_line = std::min(line_length - pixel_start, duration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Line timing, entirely invented as I can find exactly zero words of documentation:
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							//	First 342 lines:
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							//	First 32 words = pixels;
 | 
				
			||||||
 | 
							//	next 5 words = right border;
 | 
				
			||||||
 | 
							//	next 2 words = sync level;
 | 
				
			||||||
 | 
							//	final 5 words = left border.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							//	Then 12 lines of border, 3 of sync, 11 more of border.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const int first_word = int(pixel_start.as_integral()) >> 4;
 | 
				
			||||||
 | 
							const int final_word = int((pixel_start + cycles_left_in_line).as_integral()) >> 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(first_word != final_word) {
 | 
				
			||||||
 | 
								if(line < 342) {
 | 
				
			||||||
 | 
									// If there are any pixels left to output, do so.
 | 
				
			||||||
 | 
									if(first_word < 32) {
 | 
				
			||||||
 | 
										const int final_pixel_word = std::min(final_word, 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(!first_word) {
 | 
				
			||||||
 | 
											pixel_buffer_ = crt_.begin_data(512);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(pixel_buffer_) {
 | 
				
			||||||
 | 
											for(int c = first_word; c < final_pixel_word; ++c) {
 | 
				
			||||||
 | 
												uint16_t pixels = ram_[video_base + video_address_] ^ 0xffff;
 | 
				
			||||||
 | 
												++video_address_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												pixel_buffer_[15] = pixels & 0x01;
 | 
				
			||||||
 | 
												pixel_buffer_[14] = pixels & 0x02;
 | 
				
			||||||
 | 
												pixel_buffer_[13] = pixels & 0x04;
 | 
				
			||||||
 | 
												pixel_buffer_[12] = pixels & 0x08;
 | 
				
			||||||
 | 
												pixel_buffer_[11] = pixels & 0x10;
 | 
				
			||||||
 | 
												pixel_buffer_[10] = pixels & 0x20;
 | 
				
			||||||
 | 
												pixel_buffer_[9] = pixels & 0x40;
 | 
				
			||||||
 | 
												pixel_buffer_[8] = pixels & 0x80;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												pixels >>= 8;
 | 
				
			||||||
 | 
												pixel_buffer_[7] = pixels & 0x01;
 | 
				
			||||||
 | 
												pixel_buffer_[6] = pixels & 0x02;
 | 
				
			||||||
 | 
												pixel_buffer_[5] = pixels & 0x04;
 | 
				
			||||||
 | 
												pixel_buffer_[4] = pixels & 0x08;
 | 
				
			||||||
 | 
												pixel_buffer_[3] = pixels & 0x10;
 | 
				
			||||||
 | 
												pixel_buffer_[2] = pixels & 0x20;
 | 
				
			||||||
 | 
												pixel_buffer_[1] = pixels & 0x40;
 | 
				
			||||||
 | 
												pixel_buffer_[0] = pixels & 0x80;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												pixel_buffer_ += 16;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(final_pixel_word == 32) {
 | 
				
			||||||
 | 
											crt_.output_data(512);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if(first_word < sync_start && final_word >= sync_start)	crt_.output_blank((sync_start - 32) * 16);
 | 
				
			||||||
 | 
									if(first_word < sync_end && final_word >= sync_end)		crt_.output_sync((sync_end - sync_start) * 16);
 | 
				
			||||||
 | 
									if(final_word == 44)									crt_.output_blank((44 - sync_end) * 16);
 | 
				
			||||||
 | 
								} else if(final_word == 44) {
 | 
				
			||||||
 | 
									if(line >= 353 && line < 356) {
 | 
				
			||||||
 | 
										/* Output a sync line. */
 | 
				
			||||||
 | 
										crt_.output_sync(sync_start * 16);
 | 
				
			||||||
 | 
										crt_.output_blank((sync_end - sync_start) * 16);
 | 
				
			||||||
 | 
										crt_.output_sync((44 - sync_end) * 16);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										/* Output a blank line. */
 | 
				
			||||||
 | 
										crt_.output_blank(sync_start * 16);
 | 
				
			||||||
 | 
										crt_.output_sync((sync_end - sync_start) * 16);
 | 
				
			||||||
 | 
										crt_.output_blank((44 - sync_end) * 16);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Audio and disk fetches occur "just before video data".
 | 
				
			||||||
 | 
								if(final_word == 44) {
 | 
				
			||||||
 | 
									const uint16_t audio_word = ram_[audio_address_ + audio_base];
 | 
				
			||||||
 | 
									++audio_address_;
 | 
				
			||||||
 | 
									audio_.audio.post_sample(audio_word >> 8);
 | 
				
			||||||
 | 
									drive_speed_accumulator_.post_sample(audio_word & 0xff);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							duration -= cycles_left_in_line;
 | 
				
			||||||
 | 
							frame_position_ = frame_position_ + cycles_left_in_line;
 | 
				
			||||||
 | 
							if(frame_position_ == frame_length) {
 | 
				
			||||||
 | 
								frame_position_ = HalfCycles(0);
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers.
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
								video_address_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									"The main sound buffer is at $1FD00 in a 128K Macintosh, and the alternate buffer is at $1A100;
 | 
				
			||||||
 | 
									for a 512K Macintosh, add $60000 to these values."
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
								audio_address_ = 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Video::vsync() {
 | 
				
			||||||
 | 
						const auto line = (frame_position_ / line_length).as_integral();
 | 
				
			||||||
 | 
						return line >= 353 && line < 356;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HalfCycles Video::get_next_sequence_point() {
 | 
				
			||||||
 | 
						const auto line = (frame_position_ / line_length).as_integral();
 | 
				
			||||||
 | 
						if(line >= 353 && line < 356) {
 | 
				
			||||||
 | 
							// Currently in vsync, so get time until start of line 357,
 | 
				
			||||||
 | 
							// when vsync will end.
 | 
				
			||||||
 | 
							return HalfCycles(356) * line_length - frame_position_;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Not currently in vsync, so get time until start of line 353.
 | 
				
			||||||
 | 
							const auto start_of_vsync = HalfCycles(353) * line_length;
 | 
				
			||||||
 | 
							if(frame_position_ < start_of_vsync)
 | 
				
			||||||
 | 
								return start_of_vsync - frame_position_;
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								return start_of_vsync + HalfCycles(number_of_lines) * line_length - frame_position_;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
 | 
				
			||||||
 | 
						use_alternate_screen_buffer_ = use_alternate_screen_buffer;
 | 
				
			||||||
 | 
						use_alternate_audio_buffer_ = use_alternate_audio_buffer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Video::set_ram(uint16_t *ram, uint32_t mask) {
 | 
				
			||||||
 | 
						ram_ = ram;
 | 
				
			||||||
 | 
						ram_mask_ = mask;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										105
									
								
								Machines/Apple/Macintosh/Video.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								Machines/Apple/Macintosh/Video.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Video.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 03/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Video_hpp
 | 
				
			||||||
 | 
					#define Video_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Outputs/CRT/CRT.hpp"
 | 
				
			||||||
 | 
					#include "../../../ClockReceiver/ClockReceiver.hpp"
 | 
				
			||||||
 | 
					#include "DeferredAudio.hpp"
 | 
				
			||||||
 | 
					#include "DriveSpeedAccumulator.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr HalfCycles line_length(704);
 | 
				
			||||||
 | 
					constexpr int number_of_lines = 370;
 | 
				
			||||||
 | 
					constexpr HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
 | 
				
			||||||
 | 
					constexpr int sync_start = 36;
 | 
				
			||||||
 | 
					constexpr int sync_end = 38;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image,
 | 
				
			||||||
 | 
						within a total scanning area of 370 lines, at 352 cycles per line.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						This class also collects audio and 400kb drive-speed data, forwarding those values.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class Video {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Constructs an instance of @c Video sourcing its pixel data from @c ram and
 | 
				
			||||||
 | 
								providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the target device for video data.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Produces the next @c duration period of pixels.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void run_for(HalfCycles duration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets whether the alternate screen and/or audio buffers should be used to source data.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are
 | 
				
			||||||
 | 
								actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_ram(uint16_t *ram, uint32_t mask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns @c true if the video is currently outputting a vertical sync, @c false otherwise.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							bool vsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
								@returns @c true if in @c offset half cycles from now, the video will be outputting pixels;
 | 
				
			||||||
 | 
									@c false otherwise.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							bool is_outputting(HalfCycles offset = HalfCycles(0)) {
 | 
				
			||||||
 | 
								const auto offset_position = frame_position_ + offset % frame_length;
 | 
				
			||||||
 | 
								const int column = int((offset_position % line_length).as_integral()) >> 4;
 | 
				
			||||||
 | 
								const int line = int((offset_position / line_length).as_integral());
 | 
				
			||||||
 | 
								return line < 342 && column < 32;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns the amount of time until there is next a transition on the
 | 
				
			||||||
 | 
									vsync signal.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							HalfCycles get_next_sequence_point();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							DeferredAudio &audio_;
 | 
				
			||||||
 | 
							DriveSpeedAccumulator &drive_speed_accumulator_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Outputs::CRT::CRT crt_;
 | 
				
			||||||
 | 
							uint16_t *ram_ = nullptr;
 | 
				
			||||||
 | 
							uint32_t ram_mask_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							HalfCycles frame_position_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							size_t video_address_ = 0;
 | 
				
			||||||
 | 
							size_t audio_address_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t *pixel_buffer_ = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool use_alternate_screen_buffer_ = false;
 | 
				
			||||||
 | 
							bool use_alternate_audio_buffer_ = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Video_hpp */
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user