mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 20:16:07 +00:00 
			
		
		
		
	Compare commits
	
		
			1759 Commits
		
	
	
		
			2020-01-16
			...
			2021-04-06
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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 | ||
|  | 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 | ||
|  | ecb5807ec0 | ||
|  | 942986aadc | ||
|  | 1f539822ee | ||
|  | 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); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -145,9 +145,8 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, 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; | ||||||
|   | |||||||
| @@ -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,25 +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, | ||||||
| 		BD500, | 		BD500 | ||||||
| 		None | 	); | ||||||
| 	}; |  | ||||||
|  | 	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" | ||||||
| @@ -51,6 +54,7 @@ | |||||||
| #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 | ||||||
| @@ -62,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" | ||||||
| @@ -78,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) | ||||||
| @@ -117,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) | ||||||
| @@ -125,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(...) {} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -143,7 +170,7 @@ 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 | ||||||
| @@ -151,14 +178,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | |||||||
| 	Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST)				// STX | 	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; | ||||||
| } | } | ||||||
| @@ -178,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 */ | ||||||
							
								
								
									
										41
									
								
								Analyser/Static/ZXSpectrum/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Analyser/Static/ZXSpectrum/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  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, | ||||||
|  | 		Plus2a, | ||||||
|  | 		Plus3, | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	Model model = Model::Plus2a; | ||||||
|  | 	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 */ | ||||||
| @@ -481,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; | ||||||
| 		} | 		} | ||||||
| @@ -498,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; | ||||||
| @@ -564,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; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -816,19 +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)); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool WD1770::get_head_loaded() { | bool WD1770::get_head_loaded() const { | ||||||
| 	return head_is_loaded_; | 	return head_is_loaded_; | ||||||
| } | } | ||||||
|  |  | ||||||
| ClockingHint::Preference WD1770::preferred_clocking() { | 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,35 +46,35 @@ 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); | ||||||
| @@ -81,12 +82,12 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
| 		void set_head_loaded(bool head_loaded); | 		void set_head_loaded(bool head_loaded); | ||||||
|  |  | ||||||
| 		/// @returns The last value posted to @c set_head_loaded. | 		/// @returns The last value posted to @c set_head_loaded. | ||||||
| 		bool get_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