mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	Compare commits
	
		
			1860 Commits
		
	
	
		
			2020-01-06
			...
			2021-04-16
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					06cedb2e50 | ||
| 
						 | 
					7fdb1d848b | ||
| 
						 | 
					246fd9442f | ||
| 
						 | 
					eb99a64b29 | ||
| 
						 | 
					d7954a4cb1 | ||
| 
						 | 
					ef636da866 | ||
| 
						 | 
					fa18b06dbf | ||
| 
						 | 
					349b9ce502 | ||
| 
						 | 
					b2cf121410 | ||
| 
						 | 
					71cf63bd35 | ||
| 
						 | 
					d1bb3aada4 | ||
| 
						 | 
					b4214c6e08 | ||
| 
						 | 
					f5c7746493 | ||
| 
						 | 
					f10ec80153 | ||
| 
						 | 
					0af405aa46 | ||
| 
						 | 
					cf481effa6 | ||
| 
						 | 
					a1511f9600 | ||
| 
						 | 
					325e2b3941 | ||
| 
						 | 
					7017324d60 | ||
| 
						 | 
					deb5d69ac7 | ||
| 
						 | 
					68a04f4e6a | ||
| 
						 | 
					0d61902b10 | ||
| 
						 | 
					3eec210b30 | ||
| 
						 | 
					5998f3b35b | ||
| 
						 | 
					869567fdd9 | ||
| 
						 | 
					2e70b5eb9f | ||
| 
						 | 
					8a3bfb8672 | ||
| 
						 | 
					06f1e64177 | ||
| 
						 | 
					b42780173a | ||
| 
						 | 
					36c8821c4c | ||
| 
						 | 
					947de2d54a | ||
| 
						 | 
					9347fe5f44 | ||
| 
						 | 
					e82367def3 | ||
| 
						 | 
					9cde7c12ba | ||
| 
						 | 
					015556cc91 | ||
| 
						 | 
					47c5a243aa | ||
| 
						 | 
					070e359d82 | ||
| 
						 | 
					b397059d5e | ||
| 
						 | 
					400f54e508 | ||
| 
						 | 
					e0736435f8 | ||
| 
						 | 
					b09c5538c6 | ||
| 
						 | 
					ce3d2913bf | ||
| 
						 | 
					87202a2a27 | ||
| 
						 | 
					818a4dff25 | ||
| 
						 | 
					eacffa49f5 | ||
| 
						 | 
					9e506c3206 | ||
| 
						 | 
					29cf80339a | ||
| 
						 | 
					50f53f7d97 | ||
| 
						 | 
					73fbd89c85 | ||
| 
						 | 
					f74fa06f2d | ||
| 
						 | 
					ee989ab762 | ||
| 
						 | 
					818655a9b6 | ||
| 
						 | 
					57a7e0834f | ||
| 
						 | 
					cd787486d2 | ||
| 
						 | 
					67fd6787a6 | ||
| 
						 | 
					627b96f73c | ||
| 
						 | 
					8a6985c2e8 | ||
| 
						 | 
					60e8273de2 | ||
| 
						 | 
					aa8ce5c1ac | ||
| 
						 | 
					dd28246f9f | ||
| 
						 | 
					dc25a60b9b | ||
| 
						 | 
					094d623485 | ||
| 
						 | 
					1266bbb224 | ||
| 
						 | 
					bd1ea5740a | ||
| 
						 | 
					3e04b51122 | ||
| 
						 | 
					76f2aba51a | ||
| 
						 | 
					fd88071c0a | ||
| 
						 | 
					16bfe1a55c | ||
| 
						 | 
					90c3d6a1e8 | ||
| 
						 | 
					18d6197d6c | ||
| 
						 | 
					27eddf6dff | ||
| 
						 | 
					57b32d9537 | ||
| 
						 | 
					837b9499d5 | ||
| 
						 | 
					c2fde2b147 | ||
| 
						 | 
					f26bf4b9e4 | ||
| 
						 | 
					1da51bee6c | ||
| 
						 | 
					5a66956221 | ||
| 
						 | 
					91d973c4a9 | ||
| 
						 | 
					fa79589db8 | ||
| 
						 | 
					e52649f74d | ||
| 
						 | 
					d77ddaf4fa | ||
| 
						 | 
					9ff392279a | ||
| 
						 | 
					448d9dc3e1 | ||
| 
						 | 
					afb4e6d37d | ||
| 
						 | 
					158122fbf4 | ||
| 
						 | 
					417ece2386 | ||
| 
						 | 
					77b241af4f | ||
| 
						 | 
					25b8c4c062 | ||
| 
						 | 
					1be88a5308 | ||
| 
						 | 
					294280a94e | ||
| 
						 | 
					32aebfebe0 | ||
| 
						 | 
					14663bd06b | ||
| 
						 | 
					68abd197aa | ||
| 
						 | 
					18fd21eae7 | ||
| 
						 | 
					3296347370 | ||
| 
						 | 
					28c9463e0d | ||
| 
						 | 
					044ac949ba | ||
| 
						 | 
					87317f5673 | ||
| 
						 | 
					5e21a49841 | ||
| 
						 | 
					687c05365e | ||
| 
						 | 
					4f80523828 | ||
| 
						 | 
					76299a2add | ||
| 
						 | 
					48f794dc2d | ||
| 
						 | 
					51b8dcd011 | ||
| 
						 | 
					acdbd88b9e | ||
| 
						 | 
					00a3a3c724 | ||
| 
						 | 
					729edeac6c | ||
| 
						 | 
					faaa4961ed | ||
| 
						 | 
					7937cc2d0f | ||
| 
						 | 
					8a11a5832c | ||
| 
						 | 
					53ba0e67d1 | ||
| 
						 | 
					e8825aeada | ||
| 
						 | 
					e90e30e766 | ||
| 
						 | 
					9f6bb325e6 | ||
| 
						 | 
					6e2c65435a | ||
| 
						 | 
					052ab44f1c | ||
| 
						 | 
					daa5679241 | ||
| 
						 | 
					e055668554 | ||
| 
						 | 
					c96829c29e | ||
| 
						 | 
					c88abed2dc | ||
| 
						 | 
					e42b6cb3c8 | ||
| 
						 | 
					465ecc4a78 | ||
| 
						 | 
					ae4ccdf5e6 | ||
| 
						 | 
					6bdaa54aaf | ||
| 
						 | 
					3543a25168 | ||
| 
						 | 
					03ef81b07c | ||
| 
						 | 
					0ac11fc39e | ||
| 
						 | 
					3d0503a35e | ||
| 
						 | 
					ad8cb52f11 | ||
| 
						 | 
					496a294c71 | ||
| 
						 | 
					465c74ab86 | ||
| 
						 | 
					4e8f82a39c | ||
| 
						 | 
					584a5ad7fb | ||
| 
						 | 
					0ab85cce20 | ||
| 
						 | 
					44e5caf803 | ||
| 
						 | 
					04291e9a86 | ||
| 
						 | 
					d0776b58cf | ||
| 
						 | 
					6da099d7e1 | ||
| 
						 | 
					60e77785e8 | ||
| 
						 | 
					19cd6a55d3 | ||
| 
						 | 
					08432dd94b | ||
| 
						 | 
					cc3c3663f6 | ||
| 
						 | 
					b76c923ff4 | ||
| 
						 | 
					3c1131a84b | ||
| 
						 | 
					a3cd953415 | ||
| 
						 | 
					c0abdf1b86 | ||
| 
						 | 
					3ef2715eee | ||
| 
						 | 
					4a12d7086d | ||
| 
						 | 
					a6b75b8637 | ||
| 
						 | 
					bdb3bce8d6 | ||
| 
						 | 
					a26716919c | ||
| 
						 | 
					8dbc7649aa | ||
| 
						 | 
					42a9dc7c2b | ||
| 
						 | 
					7965772745 | ||
| 
						 | 
					f37f89a7d3 | ||
| 
						 | 
					d987e5a9d7 | ||
| 
						 | 
					fcba0cc3d6 | ||
| 
						 | 
					c097ed348a | ||
| 
						 | 
					0f9ab53ea0 | ||
| 
						 | 
					21b1dab4a5 | ||
| 
						 | 
					dd7419282d | ||
| 
						 | 
					7562917740 | ||
| 
						 | 
					3925eee575 | ||
| 
						 | 
					6482303063 | ||
| 
						 | 
					388b136980 | ||
| 
						 | 
					9ce1dbaebb | ||
| 
						 | 
					064667c0c3 | ||
| 
						 | 
					58be770eaa | ||
| 
						 | 
					1b0f45649e | ||
| 
						 | 
					42bfabbe8c | ||
| 
						 | 
					986c4006a6 | ||
| 
						 | 
					07a63d62dd | ||
| 
						 | 
					26911a16e8 | ||
| 
						 | 
					cf9a5d595b | ||
| 
						 | 
					09a6a1905b | ||
| 
						 | 
					2ad2b4384b | ||
| 
						 | 
					7729f1f3d0 | ||
| 
						 | 
					7d59ff6d8f | ||
| 
						 | 
					2ee478b4c4 | ||
| 
						 | 
					bb0d35e3d0 | ||
| 
						 | 
					84774a7910 | ||
| 
						 | 
					a482ce1546 | ||
| 
						 | 
					a35e1f4fbe | ||
| 
						 | 
					2371048ad1 | ||
| 
						 | 
					93b9ea67e6 | ||
| 
						 | 
					60a0f8e824 | ||
| 
						 | 
					b3fc64d4f2 | ||
| 
						 | 
					650b9a139b | ||
| 
						 | 
					5758693b7d | ||
| 
						 | 
					f8c9ef2950 | ||
| 
						 | 
					69ca2e8803 | ||
| 
						 | 
					87fac15cc4 | ||
| 
						 | 
					2d51924a3c | ||
| 
						 | 
					c3d96b30d7 | ||
| 
						 | 
					44240773ef | ||
| 
						 | 
					ed587a4db5 | ||
| 
						 | 
					020a04006e | ||
| 
						 | 
					622a8abf7f | ||
| 
						 | 
					871bac6c8a | ||
| 
						 | 
					fe3e8f87e7 | ||
| 
						 | 
					87fc7c02e8 | ||
| 
						 | 
					f2620e6afb | ||
| 
						 | 
					ab2ad70885 | ||
| 
						 | 
					135134acfd | ||
| 
						 | 
					5664e81d48 | ||
| 
						 | 
					c353923557 | ||
| 
						 | 
					b830d62850 | ||
| 
						 | 
					17f551e89d | ||
| 
						 | 
					4a4da90d56 | ||
| 
						 | 
					404c1f06e6 | ||
| 
						 | 
					730bfcd1fd | ||
| 
						 | 
					97249b0edd | ||
| 
						 | 
					5a1bda1d82 | ||
| 
						 | 
					b7d6b8efcf | ||
| 
						 | 
					9bec91c2b9 | ||
| 
						 | 
					3d1775d853 | ||
| 
						 | 
					814c057570 | ||
| 
						 | 
					b63ca16ce2 | ||
| 
						 | 
					0ddf09ac0f | ||
| 
						 | 
					e53586df1d | ||
| 
						 | 
					54491b35ef | ||
| 
						 | 
					b447f5f174 | ||
| 
						 | 
					39a105b48a | ||
| 
						 | 
					cdc19c6990 | ||
| 
						 | 
					397704a1e6 | ||
| 
						 | 
					1a5dafae00 | ||
| 
						 | 
					d368dae94a | ||
| 
						 | 
					54e2eb0948 | ||
| 
						 | 
					7d778bc328 | ||
| 
						 | 
					7a8317ad81 | ||
| 
						 | 
					a32a2f36be | ||
| 
						 | 
					064fe7658c | ||
| 
						 | 
					cd215ef521 | ||
| 
						 | 
					14c5e038e2 | ||
| 
						 | 
					82717b39bb | ||
| 
						 | 
					f190a1395a | ||
| 
						 | 
					4eaf3440bd | ||
| 
						 | 
					f985248902 | ||
| 
						 | 
					5c90744f0c | ||
| 
						 | 
					e9177bbb2a | ||
| 
						 | 
					ab5e4ca9c7 | ||
| 
						 | 
					40516c9cec | ||
| 
						 | 
					d93d380c88 | ||
| 
						 | 
					8a1c6978de | ||
| 
						 | 
					6839e9e3b3 | ||
| 
						 | 
					83cbbe09c6 | ||
| 
						 | 
					166ddab5e0 | ||
| 
						 | 
					67408521cd | ||
| 
						 | 
					f05260b839 | ||
| 
						 | 
					62949d2f8b | ||
| 
						 | 
					2f18f40697 | ||
| 
						 | 
					eea4c1f148 | ||
| 
						 | 
					63a792f434 | ||
| 
						 | 
					7b164de6fd | ||
| 
						 | 
					26ad760904 | ||
| 
						 | 
					24e68166c6 | ||
| 
						 | 
					b72474f418 | ||
| 
						 | 
					38046d49aa | ||
| 
						 | 
					4601421aa6 | ||
| 
						 | 
					86fd47545d | ||
| 
						 | 
					c8471eb993 | ||
| 
						 | 
					83d0cfc24e | ||
| 
						 | 
					cbf5a79ee8 | ||
| 
						 | 
					2f45e07d82 | ||
| 
						 | 
					496b6b5cfc | ||
| 
						 | 
					8604b1786e | ||
| 
						 | 
					267e28e012 | ||
| 
						 | 
					631a8a7421 | ||
| 
						 | 
					7dcb0553e4 | ||
| 
						 | 
					2a7ea9f57c | ||
| 
						 | 
					e2b20568c6 | ||
| 
						 | 
					4f5eb4d71b | ||
| 
						 | 
					a1df8452ce | ||
| 
						 | 
					9781460c41 | ||
| 
						 | 
					55c9d152e9 | ||
| 
						 | 
					71a107fe75 | ||
| 
						 | 
					6cf9099ce1 | ||
| 
						 | 
					e6dc39f6f0 | ||
| 
						 | 
					f6466fd657 | ||
| 
						 | 
					28ce675c96 | ||
| 
						 | 
					3d91b0a31b | ||
| 
						 | 
					5d1970d201 | ||
| 
						 | 
					72d7901c88 | ||
| 
						 | 
					60cfec6a65 | ||
| 
						 | 
					2e9065b34c | ||
| 
						 | 
					992ee6d631 | ||
| 
						 | 
					772093c311 | ||
| 
						 | 
					e42843cca0 | ||
| 
						 | 
					3336a123f8 | ||
| 
						 | 
					bd54e30748 | ||
| 
						 | 
					35be402354 | ||
| 
						 | 
					28bd620e7f | ||
| 
						 | 
					96f2d802d9 | ||
| 
						 | 
					b117df3367 | ||
| 
						 | 
					fa8236741d | ||
| 
						 | 
					e16d5f33d1 | ||
| 
						 | 
					2a45e7a8d4 | ||
| 
						 | 
					f8f0ff0fae | ||
| 
						 | 
					f5dcff2f29 | ||
| 
						 | 
					e773b331cd | ||
| 
						 | 
					99c21925f4 | ||
| 
						 | 
					eccf5ca043 | ||
| 
						 | 
					24af62a3e5 | ||
| 
						 | 
					52cf15c3e6 | ||
| 
						 | 
					a791680e6f | ||
| 
						 | 
					a3e98907ca | ||
| 
						 | 
					6e53b4c507 | ||
| 
						 | 
					52c38e72f6 | ||
| 
						 | 
					a51d143c35 | ||
| 
						 | 
					17e9305282 | ||
| 
						 | 
					c284b34003 | ||
| 
						 | 
					2ab3bba695 | ||
| 
						 | 
					2c4dcf8843 | ||
| 
						 | 
					ea40b2c982 | ||
| 
						 | 
					adfdfa205f | ||
| 
						 | 
					e83b2120ce | ||
| 
						 | 
					33abdc95aa | ||
| 
						 | 
					6ca8aa99fc | ||
| 
						 | 
					17bac4c8cf | ||
| 
						 | 
					46bd20b5e0 | ||
| 
						 | 
					3c7f9a43ad | ||
| 
						 | 
					82312d3b59 | ||
| 
						 | 
					93a80a30d3 | ||
| 
						 | 
					77b1efd176 | ||
| 
						 | 
					acfab1dfb3 | ||
| 
						 | 
					819e9039ab | ||
| 
						 | 
					6526c645a5 | ||
| 
						 | 
					3d2490b774 | ||
| 
						 | 
					1e041f1adf | ||
| 
						 | 
					4fdf01a1a8 | ||
| 
						 | 
					beb514b231 | ||
| 
						 | 
					f57e897085 | ||
| 
						 | 
					2a8e8a4982 | ||
| 
						 | 
					9f202d4238 | ||
| 
						 | 
					1a40cc048e | ||
| 
						 | 
					53514c7fdc | ||
| 
						 | 
					274b3c7d24 | ||
| 
						 | 
					07df7572b3 | ||
| 
						 | 
					906b6ccdb7 | ||
| 
						 | 
					f1ba040dd8 | ||
| 
						 | 
					8db289e229 | ||
| 
						 | 
					8142487d57 | ||
| 
						 | 
					2860be7068 | ||
| 
						 | 
					b5ecd5f7ef | ||
| 
						 | 
					7e720e754b | ||
| 
						 | 
					41a618c957 | ||
| 
						 | 
					3d85e6bb97 | ||
| 
						 | 
					d54085c7fd | ||
| 
						 | 
					0bb8bdf938 | ||
| 
						 | 
					865058b8d6 | ||
| 
						 | 
					b6bc0a21fb | ||
| 
						 | 
					8311ac4a7c | ||
| 
						 | 
					4636d8dfb7 | ||
| 
						 | 
					ac95e4d758 | ||
| 
						 | 
					b8c6d4b153 | ||
| 
						 | 
					5eddc92846 | ||
| 
						 | 
					f50e8b5106 | ||
| 
						 | 
					dcc2fe0990 | ||
| 
						 | 
					56111c75ae | ||
| 
						 | 
					cc90935abd | ||
| 
						 | 
					413e42e1b6 | ||
| 
						 | 
					fc4bda0047 | ||
| 
						 | 
					c8beb59172 | ||
| 
						 | 
					8789ffda15 | ||
| 
						 | 
					e8e604dc3c | ||
| 
						 | 
					57e0fdfadc | ||
| 
						 | 
					7f62732476 | ||
| 
						 | 
					36aebe0ff9 | ||
| 
						 | 
					051d2b83f4 | ||
| 
						 | 
					17b12120eb | ||
| 
						 | 
					6e9ce50569 | ||
| 
						 | 
					adef2e9b4e | ||
| 
						 | 
					0fafbf5092 | ||
| 
						 | 
					3c887aff95 | ||
| 
						 | 
					e5076b295b | ||
| 
						 | 
					c10c161d39 | ||
| 
						 | 
					04024ca159 | ||
| 
						 | 
					64d556f60f | ||
| 
						 | 
					8564e7406b | ||
| 
						 | 
					ebdb58d790 | ||
| 
						 | 
					cf8afc70b2 | ||
| 
						 | 
					4f02e8fbaf | ||
| 
						 | 
					6e618a6bb7 | ||
| 
						 | 
					df1bc18fb3 | ||
| 
						 | 
					9f12ce2fb8 | ||
| 
						 | 
					b9672c0669 | ||
| 
						 | 
					e58608b25a | ||
| 
						 | 
					e502d76371 | ||
| 
						 | 
					b0c790f3c6 | ||
| 
						 | 
					aa478cd222 | ||
| 
						 | 
					c78c121159 | ||
| 
						 | 
					e71e506883 | ||
| 
						 | 
					a601ac0cab | ||
| 
						 | 
					9b92753e0a | ||
| 
						 | 
					ec0018df79 | ||
| 
						 | 
					8b19c523cf | ||
| 
						 | 
					5ace61f9b9 | ||
| 
						 | 
					8a74f5911c | ||
| 
						 | 
					4982430a29 | ||
| 
						 | 
					dea79c6dea | ||
| 
						 | 
					ad03858c6e | ||
| 
						 | 
					54b26c7991 | ||
| 
						 | 
					17c3a3eb4b | ||
| 
						 | 
					5f413a38df | ||
| 
						 | 
					8860d0ff51 | ||
| 
						 | 
					8bd471fa3c | ||
| 
						 | 
					cd6ac51aa6 | ||
| 
						 | 
					10caa1a1fb | ||
| 
						 | 
					722e0068ca | ||
| 
						 | 
					8f2eea8819 | ||
| 
						 | 
					3b2d65fa16 | ||
| 
						 | 
					3dc36b704a | ||
| 
						 | 
					37a20e125c | ||
| 
						 | 
					2910faf963 | ||
| 
						 | 
					321e10fffb | ||
| 
						 | 
					1acb8c3c42 | ||
| 
						 | 
					f667dd223f | ||
| 
						 | 
					e0d90f69ec | ||
| 
						 | 
					d82187bee2 | ||
| 
						 | 
					3c20e1f037 | ||
| 
						 | 
					15bedc74d4 | ||
| 
						 | 
					4bd6ffa9e4 | ||
| 
						 | 
					9c2c918760 | ||
| 
						 | 
					47d20699d8 | ||
| 
						 | 
					e8ce70dccb | ||
| 
						 | 
					fa4938f29c | ||
| 
						 | 
					ddb4bb1421 | ||
| 
						 | 
					ca94e9038e | ||
| 
						 | 
					2c72a77a25 | ||
| 
						 | 
					8c0e06e645 | ||
| 
						 | 
					a24ae727a7 | ||
| 
						 | 
					5058a8b96a | ||
| 
						 | 
					762ecab3aa | ||
| 
						 | 
					9ba5b7c1d4 | ||
| 
						 | 
					5f807b6e47 | ||
| 
						 | 
					718f950071 | ||
| 
						 | 
					68fe16a092 | ||
| 
						 | 
					97a64db5e0 | ||
| 
						 | 
					86577b772b | ||
| 
						 | 
					306df7554e | ||
| 
						 | 
					30c2c0f050 | ||
| 
						 | 
					205649cac2 | ||
| 
						 | 
					fd49b72e31 | ||
| 
						 | 
					995904993d | ||
| 
						 | 
					17cbba85fc | ||
| 
						 | 
					9d7d45338f | ||
| 
						 | 
					3b55d3f158 | ||
| 
						 | 
					fda2293d6b | ||
| 
						 | 
					da814c62bc | ||
| 
						 | 
					d4095b1b3b | ||
| 
						 | 
					ed41154338 | ||
| 
						 | 
					38bca5f0f0 | ||
| 
						 | 
					a8738b533a | ||
| 
						 | 
					29cf96c703 | ||
| 
						 | 
					782dc3d046 | ||
| 
						 | 
					0ae217f51d | ||
| 
						 | 
					adcb2e03e8 | ||
| 
						 | 
					11b6c1d4b5 | ||
| 
						 | 
					367cb1789d | ||
| 
						 | 
					adf1484ecc | ||
| 
						 | 
					5401ff6c78 | ||
| 
						 | 
					eb8d0eefd5 | ||
| 
						 | 
					c934e22cee | ||
| 
						 | 
					1a3effc692 | ||
| 
						 | 
					32c942d154 | ||
| 
						 | 
					9c5dc0ed29 | ||
| 
						 | 
					290972cedf | ||
| 
						 | 
					dc9d370952 | ||
| 
						 | 
					a41be61f99 | ||
| 
						 | 
					3d1783ddae | ||
| 
						 | 
					8151c8e409 | ||
| 
						 | 
					0ef42f93ff | ||
| 
						 | 
					d318ab4e70 | ||
| 
						 | 
					ebfa35c2c7 | ||
| 
						 | 
					db50b0fe23 | ||
| 
						 | 
					233a69a1d8 | ||
| 
						 | 
					3749b7b776 | ||
| 
						 | 
					ed63e7ea75 | ||
| 
						 | 
					31d68622c8 | ||
| 
						 | 
					ee5f45c979 | ||
| 
						 | 
					3d79b11f92 | ||
| 
						 | 
					dfe4e49110 | ||
| 
						 | 
					12784a71e2 | ||
| 
						 | 
					e0b36c9c3d | ||
| 
						 | 
					c5c56f9d05 | ||
| 
						 | 
					9f0129cab8 | ||
| 
						 | 
					5a48e50355 | ||
| 
						 | 
					86283b1815 | ||
| 
						 | 
					a38d964f62 | ||
| 
						 | 
					114d48b076 | ||
| 
						 | 
					6e9d517c26 | ||
| 
						 | 
					3b2e97e77c | ||
| 
						 | 
					159924dcc0 | ||
| 
						 | 
					5d8f284757 | ||
| 
						 | 
					c978a95463 | ||
| 
						 | 
					fe4caf7a41 | ||
| 
						 | 
					4bf85abf30 | ||
| 
						 | 
					49cee90b4d | ||
| 
						 | 
					394f6b58d8 | ||
| 
						 | 
					dbdea95241 | ||
| 
						 | 
					1928c955d9 | ||
| 
						 | 
					a91a13b46b | ||
| 
						 | 
					2f86d5ebaf | ||
| 
						 | 
					b589d6e3ef | ||
| 
						 | 
					db8b265e80 | ||
| 
						 | 
					8560b38ffa | ||
| 
						 | 
					049a78c667 | ||
| 
						 | 
					574a37814c | ||
| 
						 | 
					94eb17db0c | ||
| 
						 | 
					9577c8e27f | ||
| 
						 | 
					c72bdd776e | ||
| 
						 | 
					d35def4bbc | ||
| 
						 | 
					d5f209366a | ||
| 
						 | 
					9062e80e9d | ||
| 
						 | 
					fd3760cedc | ||
| 
						 | 
					9b73331ee9 | ||
| 
						 | 
					65ca931e83 | ||
| 
						 | 
					6cb71eb11b | ||
| 
						 | 
					43251193ee | ||
| 
						 | 
					55de98fb46 | ||
| 
						 | 
					1422d43c35 | ||
| 
						 | 
					6273ef8ba2 | ||
| 
						 | 
					3c6f09a898 | ||
| 
						 | 
					24fcb0c24b | ||
| 
						 | 
					3162873a9c | ||
| 
						 | 
					03e2b6a265 | ||
| 
						 | 
					ee22cf7ca1 | ||
| 
						 | 
					187f507532 | ||
| 
						 | 
					6000bd3a5e | ||
| 
						 | 
					87069da3dd | ||
| 
						 | 
					5cb4077576 | ||
| 
						 | 
					e9c7e0b9dd | ||
| 
						 | 
					35aa7612bb | ||
| 
						 | 
					acaa841822 | ||
| 
						 | 
					46c1c9b5ee | ||
| 
						 | 
					4bdbca64b2 | ||
| 
						 | 
					3da6b4709c | ||
| 
						 | 
					11fe8ab6db | ||
| 
						 | 
					a9ce43d244 | ||
| 
						 | 
					091bce9350 | ||
| 
						 | 
					32ccce3040 | ||
| 
						 | 
					ab3fcb3ea0 | ||
| 
						 | 
					9610672615 | ||
| 
						 | 
					5ee9630624 | ||
| 
						 | 
					1b3836eb1c | ||
| 
						 | 
					1302a046e9 | ||
| 
						 | 
					33dec3c220 | ||
| 
						 | 
					7c29c3a944 | ||
| 
						 | 
					c9ca1fc7a0 | ||
| 
						 | 
					a965c8de9f | ||
| 
						 | 
					0b4b271e3d | ||
| 
						 | 
					5fc6dd1a4d | ||
| 
						 | 
					79ef026b93 | ||
| 
						 | 
					a4ab5b0b49 | ||
| 
						 | 
					310282b7c9 | ||
| 
						 | 
					af667c718e | ||
| 
						 | 
					950f5b1691 | ||
| 
						 | 
					f54a3f8619 | ||
| 
						 | 
					cbc0d848ad | ||
| 
						 | 
					f4d13d1f6f | ||
| 
						 | 
					6808ad6f5d | ||
| 
						 | 
					7a8920ee38 | ||
| 
						 | 
					4870506f6e | ||
| 
						 | 
					6f47f9d67c | ||
| 
						 | 
					8093f67173 | ||
| 
						 | 
					72884f37c3 | ||
| 
						 | 
					8edb3fcd5f | ||
| 
						 | 
					b0efc647f1 | ||
| 
						 | 
					fdd102df52 | ||
| 
						 | 
					73d28838c0 | ||
| 
						 | 
					03a893dc74 | ||
| 
						 | 
					56de2512ae | ||
| 
						 | 
					cdc2311045 | ||
| 
						 | 
					c6c12209e8 | ||
| 
						 | 
					eec27c3406 | ||
| 
						 | 
					2ac6f96806 | ||
| 
						 | 
					0bd3103949 | ||
| 
						 | 
					098a22aa95 | ||
| 
						 | 
					9a819d6ca0 | ||
| 
						 | 
					b4bf541eec | ||
| 
						 | 
					7ede3d2b9e | ||
| 
						 | 
					e7160fe3c3 | ||
| 
						 | 
					9d61665014 | ||
| 
						 | 
					d2938ad7c8 | ||
| 
						 | 
					9e0e063f8a | ||
| 
						 | 
					46f7ff07f7 | ||
| 
						 | 
					8ace258fbc | ||
| 
						 | 
					4359fb1746 | ||
| 
						 | 
					a34f294ba8 | ||
| 
						 | 
					cd7d080b7a | ||
| 
						 | 
					b0936b6ef4 | ||
| 
						 | 
					8fae74f93e | ||
| 
						 | 
					fca48e4b66 | ||
| 
						 | 
					dd816c5a0a | ||
| 
						 | 
					3b2ea37428 | ||
| 
						 | 
					8a805b6ba1 | ||
| 
						 | 
					3cc89cb4d2 | ||
| 
						 | 
					9b45c5a1cd | ||
| 
						 | 
					3cba3a5ac0 | ||
| 
						 | 
					4b024c5787 | ||
| 
						 | 
					4a42de4f18 | ||
| 
						 | 
					d00e5d23ef | ||
| 
						 | 
					2c9ce116a2 | ||
| 
						 | 
					3512352c32 | ||
| 
						 | 
					4d9372c52f | ||
| 
						 | 
					1d288b08b6 | ||
| 
						 | 
					f3c7c11772 | ||
| 
						 | 
					4b9fe805e9 | ||
| 
						 | 
					a7051e4e42 | ||
| 
						 | 
					34794223b4 | ||
| 
						 | 
					96cf617ee6 | ||
| 
						 | 
					69dddf34b9 | ||
| 
						 | 
					8f4597f742 | ||
| 
						 | 
					98347cb1c3 | ||
| 
						 | 
					c7ab3d4075 | ||
| 
						 | 
					cddd72876f | ||
| 
						 | 
					62f936128d | ||
| 
						 | 
					bb80e53021 | ||
| 
						 | 
					952891d1b6 | ||
| 
						 | 
					6dfad6a44b | ||
| 
						 | 
					e4c5bfdd5c | ||
| 
						 | 
					da8563733b | ||
| 
						 | 
					e41faeb557 | ||
| 
						 | 
					9a55eb56ea | ||
| 
						 | 
					9206ab5dc3 | ||
| 
						 | 
					7e39550fc0 | ||
| 
						 | 
					96e79301f3 | ||
| 
						 | 
					c3f5fbd300 | ||
| 
						 | 
					1db713fec1 | ||
| 
						 | 
					68ba73bee0 | ||
| 
						 | 
					cdacf280e1 | ||
| 
						 | 
					1538a02e18 | ||
| 
						 | 
					f9cec9a102 | ||
| 
						 | 
					adda3d8f42 | ||
| 
						 | 
					ec3ff0da12 | ||
| 
						 | 
					73c38b3b0d | ||
| 
						 | 
					edc8050b36 | ||
| 
						 | 
					37815a982a | ||
| 
						 | 
					bd8af25294 | ||
| 
						 | 
					3207183f05 | ||
| 
						 | 
					e803f993b7 | ||
| 
						 | 
					5dbc87caf0 | ||
| 
						 | 
					4862ccc947 | ||
| 
						 | 
					e1ecf66485 | ||
| 
						 | 
					2c71ba0744 | ||
| 
						 | 
					a7aeb779e9 | ||
| 
						 | 
					e72cfbf447 | ||
| 
						 | 
					0c04a376c4 | ||
| 
						 | 
					3c6dc4c448 | ||
| 
						 | 
					5d154e3d0c | ||
| 
						 | 
					86a24cc928 | ||
| 
						 | 
					e8b52d20e9 | ||
| 
						 | 
					b0fc2f6ecf | ||
| 
						 | 
					715a1b9cd6 | ||
| 
						 | 
					81969bbea9 | ||
| 
						 | 
					86310849eb | ||
| 
						 | 
					a2a928e262 | ||
| 
						 | 
					ffc9e229b6 | ||
| 
						 | 
					3813e00ca3 | ||
| 
						 | 
					5698aa6499 | ||
| 
						 | 
					1f5908dc51 | ||
| 
						 | 
					72884c3ead | ||
| 
						 | 
					80358cf5bd | ||
| 
						 | 
					a15af1df5e | ||
| 
						 | 
					6d511f01a4 | ||
| 
						 | 
					da9e378ab1 | ||
| 
						 | 
					6d3d7c6006 | ||
| 
						 | 
					8024bbd721 | ||
| 
						 | 
					ece9382a4e | ||
| 
						 | 
					6ba517a4c1 | ||
| 
						 | 
					20fd5adb24 | ||
| 
						 | 
					abb350ff5b | ||
| 
						 | 
					dc8d4d49f5 | ||
| 
						 | 
					54352cb1cb | ||
| 
						 | 
					7e106c6add | ||
| 
						 | 
					0ae49b356a | ||
| 
						 | 
					32374444ba | ||
| 
						 | 
					287bfeb924 | ||
| 
						 | 
					b5fa574686 | ||
| 
						 | 
					7aea3dc124 | ||
| 
						 | 
					81c38c7200 | ||
| 
						 | 
					3bb3d8c5c1 | ||
| 
						 | 
					b57a2bfec9 | ||
| 
						 | 
					93968d267d | ||
| 
						 | 
					d27fb5f199 | ||
| 
						 | 
					a51f4122f0 | ||
| 
						 | 
					35ba5fc894 | ||
| 
						 | 
					228d901253 | ||
| 
						 | 
					d37ba62343 | ||
| 
						 | 
					699fb0aa4b | ||
| 
						 | 
					613d4b7c8b | ||
| 
						 | 
					4f9d06d8c7 | ||
| 
						 | 
					5149e4364a | ||
| 
						 | 
					6b29e1f598 | ||
| 
						 | 
					6c9edbb7a2 | ||
| 
						 | 
					282d0f1ebb | ||
| 
						 | 
					f466cbadec | ||
| 
						 | 
					189a468ad4 | ||
| 
						 | 
					a3414c2673 | ||
| 
						 | 
					5126163c5d | ||
| 
						 | 
					46ee98639e | ||
| 
						 | 
					cc6c0d535c | ||
| 
						 | 
					78b57e73d5 | ||
| 
						 | 
					9e2a6526d1 | ||
| 
						 | 
					d3c7253981 | ||
| 
						 | 
					e3147b6b45 | ||
| 
						 | 
					d50b059a17 | ||
| 
						 | 
					cc5ec78156 | ||
| 
						 | 
					ddc44ce0d1 | ||
| 
						 | 
					5cbb91f352 | ||
| 
						 | 
					91ea2eff4c | ||
| 
						 | 
					bf85d71674 | ||
| 
						 | 
					426e90eebf | ||
| 
						 | 
					3889646d6b | ||
| 
						 | 
					0178aaee2b | ||
| 
						 | 
					53f60f7c87 | ||
| 
						 | 
					2da71acefd | ||
| 
						 | 
					45f5896b76 | ||
| 
						 | 
					531a3bb7e6 | ||
| 
						 | 
					1b28d929e4 | ||
| 
						 | 
					e8943618dc | ||
| 
						 | 
					1ae2f6f449 | ||
| 
						 | 
					88e26b42f5 | ||
| 
						 | 
					03d1aff6c0 | ||
| 
						 | 
					e4459b6256 | ||
| 
						 | 
					2be817a6a1 | ||
| 
						 | 
					a833bb892b | ||
| 
						 | 
					7f3f6c339f | ||
| 
						 | 
					0d562699a2 | ||
| 
						 | 
					034056d0cd | ||
| 
						 | 
					1249fb598b | ||
| 
						 | 
					5a8b8478d2 | ||
| 
						 | 
					6c54699c44 | ||
| 
						 | 
					266022b193 | ||
| 
						 | 
					94a6da6b7d | ||
| 
						 | 
					885fae1534 | ||
| 
						 | 
					1df2ce513a | ||
| 
						 | 
					1e4679ae14 | ||
| 
						 | 
					267dd59a59 | ||
| 
						 | 
					0a91ac5af5 | ||
| 
						 | 
					ad93ad6018 | ||
| 
						 | 
					0c700094ea | ||
| 
						 | 
					20631a157b | ||
| 
						 | 
					bdda84dfde | ||
| 
						 | 
					e44f95a882 | ||
| 
						 | 
					31cd45f8b5 | ||
| 
						 | 
					74f9f6ad3b | ||
| 
						 | 
					1dfdb51e61 | ||
| 
						 | 
					18832dc19d | ||
| 
						 | 
					3dee0666cb | ||
| 
						 | 
					f830f6a57a | ||
| 
						 | 
					82c733c68c | ||
| 
						 | 
					ed510409c4 | ||
| 
						 | 
					7614eba4bf | ||
| 
						 | 
					13c8032465 | ||
| 
						 | 
					44fc08cd5b | ||
| 
						 | 
					7631b11c55 | ||
| 
						 | 
					726b5f62bb | ||
| 
						 | 
					ddd84db510 | ||
| 
						 | 
					966241b4cc | ||
| 
						 | 
					9371a8993f | ||
| 
						 | 
					410c99de54 | ||
| 
						 | 
					817f93a490 | ||
| 
						 | 
					43611792ac | ||
| 
						 | 
					62231708d7 | ||
| 
						 | 
					a5dcab4092 | ||
| 
						 | 
					8bde2e5f4c | ||
| 
						 | 
					5287c57ee0 | ||
| 
						 | 
					3aa47f9c68 | ||
| 
						 | 
					ab07814614 | ||
| 
						 | 
					1653abdf88 | ||
| 
						 | 
					b3ab9fff9b | ||
| 
						 | 
					14718b93a4 | ||
| 
						 | 
					69450e27ad | ||
| 
						 | 
					0cd08aa79d | ||
| 
						 | 
					1fa94e1b08 | ||
| 
						 | 
					76d9893866 | ||
| 
						 | 
					c3f8982c62 | ||
| 
						 | 
					99eba2f8ba | ||
| 
						 | 
					69509f6502 | ||
| 
						 | 
					c3187fdbe1 | ||
| 
						 | 
					42228ea955 | ||
| 
						 | 
					e5f57ea743 | ||
| 
						 | 
					3b398f7a9a | ||
| 
						 | 
					096add7551 | ||
| 
						 | 
					334e0666b7 | ||
| 
						 | 
					98c81749c8 | ||
| 
						 | 
					5dcf720bb5 | ||
| 
						 | 
					9c0c0255f6 | ||
| 
						 | 
					68c15bd605 | ||
| 
						 | 
					9a2f32795f | ||
| 
						 | 
					7aa6cf4c6b | ||
| 
						 | 
					dfda2adf0d | ||
| 
						 | 
					c0a1c34012 | ||
| 
						 | 
					3c6adc1ff4 | ||
| 
						 | 
					e511d33a7c | ||
| 
						 | 
					c35969d677 | ||
| 
						 | 
					27afb8f0a7 | ||
| 
						 | 
					327ab81436 | ||
| 
						 | 
					db7178495f | ||
| 
						 | 
					979186e71d | ||
| 
						 | 
					f05e0d956b | ||
| 
						 | 
					b22aa5d699 | ||
| 
						 | 
					3e6a2adaaf | ||
| 
						 | 
					8f5537aaaa | ||
| 
						 | 
					a15d4a156b | ||
| 
						 | 
					6a47571d17 | ||
| 
						 | 
					7479dc74ed | ||
| 
						 | 
					28da1a724a | ||
| 
						 | 
					f529eadbec | ||
| 
						 | 
					5dc3cd3a2f | ||
| 
						 | 
					3039a445f0 | ||
| 
						 | 
					82797fd395 | ||
| 
						 | 
					a0885ab7d0 | ||
| 
						 | 
					8eaf1303a3 | ||
| 
						 | 
					20cbe72985 | ||
| 
						 | 
					071ad6b767 | ||
| 
						 | 
					0619e49eac | ||
| 
						 | 
					b8848d8580 | ||
| 
						 | 
					aface1f8be | ||
| 
						 | 
					ae87728770 | ||
| 
						 | 
					28c8ba70c1 | ||
| 
						 | 
					486324ecab | ||
| 
						 | 
					6892ac13e8 | ||
| 
						 | 
					340ad093a6 | ||
| 
						 | 
					0fe09cd1e4 | ||
| 
						 | 
					da4702851f | ||
| 
						 | 
					09fba72d58 | ||
| 
						 | 
					d17c90edf7 | ||
| 
						 | 
					7966592fae | ||
| 
						 | 
					6efe4e1753 | ||
| 
						 | 
					536c4d45c1 | ||
| 
						 | 
					a02f88fe7c | ||
| 
						 | 
					d9be6ab806 | ||
| 
						 | 
					290598429a | ||
| 
						 | 
					92e72959c3 | ||
| 
						 | 
					776f014dbe | ||
| 
						 | 
					c01bc784b9 | ||
| 
						 | 
					abcd86a294 | ||
| 
						 | 
					451f83ba51 | ||
| 
						 | 
					b439f40fe2 | ||
| 
						 | 
					968166b06d | ||
| 
						 | 
					88293909f4 | ||
| 
						 | 
					9b6c48631d | ||
| 
						 | 
					0ed98cbfac | ||
| 
						 | 
					7dde7cc743 | ||
| 
						 | 
					755627f12d | ||
| 
						 | 
					f8004d7096 | ||
| 
						 | 
					0418f51ef2 | ||
| 
						 | 
					054e0af071 | ||
| 
						 | 
					907c3374c3 | ||
| 
						 | 
					b578240993 | ||
| 
						 | 
					f83ee97439 | ||
| 
						 | 
					19aea85184 | ||
| 
						 | 
					1ba0a117e7 | ||
| 
						 | 
					b510b9d337 | ||
| 
						 | 
					b608e11965 | ||
| 
						 | 
					e68b3a2f32 | ||
| 
						 | 
					f7b119ffe1 | ||
| 
						 | 
					a4cec95db1 | ||
| 
						 | 
					84c4fa197b | ||
| 
						 | 
					eac722cf59 | ||
| 
						 | 
					7439a326a6 | ||
| 
						 | 
					5ca1c0747f | ||
| 
						 | 
					466ca38dfa | ||
| 
						 | 
					93b0839036 | ||
| 
						 | 
					e068cbc103 | ||
| 
						 | 
					5c809e5fbf | ||
| 
						 | 
					3933bf49cf | ||
| 
						 | 
					7065ba4857 | ||
| 
						 | 
					ebff83018e | ||
| 
						 | 
					9ce9167e3c | ||
| 
						 | 
					993eff1d3d | ||
| 
						 | 
					7be983ec00 | ||
| 
						 | 
					18e8d6ce06 | ||
| 
						 | 
					b7ba0d4327 | ||
| 
						 | 
					825201f4f2 | ||
| 
						 | 
					9a05c68ce7 | ||
| 
						 | 
					d8dccf2500 | ||
| 
						 | 
					b416aa640f | ||
| 
						 | 
					4ebf594b3b | ||
| 
						 | 
					8a83024962 | ||
| 
						 | 
					bdc1136b96 | ||
| 
						 | 
					da78dea98f | ||
| 
						 | 
					dcf8cb14e2 | ||
| 
						 | 
					38912859e1 | ||
| 
						 | 
					b83d93abc2 | ||
| 
						 | 
					36f843bc6e | ||
| 
						 | 
					15c87e02e9 | ||
| 
						 | 
					00923eac7c | ||
| 
						 | 
					a72ac8294c | ||
| 
						 | 
					4f03bf754d | ||
| 
						 | 
					78b3ec4b10 | ||
| 
						 | 
					ef1a514785 | ||
| 
						 | 
					6635876e7e | ||
| 
						 | 
					5645f90abe | ||
| 
						 | 
					b96cd4d18b | ||
| 
						 | 
					ad8a2e2cb9 | ||
| 
						 | 
					fa438e5113 | ||
| 
						 | 
					8641494809 | ||
| 
						 | 
					f4a23af5d6 | ||
| 
						 | 
					5449e90b34 | ||
| 
						 | 
					1cd664ad85 | ||
| 
						 | 
					e680022b1f | ||
| 
						 | 
					67c2ce2174 | ||
| 
						 | 
					596e700b60 | ||
| 
						 | 
					4a53b6e538 | ||
| 
						 | 
					687f4bb3bb | ||
| 
						 | 
					473799cb62 | ||
| 
						 | 
					ce0536cdfa | ||
| 
						 | 
					3dc22a9fd5 | ||
| 
						 | 
					f54b655606 | ||
| 
						 | 
					d2e868ea2b | ||
| 
						 | 
					3fc649359a | ||
| 
						 | 
					1512ac11da | ||
| 
						 | 
					5039cc7bb2 | ||
| 
						 | 
					5360a7b4ce | ||
| 
						 | 
					2957a31f40 | ||
| 
						 | 
					8c11df52bf | ||
| 
						 | 
					2b7ffcd48f | ||
| 
						 | 
					7980a9033e | ||
| 
						 | 
					125ddfa513 | ||
| 
						 | 
					636e929607 | ||
| 
						 | 
					22c792dc46 | ||
| 
						 | 
					95af1815c8 | ||
| 
						 | 
					d707c5ac95 | ||
| 
						 | 
					5c9192e5e6 | ||
| 
						 | 
					72b5584042 | ||
| 
						 | 
					f9045b5352 | ||
| 
						 | 
					f87fe92bc8 | ||
| 
						 | 
					669d8e64ab | ||
| 
						 | 
					9447aa38be | ||
| 
						 | 
					a781c3eb4d | ||
| 
						 | 
					c0b1308dfd | ||
| 
						 | 
					2d9dd6704a | ||
| 
						 | 
					94dba70bbe | ||
| 
						 | 
					022ec20e75 | ||
| 
						 | 
					41f69405d8 | ||
| 
						 | 
					5741e22e29 | ||
| 
						 | 
					8e242eea54 | ||
| 
						 | 
					703065a0a5 | ||
| 
						 | 
					291aa42fe1 | ||
| 
						 | 
					8fc3496cc9 | ||
| 
						 | 
					e807a462a1 | ||
| 
						 | 
					18790a90ae | ||
| 
						 | 
					21afc70261 | ||
| 
						 | 
					7bb74af478 | ||
| 
						 | 
					894269aa06 | ||
| 
						 | 
					8b16da9695 | ||
| 
						 | 
					f783ec6269 | ||
| 
						 | 
					22c9734874 | ||
| 
						 | 
					a17d0e428f | ||
| 
						 | 
					bb57f0bcc7 | ||
| 
						 | 
					b1aefbfe85 | ||
| 
						 | 
					061288f5a7 | ||
| 
						 | 
					5a53474536 | ||
| 
						 | 
					18d0fff8da | ||
| 
						 | 
					0ac2145740 | ||
| 
						 | 
					bc8787ded6 | ||
| 
						 | 
					69d21daaa3 | ||
| 
						 | 
					5651ef606d | ||
| 
						 | 
					b831b31382 | ||
| 
						 | 
					2fd5cc056c | ||
| 
						 | 
					82dbdf7dfc | ||
| 
						 | 
					eb9903cd10 | ||
| 
						 | 
					227e98d6d7 | ||
| 
						 | 
					35476063b7 | ||
| 
						 | 
					8557bb2136 | ||
| 
						 | 
					c0c7818d5d | ||
| 
						 | 
					ceeadd6a33 | ||
| 
						 | 
					1a2545fdea | ||
| 
						 | 
					c5e9a74c88 | ||
| 
						 | 
					d7972a7b86 | ||
| 
						 | 
					7dd4c67304 | ||
| 
						 | 
					e113780fd1 | ||
| 
						 | 
					e32ae6c191 | ||
| 
						 | 
					bcaceff378 | ||
| 
						 | 
					d7b405c6f8 | ||
| 
						 | 
					edf8cf4dc6 | ||
| 
						 | 
					dfcc8e9822 | ||
| 
						 | 
					016e96e6f8 | ||
| 
						 | 
					e7ce03c418 | ||
| 
						 | 
					3d392dd81d | ||
| 
						 | 
					42d810db7f | ||
| 
						 | 
					18571e8351 | ||
| 
						 | 
					dda1649ab7 | ||
| 
						 | 
					c82e0df071 | ||
| 
						 | 
					06b7ea5a6e | ||
| 
						 | 
					c49fcb9ec9 | ||
| 
						 | 
					0e44d6d214 | ||
| 
						 | 
					6adad7fbf5 | ||
| 
						 | 
					de6ed7b615 | ||
| 
						 | 
					07dcb4dbb1 | ||
| 
						 | 
					e99896eadc | ||
| 
						 | 
					489701afcb | ||
| 
						 | 
					55e576cc57 | ||
| 
						 | 
					6bd8ec9545 | ||
| 
						 | 
					5cd8d86eef | ||
| 
						 | 
					74d0acdaec | ||
| 
						 | 
					0288a1974b | ||
| 
						 | 
					6efd8782fe | ||
| 
						 | 
					8bab9d5d60 | ||
| 
						 | 
					6ef1dfd8be | ||
| 
						 | 
					7e58648743 | ||
| 
						 | 
					0f0c3e616d | ||
| 
						 | 
					c7ce65ea4c | ||
| 
						 | 
					c36247b609 | ||
| 
						 | 
					15296e43a4 | ||
| 
						 | 
					f2929230a2 | ||
| 
						 | 
					bf252b8061 | ||
| 
						 | 
					9e2bf2af7e | ||
| 
						 | 
					245f2654f0 | ||
| 
						 | 
					67ca298a72 | ||
| 
						 | 
					67d4dbf91a | ||
| 
						 | 
					b344269140 | ||
| 
						 | 
					bb547610f2 | ||
| 
						 | 
					1e1f007bb7 | ||
| 
						 | 
					c40d858f02 | ||
| 
						 | 
					3d564d85fd | ||
| 
						 | 
					02cea40ffa | ||
| 
						 | 
					e502d336db | ||
| 
						 | 
					807cb99f6d | ||
| 
						 | 
					8b6879a782 | ||
| 
						 | 
					7ca0362f23 | ||
| 
						 | 
					56c7bd242a | ||
| 
						 | 
					5c6112415a | ||
| 
						 | 
					bf6a0c9fc4 | ||
| 
						 | 
					d54b937ab6 | ||
| 
						 | 
					7c23c32e44 | ||
| 
						 | 
					4e21d24b5f | ||
| 
						 | 
					ad6fb85fda | ||
| 
						 | 
					5dc39a5d24 | ||
| 
						 | 
					3597f687de | ||
| 
						 | 
					8811506adf | ||
| 
						 | 
					11dec6fc0f | ||
| 
						 | 
					59c4c8233f | ||
| 
						 | 
					9da79d2d81 | ||
| 
						 | 
					246b474a25 | ||
| 
						 | 
					27e8a3a1b5 | ||
| 
						 | 
					745797b596 | ||
| 
						 | 
					940e9e037e | ||
| 
						 | 
					512c0079a9 | ||
| 
						 | 
					645c29f853 | ||
| 
						 | 
					e55945674d | ||
| 
						 | 
					7ac88536dd | ||
| 
						 | 
					230b9fc9e6 | ||
| 
						 | 
					27ca782cac | ||
| 
						 | 
					a136a00a2f | ||
| 
						 | 
					637ec35d6a | ||
| 
						 | 
					4b55df1cb4 | ||
| 
						 | 
					b9309268ba | ||
| 
						 | 
					8fa89baf54 | ||
| 
						 | 
					8374a5e579 | ||
| 
						 | 
					525233e10b | ||
| 
						 | 
					eadda6a967 | ||
| 
						 | 
					3d6590af89 | ||
| 
						 | 
					28d933d5d6 | ||
| 
						 | 
					c1dc42a094 | ||
| 
						 | 
					6384ff3ee7 | ||
| 
						 | 
					a118594c8b | ||
| 
						 | 
					93c6105442 | ||
| 
						 | 
					ced4a75a1a | ||
| 
						 | 
					57fecdc09e | ||
| 
						 | 
					cd491bb6e0 | ||
| 
						 | 
					f16ad8f71d | ||
| 
						 | 
					e340685a99 | ||
| 
						 | 
					df89a8771c | ||
| 
						 | 
					bdcf266e45 | ||
| 
						 | 
					edf41b06fd | ||
| 
						 | 
					38960a08d6 | ||
| 
						 | 
					fbda7aab23 | ||
| 
						 | 
					c575aa0640 | ||
| 
						 | 
					583f6b1ba2 | ||
| 
						 | 
					bb55ecc101 | ||
| 
						 | 
					4421acef34 | ||
| 
						 | 
					4c9418f59a | ||
| 
						 | 
					219923bd63 | ||
| 
						 | 
					7551782a25 | ||
| 
						 | 
					5c836604c0 | ||
| 
						 | 
					eff24a8726 | ||
| 
						 | 
					72df6e52cd | ||
| 
						 | 
					e235a45abb | ||
| 
						 | 
					d20c11e401 | ||
| 
						 | 
					693b889fdd | ||
| 
						 | 
					671f48dc10 | ||
| 
						 | 
					7b1708f0bc | ||
| 
						 | 
					f34a9b4346 | ||
| 
						 | 
					1e6d03246b | ||
| 
						 | 
					cdde57fcf2 | ||
| 
						 | 
					c0a61ac1ee | ||
| 
						 | 
					9c97c0a906 | ||
| 
						 | 
					8cacab196d | ||
| 
						 | 
					b14bedbe29 | ||
| 
						 | 
					6bc66d8b96 | ||
| 
						 | 
					23f381f381 | ||
| 
						 | 
					51ad423eca | ||
| 
						 | 
					72a8fef989 | ||
| 
						 | 
					02f41ee513 | ||
| 
						 | 
					9410594486 | ||
| 
						 | 
					1c6223cc11 | ||
| 
						 | 
					82d6a5387f | ||
| 
						 | 
					5165e65021 | ||
| 
						 | 
					1942742d73 | ||
| 
						 | 
					b7760bb052 | ||
| 
						 | 
					2470055d90 | ||
| 
						 | 
					62be2a2eec | ||
| 
						 | 
					b1e062945e | ||
| 
						 | 
					3db4a8c312 | ||
| 
						 | 
					db8e1b0edf | ||
| 
						 | 
					71c3f58c99 | ||
| 
						 | 
					7c05b1788e | ||
| 
						 | 
					77c5b86acc | ||
| 
						 | 
					bc6426313e | ||
| 
						 | 
					8bef7ff4c5 | ||
| 
						 | 
					a2db6ddea5 | ||
| 
						 | 
					f9f500c194 | ||
| 
						 | 
					6ad1e3e17e | ||
| 
						 | 
					e097a841d2 | ||
| 
						 | 
					fa95a17af5 | ||
| 
						 | 
					b961665985 | ||
| 
						 | 
					8af35bc6bb | ||
| 
						 | 
					9b75287a52 | ||
| 
						 | 
					84d5316aa7 | ||
| 
						 | 
					89acb70091 | ||
| 
						 | 
					66165a6dea | ||
| 
						 | 
					84dcf9925b | ||
| 
						 | 
					ee1d7eb61f | ||
| 
						 | 
					e260f92988 | ||
| 
						 | 
					74788ccf8e | ||
| 
						 | 
					0da5c07942 | ||
| 
						 | 
					e8cd5a0511 | ||
| 
						 | 
					5ebbab6f35 | ||
| 
						 | 
					84dd194afd | ||
| 
						 | 
					e1ad1a4cb6 | ||
| 
						 | 
					9de43dac95 | ||
| 
						 | 
					47f121ee4c | ||
| 
						 | 
					d8b699c869 | ||
| 
						 | 
					a7855e8c98 | ||
| 
						 | 
					8dcb48254a | ||
| 
						 | 
					f6b7467d75 | ||
| 
						 | 
					9d1d162cc8 | ||
| 
						 | 
					4ee29b3266 | ||
| 
						 | 
					cbb0594e6b | ||
| 
						 | 
					8aeebdbc99 | ||
| 
						 | 
					c7ef258494 | ||
| 
						 | 
					4fec7c82ab | ||
| 
						 | 
					9a952c889f | ||
| 
						 | 
					8da7806ee9 | ||
| 
						 | 
					aed61f6251 | ||
| 
						 | 
					d065d6d98f | ||
| 
						 | 
					ab20a23f2b | ||
| 
						 | 
					e1c57b6fbe | ||
| 
						 | 
					371c26251c | ||
| 
						 | 
					645d198bee | ||
| 
						 | 
					ab1a999df4 | ||
| 
						 | 
					42f2bf05bf | ||
| 
						 | 
					5e55d3d7c7 | ||
| 
						 | 
					1288369865 | ||
| 
						 | 
					d86b0d4213 | ||
| 
						 | 
					d91cf598be | ||
| 
						 | 
					9be56aa4a2 | ||
| 
						 | 
					86737454a0 | ||
| 
						 | 
					f0c0caf800 | ||
| 
						 | 
					223a960a06 | ||
| 
						 | 
					f72570386c | ||
| 
						 | 
					56e5491e5c | ||
| 
						 | 
					be1c3e9136 | ||
| 
						 | 
					2d223305eb | ||
| 
						 | 
					48c2dcf50e | ||
| 
						 | 
					fa26c82273 | ||
| 
						 | 
					0763ae38dd | ||
| 
						 | 
					2230ac6c38 | ||
| 
						 | 
					abe1e7f244 | ||
| 
						 | 
					24b03f733f | ||
| 
						 | 
					5a729f92c1 | ||
| 
						 | 
					366793498a | ||
| 
						 | 
					cdda3f74ab | ||
| 
						 | 
					51f4242e9b | ||
| 
						 | 
					d97ebae200 | ||
| 
						 | 
					daa195a7fb | ||
| 
						 | 
					2d5e9bf1bb | ||
| 
						 | 
					402f2ddbd9 | ||
| 
						 | 
					8bf5ed52ea | ||
| 
						 | 
					b850183a1e | ||
| 
						 | 
					f7e13356c4 | ||
| 
						 | 
					55cc3089f9 | ||
| 
						 | 
					a096a09c72 | ||
| 
						 | 
					365e1f2e85 | ||
| 
						 | 
					b9e117cdcf | ||
| 
						 | 
					fc3a03a856 | ||
| 
						 | 
					f6e5a2fb04 | ||
| 
						 | 
					404c35feb5 | ||
| 
						 | 
					b5962c58bb | ||
| 
						 | 
					74da762ae1 | ||
| 
						 | 
					d87c840b76 | ||
| 
						 | 
					afb835398f | ||
| 
						 | 
					c982c78285 | ||
| 
						 | 
					b4cdf8d987 | ||
| 
						 | 
					6925a04088 | ||
| 
						 | 
					a0e534b309 | ||
| 
						 | 
					74d1ca4fa8 | ||
| 
						 | 
					3c896050fb | ||
| 
						 | 
					387500f01a | ||
| 
						 | 
					21c41ed4cb | ||
| 
						 | 
					293ab25634 | ||
| 
						 | 
					3ddc1a1722 | ||
| 
						 | 
					478d081095 | ||
| 
						 | 
					9d4b49bbb5 | ||
| 
						 | 
					4417f81014 | ||
| 
						 | 
					b96f7711e3 | ||
| 
						 | 
					1875a03757 | ||
| 
						 | 
					13336b8ad5 | ||
| 
						 | 
					b17cceaeaf | ||
| 
						 | 
					782a62585e | ||
| 
						 | 
					c5d8d9127b | ||
| 
						 | 
					336dffefe0 | ||
| 
						 | 
					e297d4cced | ||
| 
						 | 
					b052ca5ca2 | ||
| 
						 | 
					68d4d7d10a | ||
| 
						 | 
					a03211c410 | ||
| 
						 | 
					c953ab09db | ||
| 
						 | 
					68645742f7 | ||
| 
						 | 
					1fbb733f7f | ||
| 
						 | 
					6e4b8d58a5 | ||
| 
						 | 
					7af8646470 | ||
| 
						 | 
					945a9da94f | ||
| 
						 | 
					2477752fa4 | ||
| 
						 | 
					240d3c482b | ||
| 
						 | 
					91229a1dbd | ||
| 
						 | 
					4f9b3259d5 | ||
| 
						 | 
					3cb1072c29 | ||
| 
						 | 
					12ee8e4db4 | ||
| 
						 | 
					95e98323c5 | ||
| 
						 | 
					7431d56166 | ||
| 
						 | 
					222c16c5b8 | ||
| 
						 | 
					4e83e80962 | ||
| 
						 | 
					4fdbe578cc | ||
| 
						 | 
					c5cad865d7 | ||
| 
						 | 
					ae5fe9225f | ||
| 
						 | 
					327b9051c8 | ||
| 
						 | 
					8151c24cf5 | ||
| 
						 | 
					ee659095c2 | ||
| 
						 | 
					8c35fe1062 | ||
| 
						 | 
					9ca6a1031c | ||
| 
						 | 
					59458f6444 | ||
| 
						 | 
					e8939aada4 | ||
| 
						 | 
					17bb3dce26 | ||
| 
						 | 
					495024d6fe | ||
| 
						 | 
					902b33d25d | ||
| 
						 | 
					ac732e2e7b | ||
| 
						 | 
					d08ffd6c8b | ||
| 
						 | 
					530ff7471d | ||
| 
						 | 
					79833deeaf | ||
| 
						 | 
					405e9e7c68 | ||
| 
						 | 
					5f13ee7c19 | ||
| 
						 | 
					d9f02aecdf | ||
| 
						 | 
					bcb23d9a15 | ||
| 
						 | 
					d027450502 | ||
| 
						 | 
					63ad1290f4 | ||
| 
						 | 
					7c7cb61d2f | ||
| 
						 | 
					68b165e244 | ||
| 
						 | 
					fe1b6812f1 | ||
| 
						 | 
					378ff39e5e | ||
| 
						 | 
					e47cb91223 | ||
| 
						 | 
					d62fb16a58 | ||
| 
						 | 
					235efcb2d4 | ||
| 
						 | 
					a6ada129e8 | ||
| 
						 | 
					a681576d6c | ||
| 
						 | 
					fdc234ed3b | ||
| 
						 | 
					e2ceb77501 | ||
| 
						 | 
					11c28357a1 | ||
| 
						 | 
					f215405beb | ||
| 
						 | 
					ba2a0600dc | ||
| 
						 | 
					ab53165b34 | ||
| 
						 | 
					a30723c3d4 | ||
| 
						 | 
					d64b4fbc26 | ||
| 
						 | 
					73131735fa | ||
| 
						 | 
					5e0bea9d1c | ||
| 
						 | 
					48afc54af6 | ||
| 
						 | 
					d066dd2b44 | ||
| 
						 | 
					0bf7de9d43 | ||
| 
						 | 
					267006782f | ||
| 
						 | 
					a9de745e51 | ||
| 
						 | 
					ca1f3c600d | ||
| 
						 | 
					743353a0ed | ||
| 
						 | 
					f7c10ef9e9 | ||
| 
						 | 
					ecb44711d1 | ||
| 
						 | 
					603b747ac5 | ||
| 
						 | 
					0f2f776e6a | ||
| 
						 | 
					1308f119a6 | ||
| 
						 | 
					51d684820f | ||
| 
						 | 
					023d76a3e7 | ||
| 
						 | 
					4d34d9ae2b | ||
| 
						 | 
					c83c827484 | ||
| 
						 | 
					b8b880a91d | ||
| 
						 | 
					bb2f21a22e | ||
| 
						 | 
					b3587d4cde | ||
| 
						 | 
					39ffe45f3c | ||
| 
						 | 
					d36e592afb | ||
| 
						 | 
					74fb697fa6 | ||
| 
						 | 
					512a52e88d | ||
| 
						 | 
					41fc6c20a0 | ||
| 
						 | 
					28881cb391 | ||
| 
						 | 
					a16b710d22 | ||
| 
						 | 
					a3d4c7599b | ||
| 
						 | 
					6f16928215 | ||
| 
						 | 
					ff3c2fdc59 | ||
| 
						 | 
					57edfe8751 | ||
| 
						 | 
					dcc0ee3679 | ||
| 
						 | 
					f7a16762b4 | ||
| 
						 | 
					375835a950 | ||
| 
						 | 
					4481386a3d | ||
| 
						 | 
					8b76d4007e | ||
| 
						 | 
					4f30118b37 | ||
| 
						 | 
					c5b746543b | ||
| 
						 | 
					11d936331d | ||
| 
						 | 
					4f619de675 | ||
| 
						 | 
					80f2836cb8 | ||
| 
						 | 
					3709aa7555 | ||
| 
						 | 
					7c9d9ee048 | ||
| 
						 | 
					e4335577ca | ||
| 
						 | 
					66c2eb0414 | ||
| 
						 | 
					f82e4ee923 | ||
| 
						 | 
					b62ee33318 | ||
| 
						 | 
					8596a9826f | ||
| 
						 | 
					3f2fb1fa58 | ||
| 
						 | 
					5f39938a19 | ||
| 
						 | 
					d964ebd4c1 | ||
| 
						 | 
					9458963311 | ||
| 
						 | 
					44690b1066 | ||
| 
						 | 
					c41028cdc7 | ||
| 
						 | 
					64c62c16fb | ||
| 
						 | 
					afef4f05fe | ||
| 
						 | 
					fc0f290c85 | ||
| 
						 | 
					81d70ee325 | ||
| 
						 | 
					6dc7a4471d | ||
| 
						 | 
					fcb8bd00b6 | ||
| 
						 | 
					05c3f2a30d | ||
| 
						 | 
					25996ce180 | ||
| 
						 | 
					3729bddb2a | ||
| 
						 | 
					4136428db3 | ||
| 
						 | 
					31c6faf3c8 | ||
| 
						 | 
					5c1ae40a9c | ||
| 
						 | 
					4c6d0f7fa0 | ||
| 
						 | 
					40b60fe5d4 | ||
| 
						 | 
					eed357abb4 | ||
| 
						 | 
					8f541602c1 | ||
| 
						 | 
					668f4b77f3 | ||
| 
						 | 
					303965fbb8 | ||
| 
						 | 
					792aed242d | ||
| 
						 | 
					dc5654b941 | ||
| 
						 | 
					e51e2425cc | ||
| 
						 | 
					95c6b9b55d | ||
| 
						 | 
					ea25ead19d | ||
| 
						 | 
					24100ec3b0 | ||
| 
						 | 
					32437fbf8b | ||
| 
						 | 
					5219a86a41 | ||
| 
						 | 
					e12dc5d894 | ||
| 
						 | 
					75315406bb | ||
| 
						 | 
					ea42fe638a | ||
| 
						 | 
					744211cec0 | ||
| 
						 | 
					1a4321d7d0 | ||
| 
						 | 
					b943441901 | ||
| 
						 | 
					0505b82384 | ||
| 
						 | 
					c9fb5721cd | ||
| 
						 | 
					386a7ca442 | ||
| 
						 | 
					e929d5d819 | ||
| 
						 | 
					94614ae4c3 | ||
| 
						 | 
					1223c99e0f | ||
| 
						 | 
					1ff5ea0a6e | ||
| 
						 | 
					9d2691d1d2 | ||
| 
						 | 
					e4ef2c68bb | ||
| 
						 | 
					7fffafdfd4 | ||
| 
						 | 
					5896288edd | ||
| 
						 | 
					c4135fad2b | ||
| 
						 | 
					1f34214fb3 | ||
| 
						 | 
					f899af0eef | ||
| 
						 | 
					9f0c8bcae7 | ||
| 
						 | 
					2bc36a6cde | ||
| 
						 | 
					ee10fe3d2c | ||
| 
						 | 
					a424e867f9 | ||
| 
						 | 
					f52b40396a | ||
| 
						 | 
					cd2ab70a58 | ||
| 
						 | 
					a5d1941d28 | ||
| 
						 | 
					65a3783dd2 | ||
| 
						 | 
					b9b5c2a3bc | ||
| 
						 | 
					12c618642e | ||
| 
						 | 
					6ebc93c995 | ||
| 
						 | 
					6d4e29c851 | ||
| 
						 | 
					b3979e2fda | ||
| 
						 | 
					983c32bf75 | ||
| 
						 | 
					9e3614066a | ||
| 
						 | 
					c7ad6b1b50 | ||
| 
						 | 
					676dcf7fbb | ||
| 
						 | 
					50d725330c | ||
| 
						 | 
					2886dd1dae | ||
| 
						 | 
					40424ac38b | ||
| 
						 | 
					a4d3865394 | ||
| 
						 | 
					0ac99e8d42 | ||
| 
						 | 
					bdce1c464a | ||
| 
						 | 
					475d75c16a | ||
| 
						 | 
					32fd1897d0 | ||
| 
						 | 
					39e6a28730 | ||
| 
						 | 
					3852e119aa | ||
| 
						 | 
					f19fd7c166 | ||
| 
						 | 
					100fddcee1 | ||
| 
						 | 
					99fa86a67e | ||
| 
						 | 
					6568c29c54 | ||
| 
						 | 
					c54bbc5a04 | ||
| 
						 | 
					92d0c466c2 | ||
| 
						 | 
					020c760976 | ||
| 
						 | 
					cdfd7de221 | ||
| 
						 | 
					3da2e91acf | ||
| 
						 | 
					3948304172 | ||
| 
						 | 
					4a295cd95e | ||
| 
						 | 
					6f7c8b35c5 | ||
| 
						 | 
					e58ba27c00 | ||
| 
						 | 
					0aceddd088 | ||
| 
						 | 
					30ff399218 | ||
| 
						 | 
					a7e63b61eb | ||
| 
						 | 
					b13b0d9311 | ||
| 
						 | 
					d8380dc3e2 | ||
| 
						 | 
					d805e9a8f0 | ||
| 
						 | 
					aa45142728 | ||
| 
						 | 
					09d1aed3a5 | ||
| 
						 | 
					a1f80b5142 | ||
| 
						 | 
					cb1970ebab | ||
| 
						 | 
					d3fbdba77c | ||
| 
						 | 
					632d797c9d | ||
| 
						 | 
					559a2d81c1 | ||
| 
						 | 
					7a5f23c0a5 | ||
| 
						 | 
					84b115f15f | ||
| 
						 | 
					a0d14f4030 | ||
| 
						 | 
					dd6769bfbc | ||
| 
						 | 
					027af5acca | ||
| 
						 | 
					db4b71fc9a | ||
| 
						 | 
					d9e41d42b5 | ||
| 
						 | 
					0ed7d257e1 | ||
| 
						 | 
					335a68396f | ||
| 
						 | 
					84cdf6130f | ||
| 
						 | 
					b0abc4f7bb | ||
| 
						 | 
					ab81d1093d | ||
| 
						 | 
					e4d4e4e002 | ||
| 
						 | 
					cc357a6afa | ||
| 
						 | 
					dfc1c7d358 | ||
| 
						 | 
					7ed8e33622 | ||
| 
						 | 
					474822e83d | ||
| 
						 | 
					fe3942c5b3 | ||
| 
						 | 
					f417fa82a4 | ||
| 
						 | 
					c4b114133a | ||
| 
						 | 
					2f4b0c2b9a | ||
| 
						 | 
					a491650c8b | ||
| 
						 | 
					6805acd74f | ||
| 
						 | 
					95c68c76e1 | ||
| 
						 | 
					60aa383c95 | ||
| 
						 | 
					edc553fa1d | ||
| 
						 | 
					4f2ebad8e0 | ||
| 
						 | 
					1810ef60be | ||
| 
						 | 
					f720a6201b | ||
| 
						 | 
					cfb75b58ca | ||
| 
						 | 
					4fbe983527 | ||
| 
						 | 
					272383cac7 | ||
| 
						 | 
					39380c63cb | ||
| 
						 | 
					ea26f4f7bf | ||
| 
						 | 
					5fd2be3c8e | ||
| 
						 | 
					2320b5c1fe | ||
| 
						 | 
					e5cbdfc67c | ||
| 
						 | 
					894d196b64 | ||
| 
						 | 
					af037649c3 | ||
| 
						 | 
					cfca3e2507 | ||
| 
						 | 
					7a12a0149a | ||
| 
						 | 
					fcdc1bfbd0 | ||
| 
						 | 
					d1d14ba9a0 | ||
| 
						 | 
					0e502f6d5c | ||
| 
						 | 
					d3bac57d6a | ||
| 
						 | 
					bd1b4b8a9f | ||
| 
						 | 
					38d81c394f | ||
| 
						 | 
					72103a4adb | ||
| 
						 | 
					e6bae261c4 | ||
| 
						 | 
					5edb0c0ee7 | ||
| 
						 | 
					442ce403f9 | ||
| 
						 | 
					7398cb44e2 | ||
| 
						 | 
					15d54dfb4c | ||
| 
						 | 
					9087bb9b08 | ||
| 
						 | 
					0c689e85a5 | ||
| 
						 | 
					75f2b0487e | ||
| 
						 | 
					5a1bae8a9c | ||
| 
						 | 
					129bc485bf | ||
| 
						 | 
					69277bbb27 | ||
| 
						 | 
					b8b335f67d | ||
| 
						 | 
					eef7868199 | ||
| 
						 | 
					23aa7ea85f | ||
| 
						 | 
					c1b69fd091 | ||
| 
						 | 
					7ab7efdbc1 | ||
| 
						 | 
					b8ebdc012f | ||
| 
						 | 
					9995d776de | ||
| 
						 | 
					c6f35c9aac | ||
| 
						 | 
					615ea2f573 | ||
| 
						 | 
					311458f41f | ||
| 
						 | 
					b2a381d401 | ||
| 
						 | 
					ffc1b0ff29 | ||
| 
						 | 
					ead2823322 | ||
| 
						 | 
					a7e1920597 | ||
| 
						 | 
					ec6664f590 | ||
| 
						 | 
					8c6ca89da2 | ||
| 
						 | 
					b6e81242e7 | ||
| 
						 | 
					f9ca443667 | ||
| 
						 | 
					394ee61c78 | ||
| 
						 | 
					1d40aa687e | ||
| 
						 | 
					8e3bf0dbca | ||
| 
						 | 
					2031a33edf | ||
| 
						 | 
					fc3d3c76f8 | ||
| 
						 | 
					880bed04f5 | ||
| 
						 | 
					f9c8470b20 | ||
| 
						 | 
					36acc2dddd | ||
| 
						 | 
					a59963b6a0 | ||
| 
						 | 
					cab4bead72 | ||
| 
						 | 
					1a2872c815 | ||
| 
						 | 
					f27e0a141d | ||
| 
						 | 
					52f644c4f1 | ||
| 
						 | 
					06c08a0574 | ||
| 
						 | 
					724e2e6d27 | ||
| 
						 | 
					fd052189ca | ||
| 
						 | 
					044a2b67e1 | ||
| 
						 | 
					7e8b86e9bb | ||
| 
						 | 
					ce80825abb | ||
| 
						 | 
					a99bb3ba6d | ||
| 
						 | 
					3428e9887d | ||
| 
						 | 
					5a8fcac4dc | ||
| 
						 | 
					6a9b14f7d1 | ||
| 
						 | 
					a74d8bd6e8 | ||
| 
						 | 
					3c70f056ed | ||
| 
						 | 
					a546880a65 | ||
| 
						 | 
					238145f27f | ||
| 
						 | 
					0502e6be67 | ||
| 
						 | 
					6a8c6f5a06 | ||
| 
						 | 
					5248475e73 | ||
| 
						 | 
					d6c6b9bdb8 | ||
| 
						 | 
					7bf04d5338 | ||
| 
						 | 
					9668ec789a | ||
| 
						 | 
					ead32fb6b2 | ||
| 
						 | 
					2ee24d29e5 | ||
| 
						 | 
					a560601338 | ||
| 
						 | 
					a51fe70498 | ||
| 
						 | 
					e47aa7653b | ||
| 
						 | 
					58b8dfb929 | ||
| 
						 | 
					462a76dd96 | ||
| 
						 | 
					3758ec79ac | ||
| 
						 | 
					a0311858f9 | ||
| 
						 | 
					f08d500fd6 | ||
| 
						 | 
					df76d57c47 | ||
| 
						 | 
					0ef953a1ea | ||
| 
						 | 
					05cbed6b6c | ||
| 
						 | 
					9225c4ef70 | ||
| 
						 | 
					32136b75cd | ||
| 
						 | 
					1f41d9c5f5 | ||
| 
						 | 
					dc47a2b7d7 | ||
| 
						 | 
					1a539521f2 | ||
| 
						 | 
					2db30a91c6 | ||
| 
						 | 
					b2c07b3110 | ||
| 
						 | 
					90e6bef6d7 | ||
| 
						 | 
					535634daca | ||
| 
						 | 
					575d0da4d1 | ||
| 
						 | 
					ed18092088 | ||
| 
						 | 
					611182910a | ||
| 
						 | 
					9273e9b6ed | ||
| 
						 | 
					77c0cc8b5f | ||
| 
						 | 
					0705a99ea0 | ||
| 
						 | 
					560394fead | ||
| 
						 | 
					86a09b5e7d | ||
| 
						 | 
					32b2026734 | ||
| 
						 | 
					b33f568fdd | ||
| 
						 | 
					6e4bd4f505 | ||
| 
						 | 
					b971e2a42c | ||
| 
						 | 
					3c103506c9 | ||
| 
						 | 
					41d2062342 | ||
| 
						 | 
					672c59f970 | ||
| 
						 | 
					99229df017 | ||
| 
						 | 
					346d80e30b | ||
| 
						 | 
					54b3e511e9 | ||
| 
						 | 
					f25683ebec | ||
| 
						 | 
					d5e781e8e1 | ||
| 
						 | 
					4572c86f0f | ||
| 
						 | 
					8a5c4e384a | ||
| 
						 | 
					4594a3c02b | ||
| 
						 | 
					bd45c1c963 | ||
| 
						 | 
					5f8bb92f36 | ||
| 
						 | 
					3f64cdaff8 | ||
| 
						 | 
					7ac0ea8529 | ||
| 
						 | 
					a3569d7201 | ||
| 
						 | 
					01faffd5bf | ||
| 
						 | 
					26de5be07c | ||
| 
						 | 
					87474d5916 | ||
| 
						 | 
					a366077509 | ||
| 
						 | 
					06163165d9 | ||
| 
						 | 
					ec82c075be | ||
| 
						 | 
					3b0df172a7 | ||
| 
						 | 
					7058dbc3cc | ||
| 
						 | 
					b64de89d2d | ||
| 
						 | 
					8878396339 | ||
| 
						 | 
					da6d5e2e24 | ||
| 
						 | 
					18bb90329a | ||
| 
						 | 
					604bb50adf | ||
| 
						 | 
					e4887c0c56 | ||
| 
						 | 
					3097c4ccae | ||
| 
						 | 
					7959d243f6 | ||
| 
						 | 
					79dd402bc8 | ||
| 
						 | 
					3f3229851b | ||
| 
						 | 
					989628a024 | ||
| 
						 | 
					e0475343f5 | ||
| 
						 | 
					da0a9113d4 | ||
| 
						 | 
					cf7ab97451 | ||
| 
						 | 
					2370575eb5 | ||
| 
						 | 
					825b68e5c4 | ||
| 
						 | 
					851cba0b25 | ||
| 
						 | 
					f0ec168ac7 | ||
| 
						 | 
					fa933952f7 | ||
| 
						 | 
					ba6e23784c | ||
| 
						 | 
					614032198e | ||
| 
						 | 
					3715e6b48a | ||
| 
						 | 
					91e7400bbb | ||
| 
						 | 
					a8d082c7d2 | ||
| 
						 | 
					95756f9716 | ||
| 
						 | 
					a5e1765ce4 | ||
| 
						 | 
					f43c31da1f | ||
| 
						 | 
					95d0adf10e | ||
| 
						 | 
					2e1b245cd8 | ||
| 
						 | 
					5400c47f07 | ||
| 
						 | 
					4153442703 | ||
| 
						 | 
					5e4b721e97 | ||
| 
						 | 
					aca41ac089 | ||
| 
						 | 
					01a883e669 | ||
| 
						 | 
					1e4356f83a | ||
| 
						 | 
					545a6177bb | ||
| 
						 | 
					50d356be2f | ||
| 
						 | 
					9835e800ec | ||
| 
						 | 
					5242362f31 | ||
| 
						 | 
					808e4e8537 | ||
| 
						 | 
					43740a4b2f | ||
| 
						 | 
					f99d672237 | ||
| 
						 | 
					337cb4fb86 | ||
| 
						 | 
					90856a0e7a | ||
| 
						 | 
					ea1c8a3b81 | ||
| 
						 | 
					d55d077a95 | ||
| 
						 | 
					f760a68173 | ||
| 
						 | 
					e66a3523b6 | ||
| 
						 | 
					89d6b85b83 | ||
| 
						 | 
					e02d109864 | ||
| 
						 | 
					743981e9ad | ||
| 
						 | 
					49b8e771b5 | ||
| 
						 | 
					dde672701f | ||
| 
						 | 
					9ca2d8f9f2 | ||
| 
						 | 
					fd786412aa | ||
| 
						 | 
					eb88c7cfba | ||
| 
						 | 
					e1892ff370 | ||
| 
						 | 
					763159a6f6 | ||
| 
						 | 
					6810a6ee58 | ||
| 
						 | 
					65e6c3a9fe | ||
| 
						 | 
					dcbbf988c1 | ||
| 
						 | 
					199cafebcf | ||
| 
						 | 
					555d807d76 | ||
| 
						 | 
					003c6ad11b | ||
| 
						 | 
					dc77d87427 | ||
| 
						 | 
					cfc44cf778 | ||
| 
						 | 
					3df99788ff | ||
| 
						 | 
					3600d2d193 | ||
| 
						 | 
					5f661adb7f | ||
| 
						 | 
					109d072cb6 | ||
| 
						 | 
					0c1c5a0ab8 | ||
| 
						 | 
					e01c66fd65 | ||
| 
						 | 
					9f32fa7f5b | ||
| 
						 | 
					91a3d42919 | ||
| 
						 | 
					3cb6bbf771 | ||
| 
						 | 
					452e281009 | ||
| 
						 | 
					3da948db52 | ||
| 
						 | 
					0c2f77305f | ||
| 
						 | 
					05bcd73f82 | ||
| 
						 | 
					654f5b0478 | ||
| 
						 | 
					886d923e30 | ||
| 
						 | 
					6624cb7a78 | ||
| 
						 | 
					6147134423 | ||
| 
						 | 
					bf6bc7c684 | ||
| 
						 | 
					0b0a7e241b | ||
| 
						 | 
					705d14259c | ||
| 
						 | 
					f1cd35fa16 | ||
| 
						 | 
					6bda4034c6 | ||
| 
						 | 
					b04daca98e | ||
| 
						 | 
					85dcdbfe9e | ||
| 
						 | 
					24340d1d4f | ||
| 
						 | 
					6ae42d07a7 | ||
| 
						 | 
					2ea1e059a8 | ||
| 
						 | 
					b5d6126a2d | ||
| 
						 | 
					dac217c98c | ||
| 
						 | 
					c26c8992ae | ||
| 
						 | 
					b76a5870b3 | ||
| 
						 | 
					7c0f3bb237 | ||
| 
						 | 
					f615d096ca | ||
| 
						 | 
					09132306e4 | ||
| 
						 | 
					f95b07efea | ||
| 
						 | 
					14d976eecb | ||
| 
						 | 
					e1cbad0b6d | ||
| 
						 | 
					e7410b8ed8 | ||
| 
						 | 
					5caf74b930 | ||
| 
						 | 
					b41920990f | ||
| 
						 | 
					709c229cd7 | ||
| 
						 | 
					01fd1b1a2e | ||
| 
						 | 
					96769c52f6 | ||
| 
						 | 
					cf9729c74f | ||
| 
						 | 
					0f2783075f | ||
| 
						 | 
					256f4a6679 | ||
| 
						 | 
					0310f94f0c | ||
| 
						 | 
					085529ed72 | ||
| 
						 | 
					8aabf1b374 | ||
| 
						 | 
					ff39f71ca0 | ||
| 
						 | 
					019474300d | ||
| 
						 | 
					af976b8b3d | ||
| 
						 | 
					f3db1a0c60 | ||
| 
						 | 
					ce28213a5e | ||
| 
						 | 
					f9ce50d2bb | ||
| 
						 | 
					ee16095863 | ||
| 
						 | 
					f0a6e0f3d5 | ||
| 
						 | 
					8c4fb0f688 | ||
| 
						 | 
					baa51853c4 | ||
| 
						 | 
					0e29c6b0ab | ||
| 
						 | 
					1b27eedf6b | ||
| 
						 | 
					8b1f183198 | ||
| 
						 | 
					4766ec55fe | ||
| 
						 | 
					c5edc879b6 | ||
| 
						 | 
					65309e60c4 | ||
| 
						 | 
					5c4623e9f7 | ||
| 
						 | 
					2c0cab9e4d | ||
| 
						 | 
					d0117556d1 | ||
| 
						 | 
					b1ff031b54 | ||
| 
						 | 
					7e8405e68a | ||
| 
						 | 
					c8fd00217d | ||
| 
						 | 
					9d340599a6 | ||
| 
						 | 
					8e094598ca | ||
| 
						 | 
					189122ab84 | ||
| 
						 | 
					4b53f6a9f0 | ||
| 
						 | 
					561e149058 | ||
| 
						 | 
					5975fc8e63 | ||
| 
						 | 
					7316a3aa88 | ||
| 
						 | 
					50be991415 | ||
| 
						 | 
					52e49439a6 | ||
| 
						 | 
					6bcdd3177d | ||
| 
						 | 
					fbe479c43f | ||
| 
						 | 
					83dbd257e1 | ||
| 
						 | 
					b514756272 | ||
| 
						 | 
					7e4c13c43e | ||
| 
						 | 
					79bb0f8222 | ||
| 
						 | 
					43bf6aca67 | ||
| 
						 | 
					03d23aad41 | ||
| 
						 | 
					c398aa60c1 | ||
| 
						 | 
					9666193c67 | ||
| 
						 | 
					3f57020b00 | ||
| 
						 | 
					294e09f275 | ||
| 
						 | 
					ba516387ba | ||
| 
						 | 
					2103e1b470 | ||
| 
						 | 
					7bac439e95 | ||
| 
						 | 
					9136917f00 | ||
| 
						 | 
					6802318784 | ||
| 
						 | 
					428d141bc9 | ||
| 
						 | 
					a86fb33789 | ||
| 
						 | 
					beefb70f75 | ||
| 
						 | 
					3c6a00dc3c | ||
| 
						 | 
					8404409c0d | ||
| 
						 | 
					a5f285b4ce | ||
| 
						 | 
					9d97a294a7 | ||
| 
						 | 
					56448373ae | ||
| 
						 | 
					a71c5946f0 | ||
| 
						 | 
					e7fff6e123 | ||
| 
						 | 
					82e5def7c4 | ||
| 
						 | 
					d97a073d1b | ||
| 
						 | 
					e74f37d6ed | ||
| 
						 | 
					3aa2c297a2 | ||
| 
						 | 
					290db67f09 | ||
| 
						 | 
					4de121142b | ||
| 
						 | 
					3c760e585a | ||
| 
						 | 
					8adb2283b5 | ||
| 
						 | 
					cb61e84868 | ||
| 
						 | 
					8349005c4b | ||
| 
						 | 
					a2847f4f8e | ||
| 
						 | 
					add3ebcb44 | ||
| 
						 | 
					98daad45c7 | ||
| 
						 | 
					1b4b6b0aee | ||
| 
						 | 
					8f94da9daf | ||
| 
						 | 
					357137918d | ||
| 
						 | 
					b0f7b762af | ||
| 
						 | 
					da3ee381f4 | ||
| 
						 | 
					d27d14d2b0 | ||
| 
						 | 
					b0326530d6 | ||
| 
						 | 
					c2bd5be51a | ||
| 
						 | 
					84f5feab70 | ||
| 
						 | 
					4b2c68c3d3 | ||
| 
						 | 
					5391a699a4 | ||
| 
						 | 
					f3f8345e5e | ||
| 
						 | 
					c755411636 | ||
| 
						 | 
					f02759b76b | ||
| 
						 | 
					f34ddce28f | ||
| 
						 | 
					50348c9fe7 | ||
| 
						 | 
					3bfeebf2a1 | ||
| 
						 | 
					dca79ea10e | ||
| 
						 | 
					b7fd4de32f | ||
| 
						 | 
					78d08278ed | ||
| 
						 | 
					d4be052e76 | ||
| 
						 | 
					d674fd0e67 | ||
| 
						 | 
					229b7b36ed | ||
| 
						 | 
					8a8b8db5d1 | ||
| 
						 | 
					d30f83871d | ||
| 
						 | 
					1422f8a93a | ||
| 
						 | 
					f0da75f8e9 | ||
| 
						 | 
					cb8a7a4137 | ||
| 
						 | 
					efd684dc56 | ||
| 
						 | 
					aeac6b5888 | ||
| 
						 | 
					9bb294a023 | ||
| 
						 | 
					1972ca00a4 | ||
| 
						 | 
					6a185a574a | ||
| 
						 | 
					c606931c93 | ||
| 
						 | 
					93cecf0882 | ||
| 
						 | 
					aac3d27c10 | ||
| 
						 | 
					99122efbbc | ||
| 
						 | 
					30e856b9e4 | ||
| 
						 | 
					91fae86e73 | ||
| 
						 | 
					f5c194386c | ||
| 
						 | 
					98f7662185 | ||
| 
						 | 
					62c3720c97 | ||
| 
						 | 
					6b08239199 | ||
| 
						 | 
					f258fc2971 | ||
| 
						 | 
					6b84ae3095 | ||
| 
						 | 
					5dd8c677f1 | ||
| 
						 | 
					1cbcd5355f | ||
| 
						 | 
					9799250f2c | ||
| 
						 | 
					ecb5807ec0 | ||
| 
						 | 
					942986aadc | ||
| 
						 | 
					1f539822ee | ||
| 
						 | 
					fab35b360a | ||
| 
						 | 
					80fcf5b5c0 | ||
| 
						 | 
					b3b2e18c4b | ||
| 
						 | 
					2d233b6358 | ||
| 
						 | 
					83ed36eb08 | ||
| 
						 | 
					89f4032ffc | ||
| 
						 | 
					8c90ec4636 | ||
| 
						 | 
					514141f8c5 | ||
| 
						 | 
					8e3a618619 | ||
| 
						 | 
					6df6af09de | ||
| 
						 | 
					f42655a0fc | ||
| 
						 | 
					f81a7f0faf | ||
| 
						 | 
					2b4c924399 | ||
| 
						 | 
					64517a02b7 | ||
| 
						 | 
					b4befd57a9 | ||
| 
						 | 
					2c742a051e | ||
| 
						 | 
					6595f8f527 | ||
| 
						 | 
					985b36da73 | ||
| 
						 | 
					45a391d69e | ||
| 
						 | 
					15bc18b64f | ||
| 
						 | 
					fb0343cafb | ||
| 
						 | 
					7d9bedf7de | ||
| 
						 | 
					6c99048211 | ||
| 
						 | 
					2638a901d9 | ||
| 
						 | 
					71083fd0f7 | 
							
								
								
									
										13
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					# These are supported funding model platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 | 
				
			||||||
 | 
					patreon: # Replace with a single Patreon username
 | 
				
			||||||
 | 
					open_collective: # Replace with a single Open Collective username
 | 
				
			||||||
 | 
					ko_fi: # Replace with a single Ko-fi username
 | 
				
			||||||
 | 
					tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
				
			||||||
 | 
					community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
				
			||||||
 | 
					liberapay: # Replace with a single Liberapay username
 | 
				
			||||||
 | 
					issuehunt: # Replace with a single IssueHunt username
 | 
				
			||||||
 | 
					otechie: # Replace with a single Otechie username
 | 
				
			||||||
 | 
					custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
 | 
				
			||||||
 | 
					# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
 | 
				
			||||||
							
								
								
									
										16
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,13 +1,6 @@
 | 
				
			|||||||
name: SDL/Ubuntu
 | 
					name: SDL/Ubuntu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on: [push, pull_request]
 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches: 
 | 
					 | 
				
			||||||
      - master
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  pull_request:
 | 
					 | 
				
			||||||
    branches: 
 | 
					 | 
				
			||||||
      - master
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build:
 | 
					  build:
 | 
				
			||||||
@@ -15,8 +8,9 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v1
 | 
					    - uses: actions/checkout@v2
 | 
				
			||||||
    - name: Install dependencies
 | 
					    - name: Install dependencies
 | 
				
			||||||
      run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
 | 
					      run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
 | 
				
			||||||
    - name: Make
 | 
					    - name: Make
 | 
				
			||||||
      run: cd OSBindings/SDL; scons
 | 
					      working-directory: OSBindings/SDL
 | 
				
			||||||
 | 
					      run: scons -j$(nproc --all)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,13 +24,13 @@ namespace Activity {
 | 
				
			|||||||
class Observer {
 | 
					class Observer {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Announces to the receiver that there is an LED of name @c name.
 | 
							/// Announces to the receiver that there is an LED of name @c name.
 | 
				
			||||||
		virtual void register_led(const std::string &name) {}
 | 
							virtual void register_led([[maybe_unused]] const std::string &name) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Announces to the receiver that there is a drive of name @c name.
 | 
							/// Announces to the receiver that there is a drive of name @c name.
 | 
				
			||||||
		virtual void register_drive(const std::string &name) {}
 | 
							virtual void register_drive([[maybe_unused]] const std::string &name) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Informs the receiver of the new state of the LED with name @c name.
 | 
							/// Informs the receiver of the new state of the LED with name @c name.
 | 
				
			||||||
		virtual void set_led_status(const std::string &name, bool lit) {}
 | 
							virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		enum class DriveEvent {
 | 
							enum class DriveEvent {
 | 
				
			||||||
			StepNormal,
 | 
								StepNormal,
 | 
				
			||||||
@@ -39,10 +39,10 @@ class Observer {
 | 
				
			|||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Informs the receiver that the named event just occurred for the drive with name @c name.
 | 
							/// Informs the receiver that the named event just occurred for the drive with name @c name.
 | 
				
			||||||
		virtual void announce_drive_event(const std::string &name, DriveEvent event) {}
 | 
							virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Informs the receiver of the motor-on status of the drive with name @c name.
 | 
							/// Informs the receiver of the motor-on status of the drive with name @c name.
 | 
				
			||||||
		virtual void set_drive_motor_status(const std::string &name, bool is_on) {}
 | 
							virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,20 +11,20 @@
 | 
				
			|||||||
using namespace Analyser::Dynamic;
 | 
					using namespace Analyser::Dynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
float ConfidenceCounter::get_confidence() {
 | 
					float ConfidenceCounter::get_confidence() {
 | 
				
			||||||
	return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
 | 
						return float(hits_) / float(hits_ + misses_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ConfidenceCounter::add_hit() {
 | 
					void ConfidenceCounter::add_hit() {
 | 
				
			||||||
	hits_++;
 | 
						++hits_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ConfidenceCounter::add_miss() {
 | 
					void ConfidenceCounter::add_miss() {
 | 
				
			||||||
	misses_++;
 | 
						++misses_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ConfidenceCounter::add_equivocal() {
 | 
					void ConfidenceCounter::add_equivocal() {
 | 
				
			||||||
	if(hits_ > misses_) {
 | 
						if(hits_ > misses_) {
 | 
				
			||||||
		hits_++;
 | 
							++hits_;
 | 
				
			||||||
		misses_++;
 | 
							++misses_;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ namespace Dynamic {
 | 
				
			|||||||
class ConfidenceCounter: public ConfidenceSource {
 | 
					class ConfidenceCounter: public ConfidenceSource {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/*! @returns The computed probability, based on the history of events. */
 | 
							/*! @returns The computed probability, based on the history of events. */
 | 
				
			||||||
		float get_confidence() override;
 | 
							float get_confidence() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
 | 
							/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
 | 
				
			||||||
		void add_hit();
 | 
							void add_hit();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,11 +32,11 @@ class ConfidenceSummary: public ConfidenceSource {
 | 
				
			|||||||
			const std::vector<float> &weights);
 | 
								const std::vector<float> &weights);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*! @returns The weighted sum of all sources. */
 | 
							/*! @returns The weighted sum of all sources. */
 | 
				
			||||||
		float get_confidence() override;
 | 
							float get_confidence() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::vector<ConfidenceSource *> sources_;
 | 
							const std::vector<ConfidenceSource *> sources_;
 | 
				
			||||||
		std::vector<float> weights_;
 | 
							const std::vector<float> weights_;
 | 
				
			||||||
		float weight_sum_;
 | 
							float weight_sum_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,87 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
//  MultiCRTMachine.cpp
 | 
					 | 
				
			||||||
//  Clock Signal
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//  Created by Thomas Harte on 29/01/2018.
 | 
					 | 
				
			||||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "MultiCRTMachine.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <condition_variable>
 | 
					 | 
				
			||||||
#include <mutex>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace Analyser::Dynamic;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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()) {
 | 
					 | 
				
			||||||
	speaker_ = MultiSpeaker::create(machines);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
 | 
					 | 
				
			||||||
	// Apply a blunt force parallelisation of the machines; each run_for is dispatched
 | 
					 | 
				
			||||||
	// to a separate queue and this queue will block until all are done.
 | 
					 | 
				
			||||||
	volatile std::size_t outstanding_machines;
 | 
					 | 
				
			||||||
	std::condition_variable condition;
 | 
					 | 
				
			||||||
	std::mutex mutex;
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
 | 
					 | 
				
			||||||
		std::lock_guard<std::mutex> lock(mutex);
 | 
					 | 
				
			||||||
		outstanding_machines = machines_.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for(std::size_t index = 0; index < machines_.size(); ++index) {
 | 
					 | 
				
			||||||
			CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
 | 
					 | 
				
			||||||
			queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
 | 
					 | 
				
			||||||
				if(crt_machine) function(crt_machine);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				std::lock_guard<std::mutex> lock(mutex);
 | 
					 | 
				
			||||||
				outstanding_machines--;
 | 
					 | 
				
			||||||
				condition.notify_all();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	std::unique_lock<std::mutex> lock(mutex);
 | 
					 | 
				
			||||||
	condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
 | 
					 | 
				
			||||||
	std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
 | 
					 | 
				
			||||||
	for(const auto &machine: machines_) {
 | 
					 | 
				
			||||||
		CRTMachine::Machine *const crt_machine = machine->crt_machine();
 | 
					 | 
				
			||||||
		if(crt_machine) function(crt_machine);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
					 | 
				
			||||||
	scan_target_ = scan_target;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
 | 
					 | 
				
			||||||
	if(crt_machine) crt_machine->set_scan_target(scan_target);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
 | 
					 | 
				
			||||||
	return speaker_;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MultiCRTMachine::run_for(Time::Seconds duration) {
 | 
					 | 
				
			||||||
	perform_parallel([=](::CRTMachine::Machine *machine) {
 | 
					 | 
				
			||||||
		if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(delegate_) delegate_->multi_crt_did_run_machines();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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_) {
 | 
					 | 
				
			||||||
		speaker_->set_new_front_machine(machines_.front().get());
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -12,6 +12,97 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Dynamic;
 | 
					using namespace Analyser::Dynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MultiStruct: public Reflection::Struct {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
 | 
				
			||||||
 | 
								for(auto device: devices) {
 | 
				
			||||||
 | 
									options_.emplace_back(device->get_options());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void apply() {
 | 
				
			||||||
 | 
								auto options = options_.begin();
 | 
				
			||||||
 | 
								for(auto device: devices_) {
 | 
				
			||||||
 | 
									device->set_options(*options);
 | 
				
			||||||
 | 
									++options;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::vector<std::string> all_keys() const final {
 | 
				
			||||||
 | 
								std::set<std::string> keys;
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									const auto new_keys = options->all_keys();
 | 
				
			||||||
 | 
									keys.insert(new_keys.begin(), new_keys.end());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return std::vector<std::string>(keys.begin(), keys.end());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::vector<std::string> values_for(const std::string &name) const final {
 | 
				
			||||||
 | 
								std::set<std::string> values;
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									const auto new_values = options->values_for(name);
 | 
				
			||||||
 | 
									values.insert(new_values.begin(), new_values.end());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return std::vector<std::string>(values.begin(), values.end());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const std::type_info *type_of(const std::string &name) const final {
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									auto info = options->type_of(name);
 | 
				
			||||||
 | 
									if(info) return info;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							size_t count_of(const std::string &name) const final {
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									auto info = options->type_of(name);
 | 
				
			||||||
 | 
									if(info) return options->count_of(name);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const void *get(const std::string &name) const final {
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									auto value = options->get(name);
 | 
				
			||||||
 | 
									if(value) return value;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void *get(const std::string &name) final {
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									auto value = options->get(name);
 | 
				
			||||||
 | 
									if(value) return value;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set(const std::string &name, const void *value, size_t offset) final {
 | 
				
			||||||
 | 
								const auto safe_type = type_of(name);
 | 
				
			||||||
 | 
								if(!safe_type) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set this property only where the child's type is the same as that
 | 
				
			||||||
 | 
								// which was returned from here for type_of.
 | 
				
			||||||
 | 
								for(auto &options: options_) {
 | 
				
			||||||
 | 
									const auto type = options->type_of(name);
 | 
				
			||||||
 | 
									if(!type) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if(*type == *safe_type) {
 | 
				
			||||||
 | 
										options->set(name, value, offset);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							const std::vector<Configurable::Device *> &devices_;
 | 
				
			||||||
 | 
							std::vector<std::unique_ptr<Reflection::Struct>> options_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
					MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
				
			||||||
	for(const auto &machine: machines) {
 | 
						for(const auto &machine: machines) {
 | 
				
			||||||
		Configurable::Device *device = machine->configurable_device();
 | 
							Configurable::Device *device = machine->configurable_device();
 | 
				
			||||||
@@ -19,46 +110,11 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
 | 
					void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &str) {
 | 
				
			||||||
	std::vector<std::unique_ptr<Configurable::Option>> options;
 | 
						const auto options = dynamic_cast<MultiStruct *>(str.get());
 | 
				
			||||||
 | 
						options->apply();
 | 
				
			||||||
	// Produce the list of unique options.
 | 
					 | 
				
			||||||
	for(const auto &device : devices_) {
 | 
					 | 
				
			||||||
		std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
 | 
					 | 
				
			||||||
		for(auto &option : device_options) {
 | 
					 | 
				
			||||||
			if(std::find(options.begin(), options.end(), option) == options.end()) {
 | 
					 | 
				
			||||||
				options.push_back(std::move(option));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return options;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
 | 
					std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
 | 
				
			||||||
	for(const auto &device : devices_) {
 | 
						return std::make_unique<MultiStruct>(devices_);
 | 
				
			||||||
		device->set_selections(selection_by_option);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
 | 
					 | 
				
			||||||
	Configurable::SelectionSet set;
 | 
					 | 
				
			||||||
	for(const auto &device : devices_) {
 | 
					 | 
				
			||||||
		Configurable::SelectionSet device_set = device->get_accurate_selections();
 | 
					 | 
				
			||||||
		for(auto &selection : device_set) {
 | 
					 | 
				
			||||||
			set.insert(std::move(selection));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return set;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
 | 
					 | 
				
			||||||
	Configurable::SelectionSet set;
 | 
					 | 
				
			||||||
	for(const auto &device : devices_) {
 | 
					 | 
				
			||||||
		Configurable::SelectionSet device_set = device->get_user_friendly_selections();
 | 
					 | 
				
			||||||
		for(auto &selection : device_set) {
 | 
					 | 
				
			||||||
			set.insert(std::move(selection));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return set;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
#define MultiConfigurable_hpp
 | 
					#define MultiConfigurable_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
					#include "../../../../Machines/DynamicMachine.hpp"
 | 
				
			||||||
 | 
					#include "../../../../Configurable/Configurable.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
@@ -28,10 +29,8 @@ class MultiConfigurable: public Configurable::Device {
 | 
				
			|||||||
		MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
							MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Below is the standard Configurable::Device interface; see there for documentation.
 | 
							// Below is the standard Configurable::Device interface; see there for documentation.
 | 
				
			||||||
		std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
 | 
							void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
 | 
				
			||||||
		void set_selections(const Configurable::SelectionSet &selection_by_option) override;
 | 
							std::unique_ptr<Reflection::Struct> get_options() final;
 | 
				
			||||||
		Configurable::SelectionSet get_accurate_selections() override;
 | 
					 | 
				
			||||||
		Configurable::SelectionSet get_user_friendly_selections() override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::vector<Configurable::Device *> devices_;
 | 
							std::vector<Configurable::Device *> devices_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ namespace {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class MultiJoystick: public Inputs::Joystick {
 | 
					class MultiJoystick: public Inputs::Joystick {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
 | 
							MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
 | 
				
			||||||
			for(const auto &machine: machines) {
 | 
								for(const auto &machine: machines) {
 | 
				
			||||||
				const auto &joysticks = machine->get_joysticks();
 | 
									const auto &joysticks = machine->get_joysticks();
 | 
				
			||||||
				if(joysticks.size() >= index) {
 | 
									if(joysticks.size() >= index) {
 | 
				
			||||||
@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::vector<Input> &get_inputs() override {
 | 
							const std::vector<Input> &get_inputs() final {
 | 
				
			||||||
			if(inputs.empty()) {
 | 
								if(inputs.empty()) {
 | 
				
			||||||
				for(const auto &joystick: joysticks_) {
 | 
									for(const auto &joystick: joysticks_) {
 | 
				
			||||||
					std::vector<Input> joystick_inputs = joystick->get_inputs();
 | 
										std::vector<Input> joystick_inputs = joystick->get_inputs();
 | 
				
			||||||
@@ -40,19 +40,19 @@ class MultiJoystick: public Inputs::Joystick {
 | 
				
			|||||||
			return inputs;
 | 
								return inputs;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_input(const Input &digital_input, bool is_active) override {
 | 
							void set_input(const Input &digital_input, bool is_active) final {
 | 
				
			||||||
			for(const auto &joystick: joysticks_) {
 | 
								for(const auto &joystick: joysticks_) {
 | 
				
			||||||
				joystick->set_input(digital_input, is_active);
 | 
									joystick->set_input(digital_input, is_active);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_input(const Input &digital_input, float value) override {
 | 
							void set_input(const Input &digital_input, float value) final {
 | 
				
			||||||
			for(const auto &joystick: joysticks_) {
 | 
								for(const auto &joystick: joysticks_) {
 | 
				
			||||||
				joystick->set_input(digital_input, value);
 | 
									joystick->set_input(digital_input, value);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void reset_all_inputs() override {
 | 
							void reset_all_inputs() final {
 | 
				
			||||||
			for(const auto &joystick: joysticks_) {
 | 
								for(const auto &joystick: joysticks_) {
 | 
				
			||||||
				joystick->reset_all_inputs();
 | 
									joystick->reset_all_inputs();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -67,9 +67,9 @@ class MultiJoystick: public Inputs::Joystick {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
					MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
				
			||||||
	std::size_t total_joysticks = 0;
 | 
						std::size_t total_joysticks = 0;
 | 
				
			||||||
	std::vector<JoystickMachine::Machine *> joystick_machines;
 | 
						std::vector<MachineTypes::JoystickMachine *> joystick_machines;
 | 
				
			||||||
	for(const auto &machine: machines) {
 | 
						for(const auto &machine: machines) {
 | 
				
			||||||
		JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
 | 
							auto joystick_machine = machine->joystick_machine();
 | 
				
			||||||
		if(joystick_machine) {
 | 
							if(joystick_machine) {
 | 
				
			||||||
			joystick_machines.push_back(joystick_machine);
 | 
								joystick_machines.push_back(joystick_machine);
 | 
				
			||||||
			total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
 | 
								total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,12 +23,12 @@ namespace Dynamic {
 | 
				
			|||||||
	Makes a static internal copy of the list of machines; makes no guarantees about the
 | 
						Makes a static internal copy of the list of machines; makes no guarantees about the
 | 
				
			||||||
	order of delivered messages.
 | 
						order of delivered messages.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class MultiJoystickMachine: public JoystickMachine::Machine {
 | 
					class MultiJoystickMachine: public MachineTypes::JoystickMachine {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		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.
 | 
				
			||||||
		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
 | 
							const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
							std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,12 +10,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
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_) {
 | 
					 | 
				
			||||||
	for(const auto &machine: machines) {
 | 
						for(const auto &machine: machines) {
 | 
				
			||||||
		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
 | 
							auto keyboard_machine = machine->keyboard_machine();
 | 
				
			||||||
		if(keyboard_machine) machines_.push_back(keyboard_machine);
 | 
							if(keyboard_machine) machines_.push_back(keyboard_machine);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						keyboard_ = std::make_unique<MultiKeyboard>(machines_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiKeyboardMachine::clear_all_keys() {
 | 
					void MultiKeyboardMachine::clear_all_keys() {
 | 
				
			||||||
@@ -36,11 +36,19 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
 | 
					bool MultiKeyboardMachine::can_type(char c) const {
 | 
				
			||||||
	return keyboard_;
 | 
						bool can_type = true;
 | 
				
			||||||
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
 | 
							can_type &= machine->can_type(c);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return can_type;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
 | 
					Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
 | 
				
			||||||
 | 
						return *keyboard_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
 | 
				
			||||||
	: machines_(machines) {
 | 
						: machines_(machines) {
 | 
				
			||||||
	for(const auto &machine: machines_) {
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
		observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
 | 
							observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
 | 
				
			||||||
@@ -48,10 +56,12 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardM
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
 | 
					bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
 | 
				
			||||||
 | 
						bool was_consumed = false;
 | 
				
			||||||
	for(const auto &machine: machines_) {
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
		machine->get_keyboard().set_key_pressed(key, value, is_pressed);
 | 
							was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return was_consumed;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
 | 
					void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
 | 
				
			||||||
@@ -60,10 +70,10 @@ void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
 | 
					const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() const {
 | 
				
			||||||
	return observed_keys_;
 | 
						return observed_keys_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
 | 
					bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() const {
 | 
				
			||||||
	return is_exclusive_;
 | 
						return is_exclusive_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,34 +24,35 @@ namespace Dynamic {
 | 
				
			|||||||
	Makes a static internal copy of the list of machines; makes no guarantees about the
 | 
						Makes a static internal copy of the list of machines; makes no guarantees about the
 | 
				
			||||||
	order of delivered messages.
 | 
						order of delivered messages.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class MultiKeyboardMachine: public KeyboardMachine::Machine {
 | 
					class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::vector<::KeyboardMachine::Machine *> machines_;
 | 
							std::vector<MachineTypes::KeyboardMachine *> machines_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		class MultiKeyboard: public Inputs::Keyboard {
 | 
							class MultiKeyboard: public Inputs::Keyboard {
 | 
				
			||||||
			public:
 | 
								public:
 | 
				
			||||||
				MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
 | 
									MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				void set_key_pressed(Key key, char value, bool is_pressed) override;
 | 
									bool set_key_pressed(Key key, char value, bool is_pressed) final;
 | 
				
			||||||
				void reset_all_keys() override;
 | 
									void reset_all_keys() final;
 | 
				
			||||||
				const std::set<Key> &observed_keys() override;
 | 
									const std::set<Key> &observed_keys() const final;
 | 
				
			||||||
				bool is_exclusive() override;
 | 
									bool is_exclusive() const final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			private:
 | 
								private:
 | 
				
			||||||
				const std::vector<::KeyboardMachine::Machine *> &machines_;
 | 
									const std::vector<MachineTypes::KeyboardMachine *> &machines_;
 | 
				
			||||||
				std::set<Key> observed_keys_;
 | 
									std::set<Key> observed_keys_;
 | 
				
			||||||
				bool is_exclusive_ = false;
 | 
									bool is_exclusive_ = false;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		MultiKeyboard keyboard_;
 | 
							std::unique_ptr<MultiKeyboard> keyboard_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
							MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
 | 
							// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
 | 
				
			||||||
		void clear_all_keys() override;
 | 
							void clear_all_keys() final;
 | 
				
			||||||
		void set_key_state(uint16_t key, bool is_pressed) override;
 | 
							void set_key_state(uint16_t key, bool is_pressed) final;
 | 
				
			||||||
		void type_string(const std::string &) override;
 | 
							void type_string(const std::string &) final;
 | 
				
			||||||
		Inputs::Keyboard &get_keyboard() override;
 | 
							bool can_type(char c) const final;
 | 
				
			||||||
 | 
							Inputs::Keyboard &get_keyboard() final;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ using namespace Analyser::Dynamic;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
					MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
				
			||||||
	for(const auto &machine: machines) {
 | 
						for(const auto &machine: machines) {
 | 
				
			||||||
		MediaTarget::Machine *media_target = machine->media_target();
 | 
							auto media_target = machine->media_target();
 | 
				
			||||||
		if(media_target) targets_.push_back(media_target);
 | 
							if(media_target) targets_.push_back(media_target);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,15 +24,15 @@ namespace Dynamic {
 | 
				
			|||||||
	Makes a static internal copy of the list of machines; makes no guarantees about the
 | 
						Makes a static internal copy of the list of machines; makes no guarantees about the
 | 
				
			||||||
	order of delivered messages.
 | 
						order of delivered messages.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
struct MultiMediaTarget: public MediaTarget::Machine {
 | 
					struct MultiMediaTarget: public MachineTypes::MediaTarget {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
							MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
							// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
				
			||||||
		bool insert_media(const Analyser::Static::Media &media) override;
 | 
							bool insert_media(const Analyser::Static::Media &media) final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::vector<MediaTarget::Machine *> targets_;
 | 
							std::vector<MachineTypes::MediaTarget *> targets_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										105
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  MultiProducer.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 29/01/2018.
 | 
				
			||||||
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MultiProducer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <condition_variable>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Analyser::Dynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - MultiInterface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename MachineType>
 | 
				
			||||||
 | 
					void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) {
 | 
				
			||||||
 | 
						// Apply a blunt force parallelisation of the machines; each run_for is dispatched
 | 
				
			||||||
 | 
						// to a separate queue and this queue will block until all are done.
 | 
				
			||||||
 | 
						volatile std::size_t outstanding_machines;
 | 
				
			||||||
 | 
						std::condition_variable condition;
 | 
				
			||||||
 | 
						std::mutex mutex;
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::lock_guard machines_lock(machines_mutex_);
 | 
				
			||||||
 | 
							std::lock_guard lock(mutex);
 | 
				
			||||||
 | 
							outstanding_machines = machines_.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for(std::size_t index = 0; index < machines_.size(); ++index) {
 | 
				
			||||||
 | 
								const auto machine = ::Machine::get<MachineType>(*machines_[index].get());
 | 
				
			||||||
 | 
								queues_[index].enqueue([&mutex, &condition, machine, function, &outstanding_machines]() {
 | 
				
			||||||
 | 
									if(machine) function(machine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									std::lock_guard lock(mutex);
 | 
				
			||||||
 | 
									outstanding_machines--;
 | 
				
			||||||
 | 
									condition.notify_all();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::unique_lock lock(mutex);
 | 
				
			||||||
 | 
						condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename MachineType>
 | 
				
			||||||
 | 
					void MultiInterface<MachineType>::perform_serial(const std::function<void(MachineType *)> &function) {
 | 
				
			||||||
 | 
						std::lock_guard machines_lock(machines_mutex_);
 | 
				
			||||||
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
 | 
							const auto typed_machine = ::Machine::get<MachineType>(*machine.get());
 | 
				
			||||||
 | 
							if(typed_machine) function(typed_machine);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - MultiScanProducer
 | 
				
			||||||
 | 
					void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			||||||
 | 
						scan_target_ = scan_target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::lock_guard machines_lock(machines_mutex_);
 | 
				
			||||||
 | 
						const auto machine = machines_.front()->scan_producer();
 | 
				
			||||||
 | 
						if(machine) machine->set_scan_target(scan_target);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Outputs::Display::ScanStatus MultiScanProducer::get_scan_status() const {
 | 
				
			||||||
 | 
						std::lock_guard machines_lock(machines_mutex_);
 | 
				
			||||||
 | 
						const auto machine = machines_.front()->scan_producer();
 | 
				
			||||||
 | 
						if(machine) return machine->get_scan_status();
 | 
				
			||||||
 | 
						return Outputs::Display::ScanStatus();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiScanProducer::did_change_machine_order() {
 | 
				
			||||||
 | 
						if(scan_target_) scan_target_->will_change_owner();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						perform_serial([](MachineTypes::ScanProducer *machine) {
 | 
				
			||||||
 | 
							machine->set_scan_target(nullptr);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						std::lock_guard machines_lock(machines_mutex_);
 | 
				
			||||||
 | 
						const auto machine = machines_.front()->scan_producer();
 | 
				
			||||||
 | 
						if(machine) machine->set_scan_target(scan_target_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - MultiAudioProducer
 | 
				
			||||||
 | 
					MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
 | 
				
			||||||
 | 
						speaker_ = MultiSpeaker::create(machines);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Outputs::Speaker::Speaker *MultiAudioProducer::get_speaker() {
 | 
				
			||||||
 | 
						return speaker_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiAudioProducer::did_change_machine_order() {
 | 
				
			||||||
 | 
						if(speaker_) {
 | 
				
			||||||
 | 
							speaker_->set_new_front_machine(machines_.front().get());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MARK: - MultiTimedMachine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiTimedMachine::run_for(Time::Seconds duration) {
 | 
				
			||||||
 | 
						perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
 | 
				
			||||||
 | 
							if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(delegate_) delegate_->did_run_machines(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,16 +1,16 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
//  MultiCRTMachine.hpp
 | 
					//  MultiProducer.hpp
 | 
				
			||||||
//  Clock Signal
 | 
					//  Clock Signal
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
//  Created by Thomas Harte on 29/01/2018.
 | 
					//  Created by Thomas Harte on 29/01/2018.
 | 
				
			||||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef MultiCRTMachine_hpp
 | 
					#ifndef MultiProducer_hpp
 | 
				
			||||||
#define MultiCRTMachine_hpp
 | 
					#define MultiProducer_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
 | 
					#include "../../../../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
#include "../../../../Machines/CRTMachine.hpp"
 | 
					#include "../../../../Machines/MachineTypes.hpp"
 | 
				
			||||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
					#include "../../../../Machines/DynamicMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "MultiSpeaker.hpp"
 | 
					#include "MultiSpeaker.hpp"
 | 
				
			||||||
@@ -22,6 +22,91 @@
 | 
				
			|||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Dynamic {
 | 
					namespace Dynamic {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename MachineType> class MultiInterface {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
 | 
				
			||||||
 | 
								machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Performs a parallel for operation across all machines, performing the supplied
 | 
				
			||||||
 | 
								function on each and returning only once all applications have completed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								No guarantees are extended as to which thread operations will occur on.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void perform_parallel(const std::function<void(MachineType *)> &);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Performs a serial for operation across all machines, performing the supplied
 | 
				
			||||||
 | 
								function on each on the calling thread.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void perform_serial(const std::function<void(MachineType *)> &);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected:
 | 
				
			||||||
 | 
							const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
				
			||||||
 | 
							std::recursive_mutex &machines_mutex_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							std::vector<Concurrency::AsyncTaskQueue> queues_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							using MultiInterface::MultiInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Provides a mechanism by which a delegate can be informed each time a call to run_for has
 | 
				
			||||||
 | 
								been received.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							struct Delegate {
 | 
				
			||||||
 | 
								virtual void did_run_machines(MultiTimedMachine *) = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							/// Sets @c delegate as the receiver of delegate messages.
 | 
				
			||||||
 | 
							void set_delegate(Delegate *delegate) {
 | 
				
			||||||
 | 
								delegate_ = delegate;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void run_for(Time::Seconds duration) final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							void run_for(const Cycles) final {}
 | 
				
			||||||
 | 
							Delegate *delegate_ = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							using MultiInterface::MultiInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Informs the MultiScanProducer that the order of machines has changed; it
 | 
				
			||||||
 | 
								uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
 | 
				
			||||||
 | 
								are necessary to bridge the gap between one machine and the next.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void did_change_machine_order();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
 | 
				
			||||||
 | 
							Outputs::Display::ScanStatus get_scan_status() const final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Informs the MultiAudio that the order of machines has changed; it
 | 
				
			||||||
 | 
								uses this as an opportunity to switch speaker delegates as appropriate.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void did_change_machine_order();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Outputs::Speaker::Speaker *get_speaker() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							MultiSpeaker *speaker_ = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	Provides a class that multiplexes the CRT machine interface to multiple machines.
 | 
						Provides a class that multiplexes the CRT machine interface to multiple machines.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,60 +114,9 @@ namespace Dynamic {
 | 
				
			|||||||
	acquiring a supplied mutex. The owner should also call did_change_machine_order()
 | 
						acquiring a supplied mutex. The owner should also call did_change_machine_order()
 | 
				
			||||||
	if the order of machines changes.
 | 
						if the order of machines changes.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class MultiCRTMachine: public CRTMachine::Machine {
 | 
					 | 
				
			||||||
	public:
 | 
					 | 
				
			||||||
		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
 | 
					 | 
				
			||||||
			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
 | 
					 | 
				
			||||||
			are necessary to bridge the gap between one machine and the next.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void did_change_machine_order();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/*!
 | 
					 | 
				
			||||||
			Provides a mechanism by which a delegate can be informed each time a call to run_for has
 | 
					 | 
				
			||||||
			been received.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		struct Delegate {
 | 
					 | 
				
			||||||
			virtual void multi_crt_did_run_machines() = 0;
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
		/// Sets @c delegate as the receiver of delegate messages.
 | 
					 | 
				
			||||||
		void set_delegate(Delegate *delegate) {
 | 
					 | 
				
			||||||
			delegate_ = delegate;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Below is the standard CRTMachine::Machine interface; see there for documentation.
 | 
					 | 
				
			||||||
		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
 | 
					 | 
				
			||||||
		Outputs::Speaker::Speaker *get_speaker() override;
 | 
					 | 
				
			||||||
		void run_for(Time::Seconds duration) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private:
 | 
					 | 
				
			||||||
		void run_for(const Cycles cycles) override {}
 | 
					 | 
				
			||||||
		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
					 | 
				
			||||||
		std::recursive_mutex &machines_mutex_;
 | 
					 | 
				
			||||||
		std::vector<Concurrency::AsyncTaskQueue> queues_;
 | 
					 | 
				
			||||||
		MultiSpeaker *speaker_ = nullptr;
 | 
					 | 
				
			||||||
		Delegate *delegate_ = nullptr;
 | 
					 | 
				
			||||||
		Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/*!
 | 
					 | 
				
			||||||
			Performs a parallel for operation across all machines, performing the supplied
 | 
					 | 
				
			||||||
			function on each and returning only once all applications have completed.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			No guarantees are extended as to which thread operations will occur on.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/*!
 | 
					 | 
				
			||||||
			Performs a serial for operation across all machines, performing the supplied
 | 
					 | 
				
			||||||
			function on each on the calling thread.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* MultiCRTMachine_hpp */
 | 
					#endif /* MultiProducer_hpp */
 | 
				
			||||||
@@ -13,7 +13,7 @@ using namespace Analyser::Dynamic;
 | 
				
			|||||||
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
					MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
				
			||||||
	std::vector<Outputs::Speaker::Speaker *> speakers;
 | 
						std::vector<Outputs::Speaker::Speaker *> speakers;
 | 
				
			||||||
	for(const auto &machine: machines) {
 | 
						for(const auto &machine: machines) {
 | 
				
			||||||
		Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
 | 
							Outputs::Speaker::Speaker *speaker = machine->audio_producer()->get_speaker();
 | 
				
			||||||
		if(speaker) speakers.push_back(speaker);
 | 
							if(speaker) speakers.push_back(speaker);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(speakers.empty()) return nullptr;
 | 
						if(speakers.empty()) return nullptr;
 | 
				
			||||||
@@ -34,43 +34,59 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
 | 
				
			|||||||
		ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
 | 
							ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ideal / static_cast<float>(speakers_.size());
 | 
						return ideal / float(speakers_.size());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
 | 
					void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
 | 
				
			||||||
 | 
						stereo_output_ = stereo;
 | 
				
			||||||
	for(const auto &speaker: speakers_) {
 | 
						for(const auto &speaker: speakers_) {
 | 
				
			||||||
		speaker->set_output_rate(cycles_per_second, buffer_size);
 | 
							speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
 | 
					bool MultiSpeaker::get_is_stereo() {
 | 
				
			||||||
	delegate_ = delegate;
 | 
						// Return as stereo if any subspeaker is stereo.
 | 
				
			||||||
 | 
						for(const auto &speaker: speakers_) {
 | 
				
			||||||
 | 
							if(speaker->get_is_stereo()) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiSpeaker::set_output_volume(float volume) {
 | 
				
			||||||
 | 
						for(const auto &speaker: speakers_) {
 | 
				
			||||||
 | 
							speaker->set_output_volume(volume);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
 | 
					void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
 | 
				
			||||||
	if(!delegate_) return;
 | 
						auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
 | 
				
			||||||
 | 
						if(!delegate) return;
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
 | 
							std::lock_guard lock_guard(front_speaker_mutex_);
 | 
				
			||||||
		if(speaker != front_speaker_) return;
 | 
							if(speaker != front_speaker_) return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	delegate_->speaker_did_complete_samples(this, buffer);
 | 
						did_complete_samples(this, buffer, stereo_output_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
 | 
					void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
 | 
				
			||||||
	if(!delegate_) return;
 | 
						auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
 | 
				
			||||||
 | 
						if(!delegate) return;
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
 | 
							std::lock_guard lock_guard(front_speaker_mutex_);
 | 
				
			||||||
		if(speaker != front_speaker_) return;
 | 
							if(speaker != front_speaker_) return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	delegate_->speaker_did_change_input_clock(this);
 | 
						delegate->speaker_did_change_input_clock(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
 | 
					void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
 | 
							std::lock_guard lock_guard(front_speaker_mutex_);
 | 
				
			||||||
		front_speaker_ = machine->crt_machine()->get_speaker();
 | 
							front_speaker_ = machine->audio_producer()->get_speaker();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(delegate_) {
 | 
						auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
 | 
				
			||||||
		delegate_->speaker_did_change_input_clock(this);
 | 
						if(delegate) {
 | 
				
			||||||
 | 
							delegate->speaker_did_change_input_clock(this);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,18 +39,20 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
 | 
							// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
 | 
				
			||||||
		float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
 | 
							float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
 | 
				
			||||||
		void set_output_rate(float cycles_per_second, int buffer_size) override;
 | 
							void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
 | 
				
			||||||
		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
 | 
							bool get_is_stereo() override;
 | 
				
			||||||
 | 
							void set_output_volume(float) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
 | 
							void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
 | 
				
			||||||
		void speaker_did_change_input_clock(Speaker *speaker) override;
 | 
							void speaker_did_change_input_clock(Speaker *speaker) final;
 | 
				
			||||||
		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
 | 
							MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::vector<Outputs::Speaker::Speaker *> speakers_;
 | 
							std::vector<Outputs::Speaker::Speaker *> speakers_;
 | 
				
			||||||
		Outputs::Speaker::Speaker *front_speaker_ = nullptr;
 | 
							Outputs::Speaker::Speaker *front_speaker_ = nullptr;
 | 
				
			||||||
		Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
 | 
					 | 
				
			||||||
		std::mutex front_speaker_mutex_;
 | 
							std::mutex front_speaker_mutex_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool stereo_output_ = false;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,74 +16,55 @@ using namespace Analyser::Dynamic;
 | 
				
			|||||||
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
 | 
					MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
 | 
				
			||||||
	machines_(std::move(machines)),
 | 
						machines_(std::move(machines)),
 | 
				
			||||||
	configurable_(machines_),
 | 
						configurable_(machines_),
 | 
				
			||||||
	crt_machine_(machines_, machines_mutex_),
 | 
						timed_machine_(machines_, machines_mutex_),
 | 
				
			||||||
	joystick_machine_(machines),
 | 
						scan_producer_(machines_, machines_mutex_),
 | 
				
			||||||
 | 
						audio_producer_(machines_, machines_mutex_),
 | 
				
			||||||
 | 
						joystick_machine_(machines_),
 | 
				
			||||||
	keyboard_machine_(machines_),
 | 
						keyboard_machine_(machines_),
 | 
				
			||||||
	media_target_(machines_) {
 | 
						media_target_(machines_) {
 | 
				
			||||||
	crt_machine_.set_delegate(this);
 | 
						timed_machine_.set_delegate(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Activity::Source *MultiMachine::activity_source() {
 | 
					Activity::Source *MultiMachine::activity_source() {
 | 
				
			||||||
	return nullptr; // TODO
 | 
						return nullptr; // TODO
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MediaTarget::Machine *MultiMachine::media_target() {
 | 
					#define Provider(type, name, member)	\
 | 
				
			||||||
	if(has_picked_) {
 | 
						type *MultiMachine::name() {	\
 | 
				
			||||||
		return machines_.front()->media_target();
 | 
							if(has_picked_) {	\
 | 
				
			||||||
	} else {
 | 
								return machines_.front()->name();	\
 | 
				
			||||||
		return &media_target_;
 | 
							} else {	\
 | 
				
			||||||
 | 
								return &member;	\
 | 
				
			||||||
 | 
							}	\
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CRTMachine::Machine *MultiMachine::crt_machine() {
 | 
					Provider(Configurable::Device, configurable_device, configurable_)
 | 
				
			||||||
	if(has_picked_) {
 | 
					Provider(MachineTypes::TimedMachine, timed_machine, timed_machine_)
 | 
				
			||||||
		return machines_.front()->crt_machine();
 | 
					Provider(MachineTypes::ScanProducer, scan_producer, scan_producer_)
 | 
				
			||||||
	} else {
 | 
					Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
 | 
				
			||||||
		return &crt_machine_;
 | 
					Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
 | 
				
			||||||
	}
 | 
					Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
 | 
				
			||||||
}
 | 
					Provider(MachineTypes::MediaTarget, media_target, media_target_)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
JoystickMachine::Machine *MultiMachine::joystick_machine() {
 | 
					MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
 | 
				
			||||||
	if(has_picked_) {
 | 
					 | 
				
			||||||
		return machines_.front()->joystick_machine();
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return &joystick_machine_;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
 | 
					 | 
				
			||||||
	if(has_picked_) {
 | 
					 | 
				
			||||||
		return machines_.front()->keyboard_machine();
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return &keyboard_machine_;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
MouseMachine::Machine *MultiMachine::mouse_machine() {
 | 
					 | 
				
			||||||
	// TODO.
 | 
						// TODO.
 | 
				
			||||||
	return nullptr;
 | 
						return nullptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Configurable::Device *MultiMachine::configurable_device() {
 | 
					#undef Provider
 | 
				
			||||||
	if(has_picked_) {
 | 
					 | 
				
			||||||
		return machines_.front()->configurable_device();
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return &configurable_;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
 | 
					bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
		(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
 | 
							(machines.front()->timed_machine()->get_confidence() > 0.9f) ||
 | 
				
			||||||
		(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
 | 
							(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MultiMachine::multi_crt_did_run_machines() {
 | 
					void MultiMachine::did_run_machines(MultiTimedMachine *) {
 | 
				
			||||||
	std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
 | 
						std::lock_guard machines_lock(machines_mutex_);
 | 
				
			||||||
#ifndef NDEBUG
 | 
					#ifndef NDEBUG
 | 
				
			||||||
	for(const auto &machine: machines_) {
 | 
						for(const auto &machine: machines_) {
 | 
				
			||||||
		CRTMachine::Machine *crt = machine->crt_machine();
 | 
							auto timed_machine = machine->timed_machine();
 | 
				
			||||||
		LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
 | 
							LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	LOGNBR(std::endl);
 | 
						LOGNBR(std::endl);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -91,13 +72,14 @@ void MultiMachine::multi_crt_did_run_machines() {
 | 
				
			|||||||
	DynamicMachine *front = machines_.front().get();
 | 
						DynamicMachine *front = machines_.front().get();
 | 
				
			||||||
	std::stable_sort(machines_.begin(), machines_.end(),
 | 
						std::stable_sort(machines_.begin(), machines_.end(),
 | 
				
			||||||
		[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
 | 
							[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
 | 
				
			||||||
			CRTMachine::Machine *lhs_crt = lhs->crt_machine();
 | 
								auto lhs_timed = lhs->timed_machine();
 | 
				
			||||||
			CRTMachine::Machine *rhs_crt = rhs->crt_machine();
 | 
								auto rhs_timed = rhs->timed_machine();
 | 
				
			||||||
			return lhs_crt->get_confidence() > rhs_crt->get_confidence();
 | 
								return lhs_timed->get_confidence() > rhs_timed->get_confidence();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(machines_.front().get() != front) {
 | 
						if(machines_.front().get() != front) {
 | 
				
			||||||
		crt_machine_.did_change_machine_order();
 | 
							scan_producer_.did_change_machine_order();
 | 
				
			||||||
 | 
							audio_producer_.did_change_machine_order();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(would_collapse(machines_)) {
 | 
						if(would_collapse(machines_)) {
 | 
				
			||||||
@@ -107,9 +89,28 @@ void MultiMachine::multi_crt_did_run_machines() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void MultiMachine::pick_first() {
 | 
					void MultiMachine::pick_first() {
 | 
				
			||||||
	has_picked_ = true;
 | 
						has_picked_ = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure output rate specifics are properly copied; these may be set only once by the owner,
 | 
				
			||||||
 | 
						// but rather than being propagated directly by the MultiSpeaker only the derived computed
 | 
				
			||||||
 | 
						// output rate is propagated. So this ensures that if a new derivation is made, it's made correctly.
 | 
				
			||||||
 | 
						if(machines_[0]->audio_producer()) {
 | 
				
			||||||
 | 
							auto multi_speaker = audio_producer_.get_speaker();
 | 
				
			||||||
 | 
							auto specific_speaker = machines_[0]->audio_producer()->get_speaker();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(specific_speaker && multi_speaker) {
 | 
				
			||||||
 | 
								specific_speaker->copy_output_rate(*multi_speaker);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: because it is not invalid for a caller to keep a reference to anything previously returned,
 | 
				
			||||||
 | 
						// this erase can be added only once the Multi machines that take static copies of the machines list
 | 
				
			||||||
 | 
						// are updated.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Example failing use case otherwise: a caller still has reference to the MultiJoystickMachine, and
 | 
				
			||||||
 | 
						// it has dangling references to the various JoystickMachines.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// This gets into particularly long grass with the MultiConfigurable and its MultiStruct.
 | 
				
			||||||
//	machines_.erase(machines_.begin() + 1, machines_.end());
 | 
					//	machines_.erase(machines_.begin() + 1, machines_.end());
 | 
				
			||||||
	// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
 | 
					 | 
				
			||||||
	// request a close_output while the context is active.
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void *MultiMachine::raw_pointer() {
 | 
					void *MultiMachine::raw_pointer() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,8 +11,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../../../Machines/DynamicMachine.hpp"
 | 
					#include "../../../Machines/DynamicMachine.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Implementation/MultiProducer.hpp"
 | 
				
			||||||
#include "Implementation/MultiConfigurable.hpp"
 | 
					#include "Implementation/MultiConfigurable.hpp"
 | 
				
			||||||
#include "Implementation/MultiCRTMachine.hpp"
 | 
					#include "Implementation/MultiProducer.hpp"
 | 
				
			||||||
#include "Implementation/MultiJoystickMachine.hpp"
 | 
					#include "Implementation/MultiJoystickMachine.hpp"
 | 
				
			||||||
#include "Implementation/MultiKeyboardMachine.hpp"
 | 
					#include "Implementation/MultiKeyboardMachine.hpp"
 | 
				
			||||||
#include "Implementation/MultiMediaTarget.hpp"
 | 
					#include "Implementation/MultiMediaTarget.hpp"
 | 
				
			||||||
@@ -38,7 +39,7 @@ namespace Dynamic {
 | 
				
			|||||||
	If confidence for any machine becomes disproportionately low compared to
 | 
						If confidence for any machine becomes disproportionately low compared to
 | 
				
			||||||
	the others in the set, that machine stops running.
 | 
						the others in the set, that machine stops running.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
 | 
					class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
 | 
								Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
 | 
				
			||||||
@@ -50,23 +51,27 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
 | 
				
			|||||||
		static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
 | 
							static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
 | 
				
			||||||
		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
 | 
							MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Activity::Source *activity_source() override;
 | 
							Activity::Source *activity_source() final;
 | 
				
			||||||
		Configurable::Device *configurable_device() override;
 | 
							Configurable::Device *configurable_device() final;
 | 
				
			||||||
		CRTMachine::Machine *crt_machine() override;
 | 
							MachineTypes::TimedMachine *timed_machine() final;
 | 
				
			||||||
		JoystickMachine::Machine *joystick_machine() override;
 | 
							MachineTypes::ScanProducer *scan_producer() final;
 | 
				
			||||||
		MouseMachine::Machine *mouse_machine() override;
 | 
							MachineTypes::AudioProducer *audio_producer() final;
 | 
				
			||||||
		KeyboardMachine::Machine *keyboard_machine() override;
 | 
							MachineTypes::JoystickMachine *joystick_machine() final;
 | 
				
			||||||
		MediaTarget::Machine *media_target() override;
 | 
							MachineTypes::KeyboardMachine *keyboard_machine() final;
 | 
				
			||||||
		void *raw_pointer() override;
 | 
							MachineTypes::MouseMachine *mouse_machine() final;
 | 
				
			||||||
 | 
							MachineTypes::MediaTarget *media_target() final;
 | 
				
			||||||
 | 
							void *raw_pointer() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		void multi_crt_did_run_machines() override;
 | 
							void did_run_machines(MultiTimedMachine *) final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
							std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
				
			||||||
		std::recursive_mutex machines_mutex_;
 | 
							std::recursive_mutex machines_mutex_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MultiConfigurable configurable_;
 | 
							MultiConfigurable configurable_;
 | 
				
			||||||
		MultiCRTMachine crt_machine_;
 | 
							MultiTimedMachine timed_machine_;
 | 
				
			||||||
 | 
							MultiScanProducer scan_producer_;
 | 
				
			||||||
 | 
							MultiAudioProducer audio_producer_;
 | 
				
			||||||
		MultiJoystickMachine joystick_machine_;
 | 
							MultiJoystickMachine joystick_machine_;
 | 
				
			||||||
		MultiKeyboardMachine keyboard_machine_;
 | 
							MultiKeyboardMachine keyboard_machine_;
 | 
				
			||||||
		MultiMediaTarget media_target_;
 | 
							MultiMediaTarget media_target_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ namespace Analyser {
 | 
				
			|||||||
enum class Machine {
 | 
					enum class Machine {
 | 
				
			||||||
	AmstradCPC,
 | 
						AmstradCPC,
 | 
				
			||||||
	AppleII,
 | 
						AppleII,
 | 
				
			||||||
 | 
						AppleIIgs,
 | 
				
			||||||
	Atari2600,
 | 
						Atari2600,
 | 
				
			||||||
	AtariST,
 | 
						AtariST,
 | 
				
			||||||
	ColecoVision,
 | 
						ColecoVision,
 | 
				
			||||||
@@ -23,7 +24,8 @@ enum class Machine {
 | 
				
			|||||||
	MSX,
 | 
						MSX,
 | 
				
			||||||
	Oric,
 | 
						Oric,
 | 
				
			||||||
	Vic20,
 | 
						Vic20,
 | 
				
			||||||
	ZX8081
 | 
						ZX8081,
 | 
				
			||||||
 | 
						ZXSpectrum,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
					#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
				
			||||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
					#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
				
			||||||
#include "../../../NumberTheory/CRC.hpp"
 | 
					#include "../../../Numeric/CRC.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,8 +21,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
				
			|||||||
	auto catalogue = std::make_unique<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);
 | 
						const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
 | 
				
			||||||
	Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
 | 
						const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!names || !details) return nullptr;
 | 
						if(!names || !details) return nullptr;
 | 
				
			||||||
	if(names->samples.empty() || details->samples.empty()) return nullptr;
 | 
						if(names->samples.empty() || details->samples.empty()) return nullptr;
 | 
				
			||||||
@@ -48,18 +48,21 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
				
			|||||||
		char name[10];
 | 
							char name[10];
 | 
				
			||||||
		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
 | 
							snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
 | 
				
			||||||
		new_file.name = name;
 | 
							new_file.name = name;
 | 
				
			||||||
		new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
 | 
							new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
 | 
				
			||||||
		new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
 | 
							new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
 | 
				
			||||||
		new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
 | 
							if(names->samples[0][file_offset + 7] & 0x80) {
 | 
				
			||||||
 | 
								// File is locked; it may not be altered or deleted.
 | 
				
			||||||
 | 
								new_file.flags |= File::Flags::Locked;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
 | 
							long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
 | 
				
			||||||
		int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
 | 
							int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
 | 
				
			||||||
		new_file.data.reserve(static_cast<std::size_t>(data_length));
 | 
							new_file.data.reserve(size_t(data_length));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(start_sector < 2) continue;
 | 
							if(start_sector < 2) continue;
 | 
				
			||||||
		while(data_length > 0) {
 | 
							while(data_length > 0) {
 | 
				
			||||||
			uint8_t sector = static_cast<uint8_t>(start_sector % 10);
 | 
								uint8_t sector = uint8_t(start_sector % 10);
 | 
				
			||||||
			uint8_t track = static_cast<uint8_t>(start_sector / 10);
 | 
								uint8_t track = uint8_t(start_sector / 10);
 | 
				
			||||||
			start_sector++;
 | 
								start_sector++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
 | 
								Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
 | 
				
			||||||
@@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
				
			|||||||
			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
 | 
								new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
 | 
				
			||||||
			data_length -= length_from_sector;
 | 
								data_length -= length_from_sector;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(!data_length) catalogue->files.push_back(new_file);
 | 
							if(!data_length) catalogue->files.push_back(std::move(new_file));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return catalogue;
 | 
						return catalogue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
						Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
 | 
				
			||||||
 | 
						http://mdfs.net/Docs/Comp/Disk/Format/ADFS
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
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) {
 | 
				
			||||||
	auto catalogue = std::make_unique<Catalogue>();
 | 
						auto catalogue = std::make_unique<Catalogue>();
 | 
				
			||||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
						Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
@@ -84,7 +92,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
 | 
				
			|||||||
	std::vector<uint8_t> root_directory;
 | 
						std::vector<uint8_t> root_directory;
 | 
				
			||||||
	root_directory.reserve(5 * 256);
 | 
						root_directory.reserve(5 * 256);
 | 
				
			||||||
	for(uint8_t c = 2; c < 7; c++) {
 | 
						for(uint8_t c = 2; c < 7; c++) {
 | 
				
			||||||
		Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
 | 
							const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
 | 
				
			||||||
		if(!sector) return nullptr;
 | 
							if(!sector) return nullptr;
 | 
				
			||||||
		root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
 | 
							root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
 | 
				
			|||||||
		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
							case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the root directory, at least.
 | 
				
			||||||
 | 
						for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
 | 
				
			||||||
 | 
							// Obtain the name, which will be at most ten characters long, and will
 | 
				
			||||||
 | 
							// be terminated by either a NULL character or a \r.
 | 
				
			||||||
 | 
							char name[11];
 | 
				
			||||||
 | 
							std::size_t c = 0;
 | 
				
			||||||
 | 
							for(; c < 10; c++) {
 | 
				
			||||||
 | 
								const char next = root_directory[file_offset + c] & 0x7f;
 | 
				
			||||||
 | 
								name[c] = next;
 | 
				
			||||||
 | 
								if(next == '\0' || next == '\r') break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							name[c] = '\0';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Skip if the name is empty.
 | 
				
			||||||
 | 
							if(name[0] == '\0') continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Populate a file then.
 | 
				
			||||||
 | 
							File new_file;
 | 
				
			||||||
 | 
							new_file.name = name;
 | 
				
			||||||
 | 
							new_file.flags =
 | 
				
			||||||
 | 
								(root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) |
 | 
				
			||||||
 | 
								(root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new_file.load_address =
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x0a]) << 0) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x0b]) << 8) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x0c]) << 16) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x0d]) << 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new_file.execution_address =
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x0e]) << 0) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x0f]) << 8) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x10]) << 16) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x11]) << 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new_file.sequence_number = root_directory[file_offset + 0x19];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const uint32_t size =
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x12]) << 0) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x13]) << 8) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x14]) << 16) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x15]) << 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint32_t start_sector =
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x16]) << 0) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x17]) << 8) |
 | 
				
			||||||
 | 
								(uint32_t(root_directory[file_offset + 0x18]) << 16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new_file.data.reserve(size);
 | 
				
			||||||
 | 
							while(new_file.data.size() < size) {
 | 
				
			||||||
 | 
								const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
 | 
				
			||||||
 | 
								if(!sector) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
 | 
				
			||||||
 | 
								new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
 | 
				
			||||||
 | 
								++start_sector;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							catalogue->files.push_back(std::move(new_file));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return catalogue;
 | 
						return catalogue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,19 +19,38 @@ namespace Acorn {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct File {
 | 
					struct File {
 | 
				
			||||||
	std::string name;
 | 
						std::string name;
 | 
				
			||||||
	uint32_t load_address;
 | 
						uint32_t load_address = 0;
 | 
				
			||||||
	uint32_t execution_address;
 | 
						uint32_t execution_address = 0;
 | 
				
			||||||
	bool is_protected;
 | 
					
 | 
				
			||||||
 | 
						enum Flags: uint16_t {
 | 
				
			||||||
 | 
							Readable = 1 << 0,
 | 
				
			||||||
 | 
							Writable = 1 << 1,
 | 
				
			||||||
 | 
							Locked = 1 << 2,
 | 
				
			||||||
 | 
							IsDirectory = 1 << 3,
 | 
				
			||||||
 | 
							ExecuteOnly = 1 << 4,
 | 
				
			||||||
 | 
							PubliclyReadable = 1 << 5,
 | 
				
			||||||
 | 
							PubliclyWritable = 1 << 6,
 | 
				
			||||||
 | 
							PubliclyExecuteOnly = 1 << 7,
 | 
				
			||||||
 | 
							IsPrivate = 1 << 8,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable;
 | 
				
			||||||
 | 
						uint8_t sequence_number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::vector<uint8_t> data;
 | 
						std::vector<uint8_t> data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Describes a single chunk of file data; these relate to the tape and ROM filing system.
 | 
				
			||||||
 | 
						/// The File-level fields contain a 'definitive' version of the load and execution addresses,
 | 
				
			||||||
 | 
						/// but both of those filing systems also store them per chunk.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Similarly, the file-level data will contain the aggregate data of all chunks.
 | 
				
			||||||
	struct Chunk {
 | 
						struct Chunk {
 | 
				
			||||||
		std::string name;
 | 
							std::string name;
 | 
				
			||||||
		uint32_t load_address;
 | 
							uint32_t load_address = 0;
 | 
				
			||||||
		uint32_t execution_address;
 | 
							uint32_t execution_address = 0;
 | 
				
			||||||
		uint16_t block_number;
 | 
							uint16_t block_number = 0;
 | 
				
			||||||
		uint16_t block_length;
 | 
							uint16_t block_length = 0;
 | 
				
			||||||
		uint8_t block_flag;
 | 
							uint32_t next_address = 0;
 | 
				
			||||||
		uint32_t next_address;
 | 
							uint8_t block_flag = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool header_crc_matched;
 | 
							bool header_crc_matched;
 | 
				
			||||||
		bool data_crc_matched;
 | 
							bool data_crc_matched;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,8 @@
 | 
				
			|||||||
#include "Tape.hpp"
 | 
					#include "Tape.hpp"
 | 
				
			||||||
#include "Target.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Static::Acorn;
 | 
					using namespace Analyser::Static::Acorn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
					static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			||||||
@@ -29,7 +31,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
 | 
							if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// is a copyright string present?
 | 
							// is a copyright string present?
 | 
				
			||||||
		uint8_t copyright_offset = segment.data[7];
 | 
							const uint8_t copyright_offset = segment.data[7];
 | 
				
			||||||
		if(
 | 
							if(
 | 
				
			||||||
			segment.data[copyright_offset] != 0x00 ||
 | 
								segment.data[copyright_offset] != 0x00 ||
 | 
				
			||||||
			segment.data[copyright_offset+1] != 0x28 ||
 | 
								segment.data[copyright_offset+1] != 0x28 ||
 | 
				
			||||||
@@ -57,13 +59,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
	return acorn_cartridges;
 | 
						return acorn_cartridges;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 &, TargetPlatform::IntType) {
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::Electron;
 | 
					 | 
				
			||||||
	target->confidence = 0.5; // TODO: a proper estimation
 | 
					 | 
				
			||||||
	target->has_dfs = false;
 | 
					 | 
				
			||||||
	target->has_adfs = false;
 | 
					 | 
				
			||||||
	target->should_shift_restart = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// strip out inappropriate cartridges
 | 
						// strip out inappropriate cartridges
 | 
				
			||||||
	target->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
						target->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
				
			||||||
@@ -78,14 +75,14 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
 | 
				
			|||||||
		if(!files.empty()) {
 | 
							if(!files.empty()) {
 | 
				
			||||||
			bool is_basic = true;
 | 
								bool is_basic = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// protected files are always for *RUNning only
 | 
								// If a file is execute-only, that means *RUN.
 | 
				
			||||||
			if(files.front().is_protected) is_basic = false;
 | 
								if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | 
								// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | 
				
			||||||
			// so that's also justification to *RUN
 | 
								// so that's also justification to *RUN
 | 
				
			||||||
			std::size_t pointer = 0;
 | 
								std::size_t pointer = 0;
 | 
				
			||||||
			uint8_t *data = &files.front().data[0];
 | 
								uint8_t *const data = &files.front().data[0];
 | 
				
			||||||
			std::size_t data_size = files.front().data.size();
 | 
								const std::size_t data_size = files.front().data.size();
 | 
				
			||||||
			while(1) {
 | 
								while(1) {
 | 
				
			||||||
				if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
									if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
				
			||||||
					is_basic = false;
 | 
										is_basic = false;
 | 
				
			||||||
@@ -109,15 +106,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
 | 
				
			|||||||
		dfs_catalogue = GetDFSCatalogue(disk);
 | 
							dfs_catalogue = GetDFSCatalogue(disk);
 | 
				
			||||||
		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
 | 
							if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
 | 
				
			||||||
		if(dfs_catalogue || adfs_catalogue) {
 | 
							if(dfs_catalogue || adfs_catalogue) {
 | 
				
			||||||
 | 
								// Accept the disk and determine whether DFS or ADFS ROMs are implied.
 | 
				
			||||||
 | 
								// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
 | 
				
			||||||
			target->media.disks = media.disks;
 | 
								target->media.disks = media.disks;
 | 
				
			||||||
			target->has_dfs = !!dfs_catalogue;
 | 
								target->has_dfs = bool(dfs_catalogue);
 | 
				
			||||||
			target->has_adfs = !!adfs_catalogue;
 | 
								target->has_pres_adfs = bool(adfs_catalogue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check whether a simple shift+break will do for loading this disk.
 | 
				
			||||||
			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
								Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
				
			||||||
			if(bootOption != Catalogue::BootOption::None)
 | 
								if(bootOption != Catalogue::BootOption::None) {
 | 
				
			||||||
				target->should_shift_restart = true;
 | 
									target->should_shift_restart = true;
 | 
				
			||||||
			else
 | 
								} else {
 | 
				
			||||||
				target->loading_command = "*CAT\n";
 | 
									target->loading_command = "*CAT\n";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check whether adding the AP6 ROM is justified.
 | 
				
			||||||
 | 
								// For now this is an incredibly dense text search;
 | 
				
			||||||
 | 
								// if any of the commands that aren't usually present
 | 
				
			||||||
 | 
								// on a stock Electron are here, add the AP6 ROM and
 | 
				
			||||||
 | 
								// some sideways RAM such that the SR commands are useful.
 | 
				
			||||||
 | 
								for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
 | 
				
			||||||
 | 
									for(const auto &command: {
 | 
				
			||||||
 | 
										"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
 | 
				
			||||||
 | 
										"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
 | 
				
			||||||
 | 
										"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
 | 
				
			||||||
 | 
										"VERIFY", "ZERO"
 | 
				
			||||||
 | 
									}) {
 | 
				
			||||||
 | 
										if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
 | 
				
			||||||
 | 
											target->has_ap6_rom = true;
 | 
				
			||||||
 | 
											target->has_sideways_ram = true;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Enable the Acorn ADFS if a mass-storage device is attached;
 | 
				
			||||||
 | 
						// unlike the Pres ADFS it retains SCSI logic.
 | 
				
			||||||
 | 
						if(!media.mass_storage_devices.empty()) {
 | 
				
			||||||
 | 
							target->has_pres_adfs = false;	// To override a floppy selection, if one was made.
 | 
				
			||||||
 | 
							target->has_acorn_adfs = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Assume some sort of later-era Acorn work is likely to happen;
 | 
				
			||||||
 | 
							// so ensure *TYPE, etc are present.
 | 
				
			||||||
 | 
							target->has_ap6_rom = true;
 | 
				
			||||||
 | 
							target->has_sideways_ram = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							target->media.mass_storage_devices = media.mass_storage_devices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check for a boot option.
 | 
				
			||||||
 | 
							const auto sector = target->media.mass_storage_devices.front()->get_block(1);
 | 
				
			||||||
 | 
							if(sector[0xfd]) {
 | 
				
			||||||
 | 
								target->should_shift_restart = true;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								target->loading_command = "*CAT\n";
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <deque>
 | 
					#include <deque>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../../../NumberTheory/CRC.hpp"
 | 
					#include "../../../Numeric/CRC.hpp"
 | 
				
			||||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
 | 
					#include "../../../Storage/Tape/Parsers/Acorn.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Static::Acorn;
 | 
					using namespace Analyser::Static::Acorn;
 | 
				
			||||||
@@ -41,24 +41,24 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
 | 
				
			|||||||
	char name[11];
 | 
						char name[11];
 | 
				
			||||||
	std::size_t name_ptr = 0;
 | 
						std::size_t name_ptr = 0;
 | 
				
			||||||
	while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
						while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
				
			||||||
		name[name_ptr] = (char)parser.get_next_byte(tape);
 | 
							name[name_ptr] = char(parser.get_next_byte(tape));
 | 
				
			||||||
		if(!name[name_ptr]) break;
 | 
							if(!name[name_ptr]) break;
 | 
				
			||||||
		name_ptr++;
 | 
							++name_ptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	name[sizeof(name)-1] = '\0';
 | 
						name[sizeof(name)-1] = '\0';
 | 
				
			||||||
	new_chunk->name = name;
 | 
						new_chunk->name = name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// addresses
 | 
						// addresses
 | 
				
			||||||
	new_chunk->load_address = (uint32_t)parser.get_next_word(tape);
 | 
						new_chunk->load_address = uint32_t(parser.get_next_word(tape));
 | 
				
			||||||
	new_chunk->execution_address = (uint32_t)parser.get_next_word(tape);
 | 
						new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
 | 
				
			||||||
	new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
						new_chunk->block_number = uint16_t(parser.get_next_short(tape));
 | 
				
			||||||
	new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
						new_chunk->block_length = uint16_t(parser.get_next_short(tape));
 | 
				
			||||||
	new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
 | 
						new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
 | 
				
			||||||
	new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
 | 
						new_chunk->next_address = uint32_t(parser.get_next_word(tape));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	uint16_t calculated_header_crc = parser.get_crc();
 | 
						uint16_t calculated_header_crc = parser.get_crc();
 | 
				
			||||||
	uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
						uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
 | 
				
			||||||
	stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
 | 
						stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
 | 
				
			||||||
	new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
 | 
						new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!new_chunk->header_crc_matched) return nullptr;
 | 
						if(!new_chunk->header_crc_matched) return nullptr;
 | 
				
			||||||
@@ -66,13 +66,13 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
 | 
				
			|||||||
	parser.reset_crc();
 | 
						parser.reset_crc();
 | 
				
			||||||
	new_chunk->data.reserve(new_chunk->block_length);
 | 
						new_chunk->data.reserve(new_chunk->block_length);
 | 
				
			||||||
	for(int c = 0; c < new_chunk->block_length; c++) {
 | 
						for(int c = 0; c < new_chunk->block_length; c++) {
 | 
				
			||||||
		new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
 | 
							new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
 | 
						if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
 | 
				
			||||||
		uint16_t calculated_data_crc = parser.get_crc();
 | 
							uint16_t calculated_data_crc = parser.get_crc();
 | 
				
			||||||
		uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
 | 
							uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
 | 
				
			||||||
		stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
 | 
							stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
 | 
				
			||||||
		new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
 | 
							new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		new_chunk->data_crc_matched = true;
 | 
							new_chunk->data_crc_matched = true;
 | 
				
			||||||
@@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
 | 
				
			|||||||
	file->name = file->chunks.front().name;
 | 
						file->name = file->chunks.front().name;
 | 
				
			||||||
	file->load_address = file->chunks.front().load_address;
 | 
						file->load_address = file->chunks.front().load_address;
 | 
				
			||||||
	file->execution_address = file->chunks.front().execution_address;
 | 
						file->execution_address = file->chunks.front().execution_address;
 | 
				
			||||||
	file->is_protected = !!(file->chunks.back().block_flag & 0x01);	// I think the last flags are the ones that count; TODO: check.
 | 
						// I think the final chunk's flags are the ones that count; TODO: check.
 | 
				
			||||||
 | 
						if(file->chunks.back().block_flag & 0x01) {
 | 
				
			||||||
 | 
							// File is locked, which in more generalised terms means it is
 | 
				
			||||||
 | 
							// for execution only.
 | 
				
			||||||
 | 
							file->flags |= File::Flags::ExecuteOnly;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// copy all data into a single big block
 | 
						// copy all data into a single big block
 | 
				
			||||||
	for(File::Chunk chunk : file->chunks) {
 | 
						for(File::Chunk chunk : file->chunks) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
#ifndef Analyser_Static_Acorn_Target_h
 | 
					#ifndef Analyser_Static_Acorn_Target_h
 | 
				
			||||||
#define Analyser_Static_Acorn_Target_h
 | 
					#define Analyser_Static_Acorn_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,11 +17,24 @@ namespace Analyser {
 | 
				
			|||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Acorn {
 | 
					namespace Acorn {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	bool has_adfs = false;
 | 
						bool has_acorn_adfs = false;
 | 
				
			||||||
 | 
						bool has_pres_adfs = false;
 | 
				
			||||||
	bool has_dfs = false;
 | 
						bool has_dfs = false;
 | 
				
			||||||
 | 
						bool has_ap6_rom = false;
 | 
				
			||||||
 | 
						bool has_sideways_ram = false;
 | 
				
			||||||
	bool should_shift_restart = false;
 | 
						bool should_shift_restart = false;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::Electron) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(has_pres_adfs);
 | 
				
			||||||
 | 
								DeclareField(has_acorn_adfs);
 | 
				
			||||||
 | 
								DeclareField(has_dfs);
 | 
				
			||||||
 | 
								DeclareField(has_ap6_rom);
 | 
				
			||||||
 | 
								DeclareField(has_sideways_ram);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,12 +11,15 @@
 | 
				
			|||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "Target.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
 | 
					#include "../../../Storage/Disk/Parsers/CPM.hpp"
 | 
				
			||||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
					#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool strcmp_insensitive(const char *a, const char *b) {
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool strcmp_insensitive(const char *a, const char *b) {
 | 
				
			||||||
	if(std::strlen(a) != std::strlen(b)) return false;
 | 
						if(std::strlen(a) != std::strlen(b)) return false;
 | 
				
			||||||
	while(*a) {
 | 
						while(*a) {
 | 
				
			||||||
		if(std::tolower(*a) != std::tolower(*b)) return false;
 | 
							if(std::tolower(*a) != std::tolower(*b)) return false;
 | 
				
			||||||
@@ -26,20 +29,20 @@ static bool strcmp_insensitive(const char *a, const char *b) {
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool is_implied_extension(const std::string &extension) {
 | 
					bool is_implied_extension(const std::string &extension) {
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
		extension == "   " ||
 | 
							extension == "   " ||
 | 
				
			||||||
		strcmp_insensitive(extension.c_str(), "BAS") ||
 | 
							strcmp_insensitive(extension.c_str(), "BAS") ||
 | 
				
			||||||
		strcmp_insensitive(extension.c_str(), "BIN");
 | 
							strcmp_insensitive(extension.c_str(), "BIN");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void right_trim(std::string &string) {
 | 
					void right_trim(std::string &string) {
 | 
				
			||||||
	string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
 | 
						string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
 | 
				
			||||||
		return !std::isspace(ch);
 | 
							return !std::isspace(ch);
 | 
				
			||||||
	}).base(), string.end());
 | 
						}).base(), string.end());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
					std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
				
			||||||
	// Trim spaces from the name.
 | 
						// Trim spaces from the name.
 | 
				
			||||||
	std::string name = file.name;
 | 
						std::string name = file.name;
 | 
				
			||||||
	right_trim(name);
 | 
						right_trim(name);
 | 
				
			||||||
@@ -58,7 +61,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
				
			|||||||
	return command + "\n";
 | 
						return command + "\n";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void InspectCatalogue(
 | 
					void InspectCatalogue(
 | 
				
			||||||
	const Storage::Disk::CPM::Catalogue &catalogue,
 | 
						const Storage::Disk::CPM::Catalogue &catalogue,
 | 
				
			||||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
						const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -155,7 +158,7 @@ static void InspectCatalogue(
 | 
				
			|||||||
	target->loading_command = "cat\n";
 | 
						target->loading_command = "cat\n";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
					bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
				
			||||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
						Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
 | 
						Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
 | 
				
			||||||
	if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
 | 
						if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
 | 
				
			||||||
@@ -179,22 +182,49 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
 | 
				
			|||||||
	return false;
 | 
						return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
				
			||||||
 | 
						// Limited sophistication here; look for a CPC-style file header, that is
 | 
				
			||||||
 | 
						// any Spectrum-esque block with a synchronisation character of 0x2c.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// More could be done here: parse the header, look for 0x16 data records.
 | 
				
			||||||
 | 
						using Parser = Storage::Tape::ZXSpectrum::Parser;
 | 
				
			||||||
 | 
						Parser parser(Parser::MachineType::AmstradCPC);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while(true) {
 | 
				
			||||||
 | 
							const auto block = parser.find_block(tape);
 | 
				
			||||||
 | 
							if(!block) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(block->type == 0x2c) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
	TargetList destination;
 | 
						TargetList destination;
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::AmstradCPC;
 | 
					 | 
				
			||||||
	target->confidence = 0.5;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	target->model = Target::Model::CPC6128;
 | 
						target->model = Target::Model::CPC6128;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!media.tapes.empty()) {
 | 
						if(!media.tapes.empty()) {
 | 
				
			||||||
		// TODO: which of these are actually potentially CPC tapes?
 | 
							bool has_cpc_tape = false;
 | 
				
			||||||
		target->media.tapes = media.tapes;
 | 
							for(auto &tape: media.tapes) {
 | 
				
			||||||
 | 
								has_cpc_tape |= IsAmstradTape(tape);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | 
							if(has_cpc_tape) {
 | 
				
			||||||
		// enter and responding to the follow-on prompt to press a key, so just type for
 | 
								target->media.tapes = media.tapes;
 | 
				
			||||||
		// a while. Yuck!
 | 
					
 | 
				
			||||||
		target->loading_command = "|tape\nrun\"\n1234567890";
 | 
								// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | 
				
			||||||
 | 
								// enter and responding to the follow-on prompt to press a key, so just type for
 | 
				
			||||||
 | 
								// a while. Yuck!
 | 
				
			||||||
 | 
								target->loading_command = "|tape\nrun\"\n123";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!media.disks.empty()) {
 | 
						if(!media.disks.empty()) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#ifndef Analyser_Static_AmstradCPC_Target_h
 | 
					#ifndef Analyser_Static_AmstradCPC_Target_h
 | 
				
			||||||
#define Analyser_Static_AmstradCPC_Target_h
 | 
					#define Analyser_Static_AmstradCPC_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,15 +18,17 @@ namespace Analyser {
 | 
				
			|||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace AmstradCPC {
 | 
					namespace AmstradCPC {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class Model {
 | 
						ReflectableEnum(Model, CPC464, CPC664, CPC6128);
 | 
				
			||||||
		CPC464,
 | 
					 | 
				
			||||||
		CPC664,
 | 
					 | 
				
			||||||
		CPC6128
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Model model = Model::CPC464;
 | 
						Model model = Model::CPC464;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::AmstradCPC) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(model);
 | 
				
			||||||
 | 
								AnnounceEnum(Model);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,9 +9,8 @@
 | 
				
			|||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
#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 &, TargetPlatform::IntType) {
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::AppleII;
 | 
					 | 
				
			||||||
	target->media = media;
 | 
						target->media = media;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!target->media.disks.empty())
 | 
						if(!target->media.disks.empty())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,34 +6,45 @@
 | 
				
			|||||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef Target_h
 | 
					#ifndef Analyser_Static_AppleII_Target_h
 | 
				
			||||||
#define Target_h
 | 
					#define Analyser_Static_AppleII_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace AppleII {
 | 
					namespace AppleII {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class Model {
 | 
						ReflectableEnum(Model,
 | 
				
			||||||
		II,
 | 
							II,
 | 
				
			||||||
		IIplus,
 | 
							IIplus,
 | 
				
			||||||
		IIe,
 | 
							IIe,
 | 
				
			||||||
		EnhancedIIe
 | 
							EnhancedIIe
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
	enum class DiskController {
 | 
						ReflectableEnum(DiskController,
 | 
				
			||||||
		None,
 | 
							None,
 | 
				
			||||||
		SixteenSector,
 | 
							SixteenSector,
 | 
				
			||||||
		ThirteenSector
 | 
							ThirteenSector
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Model model = Model::IIe;
 | 
						Model model = Model::IIe;
 | 
				
			||||||
	DiskController disk_controller = DiskController::None;
 | 
						DiskController disk_controller = DiskController::None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::AppleII) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(model);
 | 
				
			||||||
 | 
								DeclareField(disk_controller);
 | 
				
			||||||
 | 
								AnnounceEnum(Model);
 | 
				
			||||||
 | 
								AnnounceEnum(DiskController);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* Target_h */
 | 
					#endif /* Analyser_Static_AppleII_Target_h */
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								Analyser/Static/AppleIIgs/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Analyser/Static/AppleIIgs/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 20/10/2020.
 | 
				
			||||||
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
 | 
						target->media = media;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TargetList targets;
 | 
				
			||||||
 | 
						targets.push_back(std::move(target));
 | 
				
			||||||
 | 
						return targets;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								Analyser/Static/AppleIIgs/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/AppleIIgs/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 20/10/2020.
 | 
				
			||||||
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_AppleIIgs_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					#define Analyser_Static_AppleIIgs_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/TargetPlatforms.hpp"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace AppleIIgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */
 | 
				
			||||||
							
								
								
									
										49
									
								
								Analyser/Static/AppleIIgs/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								Analyser/Static/AppleIIgs/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Target.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 20/10/2020.
 | 
				
			||||||
 | 
					//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_AppleIIgs_Target_h
 | 
				
			||||||
 | 
					#define Analyser_Static_AppleIIgs_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace AppleIIgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
 | 
						ReflectableEnum(Model,
 | 
				
			||||||
 | 
							ROM00,
 | 
				
			||||||
 | 
							ROM01,
 | 
				
			||||||
 | 
							ROM03
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						ReflectableEnum(MemoryModel,
 | 
				
			||||||
 | 
							TwoHundredAndFiftySixKB,
 | 
				
			||||||
 | 
							OneMB,
 | 
				
			||||||
 | 
							EightMB
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Model model = Model::ROM03;
 | 
				
			||||||
 | 
						MemoryModel memory_model = MemoryModel::EightMB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::AppleIIgs) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(model);
 | 
				
			||||||
 | 
								DeclareField(memory_model);
 | 
				
			||||||
 | 
								AnnounceEnum(Model);
 | 
				
			||||||
 | 
								AnnounceEnum(MemoryModel);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Analyser_Static_AppleIIgs_Target_h */
 | 
				
			||||||
@@ -16,24 +16,22 @@ using namespace Analyser::Static::Atari2600;
 | 
				
			|||||||
using Target = Analyser::Static::Atari2600::Target;
 | 
					using Target = Analyser::Static::Atari2600::Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor2kCartridge(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;
 | 
						const uint16_t entry_address = uint16_t(segment.data[0x7fc] | (segment.data[0x7fd] << 8)) & 0x1fff;
 | 
				
			||||||
 | 
						const uint16_t break_address = uint16_t(segment.data[0x7fe] | (segment.data[0x7ff] << 8)) & 0x1fff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
 | 
						// A CommaVid start address needs to be outside of its RAM.
 | 
				
			||||||
	break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// a CommaVid start address needs to be outside of its RAM
 | 
					 | 
				
			||||||
	if(entry_address < 0x1800 || break_address < 0x1800) return;
 | 
						if(entry_address < 0x1800 || break_address < 0x1800) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
 | 
						std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
 | 
				
			||||||
		address &= 0x1fff;
 | 
							address &= 0x1fff;
 | 
				
			||||||
		return static_cast<std::size_t>(address - 0x1800);
 | 
							return size_t(address - 0x1800);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	Analyser::Static::MOS6502::Disassembly high_location_disassembly =
 | 
						Analyser::Static::MOS6502::Disassembly high_location_disassembly =
 | 
				
			||||||
		Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
 | 
							Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 
						// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 
				
			||||||
	// large amounts of memory
 | 
						// large amounts of memory.
 | 
				
			||||||
	bool has_wide_area_store = false;
 | 
						bool has_wide_area_store = false;
 | 
				
			||||||
	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
						for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
				
			||||||
		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
 | 
							if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
 | 
				
			||||||
@@ -45,17 +43,17 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
 | 
						// Conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
 | 
				
			||||||
	// 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 = Target::PagingModel::CommaVid;
 | 
						if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor8kCartridge(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?).
 | 
				
			||||||
	if(
 | 
						if(
 | 
				
			||||||
		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
							segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
				
			||||||
		(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) &&
 | 
				
			||||||
@@ -65,7 +63,7 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
 | 
				
			|||||||
		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 = Target::PagingModel::Atari8k;
 | 
						target.paging_model = Target::PagingModel::Atari8k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::set<uint16_t> internal_accesses;
 | 
						std::set<uint16_t> internal_accesses;
 | 
				
			||||||
@@ -90,8 +88,8 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
 | 
				
			|||||||
	else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
 | 
						else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::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 = Target::PagingModel::Atari16k;
 | 
						target.paging_model = Target::PagingModel::Atari16k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::set<uint16_t> internal_accesses;
 | 
						std::set<uint16_t> internal_accesses;
 | 
				
			||||||
@@ -110,8 +108,8 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
 | 
				
			|||||||
	if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
 | 
						if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::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()) ?
 | 
				
			||||||
			Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
 | 
								Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
 | 
				
			||||||
@@ -123,17 +121,15 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
				
			|||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	uint16_t entry_address, break_address;
 | 
						const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
 | 
				
			||||||
 | 
						const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
 | 
				
			||||||
	entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
 | 
					 | 
				
			||||||
	break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
						std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
				
			||||||
		if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
 | 
							if(!(address & 0x1000)) return size_t(-1);
 | 
				
			||||||
		return static_cast<std::size_t>(address & 0xfff);
 | 
							return size_t(address & 0xfff);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
						const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
				
			||||||
	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
						Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch(segment.data.size()) {
 | 
						switch(segment.data.size()) {
 | 
				
			||||||
@@ -159,7 +155,7 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
				
			|||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 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 != Target::PagingModel::CBSRamPlus &&
 | 
						if(	target.paging_model != Target::PagingModel::CBSRamPlus &&
 | 
				
			||||||
@@ -174,17 +170,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
				
			|||||||
		target.uses_superchip = has_superchip;
 | 
							target.uses_superchip = has_superchip;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check for a Tigervision or Tigervision-esque scheme
 | 
						// Check for a Tigervision or Tigervision-esque scheme
 | 
				
			||||||
	if(target.paging_model == 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 = Target::PagingModel::Tigervision;
 | 
							if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Atari2600::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 &, TargetPlatform::IntType) {
 | 
				
			||||||
	// TODO: sanity checking; is this image really for an Atari 2600?
 | 
						// TODO: sanity checking; is this image really for an Atari 2600?
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	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 = Target::PagingModel::None;
 | 
						target->paging_model = Target::PagingModel::None;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,8 @@ struct Target: public ::Analyser::Static::Target {
 | 
				
			|||||||
	// TODO: shouldn't these be properties of the cartridge?
 | 
						// TODO: shouldn't these be properties of the cartridge?
 | 
				
			||||||
	PagingModel paging_model = PagingModel::None;
 | 
						PagingModel paging_model = PagingModel::None;
 | 
				
			||||||
	bool uses_superchip = false;
 | 
						bool uses_superchip = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::Atari2600) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,16 +9,15 @@
 | 
				
			|||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
#include "Target.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
						// This analyser can comprehend disks and mass-storage devices only.
 | 
				
			||||||
	if(media.disks.empty()) return {};
 | 
						if(media.disks.empty()) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// As there is at least one usable media image, wave it through.
 | 
						// As there is at least one usable media image, wave it through.
 | 
				
			||||||
	Analyser::Static::TargetList targets;
 | 
						Analyser::Static::TargetList targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	using Target = Analyser::Static::Target;
 | 
						using Target = Analyser::Static::AtariST::Target;
 | 
				
			||||||
	auto *target = new Target;
 | 
						auto *const target = new Target();
 | 
				
			||||||
	target->machine = Analyser::Machine::AtariST;
 | 
					 | 
				
			||||||
	target->media = media;
 | 
						target->media = media;
 | 
				
			||||||
	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
						targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,15 @@
 | 
				
			|||||||
#ifndef Analyser_Static_AtariST_Target_h
 | 
					#ifndef Analyser_Static_AtariST_Target_h
 | 
				
			||||||
#define Analyser_Static_AtariST_Target_h
 | 
					#define Analyser_Static_AtariST_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace AtariST {
 | 
					namespace AtariST {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::AtariST) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// the two bytes that will be first must be 0xaa and 0x55, either way around
 | 
							// the two bytes that will be first must be 0xaa and 0x55, either way around
 | 
				
			||||||
		auto *start = &segment.data[0];
 | 
							auto *start = &segment.data[0];
 | 
				
			||||||
		if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
 | 
							if((data_size & size_t(~8191)) > 32768) {
 | 
				
			||||||
			start = &segment.data[segment.data.size() - 16384];
 | 
								start = &segment.data[segment.data.size() - 16384];
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
 | 
							if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
 | 
				
			||||||
@@ -52,10 +52,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
	return coleco_cartridges;
 | 
						return coleco_cartridges;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 &, TargetPlatform::IntType) {
 | 
				
			||||||
	TargetList targets;
 | 
						TargetList targets;
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>(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);
 | 
				
			||||||
	if(!target->media.empty())
 | 
						if(!target->media.empty())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,12 +19,10 @@ using namespace Analyser::Static::Commodore;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
					class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		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 = std::make_shared<Storage::Disk::Drive>(4000000, 300, 2);
 | 
								emplace_drive(4000000, 300, 2);
 | 
				
			||||||
			set_drive(drive);
 | 
								set_drive(1);
 | 
				
			||||||
			drive->set_motor_on(true);
 | 
								get_drive().set_motor_on(true);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct Sector {
 | 
							struct Sector {
 | 
				
			||||||
@@ -40,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
			@returns a sector if one was found; @c nullptr otherwise.
 | 
								@returns a sector if one was found; @c nullptr otherwise.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
 | 
							std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
 | 
				
			||||||
			int difference = static_cast<int>(track) - static_cast<int>(track_);
 | 
								int difference = int(track) - int(track_);
 | 
				
			||||||
			track_ = track;
 | 
								track_ = track;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(difference) {
 | 
								if(difference) {
 | 
				
			||||||
@@ -61,6 +59,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
			return get_sector(sector);
 | 
								return get_sector(sector);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
 | 
								get_drive().set_disk(disk);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		unsigned int shift_register_;
 | 
							unsigned int shift_register_;
 | 
				
			||||||
		int index_count_;
 | 
							int index_count_;
 | 
				
			||||||
@@ -69,7 +71,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
		std::shared_ptr<Sector> sector_cache_[65536];
 | 
							std::shared_ptr<Sector> sector_cache_[65536];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void process_input_bit(int value) {
 | 
							void process_input_bit(int value) {
 | 
				
			||||||
			shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
 | 
								shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
 | 
				
			||||||
			bit_count_++;
 | 
								bit_count_++;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -110,15 +112,15 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::shared_ptr<Sector> get_sector(uint8_t sector) {
 | 
							std::shared_ptr<Sector> get_sector(uint8_t sector) {
 | 
				
			||||||
			uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
 | 
								const uint16_t sector_address = uint16_t((track_ << 8) | sector);
 | 
				
			||||||
			if(sector_cache_[sector_address]) return sector_cache_[sector_address];
 | 
								if(sector_cache_[sector_address]) return sector_cache_[sector_address];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			std::shared_ptr<Sector> first_sector = get_next_sector();
 | 
								const std::shared_ptr<Sector> first_sector = get_next_sector();
 | 
				
			||||||
			if(!first_sector) return first_sector;
 | 
								if(!first_sector) return first_sector;
 | 
				
			||||||
			if(first_sector->sector == sector) return first_sector;
 | 
								if(first_sector->sector == sector) return first_sector;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			while(1) {
 | 
								while(1) {
 | 
				
			||||||
				std::shared_ptr<Sector> next_sector = get_next_sector();
 | 
									const std::shared_ptr<Sector> next_sector = get_next_sector();
 | 
				
			||||||
				if(next_sector->sector == first_sector->sector) return nullptr;
 | 
									if(next_sector->sector == first_sector->sector) return nullptr;
 | 
				
			||||||
				if(next_sector->sector == sector) return next_sector;
 | 
									if(next_sector->sector == sector) return next_sector;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -136,12 +138,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// get sector details, skip if this looks malformed
 | 
									// get sector details, skip if this looks malformed
 | 
				
			||||||
				uint8_t checksum = static_cast<uint8_t>(get_next_byte());
 | 
									uint8_t checksum = uint8_t(get_next_byte());
 | 
				
			||||||
				sector->sector = static_cast<uint8_t>(get_next_byte());
 | 
									sector->sector = uint8_t(get_next_byte());
 | 
				
			||||||
				sector->track = static_cast<uint8_t>(get_next_byte());
 | 
									sector->track = uint8_t(get_next_byte());
 | 
				
			||||||
				uint8_t disk_id[2];
 | 
									uint8_t disk_id[2];
 | 
				
			||||||
				disk_id[0] = static_cast<uint8_t>(get_next_byte());
 | 
									disk_id[0] = uint8_t(get_next_byte());
 | 
				
			||||||
				disk_id[1] = static_cast<uint8_t>(get_next_byte());
 | 
									disk_id[1] = uint8_t(get_next_byte());
 | 
				
			||||||
				if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
									if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// look for the following data
 | 
									// look for the following data
 | 
				
			||||||
@@ -152,12 +154,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				checksum = 0;
 | 
									checksum = 0;
 | 
				
			||||||
				for(std::size_t c = 0; c < 256; c++) {
 | 
									for(std::size_t c = 0; c < 256; c++) {
 | 
				
			||||||
					sector->data[c] = static_cast<uint8_t>(get_next_byte());
 | 
										sector->data[c] = uint8_t(get_next_byte());
 | 
				
			||||||
					checksum ^= sector->data[c];
 | 
										checksum ^= sector->data[c];
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if(checksum == get_next_byte()) {
 | 
									if(checksum == get_next_byte()) {
 | 
				
			||||||
					uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
 | 
										uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
 | 
				
			||||||
					sector_cache_[sector_address] = sector;
 | 
										sector_cache_[sector_address] = sector;
 | 
				
			||||||
					return sector;
 | 
										return sector;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -170,7 +172,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
				
			|||||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
					std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
	std::vector<File> files;
 | 
						std::vector<File> files;
 | 
				
			||||||
	CommodoreGCRParser parser;
 | 
						CommodoreGCRParser parser;
 | 
				
			||||||
	parser.drive->set_disk(disk);
 | 
						parser.set_disk(disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// find any sector whatsoever to establish the current track
 | 
						// find any sector whatsoever to establish the current track
 | 
				
			||||||
	std::shared_ptr<CommodoreGCRParser::Sector> sector;
 | 
						std::shared_ptr<CommodoreGCRParser::Sector> sector;
 | 
				
			||||||
@@ -190,7 +192,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// parse directory
 | 
						// parse directory
 | 
				
			||||||
	std::size_t header_pointer = static_cast<std::size_t>(-32);
 | 
						std::size_t header_pointer = size_t(-32);
 | 
				
			||||||
	while(header_pointer+32+31 < directory.size()) {
 | 
						while(header_pointer+32+31 < directory.size()) {
 | 
				
			||||||
		header_pointer += 32;
 | 
							header_pointer += 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -214,7 +216,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
 | 
							new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
 | 
							std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
 | 
				
			||||||
		new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
							new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool is_first_sector = true;
 | 
							bool is_first_sector = true;
 | 
				
			||||||
@@ -225,7 +227,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
				
			|||||||
			next_track = sector->data[0];
 | 
								next_track = sector->data[0];
 | 
				
			||||||
			next_sector = sector->data[1];
 | 
								next_sector = sector->data[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8);
 | 
								if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
 | 
				
			||||||
			if(next_track)
 | 
								if(next_track)
 | 
				
			||||||
				new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
 | 
									new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ bool Analyser::Static::Commodore::File::is_basic() {
 | 
				
			|||||||
	//		... null-terminated code ...
 | 
						//		... null-terminated code ...
 | 
				
			||||||
	//	(with a next line address of 0000 indicating end of program)
 | 
						//	(with a next line address of 0000 indicating end of program)
 | 
				
			||||||
	while(1) {
 | 
						while(1) {
 | 
				
			||||||
		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
 | 
							if(size_t(line_address - starting_address) >= data.size() + 2) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint16_t next_line_address = data[line_address - starting_address];
 | 
							uint16_t next_line_address = data[line_address - starting_address];
 | 
				
			||||||
		next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
							next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
				
			||||||
@@ -33,13 +33,13 @@ bool Analyser::Static::Commodore::File::is_basic() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		if(next_line_address < line_address + 5) break;
 | 
							if(next_line_address < line_address + 5) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break;
 | 
							if(size_t(line_address - starting_address) >= data.size() + 5) break;
 | 
				
			||||||
		uint16_t next_line_number = data[line_address - starting_address + 2];
 | 
							uint16_t next_line_number = data[line_address - starting_address + 2];
 | 
				
			||||||
		next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
							next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(next_line_number <= line_number) break;
 | 
							if(next_line_number <= line_number) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		line_number = static_cast<uint16_t>(next_line_number);
 | 
							line_number = uint16_t(next_line_number);
 | 
				
			||||||
		line_address = next_line_address;
 | 
							line_address = next_line_address;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
				
			|||||||
	return vic20_cartridges;
 | 
						return vic20_cartridges;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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) {
 | 
				
			||||||
	TargetList destination;
 | 
						TargetList destination;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
@@ -94,6 +94,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
 | 
				
			|||||||
		switch(files.front().starting_address) {
 | 
							switch(files.front().starting_address) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) <<  files.front().starting_address);
 | 
									LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) <<  files.front().starting_address);
 | 
				
			||||||
 | 
									[[fallthrough]];
 | 
				
			||||||
			case 0x1001:
 | 
								case 0x1001:
 | 
				
			||||||
				memory_model = Target::MemoryModel::Unexpanded;
 | 
									memory_model = Target::MemoryModel::Unexpanded;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#ifndef Analyser_Static_Commodore_Target_h
 | 
					#ifndef Analyser_Static_Commodore_Target_h
 | 
				
			||||||
#define Analyser_Static_Commodore_Target_h
 | 
					#define Analyser_Static_Commodore_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,20 +18,20 @@ namespace Analyser {
 | 
				
			|||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Commodore {
 | 
					namespace Commodore {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class MemoryModel {
 | 
						enum class MemoryModel {
 | 
				
			||||||
		Unexpanded,
 | 
							Unexpanded,
 | 
				
			||||||
		EightKB,
 | 
							EightKB,
 | 
				
			||||||
		ThirtyTwoKB
 | 
							ThirtyTwoKB
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enum class Region {
 | 
						ReflectableEnum(Region,
 | 
				
			||||||
		American,
 | 
							American,
 | 
				
			||||||
		Danish,
 | 
							Danish,
 | 
				
			||||||
		Japanese,
 | 
							Japanese,
 | 
				
			||||||
		European,
 | 
							European,
 | 
				
			||||||
		Swedish
 | 
							Swedish
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Maps from a named memory model to a bank enabled/disabled set.
 | 
						/// Maps from a named memory model to a bank enabled/disabled set.
 | 
				
			||||||
	void set_memory_model(MemoryModel memory_model) {
 | 
						void set_memory_model(MemoryModel memory_model) {
 | 
				
			||||||
@@ -54,6 +56,19 @@ struct Target: public ::Analyser::Static::Target {
 | 
				
			|||||||
	Region region = Region::European;
 | 
						Region region = Region::European;
 | 
				
			||||||
	bool has_c1540 = false;
 | 
						bool has_c1540 = false;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::Vic20) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(enabled_ram.bank0);
 | 
				
			||||||
 | 
								DeclareField(enabled_ram.bank1);
 | 
				
			||||||
 | 
								DeclareField(enabled_ram.bank2);
 | 
				
			||||||
 | 
								DeclareField(enabled_ram.bank3);
 | 
				
			||||||
 | 
								DeclareField(enabled_ram.bank5);
 | 
				
			||||||
 | 
								DeclareField(region);
 | 
				
			||||||
 | 
								DeclareField(has_c1540);
 | 
				
			||||||
 | 
								AnnounceEnum(Region);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,12 +26,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		Instruction instruction;
 | 
							Instruction instruction;
 | 
				
			||||||
		instruction.address = address;
 | 
							instruction.address = address;
 | 
				
			||||||
		address++;
 | 
							++address;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// get operation
 | 
							// Get operation.
 | 
				
			||||||
		uint8_t operation = memory[local_address];
 | 
							const uint8_t operation = memory[local_address];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// decode addressing mode
 | 
							// Decode addressing mode.
 | 
				
			||||||
		switch(operation&0x1f) {
 | 
							switch(operation&0x1f) {
 | 
				
			||||||
			case 0x00:
 | 
								case 0x00:
 | 
				
			||||||
				if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
 | 
									if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
 | 
				
			||||||
@@ -74,7 +74,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// decode operation
 | 
							// Decode operation.
 | 
				
			||||||
#define RM_INSTRUCTION(base, op)	\
 | 
					#define RM_INSTRUCTION(base, op)	\
 | 
				
			||||||
	case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\
 | 
						case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\
 | 
				
			||||||
		instruction.operation = op;	\
 | 
							instruction.operation = op;	\
 | 
				
			||||||
@@ -222,14 +222,14 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
#undef M_INSTRUCTION
 | 
					#undef M_INSTRUCTION
 | 
				
			||||||
#undef IM_INSTRUCTION
 | 
					#undef IM_INSTRUCTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// get operand
 | 
							// Get operand.
 | 
				
			||||||
		switch(instruction.addressing_mode) {
 | 
							switch(instruction.addressing_mode) {
 | 
				
			||||||
			// zero-byte operands
 | 
								// Zero-byte operands.
 | 
				
			||||||
			case Instruction::Implied:
 | 
								case Instruction::Implied:
 | 
				
			||||||
				instruction.operand = 0;
 | 
									instruction.operand = 0;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// one-byte operands
 | 
								// One-byte operands.
 | 
				
			||||||
			case Instruction::Immediate:
 | 
								case Instruction::Immediate:
 | 
				
			||||||
			case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
 | 
								case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
 | 
				
			||||||
			case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
 | 
								case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
 | 
				
			||||||
@@ -242,7 +242,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// two-byte operands
 | 
								// Two-byte operands.
 | 
				
			||||||
			case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
 | 
								case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
 | 
				
			||||||
			case Instruction::Indirect: {
 | 
								case Instruction::Indirect: {
 | 
				
			||||||
				std::size_t low_operand_address = address_mapper(address);
 | 
									std::size_t low_operand_address = address_mapper(address);
 | 
				
			||||||
@@ -250,18 +250,18 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
				if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
 | 
									if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
 | 
				
			||||||
				address += 2;
 | 
									address += 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
 | 
									instruction.operand = memory[low_operand_address] | uint16_t(memory[high_operand_address] << 8);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// store the instruction away
 | 
							// Store the instruction.
 | 
				
			||||||
		disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
 | 
							disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: something wider-ranging than this
 | 
							// TODO: something wider-ranging than this
 | 
				
			||||||
		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
							if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
				
			||||||
			std::size_t mapped_address = address_mapper(instruction.operand);
 | 
								const size_t mapped_address = address_mapper(instruction.operand);
 | 
				
			||||||
			bool is_external = mapped_address >= memory.size();
 | 
								const bool is_external = mapped_address >= memory.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch(instruction.operation) {
 | 
								switch(instruction.operation) {
 | 
				
			||||||
				default: break;
 | 
									default: break;
 | 
				
			||||||
@@ -290,7 +290,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// decide on overall flow control
 | 
							// Decide on overall flow control.
 | 
				
			||||||
		if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
 | 
							if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
 | 
				
			||||||
		if(instruction.operation == Instruction::BRK) return;	// TODO: check whether IRQ vector is within memory range
 | 
							if(instruction.operation == Instruction::BRK) return;	// TODO: check whether IRQ vector is within memory range
 | 
				
			||||||
		if(instruction.operation == Instruction::JSR) {
 | 
							if(instruction.operation == Instruction::JSR) {
 | 
				
			||||||
@@ -302,7 +302,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
 | 
				
			|||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(instruction.addressing_mode == Instruction::Relative) {
 | 
							if(instruction.addressing_mode == Instruction::Relative) {
 | 
				
			||||||
			uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
 | 
								uint16_t destination = uint16_t(address + int8_t(instruction.operand));
 | 
				
			||||||
			disassembly.remaining_entry_points.push_back(destination);
 | 
								disassembly.remaining_entry_points.push_back(destination);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
//  AddressMapper.cpp
 | 
					 | 
				
			||||||
//  Clock Signal
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//  Created by Thomas Harte on 30/12/2017.
 | 
					 | 
				
			||||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "AddressMapper.hpp"
 | 
					 | 
				
			||||||
@@ -21,7 +21,7 @@ namespace Disassembler {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
 | 
					template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
 | 
				
			||||||
	return [start_address](T argument) {
 | 
						return [start_address](T argument) {
 | 
				
			||||||
		return static_cast<std::size_t>(argument - start_address);
 | 
							return size_t(argument - start_address);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ class Accessor {
 | 
				
			|||||||
		uint16_t word() {
 | 
							uint16_t word() {
 | 
				
			||||||
			uint8_t low = byte();
 | 
								uint8_t low = byte();
 | 
				
			||||||
			uint8_t high = byte();
 | 
								uint8_t high = byte();
 | 
				
			||||||
			return static_cast<uint16_t>(low | (high << 8));
 | 
								return uint16_t(low | (high << 8));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool overrun() {
 | 
							bool overrun() {
 | 
				
			||||||
@@ -562,7 +562,7 @@ struct Z80Disassembler {
 | 
				
			|||||||
			int access_type =
 | 
								int access_type =
 | 
				
			||||||
				((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
 | 
									((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
 | 
				
			||||||
				((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
 | 
									((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
 | 
				
			||||||
			uint16_t address = static_cast<uint16_t>(instruction.operand);
 | 
								uint16_t address = uint16_t(instruction.operand);
 | 
				
			||||||
			bool is_internal = address_mapper(address) < memory.size();
 | 
								bool is_internal = address_mapper(address) < memory.size();
 | 
				
			||||||
			switch(access_type) {
 | 
								switch(access_type) {
 | 
				
			||||||
				default: break;
 | 
									default: break;
 | 
				
			||||||
@@ -594,7 +594,7 @@ struct Z80Disassembler {
 | 
				
			|||||||
				instruction.operation == Instruction::Operation::JR ||
 | 
									instruction.operation == Instruction::Operation::JR ||
 | 
				
			||||||
				instruction.operation == Instruction::Operation::CALL ||
 | 
									instruction.operation == Instruction::Operation::CALL ||
 | 
				
			||||||
				instruction.operation == Instruction::Operation::RST) {
 | 
									instruction.operation == Instruction::Operation::RST) {
 | 
				
			||||||
				disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
 | 
									disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
 | 
								// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../AppleII/Target.hpp"
 | 
					#include "../AppleII/Target.hpp"
 | 
				
			||||||
 | 
					#include "../AppleIIgs/Target.hpp"
 | 
				
			||||||
#include "../Oric/Target.hpp"
 | 
					#include "../Oric/Target.hpp"
 | 
				
			||||||
#include "../Disassembler/6502.hpp"
 | 
					#include "../Disassembler/6502.hpp"
 | 
				
			||||||
#include "../Disassembler/AddressMapper.hpp"
 | 
					#include "../Disassembler/AddressMapper.hpp"
 | 
				
			||||||
@@ -18,10 +19,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace {
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
 | 
					Analyser::Static::Target *AppleIITarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
 | 
				
			||||||
	using Target = Analyser::Static::AppleII::Target;
 | 
						using Target = Analyser::Static::AppleII::Target;
 | 
				
			||||||
	auto *target = new Target;
 | 
						auto *const target = new Target;
 | 
				
			||||||
	target->machine = Analyser::Machine::AppleII;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
 | 
						if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
 | 
				
			||||||
		target->disk_controller = Target::DiskController::ThirteenSector;
 | 
							target->disk_controller = Target::DiskController::ThirteenSector;
 | 
				
			||||||
@@ -32,10 +32,13 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector
 | 
				
			|||||||
	return target;
 | 
						return target;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
 | 
					Analyser::Static::Target *AppleIIgsTarget() {
 | 
				
			||||||
 | 
						return new Analyser::Static::AppleIIgs::Target();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) {
 | 
				
			||||||
	using Target = Analyser::Static::Oric::Target;
 | 
						using Target = Analyser::Static::Oric::Target;
 | 
				
			||||||
	auto *target = new Target;
 | 
						auto *const target = new Target;
 | 
				
			||||||
	target->machine = Analyser::Machine::Oric;
 | 
					 | 
				
			||||||
	target->rom = Target::ROM::Pravetz;
 | 
						target->rom = Target::ROM::Pravetz;
 | 
				
			||||||
	target->disk_interface = Target::DiskInterface::Pravetz;
 | 
						target->disk_interface = Target::DiskInterface::Pravetz;
 | 
				
			||||||
	target->loading_command = "CALL 800\n";
 | 
						target->loading_command = "CALL 800\n";
 | 
				
			||||||
@@ -44,13 +47,23 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
	// This analyser can comprehend disks only.
 | 
						// This analyser can comprehend disks only.
 | 
				
			||||||
	if(media.disks.empty()) return {};
 | 
						if(media.disks.empty()) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto &disk = media.disks.front();
 | 
				
			||||||
 | 
						TargetList targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the disk image is too large for a 5.25" disk, map this to the IIgs.
 | 
				
			||||||
 | 
						if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
 | 
				
			||||||
 | 
							targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
 | 
				
			||||||
 | 
							targets.back()->media = media;
 | 
				
			||||||
 | 
							return targets;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Grab track 0, sector 0: the boot sector.
 | 
						// Grab track 0, sector 0: the boot sector.
 | 
				
			||||||
	auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
						const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
				
			||||||
	auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
 | 
						const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
 | 
				
			||||||
		Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
 | 
							Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
 | 
						const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
 | 
				
			||||||
@@ -63,12 +76,11 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// If there's no boot sector then if there are also no sectors at all,
 | 
						// If there's no boot sector then if there are also no sectors at all,
 | 
				
			||||||
	// decline to nominate a machine. Otherwise go with an Apple as the default.
 | 
						// decline to nominate a machine. Otherwise go with an Apple as the default.
 | 
				
			||||||
	TargetList targets;
 | 
					 | 
				
			||||||
	if(!sector_zero) {
 | 
						if(!sector_zero) {
 | 
				
			||||||
		if(sector_map.empty()) {
 | 
							if(sector_map.empty()) {
 | 
				
			||||||
			return targets;
 | 
								return targets;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr)));
 | 
								targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIITarget(nullptr)));
 | 
				
			||||||
			targets.back()->media = media;
 | 
								targets.back()->media = media;
 | 
				
			||||||
			return targets;
 | 
								return targets;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -77,7 +89,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
 | 
				
			|||||||
	// If the boot sector looks like it's intended for the Oric, create an Oric.
 | 
						// If the boot sector looks like it's intended for the Oric, create an Oric.
 | 
				
			||||||
	// Otherwise go with the Apple II.
 | 
						// Otherwise go with the Apple II.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
 | 
						const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool did_read_shift_register = false;
 | 
						bool did_read_shift_register = false;
 | 
				
			||||||
	bool is_oric = false;
 | 
						bool is_oric = false;
 | 
				
			||||||
@@ -118,7 +130,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
 | 
				
			|||||||
	if(is_oric) {
 | 
						if(is_oric) {
 | 
				
			||||||
		targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
 | 
							targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero)));
 | 
							targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIITarget(sector_zero)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	targets.back()->media = media;
 | 
						targets.back()->media = media;
 | 
				
			||||||
	return targets;
 | 
						return targets;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
 | 
				
			|||||||
	std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
 | 
						std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
 | 
				
			||||||
	if(segment.data.size() & 0x1fff) {
 | 
						if(segment.data.size() & 0x1fff) {
 | 
				
			||||||
		std::vector<uint8_t> truncated_data;
 | 
							std::vector<uint8_t> truncated_data;
 | 
				
			||||||
		std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff;
 | 
							std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
 | 
				
			||||||
		truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
 | 
							truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
 | 
				
			||||||
		output_segments.emplace_back(start_address, truncated_data);
 | 
							output_segments.emplace_back(start_address, truncated_data);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -35,7 +35,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto target = std::make_unique<Analyser::Static::MSX::Target>();
 | 
						auto target = std::make_unique<Analyser::Static::MSX::Target>();
 | 
				
			||||||
	target->machine = Analyser::Machine::MSX;
 | 
					 | 
				
			||||||
	target->confidence = confidence;
 | 
						target->confidence = confidence;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(type == Analyser::Static::MSX::Cartridge::Type::None) {
 | 
						if(type == Analyser::Static::MSX::Cartridge::Type::None) {
 | 
				
			||||||
@@ -97,7 +96,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
				
			|||||||
		// Reject cartridge if the ROM header wasn't found.
 | 
							// Reject cartridge if the ROM header wasn't found.
 | 
				
			||||||
		if(!found_start) continue;
 | 
							if(!found_start) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
 | 
							uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
 | 
				
			||||||
		// TODO: check for a rational init address?
 | 
							// TODO: check for a rational init address?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
 | 
							// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
 | 
				
			||||||
@@ -147,7 +146,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
				
			|||||||
//				) &&
 | 
					//				) &&
 | 
				
			||||||
//				((next_iterator->second.operand >> 13) != (0x4000 >> 13))
 | 
					//				((next_iterator->second.operand >> 13) != (0x4000 >> 13))
 | 
				
			||||||
//			) {
 | 
					//			) {
 | 
				
			||||||
//				const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
 | 
					//				const uint16_t address = uint16_t(next_iterator->second.operand);
 | 
				
			||||||
//				switch(iterator->second.operand) {
 | 
					//				switch(iterator->second.operand) {
 | 
				
			||||||
//					case 0x6000:
 | 
					//					case 0x6000:
 | 
				
			||||||
//						if(address >= 0x6000 && address < 0x8000) {
 | 
					//						if(address >= 0x6000 && address < 0x8000) {
 | 
				
			||||||
@@ -208,13 +207,13 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
				
			|||||||
			if(	instruction_pair.second.operation == Instruction::Operation::LD &&
 | 
								if(	instruction_pair.second.operation == Instruction::Operation::LD &&
 | 
				
			||||||
				instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
 | 
									instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
 | 
				
			||||||
				instruction_pair.second.source == Instruction::Location::A) {
 | 
									instruction_pair.second.source == Instruction::Location::A) {
 | 
				
			||||||
				address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
 | 
									address_counts[uint16_t(instruction_pair.second.operand)]++;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Weight confidences by number of observed hits.
 | 
							// Weight confidences by number of observed hits.
 | 
				
			||||||
		float total_hits =
 | 
							float total_hits =
 | 
				
			||||||
			static_cast<float>(
 | 
								float(
 | 
				
			||||||
				address_counts[0x6000] + address_counts[0x6800] +
 | 
									address_counts[0x6000] + address_counts[0x6800] +
 | 
				
			||||||
				address_counts[0x7000] + address_counts[0x7800] +
 | 
									address_counts[0x7000] + address_counts[0x7800] +
 | 
				
			||||||
				address_counts[0x77ff] + address_counts[0x8000] +
 | 
									address_counts[0x77ff] + address_counts[0x8000] +
 | 
				
			||||||
@@ -226,42 +225,42 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
				
			|||||||
			segment,
 | 
								segment,
 | 
				
			||||||
			start_address,
 | 
								start_address,
 | 
				
			||||||
			Analyser::Static::MSX::Cartridge::ASCII8kb,
 | 
								Analyser::Static::MSX::Cartridge::ASCII8kb,
 | 
				
			||||||
			static_cast<float>(	address_counts[0x6000] +
 | 
								float(	address_counts[0x6000] +
 | 
				
			||||||
								address_counts[0x6800] +
 | 
										address_counts[0x6800] +
 | 
				
			||||||
								address_counts[0x7000] +
 | 
										address_counts[0x7000] +
 | 
				
			||||||
								address_counts[0x7800]) / total_hits));
 | 
										address_counts[0x7800]) / total_hits));
 | 
				
			||||||
		targets.push_back(CartridgeTarget(
 | 
							targets.push_back(CartridgeTarget(
 | 
				
			||||||
			segment,
 | 
								segment,
 | 
				
			||||||
			start_address,
 | 
								start_address,
 | 
				
			||||||
			Analyser::Static::MSX::Cartridge::ASCII16kb,
 | 
								Analyser::Static::MSX::Cartridge::ASCII16kb,
 | 
				
			||||||
			static_cast<float>(	address_counts[0x6000] +
 | 
								float(	address_counts[0x6000] +
 | 
				
			||||||
								address_counts[0x7000] +
 | 
										address_counts[0x7000] +
 | 
				
			||||||
								address_counts[0x77ff]) / total_hits));
 | 
										address_counts[0x77ff]) / total_hits));
 | 
				
			||||||
		if(!is_ascii) {
 | 
							if(!is_ascii) {
 | 
				
			||||||
			targets.push_back(CartridgeTarget(
 | 
								targets.push_back(CartridgeTarget(
 | 
				
			||||||
				segment,
 | 
									segment,
 | 
				
			||||||
				start_address,
 | 
									start_address,
 | 
				
			||||||
				Analyser::Static::MSX::Cartridge::Konami,
 | 
									Analyser::Static::MSX::Cartridge::Konami,
 | 
				
			||||||
				static_cast<float>(	address_counts[0x6000] +
 | 
									float(	address_counts[0x6000] +
 | 
				
			||||||
									address_counts[0x8000] +
 | 
											address_counts[0x8000] +
 | 
				
			||||||
									address_counts[0xa000]) / total_hits));
 | 
											address_counts[0xa000]) / total_hits));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(!is_ascii) {
 | 
							if(!is_ascii) {
 | 
				
			||||||
			targets.push_back(CartridgeTarget(
 | 
								targets.push_back(CartridgeTarget(
 | 
				
			||||||
				segment,
 | 
									segment,
 | 
				
			||||||
				start_address,
 | 
									start_address,
 | 
				
			||||||
				Analyser::Static::MSX::Cartridge::KonamiWithSCC,
 | 
									Analyser::Static::MSX::Cartridge::KonamiWithSCC,
 | 
				
			||||||
				static_cast<float>(	address_counts[0x5000] +
 | 
									float(	address_counts[0x5000] +
 | 
				
			||||||
									address_counts[0x7000] +
 | 
											address_counts[0x7000] +
 | 
				
			||||||
									address_counts[0x9000] +
 | 
											address_counts[0x9000] +
 | 
				
			||||||
									address_counts[0xb000]) / total_hits));
 | 
											address_counts[0xb000]) / total_hits));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return targets;
 | 
						return targets;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
	TargetList destination;
 | 
						TargetList destination;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Append targets for any cartridges that look correct.
 | 
						// Append targets for any cartridges that look correct.
 | 
				
			||||||
@@ -295,7 +294,6 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
 | 
				
			|||||||
	target->has_disk_drive = !media.disks.empty();
 | 
						target->has_disk_drive = !media.disks.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!target->media.empty()) {
 | 
						if(!target->media.empty()) {
 | 
				
			||||||
		target->machine = Machine::MSX;
 | 
					 | 
				
			||||||
		target->confidence = 0.5;
 | 
							target->confidence = 0.5;
 | 
				
			||||||
		destination.push_back(std::move(target));
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
		for(std::size_t c = 0; c < sizeof(header); ++c) {
 | 
							for(std::size_t c = 0; c < sizeof(header); ++c) {
 | 
				
			||||||
			int next_byte = Parser::get_byte(*file_speed, tape_player);
 | 
								int next_byte = Parser::get_byte(*file_speed, tape_player);
 | 
				
			||||||
			if(next_byte == -1) break;
 | 
								if(next_byte == -1) break;
 | 
				
			||||||
			header[c] = static_cast<uint8_t>(next_byte);
 | 
								header[c] = uint8_t(next_byte);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool bytes_are_same = true;
 | 
							bool bytes_are_same = true;
 | 
				
			||||||
@@ -67,7 +67,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
		// Read file name.
 | 
							// Read file name.
 | 
				
			||||||
		char name[7];
 | 
							char name[7];
 | 
				
			||||||
		for(std::size_t c = 1; c < 6; ++c)
 | 
							for(std::size_t c = 1; c < 6; ++c)
 | 
				
			||||||
			name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
 | 
								name[c] = char(Parser::get_byte(*file_speed, tape_player));
 | 
				
			||||||
		name[6] = '\0';
 | 
							name[6] = '\0';
 | 
				
			||||||
		file.name = name;
 | 
							file.name = name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -82,7 +82,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
					int byte = Parser::get_byte(*file_speed, tape_player);
 | 
										int byte = Parser::get_byte(*file_speed, tape_player);
 | 
				
			||||||
					if(byte == -1) break;
 | 
										if(byte == -1) break;
 | 
				
			||||||
					contains_end_of_file |= (byte == 0x1a);
 | 
										contains_end_of_file |= (byte == 0x1a);
 | 
				
			||||||
					file.data.push_back(static_cast<uint8_t>(byte));
 | 
										file.data.push_back(uint8_t(byte));
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if(c != -1) break;
 | 
									if(c != -1) break;
 | 
				
			||||||
				if(contains_end_of_file) {
 | 
									if(contains_end_of_file) {
 | 
				
			||||||
@@ -105,13 +105,13 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
			for(c = 0; c < sizeof(locations); ++c) {
 | 
								for(c = 0; c < sizeof(locations); ++c) {
 | 
				
			||||||
				int byte = Parser::get_byte(*file_speed, tape_player);
 | 
									int byte = Parser::get_byte(*file_speed, tape_player);
 | 
				
			||||||
				if(byte == -1) break;
 | 
									if(byte == -1) break;
 | 
				
			||||||
				locations[c] = static_cast<uint8_t>(byte);
 | 
									locations[c] = uint8_t(byte);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if(c != sizeof(locations)) continue;
 | 
								if(c != sizeof(locations)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
 | 
								file.starting_address = uint16_t(locations[0] | (locations[1] << 8));
 | 
				
			||||||
			end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
 | 
								end_address = uint16_t(locations[2] | (locations[3] << 8));
 | 
				
			||||||
			file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
 | 
								file.entry_address = uint16_t(locations[4] | (locations[5] << 8));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(end_address < file.starting_address) continue;
 | 
								if(end_address < file.starting_address) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -119,7 +119,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
			while(length--) {
 | 
								while(length--) {
 | 
				
			||||||
				int byte = Parser::get_byte(*file_speed, tape_player);
 | 
									int byte = Parser::get_byte(*file_speed, tape_player);
 | 
				
			||||||
				if(byte == -1) continue;
 | 
									if(byte == -1) continue;
 | 
				
			||||||
				file.data.push_back(static_cast<uint8_t>(byte));
 | 
									file.data.push_back(uint8_t(byte));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			files.push_back(std::move(file));
 | 
								files.push_back(std::move(file));
 | 
				
			||||||
@@ -135,10 +135,10 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
			next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
 | 
								next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
 | 
								if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
 | 
				
			||||||
			file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
 | 
								file.data.push_back(uint8_t(next_address_buffer[0]));
 | 
				
			||||||
			file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
 | 
								file.data.push_back(uint8_t(next_address_buffer[1]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
 | 
								uint16_t next_address = uint16_t(next_address_buffer[0] | (next_address_buffer[1] << 8));
 | 
				
			||||||
			if(!next_address) {
 | 
								if(!next_address) {
 | 
				
			||||||
				files.push_back(std::move(file));
 | 
									files.push_back(std::move(file));
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
@@ -155,7 +155,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
				
			|||||||
					found_error = true;
 | 
										found_error = true;
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				file.data.push_back(static_cast<uint8_t>(byte));
 | 
									file.data.push_back(uint8_t(byte));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if(found_error) break;
 | 
								if(found_error) break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#ifndef Analyser_Static_MSX_Target_h
 | 
					#ifndef Analyser_Static_MSX_Target_h
 | 
				
			||||||
#define Analyser_Static_MSX_Target_h
 | 
					#define Analyser_Static_MSX_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,15 +18,24 @@ namespace Analyser {
 | 
				
			|||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace MSX {
 | 
					namespace MSX {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	bool has_disk_drive = false;
 | 
						bool has_disk_drive = false;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enum class Region {
 | 
						ReflectableEnum(Region,
 | 
				
			||||||
		Japan,
 | 
							Japan,
 | 
				
			||||||
		USA,
 | 
							USA,
 | 
				
			||||||
		Europe
 | 
							Europe
 | 
				
			||||||
	} region = Region::USA;
 | 
						);
 | 
				
			||||||
 | 
						Region region = Region::USA;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target(): Analyser::Static::Target(Machine::MSX) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(has_disk_drive);
 | 
				
			||||||
 | 
								DeclareField(region);
 | 
				
			||||||
 | 
								AnnounceEnum(Region);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
#include "StaticAnalyser.hpp"
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
#include "Target.hpp"
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
						// This analyser can comprehend disks and mass-storage devices only.
 | 
				
			||||||
	if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
 | 
						if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,9 +17,21 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
 | 
				
			|||||||
	Analyser::Static::TargetList targets;
 | 
						Analyser::Static::TargetList targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	using Target = Analyser::Static::Macintosh::Target;
 | 
						using Target = Analyser::Static::Macintosh::Target;
 | 
				
			||||||
	auto *target = new Target;
 | 
						auto *const target = new Target;
 | 
				
			||||||
	target->machine = Analyser::Machine::Macintosh;
 | 
					 | 
				
			||||||
	target->media = media;
 | 
						target->media = media;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If this is a single-sided floppy disk, guess the Macintosh 512kb.
 | 
				
			||||||
 | 
						if(media.mass_storage_devices.empty()) {
 | 
				
			||||||
 | 
							bool has_800kb_disks = false;
 | 
				
			||||||
 | 
							for(const auto &disk: media.disks) {
 | 
				
			||||||
 | 
								has_800kb_disks |= disk->get_head_count() > 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(!has_800kb_disks) {
 | 
				
			||||||
 | 
								target->model = Target::Model::Mac512k;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
						targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return targets;
 | 
						return targets;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,19 +9,25 @@
 | 
				
			|||||||
#ifndef Analyser_Static_Macintosh_Target_h
 | 
					#ifndef Analyser_Static_Macintosh_Target_h
 | 
				
			||||||
#define Analyser_Static_Macintosh_Target_h
 | 
					#define Analyser_Static_Macintosh_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Macintosh {
 | 
					namespace Macintosh {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class Model {
 | 
						ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
 | 
				
			||||||
		Mac128k,
 | 
					 | 
				
			||||||
		Mac512k,
 | 
					 | 
				
			||||||
		Mac512ke,
 | 
					 | 
				
			||||||
		MacPlus
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Model model = Model::MacPlus;
 | 
						Model model = Model::MacPlus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::Macintosh) {
 | 
				
			||||||
 | 
							// Boilerplate for declaring fields and potential values.
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(model);
 | 
				
			||||||
 | 
								AnnounceEnum(Model);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace Analyser::Static::Oric;
 | 
					using namespace Analyser::Static::Oric;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
 | 
				
			||||||
	int score = 0;
 | 
						int score = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(const auto address : disassembly.outward_calls)		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
						for(const auto address : disassembly.outward_calls)		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
				
			||||||
@@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons
 | 
				
			|||||||
	return score;
 | 
						return score;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	const std::set<uint16_t> rom_functions = {
 | 
						const std::set<uint16_t> rom_functions = {
 | 
				
			||||||
		0x0228,	0x022b,
 | 
							0x0228,	0x022b,
 | 
				
			||||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
							0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
				
			||||||
@@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
 | 
				
			|||||||
		0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
 | 
							0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Score(disassembly, rom_functions, variable_locations);
 | 
						return score(disassembly, rom_functions, variable_locations);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
					int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
				
			||||||
	const std::set<uint16_t> rom_functions = {
 | 
						const std::set<uint16_t> rom_functions = {
 | 
				
			||||||
		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
							0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
				
			||||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
							0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
				
			||||||
@@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
 | 
				
			|||||||
		0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
 | 
							0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Score(disassembly, rom_functions, variable_locations);
 | 
						return score(disassembly, rom_functions, variable_locations);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
 | 
					bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
		The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
 | 
							The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
 | 
				
			||||||
	*/
 | 
						*/
 | 
				
			||||||
@@ -100,17 +102,22 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			|||||||
	return !std::memcmp(signature, first_sample.data(), sizeof(signature));
 | 
						return !std::memcmp(signature, first_sample.data(), sizeof(signature));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
 | 
					bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
		The Jasmin boot sector is sector 1 of track 0 and is loaded at $400;
 | 
							Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
 | 
				
			||||||
		disassemble it to test it for validity.
 | 
							use disassembly to test for likely matches.
 | 
				
			||||||
	*/
 | 
						*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
 | 
						Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
 | 
				
			||||||
	if(!sector) return false;
 | 
						if(!sector) return false;
 | 
				
			||||||
	if(sector->samples.empty()) return false;
 | 
						if(sector->samples.empty()) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const std::vector<uint8_t> &first_sample = sector->samples[0];
 | 
						// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
 | 
				
			||||||
	if(first_sample.size() != 256) return false;
 | 
						std::vector<uint8_t> first_sample = sector->samples[0];
 | 
				
			||||||
 | 
						if(first_sample.size() < 256) return false;
 | 
				
			||||||
 | 
						if(first_sample.size() > 256) {
 | 
				
			||||||
 | 
							first_sample.erase(first_sample.end() - 256, first_sample.end());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Grab a disassembly.
 | 
						// Grab a disassembly.
 | 
				
			||||||
	const auto disassembly =
 | 
						const auto disassembly =
 | 
				
			||||||
@@ -120,17 +127,26 @@ static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			|||||||
	int register_hits = 0;
 | 
						int register_hits = 0;
 | 
				
			||||||
	for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
 | 
						for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
 | 
				
			||||||
		for(auto address : list) {
 | 
							for(auto address : list) {
 | 
				
			||||||
			register_hits += (address >= 0x3f4 && address <= 0x3ff);
 | 
								register_hits += (address >= range_start && address <= range_end);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Arbitrary, sure, but as long as at least two accesses to Jasmin registers are found, accept this.
 | 
						// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this.
 | 
				
			||||||
	return register_hits >= 2;
 | 
						return register_hits >= 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					bool is_jasmin(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			||||||
 | 
						return is_400_loader(parser, 0x3f4, 0x3ff);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
 | 
				
			||||||
 | 
						return is_400_loader(parser, 0x310, 0x323);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
	target->machine = Machine::Oric;
 | 
					 | 
				
			||||||
	target->confidence = 0.5;
 | 
						target->confidence = 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int basic10_votes = 0;
 | 
						int basic10_votes = 0;
 | 
				
			||||||
@@ -146,9 +162,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
 | 
				
			|||||||
					const Analyser::Static::MOS6502::Disassembly disassembly =
 | 
										const Analyser::Static::MOS6502::Disassembly disassembly =
 | 
				
			||||||
						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
 | 
											Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					int basic10_score = Basic10Score(disassembly);
 | 
										if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
 | 
				
			||||||
					int basic11_score = Basic11Score(disassembly);
 | 
					 | 
				
			||||||
					if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -158,17 +172,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!media.disks.empty()) {
 | 
						if(!media.disks.empty()) {
 | 
				
			||||||
		// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc and
 | 
							// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
 | 
				
			||||||
		// Jasmin formats here.
 | 
							// Jasmin and BD-DOS formats here.
 | 
				
			||||||
		for(auto &disk: media.disks) {
 | 
							for(auto &disk: media.disks) {
 | 
				
			||||||
			Storage::Encodings::MFM::Parser parser(true, disk);
 | 
								Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
			if(IsMicrodisc(parser)) {
 | 
					
 | 
				
			||||||
 | 
								if(is_microdisc(parser)) {
 | 
				
			||||||
				target->disk_interface = Target::DiskInterface::Microdisc;
 | 
									target->disk_interface = Target::DiskInterface::Microdisc;
 | 
				
			||||||
				target->media.disks.push_back(disk);
 | 
									target->media.disks.push_back(disk);
 | 
				
			||||||
			} else if(IsJasmin(parser)) {
 | 
								} else if(is_jasmin(parser)) {
 | 
				
			||||||
				target->disk_interface = Target::DiskInterface::Jasmin;
 | 
									target->disk_interface = Target::DiskInterface::Jasmin;
 | 
				
			||||||
				target->should_start_jasmin = true;
 | 
									target->should_start_jasmin = true;
 | 
				
			||||||
				target->media.disks.push_back(disk);
 | 
									target->media.disks.push_back(disk);
 | 
				
			||||||
 | 
								} else if(is_bd500(parser)) {
 | 
				
			||||||
 | 
									target->disk_interface = Target::DiskInterface::BD500;
 | 
				
			||||||
 | 
									target->media.disks.push_back(disk);
 | 
				
			||||||
 | 
									target->rom = Target::ROM::BASIC10;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,10 +49,10 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// read end and start addresses
 | 
							// read end and start addresses
 | 
				
			||||||
		new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
 | 
							new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
 | 
				
			||||||
		new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
 | 
							new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
 | 
				
			||||||
		new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
 | 
							new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
 | 
				
			||||||
		new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
 | 
							new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// skip an empty byte
 | 
							// skip an empty byte
 | 
				
			||||||
		parser.get_next_byte(tape, is_fast);
 | 
							parser.get_next_byte(tape, is_fast);
 | 
				
			||||||
@@ -61,7 +61,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
 | 
				
			|||||||
		char file_name[17];
 | 
							char file_name[17];
 | 
				
			||||||
		int name_pos = 0;
 | 
							int name_pos = 0;
 | 
				
			||||||
		while(name_pos < 16) {
 | 
							while(name_pos < 16) {
 | 
				
			||||||
			file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
 | 
								file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
 | 
				
			||||||
			if(!file_name[name_pos]) break;
 | 
								if(!file_name[name_pos]) break;
 | 
				
			||||||
			name_pos++;
 | 
								name_pos++;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -72,7 +72,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
 | 
				
			|||||||
		std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
 | 
							std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
 | 
				
			||||||
		new_file.data.reserve(body_length);
 | 
							new_file.data.reserve(body_length);
 | 
				
			||||||
		for(std::size_t c = 0; c < body_length; c++) {
 | 
							for(std::size_t c = 0; c < body_length; c++) {
 | 
				
			||||||
			new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
 | 
								new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// only one validation check: was there enough tape?
 | 
							// only one validation check: was there enough tape?
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#ifndef Analyser_Static_Oric_Target_h
 | 
					#ifndef Analyser_Static_Oric_Target_h
 | 
				
			||||||
#define Analyser_Static_Oric_Target_h
 | 
					#define Analyser_Static_Oric_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,24 +18,42 @@ namespace Analyser {
 | 
				
			|||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Oric {
 | 
					namespace Oric {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class ROM {
 | 
						ReflectableEnum(ROM,
 | 
				
			||||||
		BASIC10,
 | 
							BASIC10,
 | 
				
			||||||
		BASIC11,
 | 
							BASIC11,
 | 
				
			||||||
		Pravetz
 | 
							Pravetz
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enum class DiskInterface {
 | 
						ReflectableEnum(DiskInterface,
 | 
				
			||||||
 | 
							None,
 | 
				
			||||||
		Microdisc,
 | 
							Microdisc,
 | 
				
			||||||
		Pravetz,
 | 
							Pravetz,
 | 
				
			||||||
		Jasmin,
 | 
							Jasmin,
 | 
				
			||||||
		None
 | 
							BD500
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ReflectableEnum(Processor,
 | 
				
			||||||
 | 
							MOS6502,
 | 
				
			||||||
 | 
							WDC65816
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ROM rom = ROM::BASIC11;
 | 
						ROM rom = ROM::BASIC11;
 | 
				
			||||||
	DiskInterface disk_interface = DiskInterface::None;
 | 
						DiskInterface disk_interface = DiskInterface::None;
 | 
				
			||||||
 | 
						Processor processor = Processor::MOS6502;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
	bool should_start_jasmin = false;
 | 
						bool should_start_jasmin = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target(): Analyser::Static::Target(Machine::Oric) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(rom);
 | 
				
			||||||
 | 
								DeclareField(disk_interface);
 | 
				
			||||||
 | 
								DeclareField(processor);
 | 
				
			||||||
 | 
								AnnounceEnum(ROM);
 | 
				
			||||||
 | 
								AnnounceEnum(DiskInterface);
 | 
				
			||||||
 | 
								AnnounceEnum(Processor);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,15 +13,13 @@
 | 
				
			|||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
 | 
				
			||||||
	if(media.cartridges.empty())
 | 
						if(media.cartridges.empty())
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TargetList targets;
 | 
						TargetList targets;
 | 
				
			||||||
	auto target = std::make_unique<Target>();
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	target->machine = Machine::MasterSystem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
 | 
						// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
 | 
				
			||||||
	if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
 | 
						if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
 | 
				
			||||||
		target->model = Target::Model::SG1000;
 | 
							target->model = Target::Model::SG1000;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,23 +9,27 @@
 | 
				
			|||||||
#ifndef Analyser_Static_Sega_Target_h
 | 
					#ifndef Analyser_Static_Sega_Target_h
 | 
				
			||||||
#define Analyser_Static_Sega_Target_h
 | 
					#define Analyser_Static_Sega_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Analyser {
 | 
					namespace Analyser {
 | 
				
			||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace Sega {
 | 
					namespace Sega {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class Model {
 | 
						enum class Model {
 | 
				
			||||||
		SG1000,
 | 
							SG1000,
 | 
				
			||||||
		MasterSystem,
 | 
							MasterSystem,
 | 
				
			||||||
		MasterSystem2,
 | 
							MasterSystem2,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enum class Region {
 | 
						ReflectableEnum(Region,
 | 
				
			||||||
		Japan,
 | 
							Japan,
 | 
				
			||||||
		USA,
 | 
							USA,
 | 
				
			||||||
		Europe,
 | 
							Europe,
 | 
				
			||||||
		Brazil
 | 
							Brazil
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enum class PagingScheme {
 | 
						enum class PagingScheme {
 | 
				
			||||||
		Sega,
 | 
							Sega,
 | 
				
			||||||
@@ -35,6 +39,13 @@ struct Target: public ::Analyser::Static::Target {
 | 
				
			|||||||
	Model model = Model::MasterSystem;
 | 
						Model model = Model::MasterSystem;
 | 
				
			||||||
	Region region = Region::Japan;
 | 
						Region region = Region::Japan;
 | 
				
			||||||
	PagingScheme paging_scheme = PagingScheme::Sega;
 | 
						PagingScheme paging_scheme = PagingScheme::Sega;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target() : Analyser::Static::Target(Machine::MasterSystem) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(region);
 | 
				
			||||||
 | 
								AnnounceEnum(Region);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
 | 
					#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@
 | 
				
			|||||||
#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 "AppleIIgs/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Atari2600/StaticAnalyser.hpp"
 | 
					#include "Atari2600/StaticAnalyser.hpp"
 | 
				
			||||||
#include "AtariST/StaticAnalyser.hpp"
 | 
					#include "AtariST/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Coleco/StaticAnalyser.hpp"
 | 
					#include "Coleco/StaticAnalyser.hpp"
 | 
				
			||||||
@@ -27,12 +28,14 @@
 | 
				
			|||||||
#include "Oric/StaticAnalyser.hpp"
 | 
					#include "Oric/StaticAnalyser.hpp"
 | 
				
			||||||
#include "Sega/StaticAnalyser.hpp"
 | 
					#include "Sega/StaticAnalyser.hpp"
 | 
				
			||||||
#include "ZX8081/StaticAnalyser.hpp"
 | 
					#include "ZX8081/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "ZXSpectrum/StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Cartridges
 | 
					// Cartridges
 | 
				
			||||||
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
 | 
					#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
 | 
				
			||||||
#include "../../Storage/Cartridge/Formats/PRG.hpp"
 | 
					#include "../../Storage/Cartridge/Formats/PRG.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Disks
 | 
					// Disks
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
 | 
				
			||||||
#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"
 | 
				
			||||||
@@ -47,9 +50,11 @@
 | 
				
			|||||||
#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/ST.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
 | 
				
			||||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
					#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Mass Storage Devices (i.e. usually, hard disks)
 | 
					// Mass Storage Devices (i.e. usually, hard disks)
 | 
				
			||||||
 | 
					#include "../../Storage/MassStorage/Formats/DAT.hpp"
 | 
				
			||||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
 | 
					#include "../../Storage/MassStorage/Formats/HFV.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tapes
 | 
					// Tapes
 | 
				
			||||||
@@ -61,6 +66,7 @@
 | 
				
			|||||||
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
 | 
					#include "../../Storage/Tape/Formats/TapeUEF.hpp"
 | 
				
			||||||
#include "../../Storage/Tape/Formats/TZX.hpp"
 | 
					#include "../../Storage/Tape/Formats/TZX.hpp"
 | 
				
			||||||
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
 | 
					#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
 | 
				
			||||||
 | 
					#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Target Platform Types
 | 
					// Target Platform Types
 | 
				
			||||||
#include "../../Storage/TargetPlatforms.hpp"
 | 
					#include "../../Storage/TargetPlatforms.hpp"
 | 
				
			||||||
@@ -77,36 +83,52 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
				
			|||||||
	std::string extension = file_name.substr(final_dot + 1);
 | 
						std::string extension = file_name.substr(final_dot + 1);
 | 
				
			||||||
	std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
 | 
						std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define Insert(list, class, platforms) \
 | 
					#define InsertInstance(list, instance, platforms) \
 | 
				
			||||||
	list.emplace_back(new Storage::class(file_name));\
 | 
						list.emplace_back(instance);\
 | 
				
			||||||
	potential_platforms |= platforms;\
 | 
						potential_platforms |= platforms;\
 | 
				
			||||||
	TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
 | 
						TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
 | 
				
			||||||
	if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
 | 
						if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); \
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TryInsert(list, class, platforms) \
 | 
					#define Insert(list, class, platforms, ...) \
 | 
				
			||||||
 | 
						InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TryInsert(list, class, platforms, ...) \
 | 
				
			||||||
	try {\
 | 
						try {\
 | 
				
			||||||
		Insert(list, class, platforms) \
 | 
							Insert(list, class, platforms, __VA_ARGS__) \
 | 
				
			||||||
	} catch(...) {}
 | 
						} catch(...) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define Format(ext, list, class, platforms) \
 | 
					#define Format(ext, list, class, platforms) \
 | 
				
			||||||
	if(extension == ext)	{		\
 | 
						if(extension == ext)	{		\
 | 
				
			||||||
		TryInsert(list, class, platforms)	\
 | 
							TryInsert(list, class, platforms, file_name)	\
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2MG
 | 
				
			||||||
 | 
						if(extension == "2mg") {
 | 
				
			||||||
 | 
							// 2MG uses a factory method; defer to it.
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
 | 
				
			||||||
 | 
							} catch(...) {}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)			// ADL
 | 
				
			||||||
	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)						// BIN (cartridge dump)
 | 
						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::Coleco)								// 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("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn)							// DAT
 | 
				
			||||||
	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 | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)						// DSK (Amstrad CPC, etc)
 | 
				
			||||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II)
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II)
 | 
				
			||||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh, floppy disk)
 | 
						Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh, floppy disk)
 | 
				
			||||||
	Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk)
 | 
						Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk)
 | 
				
			||||||
@@ -116,7 +138,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
				
			|||||||
	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 | TargetPlatform::ZXSpectrum)
 | 
				
			||||||
			// 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("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)		// IMG (DiskCopy 4.2)
 | 
						Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)		// IMG (DiskCopy 4.2)
 | 
				
			||||||
	Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// IMG (DiskCopy 4.2)
 | 
						Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// IMG (DiskCopy 4.2)
 | 
				
			||||||
@@ -124,17 +146,23 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
 | 
				
			|||||||
	Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB
 | 
						Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB
 | 
				
			||||||
	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O
 | 
						Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O
 | 
				
			||||||
	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P
 | 
						Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P
 | 
				
			||||||
	Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// PO
 | 
						Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// PO (original Apple II kind)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// PO (Apple IIgs kind)
 | 
				
			||||||
 | 
						if(extension == "po")	{
 | 
				
			||||||
 | 
							TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P81
 | 
						Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P81
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// PRG
 | 
						// PRG
 | 
				
			||||||
	if(extension == "prg") {
 | 
						if(extension == "prg") {
 | 
				
			||||||
		// try instantiating as a ROM; failing that accept as a tape
 | 
							// try instantiating as a ROM; failing that accept as a tape
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
 | 
								Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
 | 
				
			||||||
		} catch(...) {
 | 
							} catch(...) {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
 | 
									Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
 | 
				
			||||||
			} catch(...) {}
 | 
								} catch(...) {}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -142,21 +170,24 @@ 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::Coleco | 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("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST)				// ST
 | 
						Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST)				// ST
 | 
				
			||||||
 | 
						Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST)				// STX
 | 
				
			||||||
	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)									// TAP (Commodore)
 | 
						Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)									// TAP (Commodore)
 | 
				
			||||||
	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)											// TAP (Oric)
 | 
						Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)											// TAP (Oric)
 | 
				
			||||||
 | 
						Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum)								// TAP (ZX Spectrum)
 | 
				
			||||||
	Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)													// TSX
 | 
						Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)													// TSX
 | 
				
			||||||
	Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)												// TZX
 | 
						Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum)					// TZX
 | 
				
			||||||
	Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)												// UEF (tape)
 | 
						Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)												// UEF (tape)
 | 
				
			||||||
	Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII)				// WOZ
 | 
						Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII)				// WOZ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#undef Format
 | 
					#undef Format
 | 
				
			||||||
#undef Insert
 | 
					#undef Insert
 | 
				
			||||||
#undef TryInsert
 | 
					#undef TryInsert
 | 
				
			||||||
 | 
					#undef InsertInstance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -176,26 +207,28 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Hand off to platform-specific determination of whether these things are actually compatible and,
 | 
						// Hand off to platform-specific determination of whether these things are actually compatible and,
 | 
				
			||||||
	// if so, how to load them.
 | 
						// if so, how to load them.
 | 
				
			||||||
	#define Append(x) {\
 | 
					#define Append(x) if(potential_platforms & TargetPlatform::x) {\
 | 
				
			||||||
		auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
 | 
						auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
 | 
				
			||||||
		std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
 | 
						std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
 | 
				
			||||||
	}
 | 
					}
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Acorn)			Append(Acorn);
 | 
						Append(Acorn);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::AmstradCPC)	Append(AmstradCPC);
 | 
						Append(AmstradCPC);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::AppleII)		Append(AppleII);
 | 
						Append(AppleII);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Atari2600)		Append(Atari2600);
 | 
						Append(AppleIIgs);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::AtariST)		Append(AtariST);
 | 
						Append(Atari2600);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco);
 | 
						Append(AtariST);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore);
 | 
						Append(Coleco);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII);
 | 
						Append(Commodore);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Macintosh)		Append(Macintosh);
 | 
						Append(DiskII);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::MSX)			Append(MSX);
 | 
						Append(Macintosh);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Oric)			Append(Oric);
 | 
						Append(MSX);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::Sega)			Append(Sega);
 | 
						Append(Oric);
 | 
				
			||||||
	if(potential_platforms & TargetPlatform::ZX8081)		Append(ZX8081);
 | 
						Append(Sega);
 | 
				
			||||||
	#undef Append
 | 
						Append(ZX8081);
 | 
				
			||||||
 | 
						Append(ZXSpectrum);
 | 
				
			||||||
 | 
					#undef Append
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Reset any tapes to their initial position
 | 
						// Reset any tapes to their initial position.
 | 
				
			||||||
	for(const auto &target : targets) {
 | 
						for(const auto &target : targets) {
 | 
				
			||||||
		for(auto &tape : target->media.tapes) {
 | 
							for(auto &tape : target->media.tapes) {
 | 
				
			||||||
			tape->reset();
 | 
								tape->reset();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,16 @@ struct Media {
 | 
				
			|||||||
	bool empty() const {
 | 
						bool empty() const {
 | 
				
			||||||
		return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
 | 
							return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Media &operator +=(const Media &rhs) {
 | 
				
			||||||
 | 
					#define append(name)	name.insert(name.end(), rhs.name.begin(), rhs.name.end());
 | 
				
			||||||
 | 
							append(disks);
 | 
				
			||||||
 | 
							append(tapes);
 | 
				
			||||||
 | 
							append(cartridges);
 | 
				
			||||||
 | 
							append(mass_storage_devices);
 | 
				
			||||||
 | 
					#undef append
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -42,11 +52,12 @@ struct Media {
 | 
				
			|||||||
	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
 | 
						and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
struct Target {
 | 
					struct Target {
 | 
				
			||||||
 | 
						Target(Machine machine) : machine(machine) {}
 | 
				
			||||||
	virtual ~Target() {}
 | 
						virtual ~Target() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Machine machine;
 | 
						Machine machine;
 | 
				
			||||||
	Media media;
 | 
						Media media;
 | 
				
			||||||
	float confidence;
 | 
						float confidence = 0.0f;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
 | 
					typedef std::vector<std::unique_ptr<Target>> TargetList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,13 +28,13 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
 | 
				
			|||||||
	return files;
 | 
						return files;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
 | 
					Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
 | 
				
			||||||
	TargetList destination;
 | 
						TargetList destination;
 | 
				
			||||||
	if(!media.tapes.empty()) {
 | 
						if(!media.tapes.empty()) {
 | 
				
			||||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
							std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
				
			||||||
		media.tapes.front()->reset();
 | 
							media.tapes.front()->reset();
 | 
				
			||||||
		if(!files.empty()) {
 | 
							if(!files.empty()) {
 | 
				
			||||||
			Target *target = new Target;
 | 
								Target *const target = new Target;
 | 
				
			||||||
			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
 | 
								destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
 | 
				
			||||||
			target->machine = Machine::ZX8081;
 | 
								target->machine = Machine::ZX8081;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#ifndef Analyser_Static_ZX8081_Target_h
 | 
					#ifndef Analyser_Static_ZX8081_Target_h
 | 
				
			||||||
#define Analyser_Static_ZX8081_Target_h
 | 
					#define Analyser_Static_ZX8081_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
#include "../StaticAnalyser.hpp"
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,17 +18,26 @@ namespace Analyser {
 | 
				
			|||||||
namespace Static {
 | 
					namespace Static {
 | 
				
			||||||
namespace ZX8081 {
 | 
					namespace ZX8081 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Target: public ::Analyser::Static::Target {
 | 
					struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
	enum class MemoryModel {
 | 
						ReflectableEnum(MemoryModel,
 | 
				
			||||||
		Unexpanded,
 | 
							Unexpanded,
 | 
				
			||||||
		SixteenKB,
 | 
							SixteenKB,
 | 
				
			||||||
		SixtyFourKB
 | 
							SixtyFourKB
 | 
				
			||||||
	};
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	MemoryModel memory_model = MemoryModel::Unexpanded;
 | 
						MemoryModel memory_model = MemoryModel::Unexpanded;
 | 
				
			||||||
	bool is_ZX81 = false;
 | 
						bool is_ZX81 = false;
 | 
				
			||||||
	bool ZX80_uses_ZX81_ROM = false;
 | 
						bool ZX80_uses_ZX81_ROM = false;
 | 
				
			||||||
	std::string loading_command;
 | 
						std::string loading_command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target(): Analyser::Static::Target(Machine::ZX8081) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(memory_model);
 | 
				
			||||||
 | 
								DeclareField(is_ZX81);
 | 
				
			||||||
 | 
								DeclareField(ZX80_uses_ZX81_ROM);
 | 
				
			||||||
 | 
								AnnounceEnum(MemoryModel);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										95
									
								
								Analyser/Static/ZXSpectrum/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								Analyser/Static/ZXSpectrum/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.cpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 17/03/2021.
 | 
				
			||||||
 | 
					//  Copyright © 2021 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Target.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
				
			||||||
 | 
						using Parser = Storage::Tape::ZXSpectrum::Parser;
 | 
				
			||||||
 | 
						Parser parser(Parser::MachineType::ZXSpectrum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while(true) {
 | 
				
			||||||
 | 
							const auto block = parser.find_block(tape);
 | 
				
			||||||
 | 
							if(!block) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check for a Spectrum header block.
 | 
				
			||||||
 | 
							if(block->type == 0x00) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
				
			||||||
 | 
						Storage::Encodings::MFM::Parser parser(true, disk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get logical sector 1; the Spectrum appears to support various physical
 | 
				
			||||||
 | 
						// sectors as sector 1.
 | 
				
			||||||
 | 
						Storage::Encodings::MFM::Sector *boot_sector = nullptr;
 | 
				
			||||||
 | 
						uint8_t sector_mask = 0;
 | 
				
			||||||
 | 
						while(!boot_sector) {
 | 
				
			||||||
 | 
							boot_sector = parser.get_sector(0, 0, sector_mask + 1);
 | 
				
			||||||
 | 
							sector_mask += 0x40;
 | 
				
			||||||
 | 
							if(!sector_mask) break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(!boot_sector) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test that the contents of the boot sector sum to 3, modulo 256.
 | 
				
			||||||
 | 
						uint8_t byte_sum = 0;
 | 
				
			||||||
 | 
						for(auto byte: boot_sector->samples[0]) {
 | 
				
			||||||
 | 
							byte_sum += byte;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return byte_sum == 3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
				
			||||||
 | 
						TargetList destination;
 | 
				
			||||||
 | 
						auto target = std::make_unique<Target>();
 | 
				
			||||||
 | 
						target->confidence = 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(!media.tapes.empty()) {
 | 
				
			||||||
 | 
							bool has_spectrum_tape = false;
 | 
				
			||||||
 | 
							for(auto &tape: media.tapes) {
 | 
				
			||||||
 | 
								has_spectrum_tape |= IsSpectrumTape(tape);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(has_spectrum_tape) {
 | 
				
			||||||
 | 
								target->media.tapes = media.tapes;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(!media.disks.empty()) {
 | 
				
			||||||
 | 
							bool has_spectrum_disk = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for(auto &disk: media.disks) {
 | 
				
			||||||
 | 
								has_spectrum_disk |= IsSpectrumDisk(disk);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(has_spectrum_disk) {
 | 
				
			||||||
 | 
								target->media.disks = media.disks;
 | 
				
			||||||
 | 
								target->model = Target::Model::Plus3;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If any media survived, add the target.
 | 
				
			||||||
 | 
						if(!target->media.empty()) {
 | 
				
			||||||
 | 
							target->should_hold_enter = true;	// To force entry into the 'loader' and thereby load the media.
 | 
				
			||||||
 | 
							destination.push_back(std::move(target));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return destination;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								Analyser/Static/ZXSpectrum/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/ZXSpectrum/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  StaticAnalyser.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 17/03/2021.
 | 
				
			||||||
 | 
					//  Copyright © 2021 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					#define Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					#include "../../../Storage/TargetPlatforms.hpp"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace ZXSpectrum {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* StaticAnalyser_hpp */
 | 
				
			||||||
							
								
								
									
										45
									
								
								Analyser/Static/ZXSpectrum/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Analyser/Static/ZXSpectrum/Target.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Target.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 18/03/2021.
 | 
				
			||||||
 | 
					//  Copyright © 2021 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Analyser_Static_ZXSpectrum_Target_h
 | 
				
			||||||
 | 
					#define Analyser_Static_ZXSpectrum_Target_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../Reflection/Enum.hpp"
 | 
				
			||||||
 | 
					#include "../../../Reflection/Struct.hpp"
 | 
				
			||||||
 | 
					#include "../StaticAnalyser.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Analyser {
 | 
				
			||||||
 | 
					namespace Static {
 | 
				
			||||||
 | 
					namespace ZXSpectrum {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
				
			||||||
 | 
						ReflectableEnum(Model,
 | 
				
			||||||
 | 
							SixteenK,
 | 
				
			||||||
 | 
							FortyEightK,
 | 
				
			||||||
 | 
							OneTwoEightK,
 | 
				
			||||||
 | 
							Plus2,
 | 
				
			||||||
 | 
							Plus2a,
 | 
				
			||||||
 | 
							Plus3,
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Model model = Model::Plus2;
 | 
				
			||||||
 | 
						bool should_hold_enter = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
 | 
				
			||||||
 | 
							if(needs_declare()) {
 | 
				
			||||||
 | 
								DeclareField(model);
 | 
				
			||||||
 | 
								AnnounceEnum(Model);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Target_h */
 | 
				
			||||||
@@ -10,7 +10,9 @@
 | 
				
			|||||||
#define ClockReceiver_hpp
 | 
					#define ClockReceiver_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "ForceInline.hpp"
 | 
					#include "ForceInline.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
	Informal pattern for all classes that run from a clock cycle:
 | 
						Informal pattern for all classes that run from a clock cycle:
 | 
				
			||||||
@@ -176,7 +178,9 @@ class Cycles: public WrappedInt<Cycles> {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
 | 
							forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
 | 
				
			||||||
		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
 | 
							forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
 | 
				
			||||||
		forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
 | 
							forceinline static constexpr Cycles max() {
 | 
				
			||||||
 | 
								return Cycles(std::numeric_limits<IntType>::max());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		friend WrappedInt;
 | 
							friend WrappedInt;
 | 
				
			||||||
@@ -196,9 +200,11 @@ class HalfCycles: public WrappedInt<HalfCycles> {
 | 
				
			|||||||
	public:
 | 
						public:
 | 
				
			||||||
		forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
 | 
							forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
 | 
				
			||||||
		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
 | 
							forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
 | 
				
			||||||
 | 
							forceinline static constexpr HalfCycles max() {
 | 
				
			||||||
 | 
								return HalfCycles(std::numeric_limits<IntType>::max());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
 | 
							forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
 | 
				
			||||||
		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.
 | 
				
			||||||
		forceinline constexpr Cycles cycles() const {
 | 
							forceinline constexpr Cycles cycles() const {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,7 +67,7 @@ class Source {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns the current preferred clocking strategy.
 | 
							/// @returns the current preferred clocking strategy.
 | 
				
			||||||
		virtual Preference preferred_clocking() = 0;
 | 
							virtual Preference preferred_clocking() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		Observer *observer_ = nullptr;
 | 
							Observer *observer_ = nullptr;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,62 +13,68 @@
 | 
				
			|||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	A DeferredQueue maintains a list of ordered actions and the times at which
 | 
						Provides the logic to insert into and traverse a list of future scheduled items.
 | 
				
			||||||
	they should happen, and divides a total execution period up into the portions
 | 
					 | 
				
			||||||
	that occur between those actions, triggering each action when it is reached.
 | 
					 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
template <typename TimeUnit> class DeferredQueue {
 | 
					template <typename TimeUnit> class DeferredQueue {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
 | 
					 | 
				
			||||||
		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.
 | 
				
			||||||
 | 
					 | 
				
			||||||
			Actions must be scheduled in the order they will occur. It is undefined behaviour
 | 
					 | 
				
			||||||
			to schedule them out of order.
 | 
					 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		void defer(TimeUnit delay, const std::function<void(void)> &action) {
 | 
							void defer(TimeUnit delay, const std::function<void(void)> &action) {
 | 
				
			||||||
			pending_actions_.emplace_back(delay, action);
 | 
								// Apply immediately if there's no delay (or a negative delay).
 | 
				
			||||||
		}
 | 
								if(delay <= TimeUnit(0)) {
 | 
				
			||||||
 | 
									action();
 | 
				
			||||||
		/*!
 | 
					 | 
				
			||||||
			Runs for @c length units of time.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			The constructor-supplied target will be called with one or more periods that add up to @c length;
 | 
					 | 
				
			||||||
			any scheduled actions will be called between periods.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void run_for(TimeUnit length) {
 | 
					 | 
				
			||||||
			// If there are no pending actions, just run for the entire length.
 | 
					 | 
				
			||||||
			// This should be the normal branch.
 | 
					 | 
				
			||||||
			if(pending_actions_.empty()) {
 | 
					 | 
				
			||||||
				target_(length);
 | 
					 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Divide the time to run according to the pending actions.
 | 
								if(!pending_actions_.empty()) {
 | 
				
			||||||
			while(length > TimeUnit(0)) {
 | 
									// Otherwise enqueue, having subtracted the delay for any preceding events,
 | 
				
			||||||
				TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
 | 
									// and subtracting from the subsequent, if any.
 | 
				
			||||||
				target_(next_period);
 | 
									auto insertion_point = pending_actions_.begin();
 | 
				
			||||||
				length -= next_period;
 | 
									while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
 | 
				
			||||||
 | 
										delay -= insertion_point->delay;
 | 
				
			||||||
 | 
										++insertion_point;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if(insertion_point != pending_actions_.end()) {
 | 
				
			||||||
 | 
										insertion_point->delay -= delay;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				off_t performances = 0;
 | 
									pending_actions_.emplace(insertion_point, delay, action);
 | 
				
			||||||
				for(auto &action: pending_actions_) {
 | 
								} else {
 | 
				
			||||||
					action.delay -= next_period;
 | 
									pending_actions_.emplace_back(delay, action);
 | 
				
			||||||
					if(!action.delay) {
 | 
								}
 | 
				
			||||||
						action.action();
 | 
							}
 | 
				
			||||||
						++performances;
 | 
					
 | 
				
			||||||
					}
 | 
							/*!
 | 
				
			||||||
				}
 | 
								@returns The amount of time until the next enqueued action will occur,
 | 
				
			||||||
				if(performances) {
 | 
									or TimeUnit(-1) if the queue is empty.
 | 
				
			||||||
					pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
 | 
							*/
 | 
				
			||||||
 | 
							TimeUnit time_until_next_action() const {
 | 
				
			||||||
 | 
								if(pending_actions_.empty()) return TimeUnit(-1);
 | 
				
			||||||
 | 
								return pending_actions_.front().delay;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Advances the queue the specified amount of time, performing any actions it reaches.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void advance(TimeUnit time) {
 | 
				
			||||||
 | 
								auto erase_iterator = pending_actions_.begin();
 | 
				
			||||||
 | 
								while(erase_iterator != pending_actions_.end()) {
 | 
				
			||||||
 | 
									erase_iterator->delay -= time;
 | 
				
			||||||
 | 
									if(erase_iterator->delay <= TimeUnit(0)) {
 | 
				
			||||||
 | 
										time = -erase_iterator->delay;
 | 
				
			||||||
 | 
										erase_iterator->action();
 | 
				
			||||||
 | 
										++erase_iterator;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if(erase_iterator != pending_actions_.begin()) {
 | 
				
			||||||
 | 
									pending_actions_.erase(pending_actions_.begin(), erase_iterator);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		std::function<void(TimeUnit)> target_;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// The list of deferred actions.
 | 
							// The list of deferred actions.
 | 
				
			||||||
		struct DeferredAction {
 | 
							struct DeferredAction {
 | 
				
			||||||
			TimeUnit delay;
 | 
								TimeUnit delay;
 | 
				
			||||||
@@ -79,4 +85,40 @@ template <typename TimeUnit> class DeferredQueue {
 | 
				
			|||||||
		std::vector<DeferredAction> pending_actions_;
 | 
							std::vector<DeferredAction> pending_actions_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						that occur between those actions, triggering each action when it is reached.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						This list is efficient only for short queues.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
 | 
				
			||||||
 | 
							constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Runs for @c length units of time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								The constructor-supplied target will be called with one or more periods that add up to @c length;
 | 
				
			||||||
 | 
								any scheduled actions will be called between periods.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void run_for(TimeUnit length) {
 | 
				
			||||||
 | 
								auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
 | 
				
			||||||
 | 
								while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
 | 
				
			||||||
 | 
									target_(time_to_next);
 | 
				
			||||||
 | 
									length -= time_to_next;
 | 
				
			||||||
 | 
									DeferredQueue<TimeUnit>::advance(time_to_next);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								DeferredQueue<TimeUnit>::advance(length);
 | 
				
			||||||
 | 
								target_(length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							std::function<void(TimeUnit)> target_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* DeferredQueue_h */
 | 
					#endif /* DeferredQueue_h */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
#define JustInTime_h
 | 
					#define JustInTime_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../Concurrency/AsyncTaskQueue.hpp"
 | 
					#include "../Concurrency/AsyncTaskQueue.hpp"
 | 
				
			||||||
 | 
					#include "ClockingHintSource.hpp"
 | 
				
			||||||
#include "ForceInline.hpp"
 | 
					#include "ForceInline.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -21,39 +22,145 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
 | 
						Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
 | 
				
			||||||
	separate @c TargetTimeScale at template declaration.
 | 
						separate @c TargetTimeScale at template declaration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
 | 
				
			||||||
 | 
						as and when sequence points are hit. Callers can use will_flush() to predict these.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						If the held object is a subclass of ClockingHint::Source, this template will register as an
 | 
				
			||||||
 | 
						observer and potentially stop clocking or stop delaying clocking until just-in-time references
 | 
				
			||||||
 | 
						as directed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TODO: incorporate and codify AsyncJustInTimeActor.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
 | 
					template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int divider = 1> class JustInTimeActor:
 | 
				
			||||||
 | 
						public ClockingHint::Observer {
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
 | 
				
			||||||
 | 
								to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								**Does not delete the object.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								This is used by the -> operators below, which provide a unique pointer to the enclosed object and
 | 
				
			||||||
 | 
								update their sequence points upon its destruction — i.e. after the caller has made whatever call
 | 
				
			||||||
 | 
								or calls as were relevant to the enclosed object.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							class SequencePointAwareDeleter {
 | 
				
			||||||
 | 
								public:
 | 
				
			||||||
 | 
									explicit SequencePointAwareDeleter(JustInTimeActor<T, LocalTimeScale, multiplier, divider> *actor) noexcept
 | 
				
			||||||
 | 
										: actor_(actor) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									forceinline void operator ()(const T *const) const {
 | 
				
			||||||
 | 
										if constexpr (has_sequence_points<T>::value) {
 | 
				
			||||||
 | 
											actor_->update_sequence_point();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								private:
 | 
				
			||||||
 | 
									JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor_;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This block of SFINAE determines whether objects of type T accepts Cycles or HalfCycles.
 | 
				
			||||||
 | 
							using HalfRunFor = void (T::*const)(HalfCycles);
 | 
				
			||||||
 | 
							static uint8_t half_sig(...);
 | 
				
			||||||
 | 
							static uint16_t half_sig(HalfRunFor);
 | 
				
			||||||
 | 
							using TargetTimeScale =
 | 
				
			||||||
 | 
								std::conditional_t<
 | 
				
			||||||
 | 
									sizeof(half_sig(&T::run_for)) == sizeof(uint16_t),
 | 
				
			||||||
 | 
									HalfCycles,
 | 
				
			||||||
 | 
									Cycles>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
 | 
							/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
 | 
				
			||||||
		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
 | 
							template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
 | 
				
			||||||
 | 
								if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
 | 
				
			||||||
 | 
									object_.set_clocking_hint_observer(this);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Adds time to the actor.
 | 
							/// Adds time to the actor.
 | 
				
			||||||
		forceinline void operator += (const LocalTimeScale &rhs) {
 | 
							///
 | 
				
			||||||
 | 
							/// @returns @c true if adding time caused a flush; @c false otherwise.
 | 
				
			||||||
 | 
							forceinline bool operator += (LocalTimeScale rhs) {
 | 
				
			||||||
 | 
								if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
 | 
				
			||||||
 | 
									if(clocking_preference_ == ClockingHint::Preference::None) {
 | 
				
			||||||
 | 
										return false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if constexpr (multiplier != 1) {
 | 
								if constexpr (multiplier != 1) {
 | 
				
			||||||
				time_since_update_ += rhs * multiplier;
 | 
									time_since_update_ += rhs * multiplier;
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				time_since_update_ += rhs;
 | 
									time_since_update_ += rhs;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			is_flushed_ = false;
 | 
								is_flushed_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
 | 
				
			||||||
 | 
									if (clocking_preference_ == ClockingHint::Preference::RealTime) {
 | 
				
			||||||
 | 
										flush();
 | 
				
			||||||
 | 
										return true;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if constexpr (has_sequence_points<T>::value) {
 | 
				
			||||||
 | 
									time_until_event_ -= rhs;
 | 
				
			||||||
 | 
									if(time_until_event_ <= LocalTimeScale(0)) {
 | 
				
			||||||
 | 
										time_overrun_ = time_until_event_;
 | 
				
			||||||
 | 
										flush();
 | 
				
			||||||
 | 
										update_sequence_point();
 | 
				
			||||||
 | 
										return true;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Flushes all accumulated time and returns a pointer to the included object.
 | 
							/// Flushes all accumulated time and returns a pointer to the included object.
 | 
				
			||||||
		forceinline T *operator->() {
 | 
							///
 | 
				
			||||||
 | 
							/// If this object provides sequence points, checks for changes to the next
 | 
				
			||||||
 | 
							/// sequence point upon deletion of the pointer.
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline auto operator->() {
 | 
				
			||||||
			flush();
 | 
								flush();
 | 
				
			||||||
 | 
								return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Acts exactly as per the standard ->, but preserves constness.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline auto operator -> () const {
 | 
				
			||||||
 | 
								auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
 | 
				
			||||||
 | 
								non_const_this->flush();
 | 
				
			||||||
 | 
								return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns a pointer to the included object, without flushing time.
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline T *last_valid() {
 | 
				
			||||||
			return &object_;
 | 
								return &object_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Returns a pointer to the included object without flushing time.
 | 
							/// @returns a const pointer to the included object, without flushing time.
 | 
				
			||||||
		forceinline T *last_valid() {
 | 
							[[nodiscard]] forceinline const T *last_valid() const {
 | 
				
			||||||
			return &object_;
 | 
								return &object_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the amount of time since the object was last flushed, in the target time scale.
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
 | 
				
			||||||
 | 
								// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
 | 
				
			||||||
 | 
								if constexpr (divider == 1) {
 | 
				
			||||||
 | 
									return time_since_update_;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return TargetTimeScale(time_since_update_.as_integral() / divider);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Flushes all accumulated time.
 | 
							/// Flushes all accumulated time.
 | 
				
			||||||
 | 
							///
 | 
				
			||||||
 | 
							/// This does not affect this actor's record of when the next sequence point will occur.
 | 
				
			||||||
		forceinline void flush() {
 | 
							forceinline void flush() {
 | 
				
			||||||
			if(!is_flushed_) {
 | 
								if(!is_flushed_) {
 | 
				
			||||||
				is_flushed_ = true;
 | 
									did_flush_ = is_flushed_ = true;
 | 
				
			||||||
				if constexpr (divider == 1) {
 | 
									if constexpr (divider == 1) {
 | 
				
			||||||
					object_.run_for(time_since_update_.template flush<TargetTimeScale>());
 | 
										const auto duration = time_since_update_.template flush<TargetTimeScale>();
 | 
				
			||||||
 | 
										object_.run_for(duration);
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
 | 
										const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
 | 
				
			||||||
					if(duration > TargetTimeScale(0))
 | 
										if(duration > TargetTimeScale(0))
 | 
				
			||||||
@@ -62,14 +169,63 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Indicates whether a flush has occurred since the last call to did_flush().
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline bool did_flush() {
 | 
				
			||||||
 | 
								const bool did_flush = did_flush_;
 | 
				
			||||||
 | 
								did_flush_ = false;
 | 
				
			||||||
 | 
								return did_flush;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
 | 
				
			||||||
 | 
							/// point from the final time at the end of the += that triggered the sequence point.
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
 | 
				
			||||||
 | 
								return time_overrun_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
 | 
				
			||||||
 | 
							/// supports sequence points; @c LocalTimeScale() otherwise.
 | 
				
			||||||
 | 
							[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
 | 
				
			||||||
 | 
								return time_until_event_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
 | 
				
			||||||
 | 
							[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
 | 
				
			||||||
 | 
								if constexpr (!has_sequence_points<T>::value) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return rhs >= time_until_event_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Updates this template's record of the next sequence point.
 | 
				
			||||||
 | 
							void update_sequence_point() {
 | 
				
			||||||
 | 
								if constexpr (has_sequence_points<T>::value) {
 | 
				
			||||||
 | 
									time_until_event_ = object_.get_next_sequence_point();
 | 
				
			||||||
 | 
									assert(time_until_event_ > LocalTimeScale(0));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns A cached copy of the object's clocking preference.
 | 
				
			||||||
 | 
							ClockingHint::Preference clocking_preference() const {
 | 
				
			||||||
 | 
								return clocking_preference_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		T object_;
 | 
							T object_;
 | 
				
			||||||
		LocalTimeScale time_since_update_;
 | 
							LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
 | 
				
			||||||
		bool is_flushed_ = true;
 | 
							bool is_flushed_ = true;
 | 
				
			||||||
 | 
							bool did_flush_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							template <typename S, typename = void> struct has_sequence_points : std::false_type {};
 | 
				
			||||||
 | 
							template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
 | 
				
			||||||
 | 
							void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
 | 
				
			||||||
 | 
								clocking_preference_ = clocking;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
	A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
 | 
						An AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
 | 
				
			||||||
	Any time the amount of accumulated time crosses a threshold provided at construction time,
 | 
						Any time the amount of accumulated time crosses a threshold provided at construction time,
 | 
				
			||||||
	the object will be updated on the AsyncTaskQueue.
 | 
						the object will be updated on the AsyncTaskQueue.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										88
									
								
								ClockReceiver/ScanSynchroniser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								ClockReceiver/ScanSynchroniser.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  ScanSynchroniser.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 09/02/2020.
 | 
				
			||||||
 | 
					//  Copyright © 2020 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef ScanSynchroniser_h
 | 
				
			||||||
 | 
					#define ScanSynchroniser_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../Outputs/ScanTarget.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Time {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Where an emulated machine is sufficiently close to a host machine's frame rate that a small nudge in
 | 
				
			||||||
 | 
						its speed multiplier will bring it into frame synchronisation, the ScanSynchroniser provides a sequence of
 | 
				
			||||||
 | 
						speed multipliers designed both to adjust the machine to the proper speed and, in a reasonable amount
 | 
				
			||||||
 | 
						of time, to bring it into phase.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class ScanSynchroniser {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns @c true if the emulated machine can be synchronised with the host frame output based on its
 | 
				
			||||||
 | 
									current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) {
 | 
				
			||||||
 | 
								ratio_ = 1.0;
 | 
				
			||||||
 | 
								if(scan_status.field_duration_gradient < 0.00001) {
 | 
				
			||||||
 | 
									// Check out the machine's current frame time.
 | 
				
			||||||
 | 
									// If it's within 3% of a non-zero integer multiple of the
 | 
				
			||||||
 | 
									// display rate, mark this time window to be split over the sync.
 | 
				
			||||||
 | 
									ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
 | 
				
			||||||
 | 
									const double integer_ratio = round(ratio_);
 | 
				
			||||||
 | 
									if(integer_ratio > 0.0) {
 | 
				
			||||||
 | 
										ratio_ /= integer_ratio;
 | 
				
			||||||
 | 
										return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
 | 
				
			||||||
 | 
									Results are undefined if @c can_synchroise returned @c false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
 | 
				
			||||||
 | 
								// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
 | 
				
			||||||
 | 
								// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
 | 
				
			||||||
 | 
								// no benefit to second guessing it here — just take it to be correct.
 | 
				
			||||||
 | 
								//
 | 
				
			||||||
 | 
								// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
 | 
				
			||||||
 | 
								// So the set speed multiplier may be adjusted slightly to aim for that.
 | 
				
			||||||
 | 
								double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
 | 
				
			||||||
 | 
								if(scan_status.current_position > 0.0) {
 | 
				
			||||||
 | 
									if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
 | 
				
			||||||
 | 
									else speed_multiplier *= phase_adjustment_ratio;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
 | 
				
			||||||
 | 
								return speed_multiplier_ * base_multiplier_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_base_speed_multiplier(double multiplier) {
 | 
				
			||||||
 | 
								base_multiplier_ = multiplier;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							double get_base_speed_multiplier() {
 | 
				
			||||||
 | 
								return base_multiplier_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							static constexpr double maximum_rate_adjustment = 1.03;
 | 
				
			||||||
 | 
							static constexpr double phase_adjustment_ratio = 1.005;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Managed local state.
 | 
				
			||||||
 | 
							double speed_multiplier_ = 1.0;
 | 
				
			||||||
 | 
							double base_multiplier_ = 1.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
 | 
				
			||||||
 | 
							double ratio_ = 1.0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* ScanSynchroniser_h */
 | 
				
			||||||
@@ -9,9 +9,16 @@
 | 
				
			|||||||
#ifndef TimeTypes_h
 | 
					#ifndef TimeTypes_h
 | 
				
			||||||
#define TimeTypes_h
 | 
					#define TimeTypes_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Time {
 | 
					namespace Time {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef double Seconds;
 | 
					typedef double Seconds;
 | 
				
			||||||
 | 
					typedef int64_t Nanos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline Nanos nanos_now() {
 | 
				
			||||||
 | 
						return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										155
									
								
								ClockReceiver/VSyncPredictor.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								ClockReceiver/VSyncPredictor.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  VSyncPredictor.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 14/06/2020.
 | 
				
			||||||
 | 
					//  Copyright © 2020 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef VSyncPredictor_hpp
 | 
				
			||||||
 | 
					#define VSyncPredictor_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "TimeTypes.hpp"
 | 
				
			||||||
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					#include <cstdio>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Time {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						For platforms that provide no avenue into vsync tracking other than block-until-sync,
 | 
				
			||||||
 | 
						this class tracks: (i) how long frame draw takes; (ii) the apparent frame period; and
 | 
				
			||||||
 | 
						(iii) optionally, timer jitter; in order to suggest when you should next start drawing.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class VSyncPredictor {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Announces to the predictor that the work of producing an output frame has begun.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void begin_redraw() {
 | 
				
			||||||
 | 
								redraw_begin_time_ = nanos_now();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Announces to the predictor that the work of producing an output frame has ended;
 | 
				
			||||||
 | 
								the predictor will use the amount of time between each begin/end pair to modify
 | 
				
			||||||
 | 
								its expectations as to how long it takes to draw a frame.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void end_redraw() {
 | 
				
			||||||
 | 
								redraw_period_.post(nanos_now() - redraw_begin_time_);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
 | 
				
			||||||
 | 
								machine calls retrace is now. The predictor uses these notifications to estimate output
 | 
				
			||||||
 | 
								frame rate.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void announce_vsync() {
 | 
				
			||||||
 | 
								const auto now = nanos_now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(last_vsync_) {
 | 
				
			||||||
 | 
									last_vsync_ += frame_duration_;
 | 
				
			||||||
 | 
									vsync_jitter_.post(last_vsync_ - now);
 | 
				
			||||||
 | 
									last_vsync_ = (last_vsync_ + now) >> 1;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									last_vsync_ = now;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the frame rate for the target display.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_frame_rate(float rate) {
 | 
				
			||||||
 | 
								frame_duration_ = Nanos(1'000'000'000.0f / rate);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@returns The time this class currently believes a whole frame occupies.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							Time::Nanos frame_duration() {
 | 
				
			||||||
 | 
								return frame_duration_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Adds a record of how much jitter was experienced in scheduling; these values will be
 | 
				
			||||||
 | 
								factored into the @c suggested_draw_time if supplied.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								A positive number means the timer occurred late. A negative number means it occurred early.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void add_timer_jitter(Time::Nanos jitter) {
 | 
				
			||||||
 | 
								timer_jitter_.post(jitter);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Announces to the vsync predictor that output is now paused. This ends frame period
 | 
				
			||||||
 | 
								calculations until the next announce_vsync() restarts frame-length counting.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void pause() {
 | 
				
			||||||
 | 
								last_vsync_ = 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								@return The time at which redrawing should begin, given the predicted frame period, how
 | 
				
			||||||
 | 
								long it appears to take to draw a frame and how much jitter there is in scheduling
 | 
				
			||||||
 | 
								(if those figures are being supplied).
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							Nanos suggested_draw_time() {
 | 
				
			||||||
 | 
								const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean();
 | 
				
			||||||
 | 
								const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Permit three standard deviations from the mean, to cover 99.9% of cases.
 | 
				
			||||||
 | 
								const auto period = mean + Nanos(3.0f * sqrt(float(variance)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return last_vsync_ + frame_duration_ - period;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							class VarianceCollector {
 | 
				
			||||||
 | 
								public:
 | 
				
			||||||
 | 
									VarianceCollector(Time::Nanos default_value) {
 | 
				
			||||||
 | 
										sum_ = default_value * 128;
 | 
				
			||||||
 | 
										for(int c = 0; c < 128; ++c) {
 | 
				
			||||||
 | 
											history_[c] = default_value;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									void post(Time::Nanos value) {
 | 
				
			||||||
 | 
										sum_ -= history_[write_pointer_];
 | 
				
			||||||
 | 
										sum_ += value;
 | 
				
			||||||
 | 
										history_[write_pointer_] = value;
 | 
				
			||||||
 | 
										write_pointer_ = (write_pointer_ + 1) & 127;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Time::Nanos mean() {
 | 
				
			||||||
 | 
										return sum_ / 128;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Time::Nanos variance() {
 | 
				
			||||||
 | 
										// I haven't yet come up with a better solution that calculating this
 | 
				
			||||||
 | 
										// in whole every time, given the way that the mean mutates.
 | 
				
			||||||
 | 
										Time::Nanos variance = 0;
 | 
				
			||||||
 | 
										for(int c = 0; c < 128; ++c) {
 | 
				
			||||||
 | 
											const auto difference = ((history_[c] * 128) - sum_) / 128;
 | 
				
			||||||
 | 
											variance += (difference * difference);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return variance / 128;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								private:
 | 
				
			||||||
 | 
									Time::Nanos sum_;
 | 
				
			||||||
 | 
									Time::Nanos history_[128];
 | 
				
			||||||
 | 
									size_t write_pointer_ = 0;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Nanos redraw_begin_time_ = 0;
 | 
				
			||||||
 | 
							Nanos last_vsync_ = 0;
 | 
				
			||||||
 | 
							Nanos frame_duration_ = 1'000'000'000 / 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							VarianceCollector vsync_jitter_{0};
 | 
				
			||||||
 | 
							VarianceCollector redraw_period_{1'000'000'000 / 60};	// A less convincing first guess.
 | 
				
			||||||
 | 
							VarianceCollector timer_jitter_{0};						// Seed at 0 in case this feature isn't used by the owner.
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* VSyncPredictor_hpp */
 | 
				
			||||||
@@ -336,6 +336,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		READ_ID();
 | 
							READ_ID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(index_hole_count_ == 6) {
 | 
							if(index_hole_count_ == 6) {
 | 
				
			||||||
 | 
								LOG("Nothing found to verify");
 | 
				
			||||||
			update_status([] (Status &status) {
 | 
								update_status([] (Status &status) {
 | 
				
			||||||
				status.seek_error = true;
 | 
									status.seek_error = true;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
@@ -480,7 +481,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
			status.data_request = true;
 | 
								status.data_request = true;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		distance_into_section_++;
 | 
							distance_into_section_++;
 | 
				
			||||||
		if(distance_into_section_ == 128 << header_[3]) {
 | 
							if(distance_into_section_ == 128 << (header_[3]&3)) {
 | 
				
			||||||
			distance_into_section_ = 0;
 | 
								distance_into_section_ = 0;
 | 
				
			||||||
			goto type2_check_crc;
 | 
								goto type2_check_crc;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -497,7 +498,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			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([] (Status &status) {
 | 
				
			||||||
					status.crc_error = true;
 | 
										status.crc_error = true;
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				goto wait_for_command;
 | 
									goto wait_for_command;
 | 
				
			||||||
@@ -563,7 +564,7 @@ void WD1770::posit_event(int new_event_type) {
 | 
				
			|||||||
		*/
 | 
							*/
 | 
				
			||||||
		write_byte(data_);
 | 
							write_byte(data_);
 | 
				
			||||||
		distance_into_section_++;
 | 
							distance_into_section_++;
 | 
				
			||||||
		if(distance_into_section_ == 128 << header_[3]) {
 | 
							if(distance_into_section_ == 128 << (header_[3]&3)) {
 | 
				
			||||||
			goto type2_write_crc;
 | 
								goto type2_write_crc;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -815,15 +816,19 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
 | 
				
			|||||||
	if(status_.busy != old_status.busy) update_clocking_observer();
 | 
						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) {}
 | 
				
			||||||
void WD1770::set_motor_on(bool motor_on) {}
 | 
					void WD1770::set_motor_on(bool) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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(int(Event1770::HeadLoad));
 | 
						if(head_loaded) posit_event(int(Event1770::HeadLoad));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ClockingHint::Preference WD1770::preferred_clocking() {
 | 
					bool WD1770::get_head_loaded() const {
 | 
				
			||||||
 | 
						return head_is_loaded_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClockingHint::Preference WD1770::preferred_clocking() const {
 | 
				
			||||||
	if(status_.busy) return ClockingHint::Preference::RealTime;
 | 
						if(status_.busy) return ClockingHint::Preference::RealTime;
 | 
				
			||||||
	return Storage::Disk::MFMController::preferred_clocking();
 | 
						return Storage::Disk::MFMController::preferred_clocking();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,7 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
			@param p The type of controller to emulate.
 | 
								@param p The type of controller to emulate.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		WD1770(Personality p);
 | 
							WD1770(Personality p);
 | 
				
			||||||
 | 
							virtual ~WD1770() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
 | 
							/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
 | 
				
			||||||
		using Storage::Disk::MFMController::set_is_double_density;
 | 
							using Storage::Disk::MFMController::set_is_double_density;
 | 
				
			||||||
@@ -45,45 +46,48 @@ class WD1770: public Storage::Disk::MFMController {
 | 
				
			|||||||
		void run_for(const Cycles cycles);
 | 
							void run_for(const Cycles cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		enum Flag: uint8_t {
 | 
							enum Flag: uint8_t {
 | 
				
			||||||
			NotReady		= 0x80,
 | 
								NotReady		= 0x80,		// 0x80
 | 
				
			||||||
			MotorOn			= 0x80,
 | 
								MotorOn			= 0x80,
 | 
				
			||||||
			WriteProtect	= 0x40,
 | 
								WriteProtect	= 0x40,		// 0x40
 | 
				
			||||||
			RecordType		= 0x20,
 | 
								RecordType		= 0x20,		// 0x20
 | 
				
			||||||
			SpinUp			= 0x20,
 | 
								SpinUp			= 0x20,
 | 
				
			||||||
			HeadLoaded		= 0x20,
 | 
								HeadLoaded		= 0x20,
 | 
				
			||||||
			RecordNotFound	= 0x10,
 | 
								RecordNotFound	= 0x10,		// 0x10
 | 
				
			||||||
			SeekError		= 0x10,
 | 
								SeekError		= 0x10,
 | 
				
			||||||
			CRCError		= 0x08,
 | 
								CRCError		= 0x08,		// 0x08
 | 
				
			||||||
			LostData		= 0x04,
 | 
								LostData		= 0x04,		// 0x04
 | 
				
			||||||
			TrackZero		= 0x04,
 | 
								TrackZero		= 0x04,
 | 
				
			||||||
			DataRequest		= 0x02,
 | 
								DataRequest		= 0x02,		// 0x02
 | 
				
			||||||
			Index			= 0x02,
 | 
								Index			= 0x02,
 | 
				
			||||||
			Busy			= 0x01
 | 
								Busy			= 0x01		// 0x01
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns The current value of the IRQ line output.
 | 
							/// @returns The current value of the IRQ line output.
 | 
				
			||||||
		inline bool get_interrupt_request_line()		{	return status_.interrupt_request;	}
 | 
							inline bool get_interrupt_request_line() const	{	return status_.interrupt_request;	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns The current value of the DRQ line output.
 | 
							/// @returns The current value of the DRQ line output.
 | 
				
			||||||
		inline bool get_data_request_line()				{	return status_.data_request;		}
 | 
							inline bool get_data_request_line() const		{	return status_.data_request;		}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		class Delegate {
 | 
							class Delegate {
 | 
				
			||||||
			public:
 | 
								public:
 | 
				
			||||||
				virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
 | 
									virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		inline void set_delegate(Delegate *delegate)	{	delegate_ = delegate;			}
 | 
							inline void set_delegate(Delegate *delegate)	{	delegate_ = delegate;				}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ClockingHint::Preference preferred_clocking() final;
 | 
							ClockingHint::Preference preferred_clocking() const 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);
 | 
				
			||||||
		void set_head_loaded(bool head_loaded);
 | 
							void set_head_loaded(bool head_loaded);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// @returns The last value posted to @c set_head_loaded.
 | 
				
			||||||
 | 
							bool get_head_loaded() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		Personality personality_;
 | 
							const Personality personality_;
 | 
				
			||||||
		inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
 | 
							bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); }
 | 
				
			||||||
		inline bool has_head_load_line() { return (personality_ == P1793 ); }
 | 
							bool has_head_load_line() const { return (personality_ == P1793 ); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct Status {
 | 
							struct Status {
 | 
				
			||||||
			bool write_protect = false;
 | 
								bool write_protect = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,9 +18,14 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
 | 
				
			|||||||
	clock_rate_(clock_rate) {
 | 
						clock_rate_(clock_rate) {
 | 
				
			||||||
	device_id_ = bus_.add_device();
 | 
						device_id_ = bus_.add_device();
 | 
				
			||||||
	bus_.add_observer(this);
 | 
						bus_.add_observer(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: use clock rate and expected phase. This implementation currently
 | 
				
			||||||
 | 
						// provides only CPU-driven polling behaviour.
 | 
				
			||||||
 | 
						(void)clock_rate_;
 | 
				
			||||||
 | 
						(void)expected_phase_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
 | 
					void NCR5380::write(int address, uint8_t value, bool) {
 | 
				
			||||||
	switch(address & 7) {
 | 
						switch(address & 7) {
 | 
				
			||||||
		case 0:
 | 
							case 0:
 | 
				
			||||||
//			LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
 | 
					//			LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
 | 
				
			||||||
@@ -128,7 +133,7 @@ void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t NCR5380::read(int address, bool dma_acknowledge) {
 | 
					uint8_t NCR5380::read(int address, bool) {
 | 
				
			||||||
	switch(address & 7) {
 | 
						switch(address & 7) {
 | 
				
			||||||
		case 0:
 | 
							case 0:
 | 
				
			||||||
//			LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
 | 
					//			LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
 | 
				
			||||||
@@ -258,6 +263,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
 | 
				
			|||||||
		case ExecutionState::WaitingForBusy:
 | 
							case ExecutionState::WaitingForBusy:
 | 
				
			||||||
			if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
 | 
								if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
 | 
				
			||||||
			state_ = ExecutionState::WatchingBusy;
 | 
								state_ = ExecutionState::WatchingBusy;
 | 
				
			||||||
 | 
								[[fallthrough]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case ExecutionState::WatchingBusy:
 | 
							case ExecutionState::WatchingBusy:
 | 
				
			||||||
			if(!(new_state & SCSI::Line::Busy)) {
 | 
								if(!(new_state & SCSI::Line::Busy)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,22 +37,22 @@ enum Line {
 | 
				
			|||||||
class PortHandler {
 | 
					class PortHandler {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Requests the current input value of @c port from the port handler.
 | 
							/// Requests the current input value of @c port from the port handler.
 | 
				
			||||||
		uint8_t get_port_input(Port port)										{	return 0xff;	}
 | 
						uint8_t get_port_input([[maybe_unused]] Port port)										{	return 0xff;	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
 | 
							/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
 | 
				
			||||||
		void set_port_output(Port port, uint8_t value, uint8_t direction_mask)	{}
 | 
							void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask)	{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the current logical output level for line @c line on port @c port.
 | 
							/// Sets the current logical output level for line @c line on port @c port.
 | 
				
			||||||
		void set_control_line_output(Port port, Line line, bool value)			{}
 | 
							void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value)			{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// 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([[maybe_unused]] bool status)									{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Provides a measure of time elapsed between other calls.
 | 
							/// Provides a measure of time elapsed between other calls.
 | 
				
			||||||
		void run_for(HalfCycles duration)										{}
 | 
							void run_for([[maybe_unused]] HalfCycles duration)										{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Receives passed-on flush() calls from the 6522.
 | 
							/// Receives passed-on flush() calls from the 6522.
 | 
				
			||||||
		void flush()															{}
 | 
							void flush()																			{}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -112,7 +112,7 @@ template <class T> class MOS6522: public MOS6522Storage {
 | 
				
			|||||||
		void run_for(const Cycles cycles);
 | 
							void run_for(const Cycles cycles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// @returns @c true if the IRQ line is currently active; @c false otherwise.
 | 
							/// @returns @c true if the IRQ line is currently active; @c false otherwise.
 | 
				
			||||||
		bool get_interrupt_line();
 | 
							bool get_interrupt_line() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Updates the port handler to the current time and then requests that it flush.
 | 
							/// Updates the port handler to the current time and then requests that it flush.
 | 
				
			||||||
		void flush();
 | 
							void flush();
 | 
				
			||||||
@@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		void access(int address);
 | 
							void access(int address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
 | 
							uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
 | 
				
			||||||
		inline void reevaluate_interrupts();
 | 
							inline void reevaluate_interrupts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// Sets the current intended output value for the port and line;
 | 
							/// Sets the current intended output value for the port and line;
 | 
				
			||||||
		/// if this affects the visible output, it will be passed to the handler.
 | 
							/// 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 set_control_line_output(Port port, Line line, LineState value);
 | 
				
			||||||
		void evaluate_cb2_output();
 | 
							void evaluate_cb2_output();
 | 
				
			||||||
 | 
							void evaluate_port_b_output();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../../../Outputs/Log.hpp"
 | 
					#include "../../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// As-yet unimplemented (incomplete list):
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	PB6 count-down mode for timer 2.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MOS {
 | 
					namespace MOS {
 | 
				
			||||||
namespace MOS6522 {
 | 
					namespace MOS6522 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,18 +38,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
 | 
				
			|||||||
	address &= 0xf;
 | 
						address &= 0xf;
 | 
				
			||||||
	access(address);
 | 
						access(address);
 | 
				
			||||||
	switch(address) {
 | 
						switch(address) {
 | 
				
			||||||
		case 0x0:	// Write Port B.
 | 
							case 0x0:	// Write Port B. ('ORB')
 | 
				
			||||||
			// Store locally and communicate outwards.
 | 
								// Store locally and communicate outwards.
 | 
				
			||||||
			registers_.output[1] = value;
 | 
								registers_.output[1] = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
								bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
			bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
 | 
								evaluate_port_b_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
								registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0xf:
 | 
							case 0xf:
 | 
				
			||||||
		case 0x1:	// Write Port A.
 | 
							case 0x1:	// Write Port A. ('ORA')
 | 
				
			||||||
			registers_.output[0] = value;
 | 
								registers_.output[0] = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
								bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
@@ -59,36 +63,52 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
 | 
				
			|||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0x2:	// Port B direction.
 | 
							case 0x2:	// Port B direction ('DDRB').
 | 
				
			||||||
			registers_.data_direction[1] = value;
 | 
								registers_.data_direction[1] = value;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0x3:	// Port A direction.
 | 
							case 0x3:	// Port A direction ('DDRA').
 | 
				
			||||||
			registers_.data_direction[0] = value;
 | 
								registers_.data_direction[0] = value;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Timer 1
 | 
							// Timer 1
 | 
				
			||||||
		case 0x6:	case 0x4:	registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;	break;
 | 
							case 0x6:	case 0x4:	// ('T1L-L' and 'T1C-L')
 | 
				
			||||||
		case 0x5:	case 0x7:
 | 
								registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;
 | 
				
			||||||
			registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
 | 
							break;
 | 
				
			||||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
							case 0x7:	// Timer 1 latch, high ('T1L-H').
 | 
				
			||||||
			if(address == 0x05) {
 | 
								registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
 | 
				
			||||||
				registers_.next_timer[0] = registers_.timer_latch[0];
 | 
							break;
 | 
				
			||||||
				timer_is_running_[0] = true;
 | 
							case 0x5:	// Timer 1 counter, high ('T1C-H').
 | 
				
			||||||
 | 
								// Fill latch.
 | 
				
			||||||
 | 
								registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Restart timer.
 | 
				
			||||||
 | 
								registers_.next_timer[0] = registers_.timer_latch[0];
 | 
				
			||||||
 | 
								timer_is_running_[0] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If PB7 output mode is active, set it low.
 | 
				
			||||||
 | 
								if(timer1_is_controlling_pb7()) {
 | 
				
			||||||
 | 
									registers_.timer_port_b_output &= 0x7f;
 | 
				
			||||||
 | 
									evaluate_port_b_output();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Clear existing interrupt flag.
 | 
				
			||||||
 | 
								registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Timer 2
 | 
							// Timer 2
 | 
				
			||||||
		case 0x8:	registers_.timer_latch[1] = value;	break;
 | 
							case 0x8:	// ('T2C-L')
 | 
				
			||||||
		case 0x9:
 | 
								registers_.timer_latch[1] = value;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
							case 0x9:	// ('T2C-H')
 | 
				
			||||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
								registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
				
			||||||
			registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
 | 
								registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8);
 | 
				
			||||||
			timer_is_running_[1] = true;
 | 
								timer_is_running_[1] = true;
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Shift
 | 
							// Shift
 | 
				
			||||||
		case 0xa:
 | 
							case 0xa:	// ('SR')
 | 
				
			||||||
			registers_.shift = value;
 | 
								registers_.shift = value;
 | 
				
			||||||
			shift_bits_remaining_ = 8;
 | 
								shift_bits_remaining_ = 8;
 | 
				
			||||||
			registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
 | 
								registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
 | 
				
			||||||
@@ -96,11 +116,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
 | 
				
			|||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Control
 | 
							// Control
 | 
				
			||||||
		case 0xb:
 | 
							case 0xb:	// Auxiliary control ('ACR').
 | 
				
			||||||
			registers_.auxiliary_control = value;
 | 
								registers_.auxiliary_control = value;
 | 
				
			||||||
			evaluate_cb2_output();
 | 
								evaluate_cb2_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// This is a bit of a guess: reset the timer-based PB7 output to its default high level
 | 
				
			||||||
 | 
								// any timer that timer-linked PB7 output is disabled.
 | 
				
			||||||
 | 
								if(!timer1_is_controlling_pb7()) {
 | 
				
			||||||
 | 
									registers_.timer_port_b_output |= 0x80;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								evaluate_port_b_output();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0xc: {
 | 
							case 0xc: {	// Peripheral control ('PCR').
 | 
				
			||||||
//			const auto old_peripheral_control = registers_.peripheral_control;
 | 
					//			const auto old_peripheral_control = registers_.peripheral_control;
 | 
				
			||||||
			registers_.peripheral_control = value;
 | 
								registers_.peripheral_control = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -141,11 +168,11 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
 | 
				
			|||||||
		} break;
 | 
							} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Interrupt control
 | 
							// Interrupt control
 | 
				
			||||||
		case 0xd:
 | 
							case 0xd:	// Interrupt flag regiser ('IFR').
 | 
				
			||||||
			registers_.interrupt_flags &= ~value;
 | 
								registers_.interrupt_flags &= ~value;
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
		case 0xe:
 | 
							case 0xe:	// Interrupt enable register ('IER').
 | 
				
			||||||
			if(value&0x80)
 | 
								if(value&0x80)
 | 
				
			||||||
				registers_.interrupt_enable |= value;
 | 
									registers_.interrupt_enable |= value;
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
@@ -159,54 +186,55 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
 | 
				
			|||||||
	address &= 0xf;
 | 
						address &= 0xf;
 | 
				
			||||||
	access(address);
 | 
						access(address);
 | 
				
			||||||
	switch(address) {
 | 
						switch(address) {
 | 
				
			||||||
		case 0x0:
 | 
							case 0x0:	// Read Port B ('IRB').
 | 
				
			||||||
			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], registers_.auxiliary_control & 0x80);
 | 
				
			||||||
		case 0xf:
 | 
							case 0xf:
 | 
				
			||||||
		case 0x1:
 | 
							case 0x1:	// Read Port A ('IRA').
 | 
				
			||||||
			registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
 | 
								registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
 | 
							return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0x2:	return registers_.data_direction[1];
 | 
							case 0x2:	return registers_.data_direction[1];	// Port B direction ('DDRB').
 | 
				
			||||||
		case 0x3:	return registers_.data_direction[0];
 | 
							case 0x3:	return registers_.data_direction[0];	// Port A direction ('DDRA').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Timer 1
 | 
							// Timer 1
 | 
				
			||||||
		case 0x4:
 | 
							case 0x4:	// Timer 1 low-order latches ('T1L-L').
 | 
				
			||||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
								registers_.interrupt_flags &= ~InterruptFlag::Timer1;
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		return registers_.timer[0] & 0x00ff;
 | 
							return registers_.timer[0] & 0x00ff;
 | 
				
			||||||
		case 0x5:	return registers_.timer[0] >> 8;
 | 
							case 0x5:	return registers_.timer[0] >> 8;			// Timer 1 high-order counter ('T1C-H')
 | 
				
			||||||
		case 0x6:	return registers_.timer_latch[0] & 0x00ff;
 | 
							case 0x6:	return registers_.timer_latch[0] & 0x00ff;	// Timer 1 low-order latches ('T1L-L').
 | 
				
			||||||
		case 0x7:	return registers_.timer_latch[0] >> 8;
 | 
							case 0x7:	return registers_.timer_latch[0] >> 8;		// Timer 1 high-order latches ('T1L-H').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Timer 2
 | 
							// Timer 2
 | 
				
			||||||
		case 0x8:
 | 
							case 0x8:	// Timer 2 low-order counter ('T2C-L').
 | 
				
			||||||
			registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
								registers_.interrupt_flags &= ~InterruptFlag::Timer2;
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		return registers_.timer[1] & 0x00ff;
 | 
							return registers_.timer[1] & 0x00ff;
 | 
				
			||||||
		case 0x9:	return registers_.timer[1] >> 8;
 | 
							case 0x9:	return registers_.timer[1] >> 8;	// Timer 2 high-order counter ('T2C-H').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0xa:
 | 
							case 0xa:	// Shift register ('SR').
 | 
				
			||||||
			shift_bits_remaining_ = 8;
 | 
								shift_bits_remaining_ = 8;
 | 
				
			||||||
			registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
 | 
								registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
 | 
				
			||||||
			reevaluate_interrupts();
 | 
								reevaluate_interrupts();
 | 
				
			||||||
		return registers_.shift;
 | 
							return registers_.shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0xb:	return registers_.auxiliary_control;
 | 
							case 0xb:	return registers_.auxiliary_control;	// Auxiliary control ('ACR').
 | 
				
			||||||
		case 0xc:	return registers_.peripheral_control;
 | 
							case 0xc:	return registers_.peripheral_control;	// Peripheral control ('PCR').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 0xd:	return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
 | 
							case 0xd:	return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);	// Interrupt flag register ('IFR').
 | 
				
			||||||
		case 0xe:	return registers_.interrupt_enable | 0x80;
 | 
							case 0xe:	return registers_.interrupt_enable | 0x80;									// Interrupt enable register ('IER').
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0xff;
 | 
						return 0xff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
 | 
					template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) {
 | 
				
			||||||
	bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
						bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
	const uint8_t input = bus_handler_.get_port_input(port);
 | 
						const uint8_t input = bus_handler_.get_port_input(port);
 | 
				
			||||||
 | 
						output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
 | 
				
			||||||
	return (input & ~output_mask) | (output & output_mask);
 | 
						return (input & ~output_mask) | (output & output_mask);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -276,16 +304,19 @@ template <typename T> void MOS6522<T>::do_phase2() {
 | 
				
			|||||||
		registers_.timer_needs_reload = false;
 | 
							registers_.timer_needs_reload = false;
 | 
				
			||||||
		registers_.timer[0] = registers_.timer_latch[0];
 | 
							registers_.timer[0] = registers_.timer_latch[0];
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		registers_.timer[0] --;
 | 
							-- registers_.timer[0];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	registers_.timer[1] --;
 | 
						// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear).
 | 
				
			||||||
 | 
						registers_.timer[1] -= timer2_clock_decrement();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: can eliminate conditional branches here.
 | 
				
			||||||
	if(registers_.next_timer[0] >= 0) {
 | 
						if(registers_.next_timer[0] >= 0) {
 | 
				
			||||||
		registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
 | 
							registers_.timer[0] = uint16_t(registers_.next_timer[0]);
 | 
				
			||||||
		registers_.next_timer[0] = -1;
 | 
							registers_.next_timer[0] = -1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(registers_.next_timer[1] >= 0) {
 | 
						if(registers_.next_timer[1] >= 0) {
 | 
				
			||||||
		registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
 | 
							registers_.timer[1] = uint16_t(registers_.next_timer[1]);
 | 
				
			||||||
		registers_.next_timer[1] = -1;
 | 
							registers_.next_timer[1] = -1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -330,20 +361,29 @@ template <typename T> void MOS6522<T>::do_phase1() {
 | 
				
			|||||||
		reevaluate_interrupts();
 | 
							reevaluate_interrupts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Determine whether to reload.
 | 
							// Determine whether to reload.
 | 
				
			||||||
		if(registers_.auxiliary_control&0x40)
 | 
							if(timer1_is_continuous())
 | 
				
			||||||
			registers_.timer_needs_reload = true;
 | 
								registers_.timer_needs_reload = true;
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			timer_is_running_[0] = false;
 | 
								timer_is_running_[0] = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Determine whether to toggle PB7.
 | 
							// Determine whether to toggle PB7.
 | 
				
			||||||
		if(registers_.auxiliary_control&0x80) {
 | 
							if(timer1_is_controlling_pb7()) {
 | 
				
			||||||
			registers_.output[1] ^= 0x80;
 | 
								registers_.timer_port_b_output ^= 0x80;
 | 
				
			||||||
			bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
								bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
 | 
				
			||||||
			bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
 | 
								evaluate_port_b_output();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> void MOS6522<T>::evaluate_port_b_output() {
 | 
				
			||||||
 | 
						// Apply current timer-linked PB7 output if any atop the stated output.
 | 
				
			||||||
 | 
						const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80;
 | 
				
			||||||
 | 
						bus_handler_.set_port_output(
 | 
				
			||||||
 | 
							Port::B,
 | 
				
			||||||
 | 
							(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit,
 | 
				
			||||||
 | 
							registers_.data_direction[1] | timer_control_bit);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*! Runs for a specified number of half cycles. */
 | 
					/*! Runs for a specified number of half cycles. */
 | 
				
			||||||
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
 | 
					template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
 | 
				
			||||||
	auto number_of_half_cycles = half_cycles.as_integral();
 | 
						auto number_of_half_cycles = half_cycles.as_integral();
 | 
				
			||||||
@@ -383,9 +423,9 @@ template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
 | 
					/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
 | 
				
			||||||
template <typename T> bool MOS6522<T>::get_interrupt_line() {
 | 
					template <typename T> bool MOS6522<T>::get_interrupt_line() const {
 | 
				
			||||||
	uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
 | 
						uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
 | 
				
			||||||
	return !!interrupt_status;
 | 
						return interrupt_status;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
 | 
					template <typename T> void MOS6522<T>::evaluate_cb2_output() {
 | 
				
			||||||
@@ -438,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename T> void MOS6522<T>::shift_out() {
 | 
					template <typename T> void MOS6522<T>::shift_out() {
 | 
				
			||||||
	// When shifting out, the shift register rotates rather than strictly shifts.
 | 
						const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning;
 | 
				
			||||||
	// TODO: is that true for all modes?
 | 
						if(is_free_running || shift_bits_remaining_) {
 | 
				
			||||||
	if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
 | 
							// Recirculate bits only if in free-running mode (?)
 | 
				
			||||||
		registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
 | 
							const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running;
 | 
				
			||||||
 | 
							registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit;
 | 
				
			||||||
		evaluate_cb2_output();
 | 
							evaluate_cb2_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		--shift_bits_remaining_;
 | 
							--shift_bits_remaining_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,9 @@ class MOS6522Storage {
 | 
				
			|||||||
			uint8_t peripheral_control = 0;
 | 
								uint8_t peripheral_control = 0;
 | 
				
			||||||
			uint8_t interrupt_flags = 0;
 | 
								uint8_t interrupt_flags = 0;
 | 
				
			||||||
			uint8_t interrupt_enable = 0;
 | 
								uint8_t interrupt_enable = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			bool timer_needs_reload = false;
 | 
								bool timer_needs_reload = false;
 | 
				
			||||||
 | 
								uint8_t timer_port_b_output = 0xff;
 | 
				
			||||||
		} registers_;
 | 
							} registers_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Control state.
 | 
							// Control state.
 | 
				
			||||||
@@ -79,12 +81,30 @@ class MOS6522Storage {
 | 
				
			|||||||
			OutUnderPhase2 = 6,
 | 
								OutUnderPhase2 = 6,
 | 
				
			||||||
			OutUnderCB1 = 7
 | 
								OutUnderCB1 = 7
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		ShiftMode shift_mode() const {
 | 
							bool timer1_is_controlling_pb7() const {
 | 
				
			||||||
			return ShiftMode((registers_.auxiliary_control >> 2) & 7);
 | 
								return registers_.auxiliary_control & 0x80;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bool timer1_is_continuous() const {
 | 
				
			||||||
 | 
								return registers_.auxiliary_control & 0x40;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		bool is_shifting_out() const {
 | 
							bool is_shifting_out() const {
 | 
				
			||||||
			return registers_.auxiliary_control & 0x10;
 | 
								return registers_.auxiliary_control & 0x10;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							int timer2_clock_decrement() const {
 | 
				
			||||||
 | 
								return 1 ^ ((registers_.auxiliary_control >> 5)&1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							int timer2_pb6_decrement() const {
 | 
				
			||||||
 | 
								return (registers_.auxiliary_control >> 5)&1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ShiftMode shift_mode() const {
 | 
				
			||||||
 | 
								return ShiftMode((registers_.auxiliary_control >> 2) & 7);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bool portb_is_latched() const {
 | 
				
			||||||
 | 
								return registers_.auxiliary_control & 0x02;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bool port1_is_latched() const {
 | 
				
			||||||
 | 
								return registers_.auxiliary_control & 0x01;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,6 @@ void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
 | 
				
			|||||||
	delegate_ = delegate;
 | 
						delegate_ = delegate;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
 | 
					void IRQDelegatePortHandler::set_interrupt_status(bool) {
 | 
				
			||||||
	if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
 | 
						if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
				case 0x04: case 0x05: case 0x06: case 0x07:
 | 
									case 0x04: case 0x05: case 0x06: case 0x07:
 | 
				
			||||||
					if(address & 0x10) {
 | 
										if(address & 0x10) {
 | 
				
			||||||
						timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10
 | 
											timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10
 | 
				
			||||||
						timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
 | 
											timer_.value = (unsigned(value) << timer_.activeShift) ;
 | 
				
			||||||
						timer_.interrupt_enabled = !!(address&0x08);
 | 
											timer_.interrupt_enabled = !!(address&0x08);
 | 
				
			||||||
						interrupt_status_ &= ~InterruptFlag::Timer;
 | 
											interrupt_status_ &= ~InterruptFlag::Timer;
 | 
				
			||||||
						evaluate_interrupts();
 | 
											evaluate_interrupts();
 | 
				
			||||||
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				// Timer and interrupt control
 | 
									// Timer and interrupt control
 | 
				
			||||||
				case 0x04: case 0x06: {
 | 
									case 0x04: case 0x06: {
 | 
				
			||||||
					uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
 | 
										uint8_t value = uint8_t(timer_.value >> timer_.activeShift);
 | 
				
			||||||
					timer_.interrupt_enabled = !!(address&0x08);
 | 
										timer_.interrupt_enabled = !!(address&0x08);
 | 
				
			||||||
					interrupt_status_ &= ~InterruptFlag::Timer;
 | 
										interrupt_status_ &= ~InterruptFlag::Timer;
 | 
				
			||||||
					evaluate_interrupts();
 | 
										evaluate_interrupts();
 | 
				
			||||||
@@ -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_integral());
 | 
								unsigned int number_of_cycles = unsigned(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) {
 | 
				
			||||||
@@ -122,7 +122,7 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MOS6532() {
 | 
							MOS6532() {
 | 
				
			||||||
			timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
 | 
								timer_.value = unsigned((rand() & 0xff) << 10);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void set_port_did_change(int port) {
 | 
							inline void set_port_did_change(int port) {
 | 
				
			||||||
@@ -142,7 +142,7 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline bool get_inerrupt_line() {
 | 
							inline bool get_inerrupt_line() const {
 | 
				
			||||||
			return interrupt_line_;
 | 
								return interrupt_line_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -173,9 +173,11 @@ template <class T> class MOS6532 {
 | 
				
			|||||||
		bool interrupt_line_ = false;
 | 
							bool interrupt_line_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// expected to be overridden
 | 
							// expected to be overridden
 | 
				
			||||||
		uint8_t get_port_input(int port)										{	return 0xff;	}
 | 
							void set_port_output([[maybe_unused]] int port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t output_mask) {}
 | 
				
			||||||
		void set_port_output(int port, uint8_t value, uint8_t output_mask)		{}
 | 
							uint8_t get_port_input([[maybe_unused]] int port) {
 | 
				
			||||||
		void set_irq_line(bool new_value)										{}
 | 
								return 0xff;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							void set_irq_line(bool) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inline void evaluate_interrupts() {
 | 
							inline void evaluate_interrupts() {
 | 
				
			||||||
			interrupt_line_ =
 | 
								interrupt_line_ =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,13 +17,13 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioGenerator::set_volume(uint8_t volume) {
 | 
					void AudioGenerator::set_volume(uint8_t volume) {
 | 
				
			||||||
	audio_queue_.defer([=]() {
 | 
						audio_queue_.defer([this, volume]() {
 | 
				
			||||||
		volume_ = static_cast<int16_t>(volume) * range_multiplier_;
 | 
							volume_ = int16_t(volume) * range_multiplier_;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioGenerator::set_control(int channel, uint8_t value) {
 | 
					void AudioGenerator::set_control(int channel, uint8_t value) {
 | 
				
			||||||
	audio_queue_.defer([=]() {
 | 
						audio_queue_.defer([this, channel, value]() {
 | 
				
			||||||
		control_registers_[channel] = value;
 | 
							control_registers_[channel] = value;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
 | 
					#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
 | 
				
			||||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
 | 
					#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
 | 
				
			||||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
 | 
					#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
 | 
				
			||||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
 | 
					// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
 | 
				
			||||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
 | 
					// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
 | 
				
			||||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
 | 
					// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
 | 
				
			||||||
@@ -114,7 +114,7 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// this sums the output of all three sounds channels plus a DC offset for volume;
 | 
							// this sums the output of all three sounds channels plus a DC offset for volume;
 | 
				
			||||||
		// TODO: what's the real ratio of this stuff?
 | 
							// TODO: what's the real ratio of this stuff?
 | 
				
			||||||
		target[c] = static_cast<int16_t>(
 | 
							target[c] = int16_t(
 | 
				
			||||||
			(shift_registers_[0]&1) +
 | 
								(shift_registers_[0]&1) +
 | 
				
			||||||
			(shift_registers_[1]&1) +
 | 
								(shift_registers_[1]&1) +
 | 
				
			||||||
			(shift_registers_[2]&1) +
 | 
								(shift_registers_[2]&1) +
 | 
				
			||||||
@@ -133,7 +133,7 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
 | 
					void AudioGenerator::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
	range_multiplier_ = static_cast<int16_t>(range / 64);
 | 
						range_multiplier_ = int16_t(range / 64);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#undef shift
 | 
					#undef shift
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
		void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
							void get_samples(std::size_t number_of_samples, int16_t *target);
 | 
				
			||||||
		void skip_samples(std::size_t number_of_samples);
 | 
							void skip_samples(std::size_t number_of_samples);
 | 
				
			||||||
		void set_sample_volume_range(std::int16_t range);
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
 | 
							static constexpr bool get_is_stereo() { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 | 
							Concurrency::DeferringAsyncTaskQueue &audio_queue_;
 | 
				
			||||||
@@ -42,7 +43,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct BusHandler {
 | 
					struct BusHandler {
 | 
				
			||||||
	void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
 | 
						void perform_read([[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *pixel_data, [[maybe_unused]] uint8_t *colour_data) {
 | 
				
			||||||
		*pixel_data = 0xff;
 | 
							*pixel_data = 0xff;
 | 
				
			||||||
		*colour_data = 0xff;
 | 
							*colour_data = 0xff;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -80,12 +81,14 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_clock_rate(double clock_rate) {
 | 
							void set_clock_rate(double clock_rate) {
 | 
				
			||||||
			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
 | 
								speaker_.set_input_rate(float(clock_rate / 4.0));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_scan_target(Outputs::Display::ScanTarget *scan_target)		{ crt_.set_scan_target(scan_target); 	}
 | 
							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::Display::ScanStatus get_scaled_scan_status() const			{ return crt_.get_scaled_scan_status() / 4.0f;	}
 | 
				
			||||||
		Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
 | 
							void set_display_type(Outputs::Display::DisplayType display_type)	{ crt_.set_display_type(display_type); 			}
 | 
				
			||||||
 | 
							Outputs::Display::DisplayType get_display_type() const				{ return crt_.get_display_type(); 				}
 | 
				
			||||||
 | 
							Outputs::Speaker::Speaker *get_speaker()	 						{ return &speaker_; 							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_high_frequency_cutoff(float cutoff) {
 | 
							void set_high_frequency_cutoff(float cutoff) {
 | 
				
			||||||
			speaker_.set_high_frequency_cutoff(cutoff);
 | 
								speaker_.set_high_frequency_cutoff(cutoff);
 | 
				
			||||||
@@ -232,7 +235,7 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
					if(column_counter_&1) {
 | 
										if(column_counter_&1) {
 | 
				
			||||||
						fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
 | 
											fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
 | 
											fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_);
 | 
				
			||||||
						video_matrix_address_counter_++;
 | 
											video_matrix_address_counter_++;
 | 
				
			||||||
						if(
 | 
											if(
 | 
				
			||||||
							(current_character_row_ == 15) ||
 | 
												(current_character_row_ == 15) ||
 | 
				
			||||||
@@ -368,7 +371,7 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				case 0x2:
 | 
									case 0x2:
 | 
				
			||||||
					registers_.number_of_columns = value & 0x7f;
 | 
										registers_.number_of_columns = value & 0x7f;
 | 
				
			||||||
					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
 | 
										registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 0x3:
 | 
									case 0x3:
 | 
				
			||||||
@@ -377,8 +380,8 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 0x5:
 | 
									case 0x5:
 | 
				
			||||||
					registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
 | 
										registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10);
 | 
				
			||||||
					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
 | 
										registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 0xa:
 | 
									case 0xa:
 | 
				
			||||||
@@ -417,11 +420,11 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
		/*
 | 
							/*
 | 
				
			||||||
			Reads from a 6560 register.
 | 
								Reads from a 6560 register.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		uint8_t read(int address) {
 | 
							uint8_t read(int address) const {
 | 
				
			||||||
			address &= 0xf;
 | 
								address &= 0xf;
 | 
				
			||||||
			switch(address) {
 | 
								switch(address) {
 | 
				
			||||||
				default: return registers_.direct_values[address];
 | 
									default: return registers_.direct_values[address];
 | 
				
			||||||
				case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
 | 
									case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
 | 
				
			||||||
				case 0x04: return (raster_value() >> 1) & 0xff;
 | 
									case 0x04: return (raster_value() >> 1) & 0xff;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -442,28 +445,28 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
		// register state
 | 
							// register state
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
			bool interlaced = false, tall_characters = false;
 | 
								bool interlaced = false, tall_characters = false;
 | 
				
			||||||
			uint8_t first_column_location, first_row_location;
 | 
								uint8_t first_column_location = 0, first_row_location = 0;
 | 
				
			||||||
			uint8_t number_of_columns, number_of_rows;
 | 
								uint8_t number_of_columns = 0, number_of_rows = 0;
 | 
				
			||||||
			uint16_t character_cell_start_address, video_matrix_start_address;
 | 
								uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
 | 
				
			||||||
			uint16_t backgroundColour, borderColour, auxiliary_colour;
 | 
								uint16_t backgroundColour = 0, borderColour = 0, auxiliary_colour = 0;
 | 
				
			||||||
			bool invertedCells = false;
 | 
								bool invertedCells = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			uint8_t direct_values[16];
 | 
								uint8_t direct_values[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 | 
				
			||||||
		} registers_;
 | 
							} registers_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// output state
 | 
							// output state
 | 
				
			||||||
		enum State {
 | 
							enum State {
 | 
				
			||||||
			Sync, ColourBurst, Border, Pixels
 | 
								Sync, ColourBurst, Border, Pixels
 | 
				
			||||||
		} this_state_, output_state_;
 | 
							} this_state_ = State::Sync, output_state_ = State::Sync;
 | 
				
			||||||
		int cycles_in_state_;
 | 
							int cycles_in_state_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 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;
 | 
				
			||||||
		const int lines_this_field() {
 | 
							int lines_this_field() const {
 | 
				
			||||||
			// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
 | 
								// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
 | 
				
			||||||
			return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
 | 
								return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		const int raster_value() {
 | 
							int raster_value() const {
 | 
				
			||||||
			const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
 | 
								const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
 | 
				
			||||||
			const int line = vertical_counter_ + bonus_line;
 | 
								const int line = vertical_counter_ + bonus_line;
 | 
				
			||||||
			const int final_line = lines_this_field();
 | 
								const int final_line = lines_this_field();
 | 
				
			||||||
@@ -478,29 +481,29 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
 | 
								// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		bool is_odd_frame() {
 | 
							bool is_odd_frame() const {
 | 
				
			||||||
			return is_odd_frame_ || !registers_.interlaced;
 | 
								return is_odd_frame_ || !registers_.interlaced;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// latches dictating start and length of drawing
 | 
							// latches dictating start and length of drawing
 | 
				
			||||||
		bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
 | 
							bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
 | 
				
			||||||
		int rows_this_field_, columns_this_line_;
 | 
							int rows_this_field_ = 0, columns_this_line_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// current drawing position counter
 | 
							// current drawing position counter
 | 
				
			||||||
		int pixel_line_cycle_, column_counter_;
 | 
							int pixel_line_cycle_ = 0, column_counter_ = 0;
 | 
				
			||||||
		int current_row_;
 | 
							int current_row_ = 0;
 | 
				
			||||||
		uint16_t current_character_row_;
 | 
							uint16_t current_character_row_ = 0;
 | 
				
			||||||
		uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
 | 
							uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// data latched from the bus
 | 
							// data latched from the bus
 | 
				
			||||||
		uint8_t character_code_, character_colour_, character_value_;
 | 
							uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bool is_odd_frame_ = false, is_odd_line_ = false;
 | 
							bool is_odd_frame_ = false, is_odd_line_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// lookup table from 6560 colour index to appropriate PAL/NTSC value
 | 
							// lookup table from 6560 colour index to appropriate PAL/NTSC value
 | 
				
			||||||
		uint16_t colours_[16];
 | 
							uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		uint16_t *pixel_pointer;
 | 
							uint16_t *pixel_pointer = nullptr;
 | 
				
			||||||
		void output_border(int number_of_cycles) {
 | 
							void output_border(int number_of_cycles) {
 | 
				
			||||||
			uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(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;
 | 
				
			||||||
@@ -508,13 +511,13 @@ template <class BusHandler> class MOS6560 {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct {
 | 
							struct {
 | 
				
			||||||
			int cycles_per_line;
 | 
								int cycles_per_line = 0;
 | 
				
			||||||
			int line_counter_increment_offset;
 | 
								int line_counter_increment_offset = 0;
 | 
				
			||||||
			int final_line_increment_position;
 | 
								int final_line_increment_position = 0;
 | 
				
			||||||
			int lines_per_progressive_field;
 | 
								int lines_per_progressive_field = 0;
 | 
				
			||||||
			bool supports_interlacing;
 | 
								bool supports_interlacing = 0;
 | 
				
			||||||
		} timing_;
 | 
							} timing_;
 | 
				
			||||||
		OutputMode output_mode_;
 | 
							OutputMode output_mode_ = OutputMode::NTSC;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -167,8 +167,8 @@ template <class T> class CRTC6845 {
 | 
				
			|||||||
	private:
 | 
						private:
 | 
				
			||||||
		inline void perform_bus_cycle_phase1() {
 | 
							inline void perform_bus_cycle_phase1() {
 | 
				
			||||||
			// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
 | 
								// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
 | 
				
			||||||
			character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
 | 
								character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | unsigned(character_is_visible_);
 | 
				
			||||||
			bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
 | 
								bus_state_.display_enable = (int(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
 | 
				
			||||||
			bus_handler_.perform_bus_cycle_phase1(bus_state_);
 | 
								bus_handler_.perform_bus_cycle_phase1(bus_state_);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -240,7 +240,7 @@ template <class T> class CRTC6845 {
 | 
				
			|||||||
		inline void do_end_of_frame() {
 | 
							inline void do_end_of_frame() {
 | 
				
			||||||
			line_counter_ = 0;
 | 
								line_counter_ = 0;
 | 
				
			||||||
			line_is_visible_ = true;
 | 
								line_is_visible_ = true;
 | 
				
			||||||
			line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
 | 
								line_address_ = uint16_t((registers_[12] << 8) | registers_[13]);
 | 
				
			||||||
			bus_state_.refresh_address = line_address_;
 | 
								bus_state_.refresh_address = line_address_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -120,7 +120,7 @@ void ACIA::consider_transmission() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ClockingHint::Preference ACIA::preferred_clocking() {
 | 
					ClockingHint::Preference ACIA::preferred_clocking() const {
 | 
				
			||||||
	// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
 | 
						// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
 | 
				
			||||||
	// is on the receiving end.
 | 
						// is on the receiving end.
 | 
				
			||||||
	if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
 | 
						if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
 | 
				
			||||||
@@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
 | 
				
			|||||||
	return value ^ (parity_ == Parity::Even);
 | 
						return value ^ (parity_ == Parity::Even);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
 | 
					bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
 | 
				
			||||||
	// Shift this bit into the 11-bit input register; this is big enough to hold
 | 
						// Shift this bit into the 11-bit input register; this is big enough to hold
 | 
				
			||||||
	// the largest transmission symbol.
 | 
						// the largest transmission symbol.
 | 
				
			||||||
	++bits_received_;
 | 
						++bits_received_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,7 +86,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
 | 
				
			|||||||
		Serial::Line request_to_send;
 | 
							Serial::Line request_to_send;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ClockingHint::Source.
 | 
							// ClockingHint::Source.
 | 
				
			||||||
		ClockingHint::Preference preferred_clocking() final;
 | 
							ClockingHint::Preference preferred_clocking() const final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct InterruptDelegate {
 | 
							struct InterruptDelegate {
 | 
				
			||||||
			virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
 | 
								virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace Motorola::MFP68901;
 | 
					using namespace Motorola::MFP68901;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ClockingHint::Preference MFP68901::preferred_clocking() {
 | 
					ClockingHint::Preference MFP68901::preferred_clocking() const {
 | 
				
			||||||
	// Rule applied: if any timer is actively running and permitted to produce an
 | 
						// Rule applied: if any timer is actively running and permitted to produce an
 | 
				
			||||||
	// interrupt, request real-time running.
 | 
						// interrupt, request real-time running.
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,7 +76,7 @@ class MFP68901: public ClockingHint::Source {
 | 
				
			|||||||
		void set_interrupt_delegate(InterruptDelegate *delegate);
 | 
							void set_interrupt_delegate(InterruptDelegate *delegate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ClockingHint::Source.
 | 
							// ClockingHint::Source.
 | 
				
			||||||
		ClockingHint::Preference preferred_clocking() final;
 | 
							ClockingHint::Preference preferred_clocking() const final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		// MARK: - Timers
 | 
							// MARK: - Timers
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,13 +9,15 @@
 | 
				
			|||||||
#ifndef i8255_hpp
 | 
					#ifndef i8255_hpp
 | 
				
			||||||
#define i8255_hpp
 | 
					#define i8255_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Intel {
 | 
					namespace Intel {
 | 
				
			||||||
namespace i8255 {
 | 
					namespace i8255 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PortHandler {
 | 
					class PortHandler {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		void set_value(int port, uint8_t value) {}
 | 
							void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {}
 | 
				
			||||||
		uint8_t get_value(int port) { return 0xff; }
 | 
							uint8_t get_value([[maybe_unused]] int port) { return 0xff; }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Modes 1 and 2.
 | 
					// TODO: Modes 1 and 2.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -79,10 +79,14 @@ namespace {
 | 
				
			|||||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
 | 
					i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
 | 
				
			||||||
	Storage::Disk::MFMController(clock_rate),
 | 
						Storage::Disk::MFMController(clock_rate),
 | 
				
			||||||
	bus_handler_(bus_handler) {
 | 
						bus_handler_(bus_handler) {
 | 
				
			||||||
	posit_event(static_cast<int>(Event8272::CommandByte));
 | 
						posit_event(int(Event8272::CommandByte));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: implement DMA, etc. I have a vague intention to implement the IBM PC
 | 
				
			||||||
 | 
						// one day, that should help to force that stuff.
 | 
				
			||||||
 | 
						(void)bus_handler_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ClockingHint::Preference i8272::preferred_clocking() {
 | 
					ClockingHint::Preference i8272::preferred_clocking() const {
 | 
				
			||||||
	const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
 | 
						const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
 | 
				
			||||||
	if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
 | 
						if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
 | 
				
			||||||
	return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
 | 
						return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
 | 
				
			||||||
@@ -97,7 +101,7 @@ void i8272::run_for(Cycles cycles) {
 | 
				
			|||||||
	if(delay_time_ > 0) {
 | 
						if(delay_time_ > 0) {
 | 
				
			||||||
		if(cycles.as_integral() >= delay_time_) {
 | 
							if(cycles.as_integral() >= delay_time_) {
 | 
				
			||||||
			delay_time_ = 0;
 | 
								delay_time_ = 0;
 | 
				
			||||||
			posit_event(static_cast<int>(Event8272::Timer));
 | 
								posit_event(int(Event8272::Timer));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			delay_time_ -= cycles.as_integral();
 | 
								delay_time_ -= cycles.as_integral();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -114,7 +118,7 @@ void i8272::run_for(Cycles cycles) {
 | 
				
			|||||||
				while(steps--) {
 | 
									while(steps--) {
 | 
				
			||||||
					// Perform a step.
 | 
										// Perform a step.
 | 
				
			||||||
					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
 | 
										int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
 | 
				
			||||||
					LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position));
 | 
										LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << int(drives_[c].head_position));
 | 
				
			||||||
					select_drive(c);
 | 
										select_drive(c);
 | 
				
			||||||
					get_drive().step(Storage::Disk::HeadPosition(direction));
 | 
										get_drive().step(Storage::Disk::HeadPosition(direction));
 | 
				
			||||||
					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
 | 
										if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
 | 
				
			||||||
@@ -156,7 +160,7 @@ void i8272::run_for(Cycles cycles) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// check for busy plus ready disabled
 | 
						// check for busy plus ready disabled
 | 
				
			||||||
	if(is_executing_ && !get_drive().get_is_ready()) {
 | 
						if(is_executing_ && !get_drive().get_is_ready()) {
 | 
				
			||||||
		posit_event(static_cast<int>(Event8272::NoLongerReady));
 | 
							posit_event(int(Event8272::NoLongerReady));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
 | 
						is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
 | 
				
			||||||
@@ -177,7 +181,7 @@ void i8272::write(int address, uint8_t value) {
 | 
				
			|||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// accumulate latest byte in the command byte sequence
 | 
							// accumulate latest byte in the command byte sequence
 | 
				
			||||||
		command_.push_back(value);
 | 
							command_.push_back(value);
 | 
				
			||||||
		posit_event(static_cast<int>(Event8272::CommandByte));
 | 
							posit_event(int(Event8272::CommandByte));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -186,7 +190,7 @@ uint8_t i8272::read(int address) {
 | 
				
			|||||||
		if(result_stack_.empty()) return 0xff;
 | 
							if(result_stack_.empty()) return 0xff;
 | 
				
			||||||
		uint8_t result = result_stack_.back();
 | 
							uint8_t result = result_stack_.back();
 | 
				
			||||||
		result_stack_.pop_back();
 | 
							result_stack_.pop_back();
 | 
				
			||||||
		if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
 | 
							if(result_stack_.empty()) posit_event(int(Event8272::ResultEmpty));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -198,16 +202,16 @@ uint8_t i8272::read(int address) {
 | 
				
			|||||||
#define END_SECTION()	}
 | 
					#define END_SECTION()	}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MS_TO_CYCLES(x)			x * 8000
 | 
					#define MS_TO_CYCLES(x)			x * 8000
 | 
				
			||||||
#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
 | 
					#define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
 | 
				
			||||||
#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
 | 
					#define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = int(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define PASTE(x, y) x##y
 | 
					#define PASTE(x, y) x##y
 | 
				
			||||||
#define CONCAT(x, y) PASTE(x, y)
 | 
					#define CONCAT(x, y) PASTE(x, y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define FIND_HEADER()	\
 | 
					#define FIND_HEADER()	\
 | 
				
			||||||
	set_data_mode(DataMode::Scanning);	\
 | 
						set_data_mode(DataMode::Scanning);	\
 | 
				
			||||||
	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
 | 
						CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
 | 
				
			||||||
	if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; }	\
 | 
						if(event_type == int(Event::IndexHole)) { index_hole_limit_--; }	\
 | 
				
			||||||
	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\
 | 
						else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\
 | 
				
			||||||
	\
 | 
						\
 | 
				
			||||||
	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\
 | 
						if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\
 | 
				
			||||||
@@ -215,8 +219,8 @@ uint8_t i8272::read(int address) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define FIND_DATA()	\
 | 
					#define FIND_DATA()	\
 | 
				
			||||||
	set_data_mode(DataMode::Scanning);	\
 | 
						set_data_mode(DataMode::Scanning);	\
 | 
				
			||||||
	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
 | 
						CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
 | 
				
			||||||
	if(event_type == static_cast<int>(Event::Token)) { \
 | 
						if(event_type == int(Event::Token)) { \
 | 
				
			||||||
		if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\
 | 
							if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -264,8 +268,8 @@ uint8_t i8272::read(int address) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void i8272::posit_event(int event_type) {
 | 
					void i8272::posit_event(int event_type) {
 | 
				
			||||||
	if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
 | 
						if(event_type == int(Event::IndexHole)) index_hole_count_++;
 | 
				
			||||||
	if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
 | 
						if(event_type == int(Event8272::NoLongerReady)) {
 | 
				
			||||||
		SetNotReady();
 | 
							SetNotReady();
 | 
				
			||||||
		goto abort;
 | 
							goto abort;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -425,12 +429,12 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
	// Performs the read data or read deleted data command.
 | 
						// Performs the read data or read deleted data command.
 | 
				
			||||||
	read_data:
 | 
						read_data:
 | 
				
			||||||
			LOG(PADHEX(2) << "Read [deleted] data ["
 | 
								LOG(PADHEX(2) << "Read [deleted] data ["
 | 
				
			||||||
				<< static_cast<int>(command_[2]) << " "
 | 
									<< int(command_[2]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[3]) << " "
 | 
									<< int(command_[3]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[4]) << " "
 | 
									<< int(command_[4]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[5]) << " ... "
 | 
									<< int(command_[5]) << " ... "
 | 
				
			||||||
				<< static_cast<int>(command_[6]) << " "
 | 
									<< int(command_[6]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[8]) << "]");
 | 
									<< int(command_[8]) << "]");
 | 
				
			||||||
		read_next_data:
 | 
							read_next_data:
 | 
				
			||||||
			goto read_write_find_header;
 | 
								goto read_write_find_header;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -439,7 +443,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
		read_data_found_header:
 | 
							read_data_found_header:
 | 
				
			||||||
			FIND_DATA();
 | 
								FIND_DATA();
 | 
				
			||||||
			ClearControlMark();
 | 
								ClearControlMark();
 | 
				
			||||||
			if(event_type == static_cast<int>(Event::Token)) {
 | 
								if(event_type == int(Event::Token)) {
 | 
				
			||||||
				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
 | 
									if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
 | 
				
			||||||
					// Something other than a data mark came next, impliedly an ID or index mark.
 | 
										// Something other than a data mark came next, impliedly an ID or index mark.
 | 
				
			||||||
					SetMissingAddressMark();
 | 
										SetMissingAddressMark();
 | 
				
			||||||
@@ -470,24 +474,24 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
		//
 | 
							//
 | 
				
			||||||
		// TODO: consider DTL.
 | 
							// TODO: consider DTL.
 | 
				
			||||||
		read_data_get_byte:
 | 
							read_data_get_byte:
 | 
				
			||||||
			WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
 | 
								WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
 | 
				
			||||||
			if(event_type == static_cast<int>(Event::Token)) {
 | 
								if(event_type == int(Event::Token)) {
 | 
				
			||||||
				result_stack_.push_back(get_latest_token().byte_value);
 | 
									result_stack_.push_back(get_latest_token().byte_value);
 | 
				
			||||||
				distance_into_section_++;
 | 
									distance_into_section_++;
 | 
				
			||||||
				SetDataRequest();
 | 
									SetDataRequest();
 | 
				
			||||||
				SetDataDirectionToProcessor();
 | 
									SetDataDirectionToProcessor();
 | 
				
			||||||
				WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
 | 
									WAIT_FOR_EVENT(int(Event8272::ResultEmpty) | int(Event::Token) | int(Event::IndexHole));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			switch(event_type) {
 | 
								switch(event_type) {
 | 
				
			||||||
				case static_cast<int>(Event8272::ResultEmpty):	// The caller read the byte in time; proceed as normal.
 | 
									case int(Event8272::ResultEmpty):	// The caller read the byte in time; proceed as normal.
 | 
				
			||||||
					ResetDataRequest();
 | 
										ResetDataRequest();
 | 
				
			||||||
					if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
 | 
										if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case static_cast<int>(Event::Token):				// The caller hasn't read the old byte yet and a new one has arrived
 | 
									case int(Event::Token):				// The caller hasn't read the old byte yet and a new one has arrived
 | 
				
			||||||
					SetOverrun();
 | 
										SetOverrun();
 | 
				
			||||||
					goto abort;
 | 
										goto abort;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case static_cast<int>(Event::IndexHole):
 | 
									case int(Event::IndexHole):
 | 
				
			||||||
					SetEndOfCylinder();
 | 
										SetEndOfCylinder();
 | 
				
			||||||
					goto abort;
 | 
										goto abort;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
@@ -515,12 +519,12 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	write_data:
 | 
						write_data:
 | 
				
			||||||
			LOG(PADHEX(2) << "Write [deleted] data ["
 | 
								LOG(PADHEX(2) << "Write [deleted] data ["
 | 
				
			||||||
				<< static_cast<int>(command_[2]) << " "
 | 
									<< int(command_[2]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[3]) << " "
 | 
									<< int(command_[3]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[4]) << " "
 | 
									<< int(command_[4]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[5]) << " ... "
 | 
									<< int(command_[5]) << " ... "
 | 
				
			||||||
				<< static_cast<int>(command_[6]) << " "
 | 
									<< int(command_[6]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[8]) << "]");
 | 
									<< int(command_[8]) << "]");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if(get_drive().get_is_read_only()) {
 | 
								if(get_drive().get_is_read_only()) {
 | 
				
			||||||
				SetNotWriteable();
 | 
									SetNotWriteable();
 | 
				
			||||||
@@ -571,7 +575,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
	// Performs the read ID command.
 | 
						// Performs the read ID command.
 | 
				
			||||||
	read_id:
 | 
						read_id:
 | 
				
			||||||
		// Establishes the drive and head being addressed, and whether in double density mode.
 | 
							// Establishes the drive and head being addressed, and whether in double density mode.
 | 
				
			||||||
			LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]");
 | 
								LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << int(command_[1]) << "]");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
 | 
							// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
 | 
				
			||||||
		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
 | 
							// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
 | 
				
			||||||
@@ -594,10 +598,10 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
	// Performs read track.
 | 
						// Performs read track.
 | 
				
			||||||
	read_track:
 | 
						read_track:
 | 
				
			||||||
			LOG(PADHEX(2) << "Read track ["
 | 
								LOG(PADHEX(2) << "Read track ["
 | 
				
			||||||
				<< static_cast<int>(command_[2]) << " "
 | 
									<< int(command_[2]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[3]) << " "
 | 
									<< int(command_[3]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[4]) << " "
 | 
									<< int(command_[4]) << " "
 | 
				
			||||||
				<< static_cast<int>(command_[5]) << "]");
 | 
									<< int(command_[5]) << "]");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Wait for the index hole.
 | 
								// Wait for the index hole.
 | 
				
			||||||
			WAIT_FOR_EVENT(Event::IndexHole);
 | 
								WAIT_FOR_EVENT(Event::IndexHole);
 | 
				
			||||||
@@ -627,7 +631,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
			distance_into_section_++;
 | 
								distance_into_section_++;
 | 
				
			||||||
			SetDataRequest();
 | 
								SetDataRequest();
 | 
				
			||||||
			// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
 | 
								// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
 | 
				
			||||||
			WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
 | 
								WAIT_FOR_EVENT(int(Event8272::ResultEmpty));
 | 
				
			||||||
			ResetDataRequest();
 | 
								ResetDataRequest();
 | 
				
			||||||
			if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
 | 
								if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -664,13 +668,13 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
			expects_input_ = true;
 | 
								expects_input_ = true;
 | 
				
			||||||
			distance_into_section_ = 0;
 | 
								distance_into_section_ = 0;
 | 
				
			||||||
		format_track_write_header:
 | 
							format_track_write_header:
 | 
				
			||||||
			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
 | 
								WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
 | 
				
			||||||
			switch(event_type) {
 | 
								switch(event_type) {
 | 
				
			||||||
				case static_cast<int>(Event::IndexHole):
 | 
									case int(Event::IndexHole):
 | 
				
			||||||
					SetOverrun();
 | 
										SetOverrun();
 | 
				
			||||||
					goto abort;
 | 
										goto abort;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
				case static_cast<int>(Event::DataWritten):
 | 
									case int(Event::DataWritten):
 | 
				
			||||||
					header_[distance_into_section_] = input_;
 | 
										header_[distance_into_section_] = input_;
 | 
				
			||||||
					write_byte(input_);
 | 
										write_byte(input_);
 | 
				
			||||||
					has_input_ = false;
 | 
										has_input_ = false;
 | 
				
			||||||
@@ -683,10 +687,10 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			LOG(PADHEX(2) << "W:"
 | 
								LOG(PADHEX(2) << "W:"
 | 
				
			||||||
				<< static_cast<int>(header_[0]) << " "
 | 
									<< int(header_[0]) << " "
 | 
				
			||||||
				<< static_cast<int>(header_[1]) << " "
 | 
									<< int(header_[1]) << " "
 | 
				
			||||||
				<< static_cast<int>(header_[2]) << " "
 | 
									<< int(header_[2]) << " "
 | 
				
			||||||
				<< static_cast<int>(header_[3]) << ", "
 | 
									<< int(header_[3]) << ", "
 | 
				
			||||||
				<< get_crc_generator().get_value());
 | 
									<< get_crc_generator().get_value());
 | 
				
			||||||
			write_crc();
 | 
								write_crc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -706,8 +710,8 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
			// Otherwise, pad out to the index hole.
 | 
								// Otherwise, pad out to the index hole.
 | 
				
			||||||
		format_track_pad:
 | 
							format_track_pad:
 | 
				
			||||||
			write_byte(get_is_double_density() ? 0x4e : 0xff);
 | 
								write_byte(get_is_double_density() ? 0x4e : 0xff);
 | 
				
			||||||
			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
 | 
								WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
 | 
				
			||||||
			if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
 | 
								if(event_type != int(Event::IndexHole)) goto format_track_pad;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			end_writing();
 | 
								end_writing();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -758,7 +762,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
				// up in run_for understands to mean 'keep going until track 0 is active').
 | 
									// up in run_for understands to mean 'keep going until track 0 is active').
 | 
				
			||||||
				if(command_.size() > 2) {
 | 
									if(command_.size() > 2) {
 | 
				
			||||||
					drives_[drive].target_head_position = command_[2];
 | 
										drives_[drive].target_head_position = command_[2];
 | 
				
			||||||
					LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
 | 
										LOG(PADHEX(2) << "Seek to " << int(command_[2]));
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					drives_[drive].target_head_position = -1;
 | 
										drives_[drive].target_head_position = -1;
 | 
				
			||||||
					drives_[drive].head_position = 0;
 | 
										drives_[drive].head_position = 0;
 | 
				
			||||||
@@ -789,7 +793,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
				// If a drive was found, return its results. Otherwise return a single 0x80.
 | 
									// If a drive was found, return its results. Otherwise return a single 0x80.
 | 
				
			||||||
				if(found_drive != -1) {
 | 
									if(found_drive != -1) {
 | 
				
			||||||
					drives_[found_drive].phase = Drive::NotSeeking;
 | 
										drives_[found_drive].phase = Drive::NotSeeking;
 | 
				
			||||||
					status_[0] = static_cast<uint8_t>(found_drive);
 | 
										status_[0] = uint8_t(found_drive);
 | 
				
			||||||
					main_status_ &= ~(1 << found_drive);
 | 
										main_status_ &= ~(1 << found_drive);
 | 
				
			||||||
					SetSeekEnd();
 | 
										SetSeekEnd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -819,7 +823,7 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
				int drive = command_[1] & 3;
 | 
									int drive = command_[1] & 3;
 | 
				
			||||||
				select_drive(drive);
 | 
									select_drive(drive);
 | 
				
			||||||
				result_stack_= {
 | 
									result_stack_= {
 | 
				
			||||||
					static_cast<uint8_t>(
 | 
										uint8_t(
 | 
				
			||||||
						(command_[1] & 7) |	// drive and head number
 | 
											(command_[1] & 7) |	// drive and head number
 | 
				
			||||||
						0x08 |				// single sided
 | 
											0x08 |				// single sided
 | 
				
			||||||
						(get_drive().get_is_track_zero() ? 0x10 : 0x00)	|
 | 
											(get_drive().get_is_track_zero() ? 0x10 : 0x00)	|
 | 
				
			||||||
@@ -853,9 +857,9 @@ void i8272::posit_event(int event_type) {
 | 
				
			|||||||
	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
 | 
						// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
 | 
				
			||||||
	// last thing in it will be returned first.
 | 
						// last thing in it will be returned first.
 | 
				
			||||||
	post_result:
 | 
						post_result:
 | 
				
			||||||
			LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
 | 
								LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; ");
 | 
				
			||||||
			for(std::size_t c = 0; c < result_stack_.size(); c++) {
 | 
								for(std::size_t c = 0; c < result_stack_.size(); c++) {
 | 
				
			||||||
				LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
 | 
									LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c]));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			LOGNBR(std::endl);
 | 
								LOGNBR(std::endl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -880,13 +884,13 @@ bool i8272::seek_is_satisfied(int drive) {
 | 
				
			|||||||
			(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
 | 
								(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void i8272::set_dma_acknowledge(bool dack) {
 | 
					void i8272::set_dma_acknowledge(bool) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void i8272::set_terminal_count(bool tc) {
 | 
					void i8272::set_terminal_count(bool) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void i8272::set_data_input(uint8_t value) {
 | 
					void i8272::set_data_input(uint8_t) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t i8272::get_data_output() {
 | 
					uint8_t i8272::get_data_output() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,8 +20,9 @@ namespace i8272 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class BusHandler {
 | 
					class BusHandler {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		virtual void set_dma_data_request(bool drq) {}
 | 
							virtual ~BusHandler() {}
 | 
				
			||||||
		virtual void set_interrupt(bool irq) {}
 | 
							virtual void set_dma_data_request([[maybe_unused]] bool drq) {}
 | 
				
			||||||
 | 
							virtual void set_interrupt([[maybe_unused]] bool irq) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class i8272 : public Storage::Disk::MFMController {
 | 
					class i8272 : public Storage::Disk::MFMController {
 | 
				
			||||||
@@ -39,13 +40,13 @@ 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() final;
 | 
							ClockingHint::Preference preferred_clocking() const final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected:
 | 
						protected:
 | 
				
			||||||
		virtual void select_drive(int number) = 0;
 | 
							virtual void select_drive(int number) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		// The bus handler, for interrupt and DMA-driven usage.
 | 
							// The bus handler, for interrupt and DMA-driven usage. [TODO]
 | 
				
			||||||
		BusHandler &bus_handler_;
 | 
							BusHandler &bus_handler_;
 | 
				
			||||||
		std::unique_ptr<BusHandler> allocated_bus_handler_;
 | 
							std::unique_ptr<BusHandler> allocated_bus_handler_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,8 +68,8 @@ class i8272 : public Storage::Disk::MFMController {
 | 
				
			|||||||
			ResultEmpty = (1 << 5),
 | 
								ResultEmpty = (1 << 5),
 | 
				
			||||||
			NoLongerReady = (1 << 6)
 | 
								NoLongerReady = (1 << 6)
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		void posit_event(int type) override;
 | 
							void posit_event(int type) final;
 | 
				
			||||||
		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
 | 
							int interesting_event_mask_ = int(Event8272::CommandByte);
 | 
				
			||||||
		int resume_point_ = 0;
 | 
							int resume_point_ = 0;
 | 
				
			||||||
		bool is_access_command_ = false;
 | 
							bool is_access_command_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "z8530.hpp"
 | 
					#include "z8530.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef NDEBUG
 | 
				
			||||||
 | 
					#define NDEBUG
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define LOG_PREFIX "[SCC] "
 | 
				
			||||||
#include "../../Outputs/Log.hpp"
 | 
					#include "../../Outputs/Log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Zilog::SCC;
 | 
					using namespace Zilog::SCC;
 | 
				
			||||||
@@ -16,7 +21,7 @@ void z8530::reset() {
 | 
				
			|||||||
	// TODO.
 | 
						// TODO.
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool z8530::get_interrupt_line() {
 | 
					bool z8530::get_interrupt_line() const {
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
		(master_interrupt_control_ & 0x8) &&
 | 
							(master_interrupt_control_ & 0x8) &&
 | 
				
			||||||
		(
 | 
							(
 | 
				
			||||||
@@ -25,22 +30,30 @@ bool z8530::get_interrupt_line() {
 | 
				
			|||||||
		);
 | 
							);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
						Per the standard defined in the header file, this implementation follows
 | 
				
			||||||
 | 
						an addressing convention of:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						A0 = A/B		(i.e. channel select)
 | 
				
			||||||
 | 
						A1 = C/D		(i.e. control or data)
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::uint8_t z8530::read(int address) {
 | 
					std::uint8_t z8530::read(int address) {
 | 
				
			||||||
	if(address & 2) {
 | 
						if(address & 2) {
 | 
				
			||||||
		// Read data register for channel
 | 
							// Read data register for channel.
 | 
				
			||||||
		return 0x00;
 | 
							return channels_[address & 1].read(true, pointer_);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// Read control register for channel.
 | 
							// Read control register for channel.
 | 
				
			||||||
		uint8_t result = 0;
 | 
							uint8_t result = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch(pointer_) {
 | 
							switch(pointer_) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				result = channels_[address & 1].read(address & 2, pointer_);
 | 
									result = channels_[address & 1].read(false, pointer_);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 2:		// Handled non-symmetrically between channels.
 | 
								case 2:		// Handled non-symmetrically between channels.
 | 
				
			||||||
				if(address & 1) {
 | 
									if(address & 1) {
 | 
				
			||||||
					LOG("[SCC] Unimplemented: register 2 status bits");
 | 
										LOG("Unimplemented: register 2 status bits");
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					result = interrupt_vector_;
 | 
										result = interrupt_vector_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,7 +76,11 @@ std::uint8_t z8530::read(int address) {
 | 
				
			|||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Cf. the two-step control register selection process in ::write. Since this
 | 
				
			||||||
 | 
							// definitely wasn't a *write* to register 0, it follows that the next selected
 | 
				
			||||||
 | 
							// control register will be 0.
 | 
				
			||||||
		pointer_ = 0;
 | 
							pointer_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		update_delegate();
 | 
							update_delegate();
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -73,24 +90,31 @@ std::uint8_t z8530::read(int address) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void z8530::write(int address, std::uint8_t value) {
 | 
					void z8530::write(int address, std::uint8_t value) {
 | 
				
			||||||
	if(address & 2) {
 | 
						if(address & 2) {
 | 
				
			||||||
		// Write data register for channel.
 | 
							// Write data register for channel. This is completely independent
 | 
				
			||||||
 | 
							// of whatever is going on over in the control realm.
 | 
				
			||||||
 | 
							channels_[address & 1].write(true, pointer_, value);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// Write control register for channel.
 | 
							// Write control register for channel; there's a two-step sequence
 | 
				
			||||||
 | 
							// here for the programmer. Initially the selected register
 | 
				
			||||||
 | 
							// (i.e. `pointer_`) is zero. That register includes a field to
 | 
				
			||||||
 | 
							// set the next selected register. After any other register has
 | 
				
			||||||
 | 
							// been written to, register 0 is selected again.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Most registers are per channel, but a couple are shared; sever
 | 
							// Most registers are per channel, but a couple are shared;
 | 
				
			||||||
		// them here.
 | 
							// sever them here, send the rest to the appropriate chnanel.
 | 
				
			||||||
		switch(pointer_) {
 | 
							switch(pointer_) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				channels_[address & 1].write(address & 2, pointer_, value);
 | 
									channels_[address & 1].write(false, pointer_, value);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 2:	// Interrupt vector register; shared between both channels.
 | 
								case 2:	// Interrupt vector register; used only by Channel B.
 | 
				
			||||||
 | 
										// So there's only one of these.
 | 
				
			||||||
				interrupt_vector_ = value;
 | 
									interrupt_vector_ = value;
 | 
				
			||||||
				LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
 | 
									LOG("Interrupt vector set to " << PADHEX(2) << int(value));
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 9:	// Master interrupt and reset register; also shared between both channels.
 | 
								case 9:	// Master interrupt and reset register; there is also only one of these.
 | 
				
			||||||
				LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
 | 
									LOG("Master interrupt and reset register: " << PADHEX(2) << int(value));
 | 
				
			||||||
				master_interrupt_control_ = value;
 | 
									master_interrupt_control_ = value;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -105,7 +129,8 @@ void z8530::write(int address, std::uint8_t value) {
 | 
				
			|||||||
			pointer_ = value & 7;
 | 
								pointer_ = value & 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// If the command part of the byte is a 'point high', also set the
 | 
								// If the command part of the byte is a 'point high', also set the
 | 
				
			||||||
			// top bit of the pointer.
 | 
								// top bit of the pointer. Channels themselves therefore need not
 | 
				
			||||||
 | 
								// (/should not) respond to the point high command.
 | 
				
			||||||
			if(((value >> 3)&7) == 1) {
 | 
								if(((value >> 3)&7) == 1) {
 | 
				
			||||||
				pointer_ |= 8;
 | 
									pointer_ |= 8;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -126,16 +151,79 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
 | 
				
			|||||||
	if(data) {
 | 
						if(data) {
 | 
				
			||||||
		return data_;
 | 
							return data_;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
							LOG("Control read from register " << int(pointer));
 | 
				
			||||||
		// Otherwise, this is a control read...
 | 
							// Otherwise, this is a control read...
 | 
				
			||||||
		switch(pointer) {
 | 
							switch(pointer) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				LOG("[SCC] Unrecognised control read from register " << int(pointer));
 | 
					 | 
				
			||||||
			return 0x00;
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 0:
 | 
								case 0x0:	// Read Register 0; see p.37 (PDF p.45).
 | 
				
			||||||
 | 
									// b0: Rx character available.
 | 
				
			||||||
 | 
									// b1: zero count.
 | 
				
			||||||
 | 
									// b2: Tx buffer empty.
 | 
				
			||||||
 | 
									// b3: DCD.
 | 
				
			||||||
 | 
									// b4: sync/hunt.
 | 
				
			||||||
 | 
									// b5: CTS.
 | 
				
			||||||
 | 
									// b6: Tx underrun/EOM.
 | 
				
			||||||
 | 
									// b7: break/abort.
 | 
				
			||||||
			return dcd_ ? 0x8 : 0x0;
 | 
								return dcd_ ? 0x8 : 0x0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 0xf:
 | 
								case 0x1:	// Read Register 1; see p.37 (PDF p.45).
 | 
				
			||||||
 | 
									// b0: all sent.
 | 
				
			||||||
 | 
									// b1: residue code 0.
 | 
				
			||||||
 | 
									// b2: residue code 1.
 | 
				
			||||||
 | 
									// b3: residue code 2.
 | 
				
			||||||
 | 
									// b4: parity error.
 | 
				
			||||||
 | 
									// b5: Rx overrun error.
 | 
				
			||||||
 | 
									// b6: CRC/framing error.
 | 
				
			||||||
 | 
									// b7: end of frame (SDLC).
 | 
				
			||||||
 | 
								return 0x01;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x2:	// Read Register 2; see p.37 (PDF p.45).
 | 
				
			||||||
 | 
									// Interrupt vector — modified by status information in B channel.
 | 
				
			||||||
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x3:	// Read Register 3; see p.37 (PDF p.45).
 | 
				
			||||||
 | 
									// B channel: all bits are 0.
 | 
				
			||||||
 | 
									// A channel:
 | 
				
			||||||
 | 
									// b0: Channel B ext/status IP.
 | 
				
			||||||
 | 
									// b1: Channel B Tx IP.
 | 
				
			||||||
 | 
									// b2: Channel B Rx IP.
 | 
				
			||||||
 | 
									// b3: Channel A ext/status IP.
 | 
				
			||||||
 | 
									// b4: Channel A Tx IP.
 | 
				
			||||||
 | 
									// b5: Channel A Rx IP.
 | 
				
			||||||
 | 
									// b6, b7: 0.
 | 
				
			||||||
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0xa:	// Read Register 10; see p.37 (PDF p.45).
 | 
				
			||||||
 | 
									// b0: 0
 | 
				
			||||||
 | 
									// b1: On loop.
 | 
				
			||||||
 | 
									// b2: 0
 | 
				
			||||||
 | 
									// b3: 0
 | 
				
			||||||
 | 
									// b4: Loop sending.
 | 
				
			||||||
 | 
									// b5: 0
 | 
				
			||||||
 | 
									// b6: Two clocks missing.
 | 
				
			||||||
 | 
									// b7: One clock missing.
 | 
				
			||||||
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0xc:	// Read Register 12; see p.37 (PDF p.45).
 | 
				
			||||||
 | 
									// Lower byte of time constant.
 | 
				
			||||||
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0xd:	// Read Register 13; see p.38 (PDF p.46).
 | 
				
			||||||
 | 
									// Upper byte of time constant.
 | 
				
			||||||
 | 
								return 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0xf:	// Read Register 15; see p.38 (PDF p.46).
 | 
				
			||||||
 | 
									// External interrupt status:
 | 
				
			||||||
 | 
									// b0: 0
 | 
				
			||||||
 | 
									// b1: Zero count.
 | 
				
			||||||
 | 
									// b2: 0
 | 
				
			||||||
 | 
									// b3: DCD.
 | 
				
			||||||
 | 
									// b4: Sync/hunt.
 | 
				
			||||||
 | 
									// b5: CTS.
 | 
				
			||||||
 | 
									// b6: Tx underrun/EOM.
 | 
				
			||||||
 | 
									// b7: Break/abort.
 | 
				
			||||||
			return external_interrupt_status_;
 | 
								return external_interrupt_status_;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -148,9 +236,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
 | 
				
			|||||||
		data_ = value;
 | 
							data_ = value;
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
							LOG("Control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
 | 
				
			||||||
		switch(pointer) {
 | 
							switch(pointer) {
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
 | 
									LOG("Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 0x0:	// Write register 0 — CRC reset and other functions.
 | 
								case 0x0:	// Write register 0 — CRC reset and other functions.
 | 
				
			||||||
@@ -158,13 +247,13 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
 | 
				
			|||||||
				switch(value >> 6) {
 | 
									switch(value >> 6) {
 | 
				
			||||||
					default:	/* Do nothing. */		break;
 | 
										default:	/* Do nothing. */		break;
 | 
				
			||||||
					case 1:
 | 
										case 1:
 | 
				
			||||||
						LOG("[SCC] TODO: reset Rx CRC checker.");
 | 
											LOG("TODO: reset Rx CRC checker.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 2:
 | 
										case 2:
 | 
				
			||||||
						LOG("[SCC] TODO: reset Tx CRC checker.");
 | 
											LOG("TODO: reset Tx CRC checker.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 3:
 | 
										case 3:
 | 
				
			||||||
						LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
 | 
											LOG("TODO: reset Tx underrun/EOM latch.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -172,32 +261,84 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
 | 
				
			|||||||
				switch((value >> 3)&7) {
 | 
									switch((value >> 3)&7) {
 | 
				
			||||||
					default:	/* Do nothing. */		break;
 | 
										default:	/* Do nothing. */		break;
 | 
				
			||||||
					case 2:
 | 
										case 2:
 | 
				
			||||||
//						LOG("[SCC] reset ext/status interrupts.");
 | 
					//						LOG("reset ext/status interrupts.");
 | 
				
			||||||
						external_status_interrupt_ = false;
 | 
											external_status_interrupt_ = false;
 | 
				
			||||||
						external_interrupt_status_ = 0;
 | 
											external_interrupt_status_ = 0;
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 3:
 | 
										case 3:
 | 
				
			||||||
						LOG("[SCC] TODO: send abort (SDLC).");
 | 
											LOG("TODO: send abort (SDLC).");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 4:
 | 
										case 4:
 | 
				
			||||||
						LOG("[SCC] TODO: enable interrupt on next Rx character.");
 | 
											LOG("TODO: enable interrupt on next Rx character.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 5:
 | 
										case 5:
 | 
				
			||||||
						LOG("[SCC] TODO: reset Tx interrupt pending.");
 | 
											LOG("TODO: reset Tx interrupt pending.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 6:
 | 
										case 6:
 | 
				
			||||||
						LOG("[SCC] TODO: reset error.");
 | 
											LOG("TODO: reset error.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case 7:
 | 
										case 7:
 | 
				
			||||||
						LOG("[SCC] TODO: reset highest IUS.");
 | 
											LOG("TODO: reset highest IUS.");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 0x1:	// Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
 | 
								case 0x1:	// Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
 | 
				
			||||||
				interrupt_mask_ = value;
 | 
									interrupt_mask_ = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									/*
 | 
				
			||||||
 | 
										b7 = 0 => Wait/Request output is inactive; 1 => output is informative.
 | 
				
			||||||
 | 
										b6 = Wait/request output is for...
 | 
				
			||||||
 | 
											0 => wait: floating when inactive, low if CPU is attempting to transfer data the SCC isn't yet ready for.
 | 
				
			||||||
 | 
											1 => request: high if inactive, low if SCC is ready to transfer data.
 | 
				
			||||||
 | 
										b5 = 1 => wait/request is relative to read buffer; 0 => relative to write buffer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										b4/b3:
 | 
				
			||||||
 | 
											00 = disable receive interrupt
 | 
				
			||||||
 | 
											01 = interrupt on first character or special condition
 | 
				
			||||||
 | 
											10 = interrupt on all characters and special conditions
 | 
				
			||||||
 | 
											11 = interrupt only upon special conditions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										b2 = 1 => parity error is a special condition; 0 => it isn't.
 | 
				
			||||||
 | 
										b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
 | 
				
			||||||
 | 
										b0 = 1 => external interrupt is enabled; 0 => it isn't.
 | 
				
			||||||
 | 
									*/
 | 
				
			||||||
 | 
									LOG("Interrupt mask: " << PADHEX(2) << int(value));
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x2:	// Write register 2 - interrupt vector.
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x3: {	// Write register 3 — Receive Parameters and Control.
 | 
				
			||||||
 | 
									// Get bit count.
 | 
				
			||||||
 | 
									int receive_bit_count = 8;
 | 
				
			||||||
 | 
									switch(value >> 6) {
 | 
				
			||||||
 | 
										default:	receive_bit_count = 5;	break;
 | 
				
			||||||
 | 
										case 1:		receive_bit_count = 7;	break;
 | 
				
			||||||
 | 
										case 2:		receive_bit_count = 6;	break;
 | 
				
			||||||
 | 
										case 3:		receive_bit_count = 8;	break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									LOG("Receive bit count: " << receive_bit_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									(void)receive_bit_count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									/*
 | 
				
			||||||
 | 
										b7,b6:
 | 
				
			||||||
 | 
											00 = 5 receive bits per character
 | 
				
			||||||
 | 
											01 = 7 bits
 | 
				
			||||||
 | 
											10 = 6 bits
 | 
				
			||||||
 | 
											11 = 8 bits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										b5 = 1 => DCD and CTS outputs are set automatically; 0 => they're inputs to read register 0.
 | 
				
			||||||
 | 
													(DCD is ignored in local loopback; CTS is ignored in both auto echo and local loopback).
 | 
				
			||||||
 | 
										b4: enter hunt mode (if set to 1, presumably?)
 | 
				
			||||||
 | 
										b3 = 1 => enable receiver CRC generation; 0 => don't.
 | 
				
			||||||
 | 
										b2: address search mode (SDLC)
 | 
				
			||||||
 | 
										b1: sync character load inhibit.
 | 
				
			||||||
 | 
										b0: Rx enable.
 | 
				
			||||||
 | 
									*/
 | 
				
			||||||
 | 
								} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 0x4:	// Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
 | 
								case 0x4:	// Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
 | 
				
			||||||
				// Bits 0 and 1 select parity mode.
 | 
									// Bits 0 and 1 select parity mode.
 | 
				
			||||||
				if(!(value&1)) {
 | 
									if(!(value&1)) {
 | 
				
			||||||
@@ -236,6 +377,23 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x5:
 | 
				
			||||||
 | 
									// b7: DTR
 | 
				
			||||||
 | 
									// b6/b5:
 | 
				
			||||||
 | 
									//	00 = Tx 5 bits (or less) per character
 | 
				
			||||||
 | 
									//	01 = Tx 7 bits per character
 | 
				
			||||||
 | 
									//	10 = Tx 6 bits per character
 | 
				
			||||||
 | 
									//	11 = Tx 8 bits per character
 | 
				
			||||||
 | 
									// b4: send break.
 | 
				
			||||||
 | 
									// b3: Tx enable.
 | 
				
			||||||
 | 
									// b2: SDLC (if 0) / CRC-16 (if 1)
 | 
				
			||||||
 | 
									// b1: RTS
 | 
				
			||||||
 | 
									// b0: Tx CRC enable.
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 0x6:
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 0xf:	// Write register 15 — External/Status Interrupt Control.
 | 
								case 0xf:	// Write register 15 — External/Status Interrupt Control.
 | 
				
			||||||
				external_interrupt_mask_ = value;
 | 
									external_interrupt_mask_ = value;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
@@ -253,12 +411,17 @@ void z8530::Channel::set_dcd(bool level) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool z8530::Channel::get_interrupt_line() {
 | 
					bool z8530::Channel::get_interrupt_line() const {
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
		(interrupt_mask_ & 1) && external_status_interrupt_;
 | 
							(interrupt_mask_ & 1) && external_status_interrupt_;
 | 
				
			||||||
	// TODO: other potential causes of an interrupt.
 | 
						// TODO: other potential causes of an interrupt.
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Evaluates the new level of the interrupt line and notifies the delegate if
 | 
				
			||||||
 | 
						both: (i) there is one; and (ii) the interrupt line has changed since last
 | 
				
			||||||
 | 
						the delegate was notified.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
void z8530::update_delegate() {
 | 
					void z8530::update_delegate() {
 | 
				
			||||||
	const bool interrupt_line = get_interrupt_line();
 | 
						const bool interrupt_line = get_interrupt_line();
 | 
				
			||||||
	if(interrupt_line != previous_interrupt_line_) {
 | 
						if(interrupt_line != previous_interrupt_line_) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,16 +30,33 @@ class z8530 {
 | 
				
			|||||||
				A/B = A0
 | 
									A/B = A0
 | 
				
			||||||
				C/D = A1
 | 
									C/D = A1
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Performs a read from the SCC; see above for conventions as to 'address'.
 | 
				
			||||||
		std::uint8_t read(int address);
 | 
							std::uint8_t read(int address);
 | 
				
			||||||
 | 
							/// Performs a write to the SCC; see above for conventions as to 'address'.
 | 
				
			||||||
		void write(int address, std::uint8_t value);
 | 
							void write(int address, std::uint8_t value);
 | 
				
			||||||
 | 
							/// Resets the SCC.
 | 
				
			||||||
		void reset();
 | 
							void reset();
 | 
				
			||||||
		bool get_interrupt_line();
 | 
					
 | 
				
			||||||
 | 
							/// @returns The current value of the status output: @c true for active; @c false for inactive.
 | 
				
			||||||
 | 
							bool get_interrupt_line() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		struct Delegate {
 | 
							struct Delegate {
 | 
				
			||||||
			virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
 | 
								/*!
 | 
				
			||||||
 | 
									Communicates that @c scc now has the interrupt line status @c new_status.
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
								virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the delegate for this SCC. If this is a new delegate it is sent
 | 
				
			||||||
 | 
								an immediate did_change_interrupt_status message, to get it
 | 
				
			||||||
 | 
								up to speed.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
		void set_delegate(Delegate *delegate) {
 | 
							void set_delegate(Delegate *delegate) {
 | 
				
			||||||
 | 
								if(delegate_ == delegate) return;
 | 
				
			||||||
			delegate_ = delegate;
 | 
								delegate_ = delegate;
 | 
				
			||||||
 | 
								delegate_->did_change_interrupt_status(this, get_interrupt_line());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*
 | 
							/*
 | 
				
			||||||
@@ -53,7 +70,7 @@ class z8530 {
 | 
				
			|||||||
				uint8_t read(bool data, uint8_t pointer);
 | 
									uint8_t read(bool data, uint8_t pointer);
 | 
				
			||||||
				void write(bool data, uint8_t pointer, uint8_t value);
 | 
									void write(bool data, uint8_t pointer, uint8_t value);
 | 
				
			||||||
				void set_dcd(bool level);
 | 
									void set_dcd(bool level);
 | 
				
			||||||
				bool get_interrupt_line();
 | 
									bool get_interrupt_line() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			private:
 | 
								private:
 | 
				
			||||||
				uint8_t data_ = 0xff;
 | 
									uint8_t data_ = 0xff;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ struct ReverseTable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ReverseTable() {
 | 
						ReverseTable() {
 | 
				
			||||||
		for(int c = 0; c < 256; ++c) {
 | 
							for(int c = 0; c < 256; ++c) {
 | 
				
			||||||
			map[c] = static_cast<uint8_t>(
 | 
								map[c] = uint8_t(
 | 
				
			||||||
				((c & 0x80) >> 7) |
 | 
									((c & 0x80) >> 7) |
 | 
				
			||||||
				((c & 0x40) >> 5) |
 | 
									((c & 0x40) >> 5) |
 | 
				
			||||||
				((c & 0x20) >> 3) |
 | 
									((c & 0x20) >> 3) |
 | 
				
			||||||
@@ -117,10 +117,22 @@ void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
				
			|||||||
	crt_.set_scan_target(scan_target);
 | 
						crt_.set_scan_target(scan_target);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Outputs::Display::ScanStatus TMS9918::get_scaled_scan_status() const {
 | 
				
			||||||
 | 
						// The input was scaled by 3/4 to convert half cycles to internal ticks,
 | 
				
			||||||
 | 
						// so undo that and also allow for: (i) the multiply by 4 that it takes
 | 
				
			||||||
 | 
						// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
 | 
				
			||||||
 | 
						// and this should really reply in whole cycles.
 | 
				
			||||||
 | 
						return crt_.get_scaled_scan_status() * (4.0f / (3.0f * 8.0f));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
 | 
					void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
 | 
				
			||||||
	crt_.set_display_type(display_type);
 | 
						crt_.set_display_type(display_type);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Outputs::Display::DisplayType TMS9918::get_display_type() const {
 | 
				
			||||||
 | 
						return crt_.get_display_type();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Base::LineBuffer::reset_sprite_collection() {
 | 
					void Base::LineBuffer::reset_sprite_collection() {
 | 
				
			||||||
	sprites_stopped = false;
 | 
						sprites_stopped = false;
 | 
				
			||||||
	active_sprite_slot = 0;
 | 
						active_sprite_slot = 0;
 | 
				
			||||||
@@ -132,7 +144,7 @@ void Base::LineBuffer::reset_sprite_collection() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
 | 
					void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
 | 
				
			||||||
	if(!(status_ & StatusSpriteOverflow)) {
 | 
						if(!(status_ & StatusSpriteOverflow)) {
 | 
				
			||||||
		status_ = static_cast<uint8_t>((status_ & ~0x1f) | (sprite_number & 0x1f));
 | 
							status_ = uint8_t((status_ & ~0x1f) | (sprite_number & 0x1f));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(buffer.sprites_stopped)
 | 
						if(buffer.sprites_stopped)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@@ -178,7 +190,9 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
	int read_cycles_pool = int_cycles;
 | 
						int read_cycles_pool = int_cycles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while(write_cycles_pool || read_cycles_pool) {
 | 
						while(write_cycles_pool || read_cycles_pool) {
 | 
				
			||||||
 | 
					#ifndef NDEBUG
 | 
				
			||||||
		LineBufferPointer backup = read_pointer_;
 | 
							LineBufferPointer backup = read_pointer_;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(write_cycles_pool) {
 | 
							if(write_cycles_pool) {
 | 
				
			||||||
			// Determine how much writing to do.
 | 
								// Determine how much writing to do.
 | 
				
			||||||
@@ -317,8 +331,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef NDEBUG
 | 
				
			||||||
		assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
 | 
							assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
 | 
				
			||||||
		backup = write_pointer_;
 | 
							backup = write_pointer_;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(read_cycles_pool) {
 | 
							if(read_cycles_pool) {
 | 
				
			||||||
@@ -519,7 +535,7 @@ void TMS9918::write(int address, uint8_t value) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// The RAM pointer is always set on a second write, regardless of
 | 
						// The RAM pointer is always set on a second write, regardless of
 | 
				
			||||||
	// whether the caller is intending to enqueue a VDP operation.
 | 
						// whether the caller is intending to enqueue a VDP operation.
 | 
				
			||||||
	ram_pointer_ = (ram_pointer_ & 0x00ff) | static_cast<uint16_t>(value << 8);
 | 
						ram_pointer_ = (ram_pointer_ & 0x00ff) | uint16_t(value << 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	write_phase_ = false;
 | 
						write_phase_ = false;
 | 
				
			||||||
	if(value & 0x80) {
 | 
						if(value & 0x80) {
 | 
				
			||||||
@@ -653,7 +669,7 @@ uint8_t TMS9918::get_current_line() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return static_cast<uint8_t>(source_row);
 | 
						return uint8_t(source_row);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t TMS9918::get_latched_horizontal_counter() {
 | 
					uint8_t TMS9918::get_latched_horizontal_counter() {
 | 
				
			||||||
@@ -692,9 +708,9 @@ HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) {
 | 
				
			|||||||
	return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
 | 
						return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HalfCycles TMS9918::get_time_until_interrupt() {
 | 
					HalfCycles TMS9918::get_next_sequence_point() {
 | 
				
			||||||
	if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1);
 | 
						if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles::max();
 | 
				
			||||||
	if(get_interrupt_line()) return HalfCycles(0);
 | 
						if(get_interrupt_line()) return HalfCycles::max();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Calculate the amount of time until the next end-of-frame interrupt.
 | 
						// Calculate the amount of time until the next end-of-frame interrupt.
 | 
				
			||||||
	const int frame_length = 342 * mode_timing_.total_lines;
 | 
						const int frame_length = 342 * mode_timing_.total_lines;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,9 +44,15 @@ class TMS9918: public Base {
 | 
				
			|||||||
		/*! Sets the scan target this TMS will post content to. */
 | 
							/*! Sets the scan target this TMS will post content to. */
 | 
				
			||||||
		void set_scan_target(Outputs::Display::ScanTarget *);
 | 
							void set_scan_target(Outputs::Display::ScanTarget *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Gets the current scan status.
 | 
				
			||||||
 | 
							Outputs::Display::ScanStatus get_scaled_scan_status() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*! Sets the type of display the CRT will request. */
 | 
							/*! Sets the type of display the CRT will request. */
 | 
				
			||||||
		void set_display_type(Outputs::Display::DisplayType);
 | 
							void set_display_type(Outputs::Display::DisplayType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*! Gets the type of display the CRT will request. */
 | 
				
			||||||
 | 
							Outputs::Display::DisplayType get_display_type() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			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
 | 
				
			||||||
			that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
 | 
								that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
 | 
				
			||||||
@@ -69,13 +75,13 @@ class TMS9918: public Base {
 | 
				
			|||||||
		void latch_horizontal_counter();
 | 
							void latch_horizontal_counter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Returns the amount of time until @c get_interrupt_line would next return true if
 | 
								Returns the amount of time until @c get_interrupt_line would next change if
 | 
				
			||||||
			there are no interceding calls to @c write or to @c read.
 | 
								there are no interceding calls to @c write or to @c read.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			If get_interrupt_line is true now, returns zero. If get_interrupt_line would
 | 
								If get_interrupt_line is true now of if get_interrupt_line would
 | 
				
			||||||
			never return true, returns -1.
 | 
								never return true, returns HalfCycles::max().
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		HalfCycles get_time_until_interrupt();
 | 
							HalfCycles get_next_sequence_point();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*!
 | 
							/*!
 | 
				
			||||||
			Returns the amount of time until the nominated line interrupt position is
 | 
								Returns the amount of time until the nominated line interrupt position is
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,7 @@ enum class TVStandard {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Base {
 | 
					class Base {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
 | 
							static uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
 | 
				
			||||||
			uint32_t result = 0;
 | 
								uint32_t result = 0;
 | 
				
			||||||
			uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
 | 
								uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
 | 
				
			||||||
			result_ptr[0] = r;
 | 
								result_ptr[0] = r;
 | 
				
			||||||
@@ -51,7 +51,7 @@ class Base {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected:
 | 
						protected:
 | 
				
			||||||
		const static int output_lag = 11;	// i.e. pixel output will occur 11 cycles after corresponding data read.
 | 
							static constexpr int output_lag = 11;	// i.e. pixel output will occur 11 cycles after corresponding data read.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// The default TMS palette.
 | 
							// The default TMS palette.
 | 
				
			||||||
		const uint32_t palette[16] = {
 | 
							const uint32_t palette[16] = {
 | 
				
			||||||
@@ -350,11 +350,14 @@ class Base {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				case MemoryAccess::Write:
 | 
									case MemoryAccess::Write:
 | 
				
			||||||
					if(master_system_.cram_is_selected) {
 | 
										if(master_system_.cram_is_selected) {
 | 
				
			||||||
						// Adjust the palette.
 | 
											// Adjust the palette. In a Master System blue has a slightly different
 | 
				
			||||||
 | 
											// scale; cf. https://www.retrorgb.com/sega-master-system-non-linear-blue-channel-findings.html
 | 
				
			||||||
 | 
											constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
 | 
				
			||||||
 | 
											constexpr uint8_t b_scale[] = {0, 104, 170, 255};
 | 
				
			||||||
						master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
 | 
											master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
 | 
				
			||||||
							static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
 | 
												rg_scale[(read_ahead_buffer_ >> 0) & 3],
 | 
				
			||||||
							static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
 | 
												rg_scale[(read_ahead_buffer_ >> 2) & 3],
 | 
				
			||||||
							static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
 | 
												b_scale[(read_ahead_buffer_ >> 4) & 3]
 | 
				
			||||||
						);
 | 
											);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						// Schedule a CRAM dot; this is scheduled for wherever it should appear
 | 
											// Schedule a CRAM dot; this is scheduled for wherever it should appear
 | 
				
			||||||
@@ -421,7 +424,8 @@ class Base {
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define slot(n)	\
 | 
					#define slot(n)	\
 | 
				
			||||||
		if(use_end && end == n) return;\
 | 
							if(use_end && end == n) return;	\
 | 
				
			||||||
 | 
							[[fallthrough]];				\
 | 
				
			||||||
		case n
 | 
							case n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define external_slot(n)	\
 | 
					#define external_slot(n)	\
 | 
				
			||||||
@@ -449,7 +453,7 @@ class Base {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***********************************************
 | 
					/***********************************************
 | 
				
			||||||
             TMS9918 Fetching Code
 | 
						TMS9918 Fetching Code
 | 
				
			||||||
************************************************/
 | 
					************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		template<bool use_end> void fetch_tms_refresh(int start, int end) {
 | 
							template<bool use_end> void fetch_tms_refresh(int start, int end) {
 | 
				
			||||||
@@ -518,7 +522,7 @@ class Base {
 | 
				
			|||||||
	fetch_columns_4(location+12, column+4);
 | 
						fetch_columns_4(location+12, column+4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
 | 
								LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
 | 
				
			||||||
			const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
 | 
								const size_t row_base = pattern_name_address_ & (0x3c00 | size_t(write_pointer_.row >> 3) * 40);
 | 
				
			||||||
			const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
 | 
								const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch(start) {
 | 
								switch(start) {
 | 
				
			||||||
@@ -693,7 +697,7 @@ class Base {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***********************************************
 | 
					/***********************************************
 | 
				
			||||||
          Master System Fetching Code
 | 
						Master System Fetching Code
 | 
				
			||||||
************************************************/
 | 
					************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		template<bool use_end> void fetch_sms(int start, int end) {
 | 
							template<bool use_end> void fetch_sms(int start, int end) {
 | 
				
			||||||
@@ -731,7 +735,7 @@ class Base {
 | 
				
			|||||||
		const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
 | 
							const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
 | 
				
			||||||
		const size_t address = row_info.pattern_address_base + (scrolled_column << 1);	\
 | 
							const size_t address = row_info.pattern_address_base + (scrolled_column << 1);	\
 | 
				
			||||||
		line_buffer.names[column].flags = ram_[address+1];	\
 | 
							line_buffer.names[column].flags = ram_[address+1];	\
 | 
				
			||||||
		line_buffer.names[column].offset = static_cast<size_t>(	\
 | 
							line_buffer.names[column].offset = size_t(	\
 | 
				
			||||||
			(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5	\
 | 
								(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5	\
 | 
				
			||||||
		) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2];	\
 | 
							) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2];	\
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -785,7 +789,7 @@ class Base {
 | 
				
			|||||||
			};
 | 
								};
 | 
				
			||||||
			const RowInfo scrolled_row_info = {
 | 
								const RowInfo scrolled_row_info = {
 | 
				
			||||||
				(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
 | 
									(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
 | 
				
			||||||
				{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
 | 
									{size_t((scrolled_row & 7) << 2), 28 ^ size_t((scrolled_row & 7) << 2)}
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
			RowInfo row_info;
 | 
								RowInfo row_info;
 | 
				
			||||||
			if(master_system_.vertical_scroll_lock) {
 | 
								if(master_system_.vertical_scroll_lock) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,17 @@
 | 
				
			|||||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
					//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "AY38910.hpp"
 | 
					#include "AY38910.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <cmath>
 | 
					//namespace GI {
 | 
				
			||||||
 | 
					//namespace AY38910 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace GI::AY38910;
 | 
					using namespace GI::AY38910;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
 | 
					template <bool is_stereo>
 | 
				
			||||||
 | 
					AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
 | 
				
			||||||
	// Don't use the low bit of the envelope position if this is an AY.
 | 
						// Don't use the low bit of the envelope position if this is an AY.
 | 
				
			||||||
	envelope_position_mask_ |= personality == Personality::AY38910;
 | 
						envelope_position_mask_ |= personality == Personality::AY38910;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,18 +74,34 @@ AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &
 | 
				
			|||||||
	set_sample_volume_range(0);
 | 
						set_sample_volume_range(0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_sample_volume_range(std::int16_t range) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::int16_t range) {
 | 
				
			||||||
	// set up volume lookup table
 | 
						// Set up volume lookup table; the function below is based on a combination of the graph
 | 
				
			||||||
 | 
						// from the YM's datasheet, showing a clear power curve, and fitting that to observed
 | 
				
			||||||
 | 
						// values reported elsewhere.
 | 
				
			||||||
	const float max_volume = float(range) / 3.0f;	// As there are three channels.
 | 
						const float max_volume = float(range) / 3.0f;	// As there are three channels.
 | 
				
			||||||
	constexpr float root_two = 1.414213562373095f;
 | 
						constexpr float root_two = 1.414213562373095f;
 | 
				
			||||||
	for(int v = 0; v < 32; v++) {
 | 
						for(int v = 0; v < 32; v++) {
 | 
				
			||||||
		volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f));
 | 
							volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	volumes_[0] = 0;	// Tie level 0 to silence.
 | 
					
 | 
				
			||||||
 | 
						// Tie level 0 to silence.
 | 
				
			||||||
 | 
						for(int v = 31; v >= 0; --v) {
 | 
				
			||||||
 | 
							volumes_[v] -= volumes_[0];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	evaluate_output_volume();
 | 
						evaluate_output_volume();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
 | 
				
			||||||
 | 
						a_left_ = uint8_t(a_left * 255.0f);
 | 
				
			||||||
 | 
						b_left_ = uint8_t(b_left * 255.0f);
 | 
				
			||||||
 | 
						c_left_ = uint8_t(c_left * 255.0f);
 | 
				
			||||||
 | 
						a_right_ = uint8_t(a_right * 255.0f);
 | 
				
			||||||
 | 
						b_right_ = uint8_t(b_right * 255.0f);
 | 
				
			||||||
 | 
						c_right_ = uint8_t(c_right * 255.0f);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <bool is_stereo> void AY38910<is_stereo>::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
 | 
						// 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
 | 
						// prior to applying its tone and noise dividers. But the YM fills the
 | 
				
			||||||
	// same total periods for noise and tone with double-precision envelopes.
 | 
						// same total periods for noise and tone with double-precision envelopes.
 | 
				
			||||||
@@ -93,7 +113,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	std::size_t c = 0;
 | 
						std::size_t c = 0;
 | 
				
			||||||
	while((master_divider_&3) && c < number_of_samples) {
 | 
						while((master_divider_&3) && c < number_of_samples) {
 | 
				
			||||||
		target[c] = output_volume_;
 | 
							if constexpr (is_stereo) {
 | 
				
			||||||
 | 
								reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								target[c] = int16_t(output_volume_);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		master_divider_++;
 | 
							master_divider_++;
 | 
				
			||||||
		c++;
 | 
							c++;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -135,7 +159,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			|||||||
		evaluate_output_volume();
 | 
							evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
 | 
							for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
 | 
				
			||||||
			target[c] = output_volume_;
 | 
								if constexpr (is_stereo) {
 | 
				
			||||||
 | 
									reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									target[c] = int16_t(output_volume_);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			c++;
 | 
								c++;
 | 
				
			||||||
			master_divider_++;
 | 
								master_divider_++;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -144,7 +172,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 | 
				
			|||||||
	master_divider_ &= 3;
 | 
						master_divider_ &= 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::evaluate_output_volume() {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
 | 
				
			||||||
	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
 | 
						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:
 | 
				
			||||||
@@ -184,34 +212,47 @@ void AY38910::evaluate_output_volume() {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
#undef channel_volume
 | 
					#undef channel_volume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Mix additively.
 | 
						// Mix additively, weighting if in stereo.
 | 
				
			||||||
	output_volume_ = static_cast<int16_t>(
 | 
						if constexpr (is_stereo) {
 | 
				
			||||||
		volumes_[volumes[0]] * channel_levels[0] +
 | 
							int16_t *const output_volumes = reinterpret_cast<int16_t *>(&output_volume_);
 | 
				
			||||||
		volumes_[volumes[1]] * channel_levels[1] +
 | 
							output_volumes[0] = int16_t((
 | 
				
			||||||
		volumes_[volumes[2]] * channel_levels[2]
 | 
								volumes_[volumes[0]] * channel_levels[0] * a_left_ +
 | 
				
			||||||
	);
 | 
								volumes_[volumes[1]] * channel_levels[1] * b_left_ +
 | 
				
			||||||
 | 
								volumes_[volumes[2]] * channel_levels[2] * c_left_
 | 
				
			||||||
 | 
							) >> 8);
 | 
				
			||||||
 | 
							output_volumes[1] = int16_t((
 | 
				
			||||||
 | 
								volumes_[volumes[0]] * channel_levels[0] * a_right_ +
 | 
				
			||||||
 | 
								volumes_[volumes[1]] * channel_levels[1] * b_right_ +
 | 
				
			||||||
 | 
								volumes_[volumes[2]] * channel_levels[2] * c_right_
 | 
				
			||||||
 | 
							) >> 8);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							output_volume_ = uint32_t(
 | 
				
			||||||
 | 
								volumes_[volumes[0]] * channel_levels[0] +
 | 
				
			||||||
 | 
								volumes_[volumes[1]] * channel_levels[1] +
 | 
				
			||||||
 | 
								volumes_[volumes[2]] * channel_levels[2]
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool AY38910::is_zero_level() {
 | 
					template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() const {
 | 
				
			||||||
	// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
 | 
						// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
 | 
				
			||||||
	return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
 | 
						return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MARK: - Register manipulation
 | 
					// MARK: - Register manipulation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::select_register(uint8_t r) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::select_register(uint8_t r) {
 | 
				
			||||||
	selected_register_ = r;
 | 
						selected_register_ = r;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_register_value(uint8_t value) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t value) {
 | 
				
			||||||
	// There are only 16 registers.
 | 
						// There are only 16 registers.
 | 
				
			||||||
	if(selected_register_ > 15) return;
 | 
						if(selected_register_ > 15) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If this is a register that affects audio output, enqueue a mutation onto the
 | 
						// If this is a register that affects audio output, enqueue a mutation onto the
 | 
				
			||||||
	// audio generation thread.
 | 
						// audio generation thread.
 | 
				
			||||||
	if(selected_register_ < 14) {
 | 
						if(selected_register_ < 14) {
 | 
				
			||||||
		const int selected_register = selected_register_;
 | 
							task_queue_.defer([this, selected_register = selected_register_, value] () {
 | 
				
			||||||
		task_queue_.defer([=] () {
 | 
					 | 
				
			||||||
			// Perform any register-specific mutation to output generation.
 | 
								// Perform any register-specific mutation to output generation.
 | 
				
			||||||
			uint8_t masked_value = value;
 | 
								uint8_t masked_value = value;
 | 
				
			||||||
			switch(selected_register) {
 | 
								switch(selected_register) {
 | 
				
			||||||
@@ -220,7 +261,7 @@ void AY38910::set_register_value(uint8_t value) {
 | 
				
			|||||||
					int channel = selected_register >> 1;
 | 
										int channel = selected_register >> 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(selected_register & 1)
 | 
										if(selected_register & 1)
 | 
				
			||||||
						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
 | 
											tone_periods_[channel] = (tone_periods_[channel] & 0xff) | uint16_t((value&0xf) << 8);
 | 
				
			||||||
					else
 | 
										else
 | 
				
			||||||
						tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
 | 
											tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -235,7 +276,7 @@ void AY38910::set_register_value(uint8_t value) {
 | 
				
			|||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 12:
 | 
									case 12:
 | 
				
			||||||
					envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
 | 
										envelope_period_ = (envelope_period_ & 0xff) | int(value << 8);
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				case 13:
 | 
									case 13:
 | 
				
			||||||
@@ -273,7 +314,7 @@ void AY38910::set_register_value(uint8_t value) {
 | 
				
			|||||||
	if(update_port_a) set_port_output(false);
 | 
						if(update_port_a) set_port_output(false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AY38910::get_register_value() {
 | 
					template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() {
 | 
				
			||||||
	// This table ensures that bits that aren't defined within the AY are returned as 0s
 | 
						// This table ensures that bits that aren't defined within the AY are returned as 0s
 | 
				
			||||||
	// when read, conforming to CPC-sourced unit tests.
 | 
						// when read, conforming to CPC-sourced unit tests.
 | 
				
			||||||
	const uint8_t register_masks[16] = {
 | 
						const uint8_t register_masks[16] = {
 | 
				
			||||||
@@ -287,24 +328,24 @@ uint8_t AY38910::get_register_value() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// MARK: - Port querying
 | 
					// MARK: - Port querying
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AY38910::get_port_output(bool port_b) {
 | 
					template <bool is_stereo> uint8_t AY38910<is_stereo>::get_port_output(bool port_b) {
 | 
				
			||||||
	return registers_[port_b ? 15 : 14];
 | 
						return registers_[port_b ? 15 : 14];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MARK: - Bus handling
 | 
					// MARK: - Bus handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_port_handler(PortHandler *handler) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_port_handler(PortHandler *handler) {
 | 
				
			||||||
	port_handler_ = handler;
 | 
						port_handler_ = handler;
 | 
				
			||||||
	set_port_output(true);
 | 
						set_port_output(true);
 | 
				
			||||||
	set_port_output(false);
 | 
						set_port_output(false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_data_input(uint8_t r) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_data_input(uint8_t r) {
 | 
				
			||||||
	data_input_ = r;
 | 
						data_input_ = r;
 | 
				
			||||||
	update_bus();
 | 
						update_bus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_port_output(bool port_b) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b) {
 | 
				
			||||||
	// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
 | 
						// 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,
 | 
						// 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.
 | 
						// report programmer selection of input mode as creating an output of 0xff.
 | 
				
			||||||
@@ -314,7 +355,7 @@ void AY38910::set_port_output(bool port_b) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AY38910::get_data_output() {
 | 
					template <bool is_stereo> uint8_t AY38910<is_stereo>::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
 | 
				
			||||||
		// value returned to the CPU when reading it is the and of the output value and any input.
 | 
							// value returned to the CPU when reading it is the and of the output value and any input.
 | 
				
			||||||
@@ -330,22 +371,22 @@ uint8_t AY38910::get_data_output() {
 | 
				
			|||||||
	return data_output_;
 | 
						return data_output_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::set_control_lines(ControlLines control_lines) {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLines control_lines) {
 | 
				
			||||||
	switch(static_cast<int>(control_lines)) {
 | 
						switch(int(control_lines)) {
 | 
				
			||||||
		default:					control_state_ = Inactive;		break;
 | 
							default:					control_state_ = Inactive;		break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case static_cast<int>(BDIR | BC2 | BC1):
 | 
							case int(BDIR | BC2 | BC1):
 | 
				
			||||||
		case BDIR:
 | 
							case BDIR:
 | 
				
			||||||
		case BC1:					control_state_ = LatchAddress;	break;
 | 
							case BC1:					control_state_ = LatchAddress;	break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case static_cast<int>(BC2 | BC1):		control_state_ = Read;			break;
 | 
							case int(BC2 | BC1):		control_state_ = Read;			break;
 | 
				
			||||||
		case static_cast<int>(BDIR | BC2):		control_state_ = Write;			break;
 | 
							case int(BDIR | BC2):		control_state_ = Write;			break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	update_bus();
 | 
						update_bus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AY38910::update_bus() {
 | 
					template <bool is_stereo> void AY38910<is_stereo>::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_) {
 | 
				
			||||||
@@ -355,3 +396,7 @@ void AY38910::update_bus() {
 | 
				
			|||||||
		case Read:			data_output_ = get_register_value();	break;
 | 
							case Read:			data_output_ = get_register_value();	break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Ensure both mono and stereo versions of the AY are built.
 | 
				
			||||||
 | 
					template class GI::AY38910::AY38910<true>;
 | 
				
			||||||
 | 
					template class GI::AY38910::AY38910<false>;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,7 @@ class PortHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			@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 input being queried is Port B. @c false if it is Port A.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		virtual uint8_t get_port_input(bool port_b) {
 | 
							virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
 | 
				
			||||||
			return 0xff;
 | 
								return 0xff;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +40,7 @@ class PortHandler {
 | 
				
			|||||||
			@param port_b @c true if the output being posted 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.
 | 
								@param value the value now being output.
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
		virtual void set_port_output(bool port_b, uint8_t value) {}
 | 
							virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*!
 | 
					/*!
 | 
				
			||||||
@@ -63,8 +63,10 @@ enum class Personality {
 | 
				
			|||||||
	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
 | 
				
			||||||
	interface ports.
 | 
						interface ports.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						This AY has an attached mono or stereo mixer.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
					template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			||||||
	public:
 | 
						public:
 | 
				
			||||||
		/// Creates a new AY38910.
 | 
							/// Creates a new AY38910.
 | 
				
			||||||
		AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
 | 
							AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
 | 
				
			||||||
@@ -91,10 +93,23 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
		*/
 | 
							*/
 | 
				
			||||||
		void set_port_handler(PortHandler *);
 | 
							void set_port_handler(PortHandler *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
 | 
				
			||||||
 | 
								channels in each of the output channels.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
 | 
							// 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() const;
 | 
				
			||||||
		void set_sample_volume_range(std::int16_t range);
 | 
							void set_sample_volume_range(std::int16_t range);
 | 
				
			||||||
 | 
							static constexpr bool get_is_stereo() { return is_stereo; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
							Concurrency::DeferringAsyncTaskQueue &task_queue_;
 | 
				
			||||||
@@ -135,12 +150,46 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		uint8_t data_input_, data_output_;
 | 
							uint8_t data_input_, data_output_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int16_t output_volume_;
 | 
							uint32_t output_volume_;
 | 
				
			||||||
		void evaluate_output_volume();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void update_bus();
 | 
							void update_bus();
 | 
				
			||||||
		PortHandler *port_handler_ = nullptr;
 | 
							PortHandler *port_handler_ = nullptr;
 | 
				
			||||||
		void set_port_output(bool port_b);
 | 
							void set_port_output(bool port_b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void evaluate_output_volume();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Output mixing control.
 | 
				
			||||||
 | 
							uint8_t a_left_ = 255, a_right_ = 255;
 | 
				
			||||||
 | 
							uint8_t b_left_ = 255, b_right_ = 255;
 | 
				
			||||||
 | 
							uint8_t c_left_ = 255, c_right_ = 255;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Provides helper code, to provide something closer to the interface exposed by many
 | 
				
			||||||
 | 
						AY-deploying machines of the era.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					struct Utility {
 | 
				
			||||||
 | 
						template <typename AY> static void write(AY &ay, bool is_data_write, uint8_t data) {
 | 
				
			||||||
 | 
							ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1)));
 | 
				
			||||||
 | 
							ay.set_data_input(data);
 | 
				
			||||||
 | 
							ay.set_control_lines(GI::AY38910::ControlLines(0));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename AY> static void select_register(AY &ay, uint8_t reg) {
 | 
				
			||||||
 | 
							write(ay, false, reg);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename AY> static void write_data(AY &ay, uint8_t reg) {
 | 
				
			||||||
 | 
							write(ay, true, reg);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename AY> static uint8_t read(AY &ay) {
 | 
				
			||||||
 | 
							ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
 | 
				
			||||||
 | 
							const uint8_t result = ay.get_data_output();
 | 
				
			||||||
 | 
							ay.set_control_lines(GI::AY38910::ControlLines(0));
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										290
									
								
								Components/AppleClock/AppleClock.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								Components/AppleClock/AppleClock.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,290 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  RealTimeClock.hpp
 | 
				
			||||||
 | 
					//  Clock Signal
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by Thomas Harte on 07/05/2019.
 | 
				
			||||||
 | 
					//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef Apple_RealTimeClock_hpp
 | 
				
			||||||
 | 
					#define Apple_RealTimeClock_hpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Apple {
 | 
				
			||||||
 | 
					namespace Clock {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Since tracking of time is pushed to this class, it is assumed
 | 
				
			||||||
 | 
						that whomever is translating real time into emulated time
 | 
				
			||||||
 | 
						will also signal interrupts — this is just the storage and time counting.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class ClockStorage {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							ClockStorage() {
 | 
				
			||||||
 | 
								// TODO: this should persist, if possible, rather than
 | 
				
			||||||
 | 
								// being default initialised.
 | 
				
			||||||
 | 
								constexpr 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(default_data));
 | 
				
			||||||
 | 
								memset(&data_[sizeof(default_data)], 0xff, sizeof(data_) - sizeof(default_data));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Advances the clock by 1 second.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								The caller should also signal an interrupt.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void update() {
 | 
				
			||||||
 | 
								for(int c = 0; c < 4; ++c) {
 | 
				
			||||||
 | 
									++seconds_[c];
 | 
				
			||||||
 | 
									if(seconds_[c]) break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected:
 | 
				
			||||||
 | 
							static constexpr uint16_t NoResult = 0x100;
 | 
				
			||||||
 | 
							static constexpr uint16_t DidComplete = 0x101;
 | 
				
			||||||
 | 
							uint16_t perform(uint8_t command) {
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
									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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										z0111abc, followed by 0defgh00
 | 
				
			||||||
 | 
														RAM address abcdefgh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										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.
 | 
				
			||||||
 | 
								*/
 | 
				
			||||||
 | 
								switch(phase_) {
 | 
				
			||||||
 | 
									case Phase::Command:
 | 
				
			||||||
 | 
										// Decode an address.
 | 
				
			||||||
 | 
										switch(command & 0x70) {
 | 
				
			||||||
 | 
											default:
 | 
				
			||||||
 | 
												if(command & 0x40) {
 | 
				
			||||||
 | 
													// RAM addresses 0x00 – 0x0f.
 | 
				
			||||||
 | 
													address_ = (command >> 2) & 0xf;
 | 
				
			||||||
 | 
												} else return DidComplete;	// Unrecognised.
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											case 0x00:
 | 
				
			||||||
 | 
												// A time access.
 | 
				
			||||||
 | 
												address_ = SecondsBuffer + ((command >> 2)&3);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
											case 0x30:
 | 
				
			||||||
 | 
												// Either a register access or an extended instruction.
 | 
				
			||||||
 | 
												if(command & 0x08) {
 | 
				
			||||||
 | 
													address_ = (command & 0x7) << 5;
 | 
				
			||||||
 | 
													phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
 | 
				
			||||||
 | 
													return NoResult;
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
											case 0x20:
 | 
				
			||||||
 | 
												// RAM addresses 0x10 – 0x13.
 | 
				
			||||||
 | 
												address_ = 0x10 + ((command >> 2) & 0x3);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// If this is a read, return a result; otherwise prepare to write.
 | 
				
			||||||
 | 
										if(command & 0x80) {
 | 
				
			||||||
 | 
											// The two registers are write-only.
 | 
				
			||||||
 | 
											if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
 | 
				
			||||||
 | 
												return DidComplete;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										phase_ = Phase::WriteData;
 | 
				
			||||||
 | 
									return NoResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Phase::SecondAddressByteRead:
 | 
				
			||||||
 | 
									case Phase::SecondAddressByteWrite:
 | 
				
			||||||
 | 
										if(command & 0x83) {
 | 
				
			||||||
 | 
											return DidComplete;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										address_ |= command >> 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(phase_ == Phase::SecondAddressByteRead) {
 | 
				
			||||||
 | 
											phase_ = Phase::Command;
 | 
				
			||||||
 | 
											return data_[address_];	// Only RAM accesses can get this far.
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											phase_ = Phase::WriteData;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									return NoResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case Phase::WriteData:
 | 
				
			||||||
 | 
										// First test: is this to the write-protect register?
 | 
				
			||||||
 | 
										if(address_ == RegisterWriteProtect) {
 | 
				
			||||||
 | 
											write_protect_ = command;
 | 
				
			||||||
 | 
											return DidComplete;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if(address_ == RegisterTest) {
 | 
				
			||||||
 | 
											// No documentation here.
 | 
				
			||||||
 | 
											return DidComplete;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// No other writing is permitted if the write protect
 | 
				
			||||||
 | 
										// register won't allow it.
 | 
				
			||||||
 | 
										if(!(write_protect_ & 0x80)) {
 | 
				
			||||||
 | 
											if(address_ >= SecondsBuffer) {
 | 
				
			||||||
 | 
												seconds_[address_ & 0xff] = command;
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												data_[address_] = command;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										phase_ = Phase::Command;
 | 
				
			||||||
 | 
									return DidComplete;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return NoResult;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							uint8_t data_[256];
 | 
				
			||||||
 | 
							uint8_t seconds_[4];
 | 
				
			||||||
 | 
							uint8_t write_protect_;
 | 
				
			||||||
 | 
							int address_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static constexpr int SecondsBuffer = 0x100;
 | 
				
			||||||
 | 
							static constexpr int RegisterTest = 0x200;
 | 
				
			||||||
 | 
							static constexpr int RegisterWriteProtect = 0x201;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							enum class Phase {
 | 
				
			||||||
 | 
								Command,
 | 
				
			||||||
 | 
								SecondAddressByteRead,
 | 
				
			||||||
 | 
								SecondAddressByteWrite,
 | 
				
			||||||
 | 
								WriteData
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							Phase phase_ = Phase::Command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Provides the serial interface implemented by the Macintosh.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class SerialClock: public ClockStorage {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							/*!
 | 
				
			||||||
 | 
								Sets the current clock and data inputs to the clock.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
							void set_input(bool clock, bool data) {
 | 
				
			||||||
 | 
								// 	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_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// If a whole byte has been collected, push it onwards.
 | 
				
			||||||
 | 
									if(!(phase_&7)) {
 | 
				
			||||||
 | 
										// Begin pessimistically.
 | 
				
			||||||
 | 
										const auto effect = perform(uint8_t(command_));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										switch(effect) {
 | 
				
			||||||
 | 
											case ClockStorage::NoResult:
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
											default:
 | 
				
			||||||
 | 
												result_ = uint8_t(effect);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
											case ClockStorage::DidComplete:
 | 
				
			||||||
 | 
												abort();
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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:
 | 
				
			||||||
 | 
							int phase_ = 0;
 | 
				
			||||||
 | 
							uint16_t command_;
 | 
				
			||||||
 | 
							uint8_t result_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool previous_clock_ = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
						Provides the parallel interface implemented by the IIgs.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					class ParallelClock: public ClockStorage {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							void set_control(uint8_t control) {
 | 
				
			||||||
 | 
								if(!(control&0x80)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if(control & 0x40) {
 | 
				
			||||||
 | 
									// Read from the RTC.
 | 
				
			||||||
 | 
									// A no-op for now.
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// Write to the RTC. Which in this implementation also sets up a future read.
 | 
				
			||||||
 | 
									data_ = uint8_t(perform(data_));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// MAGIC! The transaction took 0 seconds.
 | 
				
			||||||
 | 
								// TODO: no magic.
 | 
				
			||||||
 | 
								control_ = control & 0x7f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t get_control() {
 | 
				
			||||||
 | 
								return control_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void set_data(uint8_t data) {
 | 
				
			||||||
 | 
								data_ = data;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint8_t get_data() {
 | 
				
			||||||
 | 
								return data_;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							uint8_t data_;
 | 
				
			||||||
 | 
							uint8_t control_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* Apple_RealTimeClock_hpp */
 | 
				
			||||||
@@ -23,16 +23,16 @@ void Toggle::set_sample_volume_range(std::int16_t range) {
 | 
				
			|||||||
	volume_ = range;
 | 
						volume_ = range;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Toggle::skip_samples(const std::size_t number_of_samples) {}
 | 
					void Toggle::skip_samples(std::size_t) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Toggle::set_output(bool enabled) {
 | 
					void Toggle::set_output(bool enabled) {
 | 
				
			||||||
	if(is_enabled_ == enabled) return;
 | 
						if(is_enabled_ == enabled) return;
 | 
				
			||||||
	is_enabled_ = enabled;
 | 
						is_enabled_ = enabled;
 | 
				
			||||||
	audio_queue_.defer([=] {
 | 
						audio_queue_.defer([this, enabled] {
 | 
				
			||||||
		level_ = enabled ? volume_ : 0;
 | 
							level_ = enabled ? volume_ : 0;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Toggle::get_output() {
 | 
					bool Toggle::get_output() const {
 | 
				
			||||||
	return is_enabled_;
 | 
						return is_enabled_;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
 | 
				
			|||||||
		void skip_samples(const std::size_t number_of_samples);
 | 
							void skip_samples(const std::size_t number_of_samples);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void set_output(bool enabled);
 | 
							void set_output(bool enabled);
 | 
				
			||||||
		bool get_output();
 | 
							bool get_output() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private:
 | 
						private:
 | 
				
			||||||
		// Accessed on the calling thread.
 | 
							// Accessed on the calling thread.
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user