mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-30 14:16:04 +00:00 
			
		
		
		
	Compare commits
	
		
			965 Commits
		
	
	
		
			2017-05-16
			...
			2017-08-27
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0ced7866fc | ||
|  | d06031dfcb | ||
|  | 3f22a71276 | ||
|  | 53a88a7e12 | ||
|  | 4a66dd9e82 | ||
|  | 522839143f | ||
|  | b4c532c0d5 | ||
|  | a3e2d142e3 | ||
|  | 63ee8c9d58 | ||
|  | 437023bff6 | ||
|  | 4465098157 | ||
|  | 56dd677e9c | ||
|  | 9aa150c338 | ||
|  | fab6908129 | ||
|  | e34d4ce903 | ||
|  | d411827733 | ||
|  | f1ba7755dd | ||
|  | 57bfec285f | ||
|  | bdda701207 | ||
|  | 487fe83dca | ||
|  | 6c5a03187b | ||
|  | 97f57a3948 | ||
|  | 7d7aa2f5d5 | ||
|  | e7ad79c79a | ||
|  | 28550c0227 | ||
|  | 6e99169348 | ||
|  | 1017bb9f6b | ||
|  | 3caa4705ca | ||
|  | 039aed1bd1 | ||
|  | d77d7fdd78 | ||
|  | c6e6c3fcfb | ||
|  | ecd3350a6f | ||
|  | fa19e2d9c2 | ||
|  | 95d360251d | ||
|  | 7af3de010e | ||
|  | cefd421992 | ||
|  | a914eadc85 | ||
|  | 131b340d75 | ||
|  | e956740c56 | ||
|  | 8afd83b91f | ||
|  | 40d7a603db | ||
|  | ee71be0e7e | ||
|  | cde29c4bf4 | ||
|  | e1aded0d95 | ||
|  | 1237f174fe | ||
|  | 0cbc1753b9 | ||
|  | 5cf0395936 | ||
|  | 6315c22b80 | ||
|  | 4614a56843 | ||
|  | 8f5ae4a326 | ||
|  | 8fdc5012e4 | ||
|  | e88a51e75e | ||
|  | 49285e9caa | ||
|  | e3f2118757 | ||
|  | daeaa4752f | ||
|  | 5344e3098b | ||
|  | cedb809c21 | ||
|  | 2d9efccc98 | ||
|  | 8ce46b6e49 | ||
|  | f2699a3f2b | ||
|  | 85253a5876 | ||
|  | 911ee5a0d3 | ||
|  | 57c5b38a6d | ||
|  | 669e0caff5 | ||
|  | b24d04fc09 | ||
|  | ef07c33741 | ||
|  | e559a65ede | ||
|  | 5bdd24d93f | ||
|  | af61a7fa28 | ||
|  | c8c1792c3f | ||
|  | e6683e7f2d | ||
|  | 0c1714b695 | ||
|  | dc0ca83003 | ||
|  | 2c2dd8073c | ||
|  | 4f8b89772e | ||
|  | 733ee5a5c3 | ||
|  | fedf5a44a6 | ||
|  | 6a09022896 | ||
|  | 5b3c707959 | ||
|  | 9b21ef1507 | ||
|  | da3e8655e9 | ||
|  | 41e4386164 | ||
|  | b0a98bd239 | ||
|  | 42ad670ec8 | ||
|  | 58063b69a6 | ||
|  | 378f231499 | ||
|  | f68565a33f | ||
|  | 175faebdc9 | ||
|  | 76c6b715a2 | ||
|  | b476f06524 | ||
|  | 48290a8bbe | ||
|  | 9d9a1c341d | ||
|  | 952da1e581 | ||
|  | a988255558 | ||
|  | cca66ab450 | ||
|  | bcd7a312a4 | ||
|  | 925e774015 | ||
|  | 4c15e46fd1 | ||
|  | 3c50903a2b | ||
|  | 75208b0762 | ||
|  | f1e64169cd | ||
|  | 903a17ae11 | ||
|  | de1c526789 | ||
|  | b7e0f64892 | ||
|  | 148591b7f2 | ||
|  | 2105597910 | ||
|  | 3c148f5721 | ||
|  | 360c8a99a3 | ||
|  | 06e31f5102 | ||
|  | 42b5b66305 | ||
|  | 27018ba64e | ||
|  | e208f03636 | ||
|  | cc9d23f23b | ||
|  | 1a831bcf9b | ||
|  | 82367a2246 | ||
|  | f18206767f | ||
|  | 3947347d88 | ||
|  | 8a37a0ff2e | ||
|  | 468770b382 | ||
|  | 6cfc3daacb | ||
|  | aefbafa18d | ||
|  | d12c834f9c | ||
|  | 56de5cb2b3 | ||
|  | 709257a0c5 | ||
|  | 75a9d2bb33 | ||
|  | 7b92b235e1 | ||
|  | c196f0018f | ||
|  | 73080d6c36 | ||
|  | 9541a2a5f0 | ||
|  | 944222eba4 | ||
|  | 9d77f33611 | ||
|  | 7d132f81f7 | ||
|  | 0972f19fc5 | ||
|  | 6553bf05b4 | ||
|  | 0816d3f5a9 | ||
|  | 55055c7847 | ||
|  | 113da93796 | ||
|  | cddcd0fb79 | ||
|  | a366298022 | ||
|  | 4df9307d25 | ||
|  | d7bed958b3 | ||
|  | 9038ba622e | ||
|  | 7b8bb0297a | ||
|  | 0da02d3902 | ||
|  | 334872d374 | ||
|  | 2e5ad19fe1 | ||
|  | a10389a22c | ||
|  | cefec7a19f | ||
|  | 7264fbb3d2 | ||
|  | 0e083e9dc6 | ||
|  | 8a7b23dc9e | ||
|  | b7065575f3 | ||
|  | 7ea703f150 | ||
|  | ea64125124 | ||
|  | 1011143dbe | ||
|  | 9ace6e1f71 | ||
|  | 750f2cb883 | ||
|  | 5221837be8 | ||
|  | 1576b4500b | ||
|  | e1e9a06712 | ||
|  | 6e36f8ffa4 | ||
|  | b0a7208cc7 | ||
|  | eec42aa7ae | ||
|  | 6d2e969e7d | ||
|  | 5f42022c1d | ||
|  | 11d0c37506 | ||
|  | 58bad1e2a3 | ||
|  | 27d1dc5c37 | ||
|  | e7345c7a20 | ||
|  | 186048a88e | ||
|  | 7135259cc1 | ||
|  | 4909325e79 | ||
|  | a4ee697ed1 | ||
|  | 0f15a2f97f | ||
|  | 89ace671a4 | ||
|  | e7db2a2f6d | ||
|  | 8c33ac71ee | ||
|  | 69914faf02 | ||
|  | daafebe7ac | ||
|  | 2d81acb82e | ||
|  | 82ca49c840 | ||
|  | bfe297052d | ||
|  | ffb1a14ace | ||
|  | 7e35e44934 | ||
|  | 0c8769e335 | ||
|  | 83c7d34df2 | ||
|  | ad3c9842d7 | ||
|  | 44dace2eef | ||
|  | a12671010a | ||
|  | 23c149368b | ||
|  | 09716d4716 | ||
|  | 4b7c504d22 | ||
|  | 1e4f9d4eda | ||
|  | e4f04d0977 | ||
|  | 0f75525640 | ||
|  | edb088526f | ||
|  | 80ebc63101 | ||
|  | cf1403bc79 | ||
|  | fcf63a7547 | ||
|  | 9c0b75faba | ||
|  | 1f2bfc9581 | ||
|  | 14ab03d1e0 | ||
|  | 3831fbaca2 | ||
|  | 1d8edf58dd | ||
|  | 4785e316ff | ||
|  | 44da9de5b0 | ||
|  | 4ecd093891 | ||
|  | dd4bc87d52 | ||
|  | 570d25214e | ||
|  | f0b7e58968 | ||
|  | 0411b51582 | ||
|  | dea782cff9 | ||
|  | 388dd99762 | ||
|  | 026101a268 | ||
|  | 734099a956 | ||
|  | 6be5851484 | ||
|  | 994179f188 | ||
|  | 6a65c7a52a | ||
|  | 0d2d3ea17c | ||
|  | 5d374ebb18 | ||
|  | 62eadbb51a | ||
|  | ad8c8166bc | ||
|  | a5593bec79 | ||
|  | a1e2646301 | ||
|  | cf810d8357 | ||
|  | f258d6fbb2 | ||
|  | 4961fda2a9 | ||
|  | 6a6e5ae79c | ||
|  | 02d792c003 | ||
|  | be8e7a4144 | ||
|  | b1dbd7833a | ||
|  | 84ab05c5ef | ||
|  | 78138261c2 | ||
|  | a4c910f1de | ||
|  | 2eed24e859 | ||
|  | 7d1023ea98 | ||
|  | b11d142cff | ||
|  | 021ff8674e | ||
|  | 1b86bc21ab | ||
|  | e3d1f4fe1e | ||
|  | a7452aebff | ||
|  | 484524d781 | ||
|  | dbf9927caf | ||
|  | 3bdedfd749 | ||
|  | 005084af3d | ||
|  | 46278ff297 | ||
|  | d73dcbb268 | ||
|  | 390ecec3d9 | ||
|  | 41a30c147d | ||
|  | 4709ae80cb | ||
|  | 7fbb455836 | ||
|  | 745afd217f | ||
|  | 4427e9a254 | ||
|  | 2b0dcf8573 | ||
|  | 47732ffb98 | ||
|  | d07f3216ab | ||
|  | 56d65ba6f3 | ||
|  | 895a3cbf24 | ||
|  | d951c8c1c2 | ||
|  | a294963e98 | ||
|  | 68c73184b1 | ||
|  | 7f824d6494 | ||
|  | 3219212f03 | ||
|  | d90e35e5bd | ||
|  | 73f8488150 | ||
|  | 3853966a1e | ||
|  | d63893a437 | ||
|  | 90c74043f5 | ||
|  | 600445d90a | ||
|  | e4b405fd3d | ||
|  | 3b7ecbdf0d | ||
|  | 01efb645cb | ||
|  | b5ec1f42d5 | ||
|  | c839556a27 | ||
|  | e9972aa0dd | ||
|  | 1c9a744b01 | ||
|  | e6d4bb29d8 | ||
|  | 6c5b562d97 | ||
|  | a7103f9333 | ||
|  | c12425e141 | ||
|  | 89f6de1383 | ||
|  | 77da582e88 | ||
|  | 34eaf75352 | ||
|  | ffadb04761 | ||
|  | 29288b690e | ||
|  | 25fd3f7e50 | ||
|  | 4d60b8801c | ||
|  | 3e984e75b6 | ||
|  | 9e8645ca7a | ||
|  | caf3ac0645 | ||
|  | 0f4343cd84 | ||
|  | 192f232d3f | ||
|  | 6e4d3b8a77 | ||
|  | 8eda24261c | ||
|  | 75c59fefab | ||
|  | 4b19cf60df | ||
|  | 2b476554e7 | ||
|  | b3788fed41 | ||
|  | b999c1d8aa | ||
|  | a63aa80dc9 | ||
|  | 63f57c8c4f | ||
|  | 7e6a6365c9 | ||
|  | 3dbf26ac49 | ||
|  | f075fea78c | ||
|  | cc8380c286 | ||
|  | c0f0c68f4f | ||
|  | c2bb019381 | ||
|  | 26ce6cdab2 | ||
|  | d9097facf1 | ||
|  | b927500487 | ||
|  | e71eabedf9 | ||
|  | 33ed27c3ad | ||
|  | 45cbab6751 | ||
|  | a7b74d6164 | ||
|  | 575b1dba75 | ||
|  | a3b16b6dfa | ||
|  | c8f4de6f11 | ||
|  | bbb17acf3a | ||
|  | ad3a98387f | ||
|  | 985fbf59c2 | ||
|  | 2f2071be8a | ||
|  | 6d510e4e70 | ||
|  | 8e0736fbe6 | ||
|  | 681d1e2f8d | ||
|  | 42e70ef993 | ||
|  | 039811ce6a | ||
|  | a54ccd1969 | ||
|  | 707821ca55 | ||
|  | d3bf8fa53b | ||
|  | f5e2dd410e | ||
|  | 3ca9c38777 | ||
|  | 2d2cefb0b0 | ||
|  | 2fd071e45d | ||
|  | d7a5c3f49a | ||
|  | 819761f9fb | ||
|  | e50adf1cc8 | ||
|  | dcab10f53e | ||
|  | 633d8965e2 | ||
|  | f602f9b6ec | ||
|  | f7e66dea61 | ||
|  | bda9441620 | ||
|  | 4d5d5041df | ||
|  | 587eb3a67c | ||
|  | 6ca07f1e28 | ||
|  | 8d39a20088 | ||
|  | 4b6370eb86 | ||
|  | c6e340a8a2 | ||
|  | 31c7153301 | ||
|  | 7e04d00cc1 | ||
|  | 9d43784c65 | ||
|  | eca9586a0f | ||
|  | 0267bc237f | ||
|  | 2e4577f741 | ||
|  | f5b278d683 | ||
|  | e6854ff8db | ||
|  | 3b292273c7 | ||
|  | cb732e5d5f | ||
|  | 2d4e202be3 | ||
|  | 64da8e17d1 | ||
|  | 08ad35efd9 | ||
|  | 58b98267fc | ||
|  | ace71280a0 | ||
|  | a27946102a | ||
|  | 1d99c116e7 | ||
|  | ee27e16fb1 | ||
|  | 6ac7132799 | ||
|  | ca42abab70 | ||
|  | 933d69a256 | ||
|  | 3b1db14817 | ||
|  | 10a5581aea | ||
|  | 3ae699964f | ||
|  | 9d953421d8 | ||
|  | 763e3b65d1 | ||
|  | 42dd27c9b1 | ||
|  | 2b168f7383 | ||
|  | 0536f089e1 | ||
|  | 3df13cddd4 | ||
|  | e3f677fa37 | ||
|  | c2253c1e0f | ||
|  | 5c68b6cc21 | ||
|  | ffaa627820 | ||
|  | f742fd5d4a | ||
|  | 69b99fe127 | ||
|  | 5a396f6787 | ||
|  | cb0dc7b434 | ||
|  | e28829bd1b | ||
|  | 68ceeab610 | ||
|  | 68dca9d047 | ||
|  | d88ca151f4 | ||
|  | 3c90218c3d | ||
|  | afd409c883 | ||
|  | 26b6c03a2a | ||
|  | 9c04d851e4 | ||
|  | 1d6fe11906 | ||
|  | c0f1313830 | ||
|  | fb51fadf00 | ||
|  | 55fd9122d0 | ||
|  | 5b5720fac0 | ||
|  | d25d7d7d40 | ||
|  | ba4f2d8917 | ||
|  | a2aec39633 | ||
|  | 0bf4fdc9af | ||
|  | ed8c73eb14 | ||
|  | 3528a7f78b | ||
|  | 54bcc40192 | ||
|  | 4b5e9ffb83 | ||
|  | a7f5f035a6 | ||
|  | 4abd62e62b | ||
|  | 1fb158b297 | ||
|  | 968d2bb8ba | ||
|  | 92a3dfe44a | ||
|  | 9ef232157b | ||
|  | b9f4f7a530 | ||
|  | 761afad118 | ||
|  | 8848ebbd4f | ||
|  | 37950143fc | ||
|  | 25fd95044c | ||
|  | 1da24d10fd | ||
|  | 60e374dca3 | ||
|  | 7a65f91575 | ||
|  | 6f8b558724 | ||
|  | 1a88b62bf7 | ||
|  | 8361756dc4 | ||
|  | 273299028e | ||
|  | 847e49ccdf | ||
|  | 81a3899381 | ||
|  | 9257a3f6d7 | ||
|  | 728143247d | ||
|  | 6ec4e4e3d7 | ||
|  | 7922a12f02 | ||
|  | 37ccb9d3b6 | ||
|  | 3c254360ba | ||
|  | d2a0beaa67 | ||
|  | cda223ffc0 | ||
|  | 3ca51bedc6 | ||
|  | 36076b7ea5 | ||
|  | e90d128a26 | ||
|  | 966b5e6372 | ||
|  | 279c369a1f | ||
|  | d9c6b3bcf7 | ||
|  | 1c2f68f129 | ||
|  | 296c7cec05 | ||
|  | 75d67ee770 | ||
|  | a1e9a54765 | ||
|  | 8d1dacd951 | ||
|  | 545683df6f | ||
|  | cfbd62a5dc | ||
|  | 40339a12e1 | ||
|  | 90bf6565d0 | ||
|  | 9be9bd9106 | ||
|  | c1527cc9e2 | ||
|  | a1a3aab115 | ||
|  | c77a83d86f | ||
|  | a6e377aa57 | ||
|  | df4732be2e | ||
|  | 9435c1e12a | ||
|  | efdac2ce8c | ||
|  | 2912d7055b | ||
|  | d056f2e246 | ||
|  | 55ecb0c022 | ||
|  | 13f7aa4063 | ||
|  | 915f587ef1 | ||
|  | b7f88e8f61 | ||
|  | 677ed463f0 | ||
|  | 8a2bdb8d22 | ||
|  | 9bff787ee1 | ||
|  | b3ae920746 | ||
|  | b82bef95f3 | ||
|  | e6578defcd | ||
|  | ace8e30818 | ||
|  | ec3aa06caf | ||
|  | ba088e5545 | ||
|  | 8a0b0cb3d7 | ||
|  | 6369138bd1 | ||
|  | c2a7dffa7d | ||
|  | b0c2325adc | ||
|  | 2ff157cf7a | ||
|  | 83628b285b | ||
|  | 1ba3f262a2 | ||
|  | 97334e10af | ||
|  | 31b4f8fa31 | ||
|  | 1bbb4cb478 | ||
|  | d46da6ac9d | ||
|  | 825b38850e | ||
|  | 8755824c64 | ||
|  | 4ea835e50b | ||
|  | 5fddbec132 | ||
|  | b3d3fd70aa | ||
|  | 6633537fb8 | ||
|  | 4dec9716c4 | ||
|  | 313b36944f | ||
|  | 456fdda6c2 | ||
|  | 6437c43147 | ||
|  | 5928a24803 | ||
|  | 20a6bcc676 | ||
|  | eaf313b0f6 | ||
|  | d51b66c204 | ||
|  | 660f0e4c40 | ||
|  | 540a03f75c | ||
|  | a6b239698c | ||
|  | 92d1dd9a4a | ||
|  | 45ec5f8eab | ||
|  | 1d01acce06 | ||
|  | be750ee427 | ||
|  | dddb30477b | ||
|  | 37459a3ebc | ||
|  | 449c33ee8b | ||
|  | 2b5d0877a8 | ||
|  | 6d5807ec4b | ||
|  | 64865b3f41 | ||
|  | 53f0e1896b | ||
|  | aaa60dab12 | ||
|  | 9b72c445a7 | ||
|  | 3f609e17b3 | ||
|  | ef03c84b21 | ||
|  | 163c0f1b44 | ||
|  | 807e1d36d5 | ||
|  | 5545cc8bab | ||
|  | d6e60c8e3a | ||
|  | 2d8e7e9f8b | ||
|  | 5b4c5b0cbf | ||
|  | 2471ef805b | ||
|  | 2a7fc86b15 | ||
|  | 3c8bf52fb8 | ||
|  | a026998682 | ||
|  | 06ea81fdb2 | ||
|  | d69b1e2d11 | ||
|  | a3e0024980 | ||
|  | d9a2c32aca | ||
|  | e152ed2e61 | ||
|  | 9e975a46a2 | ||
|  | 70af075012 | ||
|  | 44e5a03cf2 | ||
|  | c8cee88e33 | ||
|  | 35296017b5 | ||
|  | 130d598ec9 | ||
|  | 0350925c1e | ||
|  | 94e3dd0d4f | ||
|  | 5d44422230 | ||
|  | eafdd7dbd7 | ||
|  | 127b584e79 | ||
|  | 2179edc7fe | ||
|  | 9108495586 | ||
|  | fa617eac6b | ||
|  | b63971caf7 | ||
|  | 7327da6350 | ||
|  | 8f72fc4a44 | ||
|  | 238348c885 | ||
|  | 7b5f93510b | ||
|  | b3861ff755 | ||
|  | 5a6b7219b9 | ||
|  | c2bc34fd87 | ||
|  | 1d3ae31755 | ||
|  | 8ddd686049 | ||
|  | e5188a60dc | ||
|  | e71d13c090 | ||
|  | ba83dfd454 | ||
|  | 2fb0aea990 | ||
|  | 51177e4e1f | ||
|  | 004d6da9fb | ||
|  | 561373e793 | ||
|  | 279f4760d7 | ||
|  | f931cd582d | ||
|  | ab51bc443b | ||
|  | c8575fe6e0 | ||
|  | 4489f120f9 | ||
|  | 253f9603ed | ||
|  | 6ca712b498 | ||
|  | b743566339 | ||
|  | 481487f084 | ||
|  | fc8313430a | ||
|  | 648618d280 | ||
|  | ae1a130843 | ||
|  | 33d16ae0cd | ||
|  | f09fe30af5 | ||
|  | 33eadb5549 | ||
|  | 368bff1a82 | ||
|  | d853841dd5 | ||
|  | eacaafeb48 | ||
|  | ac59dd8b1d | ||
|  | 353c854734 | ||
|  | 3e5c209039 | ||
|  | f56d96267e | ||
|  | ed28260aaf | ||
|  | 8ccec37a4b | ||
|  | 646622b99e | ||
|  | a25c2fd6b5 | ||
|  | ee1a9a4781 | ||
|  | a0367d6669 | ||
|  | 87658e83c1 | ||
|  | 4509c3ce34 | ||
|  | 30e93979d2 | ||
|  | d6b87053bf | ||
|  | 22389a5d2d | ||
|  | 37696532c2 | ||
|  | 54efcb7e2f | ||
|  | bcb7c27cc4 | ||
|  | e2575d6de4 | ||
|  | 23e989e170 | ||
|  | 28412150e6 | ||
|  | 6d941b0c1f | ||
|  | 2f90f35478 | ||
|  | 12f7e1b804 | ||
|  | c7fa2ed11a | ||
|  | bfbe12b94b | ||
|  | 7476c64a66 | ||
|  | 46fff8e8a2 | ||
|  | cd646aab9e | ||
|  | a3684545b5 | ||
|  | 2f42874fd3 | ||
|  | 84d0e9b4cd | ||
|  | a53011f778 | ||
|  | b842c5b8bb | ||
|  | ab1374f801 | ||
|  | a5359027f0 | ||
|  | 43951a36eb | ||
|  | 55df96491c | ||
|  | 0c037627fc | ||
|  | 344d267fd2 | ||
|  | 4211389ac7 | ||
|  | c6d00ec7d1 | ||
|  | 212ae60622 | ||
|  | a72a2e0a1a | ||
|  | 50375fb373 | ||
|  | 2d02c23574 | ||
|  | cb105fdeb4 | ||
|  | acfd4dde36 | ||
|  | 919fc48cc5 | ||
|  | aec4fd066b | ||
|  | 73c7b18900 | ||
|  | 3dfe45d225 | ||
|  | 95a6b0f85c | ||
|  | 87ee8450fe | ||
|  | f2a6bcf2a8 | ||
|  | 644ef13acd | ||
|  | 342574761f | ||
|  | b7c978e078 | ||
|  | f0398a6db8 | ||
|  | f3b1ef99cc | ||
|  | 52d9ddf9e5 | ||
|  | 93f251dbcd | ||
|  | a6810fc3ef | ||
|  | 15f6c51062 | ||
|  | 5e21c706f3 | ||
|  | e1355d4b62 | ||
|  | 7eeac3b586 | ||
|  | 4bf13610ce | ||
|  | 0e0ce379b4 | ||
|  | 36e8a11505 | ||
|  | 45f442ea63 | ||
|  | db743c90d8 | ||
|  | 10cc94f581 | ||
|  | 108da64562 | ||
|  | f85b46286e | ||
|  | 184b371649 | ||
|  | b0375bb037 | ||
|  | 48942848e7 | ||
|  | 27ac342928 | ||
|  | 25aba16ef8 | ||
|  | a0d0f383c8 | ||
|  | 6752f165db | ||
|  | e05076b258 | ||
|  | fadbfdf801 | ||
|  | cb277b8d1e | ||
|  | 234f14dbbe | ||
|  | 99ede3a9ef | ||
|  | 378233f53d | ||
|  | f903408980 | ||
|  | cc8f316941 | ||
|  | b684254908 | ||
|  | 351d90ca55 | ||
|  | 23177df26a | ||
|  | ba15371948 | ||
|  | 73dbaebbc1 | ||
|  | 8d60734737 | ||
|  | 002098d496 | ||
|  | e3244eb68e | ||
|  | 85c6fb1430 | ||
|  | 54e4643396 | ||
|  | 85c5c4405a | ||
|  | d668879ba6 | ||
|  | cb140aa06e | ||
|  | 6a769d3953 | ||
|  | 3be8ffd826 | ||
|  | bb910e14a4 | ||
|  | 69ebbe019a | ||
|  | 0d39672d32 | ||
|  | 0d1231980a | ||
|  | 82a015892b | ||
|  | 194b7f60c5 | ||
|  | ebc7356db5 | ||
|  | e1a2580b2a | ||
|  | b6f51474ff | ||
|  | 0f18768091 | ||
|  | efc7f9df37 | ||
|  | 50cd617bd9 | ||
|  | 838b818cd3 | ||
|  | cf795562bf | ||
|  | ac37424878 | ||
|  | a336048c98 | ||
|  | 87496f9978 | ||
|  | 08a542a324 | ||
|  | 9b3d05e05f | ||
|  | d8e3103a2b | ||
|  | 76a64d13a0 | ||
|  | 1e975859c2 | ||
|  | 4c5261bfa0 | ||
|  | aed2827e7b | ||
|  | e6e6e4e62b | ||
|  | 8b09b4180b | ||
|  | 626737b9fa | ||
|  | 22de481557 | ||
|  | b9dbb6bcf8 | ||
|  | a48616a138 | ||
|  | 8222aac9e3 | ||
|  | 77aa3c187e | ||
|  | ee0283c985 | ||
|  | 302c2e94de | ||
|  | 06fe07932a | ||
|  | 6913c7a018 | ||
|  | 6b602c74b7 | ||
|  | 8116f85479 | ||
|  | e40d553045 | ||
|  | 2c6414ce11 | ||
|  | e5aea632ee | ||
|  | e5b30cdfbb | ||
|  | ba5f34f827 | ||
|  | 84d2feb2e6 | ||
|  | d12e50eb02 | ||
|  | c2bc9a8c62 | ||
|  | d910a4fd38 | ||
|  | db30f53ab0 | ||
|  | 50be3a24fe | ||
|  | 256ba4028b | ||
|  | b07af2660d | ||
|  | bc0d70b2f7 | ||
|  | c6e48dfd56 | ||
|  | c775db50ef | ||
|  | ee4c8b5ad2 | ||
|  | d8b76e31c3 | ||
|  | 7e10c7f9d8 | ||
|  | c47128f433 | ||
|  | 8aab9acc10 | ||
|  | 350b36df25 | ||
|  | dbd2944c13 | ||
|  | 4603fa6f24 | ||
|  | 60300851ea | ||
|  | 58312ea2b7 | ||
|  | cb534d8b85 | ||
|  | 5626d35bc4 | ||
|  | 4677cebf40 | ||
|  | 7399f3d798 | ||
|  | faeecf7665 | ||
|  | 63e0802f4e | ||
|  | e3ee9604a5 | ||
|  | 8c66e1d99d | ||
|  | b55579c348 | ||
|  | ca9e8aecd6 | ||
|  | cc4cb45e9d | ||
|  | ebbf6e6133 | ||
|  | cba07dec7e | ||
|  | 6f7037b2b1 | ||
|  | ef4b2f963d | ||
|  | 97f3ff03b6 | ||
|  | 2fbc7a2869 | ||
|  | 4983718df7 | ||
|  | 23ca00fd9a | ||
|  | 3df6eba237 | ||
|  | 893f61b490 | ||
|  | e940e02126 | ||
|  | 7e3a46c33e | ||
|  | 7f743c6fb0 | ||
|  | 73654d51dd | ||
|  | 096551ab3e | ||
|  | c485c460f7 | ||
|  | b0a7c58287 | ||
|  | d2637123c4 | ||
|  | 02b7c3d1b0 | ||
|  | 8c1769f157 | ||
|  | 655809517c | ||
|  | 2190f60a89 | ||
|  | 18faebc93c | ||
|  | 0eebfdb4cc | ||
|  | 7811374b0f | ||
|  | a2f01b4a46 | ||
|  | f5c910beb7 | ||
|  | 4e014ca748 | ||
|  | 87095b0578 | ||
|  | fba6ac2b4c | ||
|  | 1a811b1ab1 | ||
|  | c26349624c | ||
|  | b642d9f712 | ||
|  | fd6623b5a5 | ||
|  | 0b2a3f18bc | ||
|  | b304c3a4b9 | ||
|  | 3ceef2005b | ||
|  | 0f438f524b | ||
|  | 24c84ca6f5 | ||
|  | 7898f643ac | ||
|  | 7bd45d308a | ||
|  | b3da16911f | ||
|  | e52892f75b | ||
|  | 8c41a0f0ed | ||
|  | 3e9212aaff | ||
|  | a2ec902773 | ||
|  | 1c0130fd02 | ||
|  | 3e3d6f97f4 | ||
|  | 9c3bda0111 | ||
|  | d14902700a | ||
|  | c95c32a9fe | ||
|  | 35e045d7a7 | ||
|  | 084e1f3d51 | ||
|  | 5b43cefb85 | ||
|  | aab637c9e7 | ||
|  | 7d9b197383 | ||
|  | c9dd267ec1 | ||
|  | a5254989f8 | ||
|  | 494ce073b5 | ||
|  | b99e4210ba | ||
|  | d3b74cbc91 | ||
|  | 5ff73faf48 | ||
|  | 2f7f11e2e5 | ||
|  | 5119997122 | ||
|  | b5c1773d59 | ||
|  | dfb5057342 | ||
|  | 7bddd294c9 | ||
|  | 01f7394f7f | ||
|  | 5aa8b03349 | ||
|  | b5ad910b81 | ||
|  | da65bae86e | ||
|  | a0189a6fe1 | ||
|  | 244b5ba3c2 | ||
|  | 960de7bd7b | ||
|  | c6185baa99 | ||
|  | 4d4695032c | ||
|  | 9d29cefe75 | ||
|  | 35f535b9a3 | ||
|  | 6d22f6fcd5 | ||
|  | 8bfaa487ce | ||
|  | 0d067d2f01 | ||
|  | d66755fd1e | ||
|  | 267b2add9a | ||
|  | d290e3d99e | ||
|  | a6a4c5a936 | ||
|  | 8a8f0cef20 | ||
|  | 91dc0d5f4a | ||
|  | ed7b07c8b1 | ||
|  | 3f880fa769 | ||
|  | d83dd17738 | ||
|  | 9ade0dcae3 | ||
|  | a329d85697 | ||
|  | c322410783 | ||
|  | b67331e018 | ||
|  | a47b339668 | ||
|  | ad56a9215c | ||
|  | c56a5344b9 | ||
|  | 1f62cbe21a | ||
|  | 47845f8c19 | ||
|  | 409c82ce73 | ||
|  | dc3f5b6211 | ||
|  | fb02b77e63 | ||
|  | f974d54c7a | ||
|  | 68978c6e25 | ||
|  | 6e83b7d6df | ||
|  | 5a4d448cc1 | ||
|  | 743eac8c55 | ||
|  | 6b66c8f304 | ||
|  | c976fbfcd5 | ||
|  | ed3e38ac31 | ||
|  | 76f03900d2 | ||
|  | 035df316aa | ||
|  | 9759a04c7d | ||
|  | c7cb47a1d8 | ||
|  | 0d2d04e17b | ||
|  | 98423c6e41 | ||
|  | 33c3fa21e3 | ||
|  | 2141d52794 | ||
|  | 16b8021401 | ||
|  | 151b09b5ca | ||
|  | 9bc2b48d9b | ||
|  | ab8a98f1df | ||
|  | efe354a7b1 | ||
|  | d50d3fc837 | ||
|  | 83ee92af1a | ||
|  | ea0ad9fd87 | ||
|  | ff3c60c0e1 | ||
|  | 399703a471 | ||
|  | 82017c4aea | ||
|  | bdf07c3dc9 | ||
|  | 598be24644 | ||
|  | e4e71a1e5f | ||
|  | fba5af280e | ||
|  | c668ff9472 | ||
|  | 2cadc706e2 | ||
|  | 3c6f63abcc | ||
|  | 00cd7e7e9c | ||
|  | 055c860b43 | ||
|  | 454c8628c3 | ||
|  | a23a6db4d6 | ||
|  | 6575091a78 | ||
|  | 9e25d014d2 | ||
|  | 41d5dd8679 | ||
|  | c3ea6dc1f5 | ||
|  | 22afa509ca | ||
|  | 3fb3cc8269 | ||
|  | e3e461d7cb | ||
|  | c16fccb317 | ||
|  | b9cffdf2bd | ||
|  | f2aae72cc2 | ||
|  | fe8db1873c | ||
|  | c66c715ac9 | ||
|  | 5dcfd85642 | ||
|  | c70dfe1b09 | ||
|  | 232c591655 | ||
|  | 790614b544 | ||
|  | 32c032cd97 | ||
|  | e48ee16366 | ||
|  | e92d936ce8 | ||
|  | 4e210c5396 | ||
|  | 3d3e60b1fc | ||
|  | f3f0e2f1a9 | ||
|  | 08206eea56 | ||
|  | 78296246e8 | ||
|  | 85b5dd35b1 | ||
|  | 11cfaa3e3d | ||
|  | 103c863534 | ||
|  | 6688f83226 | ||
|  | 01a064dd63 | ||
|  | 7b234078ae | ||
|  | add02a7897 | ||
|  | 19167df692 | ||
|  | 6766845e21 | ||
|  | bc3b5f3e35 | ||
|  | 5fe23113ec | ||
|  | c55e1c1d17 | ||
|  | d910405648 | ||
|  | 62b432c046 | ||
|  | eae1f78221 | ||
|  | 11d05fb3b8 | ||
|  | 58efca835f | ||
|  | da6e520b91 | ||
|  | a5099f69d8 | ||
|  | 9398b6c2c8 | ||
|  | 99f2060fc1 | ||
|  | 5d3ebcb35a | ||
|  | 509d011fbe | ||
|  | 17ffd604bf | ||
|  | a3dafa9056 | ||
|  | 21d0602305 | ||
|  | 64d6ee1be5 | ||
|  | 1378ab7278 | ||
|  | 87a021ec2d | ||
|  | 189317b80c | ||
|  | 4f0775cc7c | ||
|  | 7190f927b7 | ||
|  | d559d8b901 | ||
|  | 2562306802 | ||
|  | 50bb4f0142 | ||
|  | df80c37adb | ||
|  | 7da51602d5 | ||
|  | 5152517887 | ||
|  | eb8a2de5d6 | ||
|  | f2a1a906ff | ||
|  | 0808e9b6fb | ||
|  | b81a2cc273 | 
							
								
								
									
										209
									
								
								ClockReceiver/ClockReceiver.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								ClockReceiver/ClockReceiver.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | // | ||||||
|  | //  ClockReceiver.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/07/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ClockReceiver_hpp | ||||||
|  | #define ClockReceiver_hpp | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Informal pattern for all classes that run from a clock cycle: | ||||||
|  |  | ||||||
|  | 		Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as | ||||||
|  | 		is appropriate. | ||||||
|  |  | ||||||
|  | 		Callers that are accumulating HalfCycles but want to talk to receivers that implement | ||||||
|  | 		only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or | ||||||
|  | 		can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle | ||||||
|  | 		storage to it. | ||||||
|  |  | ||||||
|  | 	Alignment rule: | ||||||
|  |  | ||||||
|  | 		run_for(Cycles) may be called only after an even number of half cycles. E.g. the following | ||||||
|  | 		sequence will have undefined results: | ||||||
|  |  | ||||||
|  | 			run_for(HalfCycles(1)) | ||||||
|  | 			run_for(Cycles(1)) | ||||||
|  |  | ||||||
|  | 		An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and | ||||||
|  | 		run_for(HalfCycles) to use. | ||||||
|  |  | ||||||
|  | 	Reasoning: | ||||||
|  |  | ||||||
|  | 		Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles) | ||||||
|  | 		where there is a need to implement at half-cycle precision but a faster execution | ||||||
|  | 		path can be offered for full-cycle precision. Those users are permitted to assume | ||||||
|  | 		phase in run_for(Cycles) and should do so to be compatible with callers that use | ||||||
|  | 		only run_for(Cycles). | ||||||
|  |  | ||||||
|  | 	Corollary: | ||||||
|  |  | ||||||
|  | 		Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half | ||||||
|  | 		of a full cycle. The second will do the second half. Etc. | ||||||
|  |  | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that wraps a plain int, providing most of the basic arithmetic and | ||||||
|  | 	Boolean operators, but forcing callers and receivers to be explicit as to usage. | ||||||
|  | */ | ||||||
|  | template <class T> class WrappedInt { | ||||||
|  | 	public: | ||||||
|  | 		inline WrappedInt(int l) : length_(l) {} | ||||||
|  | 		inline WrappedInt() : length_(0) {} | ||||||
|  |  | ||||||
|  | 		inline T &operator =(const T &rhs) { | ||||||
|  | 			length_ = rhs.length_; | ||||||
|  | 			return *this; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator +=(const T &rhs) { | ||||||
|  | 			length_ += rhs.length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator -=(const T &rhs) { | ||||||
|  | 			length_ -= rhs.length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator ++() { | ||||||
|  | 			++ length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator ++(int) { | ||||||
|  | 			length_ ++; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator --() { | ||||||
|  | 			-- length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator --(int) { | ||||||
|  | 			length_ --; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator %=(const T &rhs) { | ||||||
|  | 			length_ %= rhs.length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T &operator &=(const T &rhs) { | ||||||
|  | 			length_ &= rhs.length_; | ||||||
|  | 			return *static_cast<T *>(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | ||||||
|  | 		inline T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | ||||||
|  |  | ||||||
|  | 		inline T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | ||||||
|  | 		inline T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | ||||||
|  |  | ||||||
|  | 		inline T operator -() const						{	return T(- length_);				} | ||||||
|  |  | ||||||
|  | 		inline bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | ||||||
|  | 		inline bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} | ||||||
|  | 		inline bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} | ||||||
|  | 		inline bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} | ||||||
|  | 		inline bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} | ||||||
|  | 		inline bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} | ||||||
|  |  | ||||||
|  | 		inline bool operator !() const					{	return !length_;					} | ||||||
|  | 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | ||||||
|  |  | ||||||
|  | 		inline int as_int() const { return length_; } | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Severs from @c this the effect of dividing by @c divisor — @c this will end up with | ||||||
|  | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
|  | 		*/ | ||||||
|  | 		inline T divide(const T &divisor) { | ||||||
|  | 			T result(length_ / divisor.length_); | ||||||
|  | 			length_ %= divisor.length_; | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Flushes the value in @c this. The current value is returned, and the internal value | ||||||
|  | 			is reset to zero. | ||||||
|  | 		*/ | ||||||
|  | 		inline T flush() { | ||||||
|  | 			T result(length_); | ||||||
|  | 			length_ = 0; | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// operator int() is deliberately not provided, to avoid accidental subtitution of | ||||||
|  | 		// classes that use this template. | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		int length_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Describes an integer number of whole cycles — pairs of clock signal transitions. | ||||||
|  | class Cycles: public WrappedInt<Cycles> { | ||||||
|  | 	public: | ||||||
|  | 		inline Cycles(int l) : WrappedInt<Cycles>(l) {} | ||||||
|  | 		inline Cycles() : WrappedInt<Cycles>() {} | ||||||
|  | 		inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Describes an integer number of half cycles — single clock signal transitions. | ||||||
|  | class HalfCycles: public WrappedInt<HalfCycles> { | ||||||
|  | 	public: | ||||||
|  | 		inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | ||||||
|  | 		inline HalfCycles() : WrappedInt<HalfCycles>() {} | ||||||
|  |  | ||||||
|  | 		inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() << 1) {} | ||||||
|  | 		inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | ||||||
|  |  | ||||||
|  | 		/// @returns The number of whole cycles completely covered by this span of half cycles. | ||||||
|  | 		inline Cycles cycles() { | ||||||
|  | 			return Cycles(length_ >> 1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		///Flushes the whole cycles in @c this, subtracting that many from the total stored here. | ||||||
|  | 		inline Cycles flush_cycles() { | ||||||
|  | 			Cycles result(length_ >> 1); | ||||||
|  | 			length_ &= 1; | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Severs from @c this the effect of dividing by @c divisor — @c this will end up with | ||||||
|  | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
|  | 		*/ | ||||||
|  | 		inline Cycles divide_cycles(const Cycles &divisor) { | ||||||
|  | 			HalfCycles half_divisor = HalfCycles(divisor); | ||||||
|  | 			Cycles result(length_ / half_divisor.length_); | ||||||
|  | 			length_ %= half_divisor.length_; | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver | ||||||
|  | 	automatically to gain run_for(HalfCycles). | ||||||
|  | */ | ||||||
|  | template <class T> class HalfClockReceiver: public T { | ||||||
|  | 	public: | ||||||
|  | 		using T::T; | ||||||
|  |  | ||||||
|  | 		using T::run_for; | ||||||
|  | 		inline void run_for(const HalfCycles half_cycles) { | ||||||
|  | 			half_cycles_ += half_cycles; | ||||||
|  | 			T::run_for(half_cycles_.flush_cycles()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		HalfCycles half_cycles_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* ClockReceiver_hpp */ | ||||||
							
								
								
									
										26
									
								
								ClockReceiver/ForceInline.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								ClockReceiver/ForceInline.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  ForceInline.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 01/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ForceInline_hpp | ||||||
|  | #define ForceInline_hpp | ||||||
|  |  | ||||||
|  | #ifdef DEBUG | ||||||
|  |  | ||||||
|  | #define forceinline | ||||||
|  |  | ||||||
|  | #else | ||||||
|  |  | ||||||
|  | #ifdef __GNUC__ | ||||||
|  | #define forceinline __attribute__((always_inline)) inline | ||||||
|  | #elif _MSC_VER | ||||||
|  | #define forceinline __forceinline | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #endif /* ForceInline_h */ | ||||||
							
								
								
									
										60
									
								
								ClockReceiver/Sleeper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ClockReceiver/Sleeper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | // | ||||||
|  | //  Sleeper.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Sleeper_hpp | ||||||
|  | #define Sleeper_hpp | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing | ||||||
|  | 	any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example. | ||||||
|  |  | ||||||
|  | 	A sleeper will signal sleeps and wakes to an observer. | ||||||
|  |  | ||||||
|  | 	This is intended to allow for performance improvements to machines with components that can sleep. The observer | ||||||
|  | 	callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions | ||||||
|  | 	into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that | ||||||
|  | 	the saved ::run_fors add up to a substantial amount. | ||||||
|  |  | ||||||
|  | 	By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint, | ||||||
|  | 	not a command. | ||||||
|  | */ | ||||||
|  | class Sleeper { | ||||||
|  | 	public: | ||||||
|  | 		Sleeper() : sleep_observer_(nullptr) {} | ||||||
|  |  | ||||||
|  | 		class SleepObserver { | ||||||
|  | 			public: | ||||||
|  | 				/// Called to inform an observer that the component @c component has either gone to sleep or become awake. | ||||||
|  | 				virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Registers @c observer as the new sleep observer; | ||||||
|  | 		void set_sleep_observer(SleepObserver *observer) { | ||||||
|  | 			sleep_observer_ = observer; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// @returns @c true if the component is currently sleeping; @c false otherwise. | ||||||
|  | 		virtual bool is_sleeping() = 0; | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		/// Provided for subclasses; send sleep announcements to the sleep_observer_. | ||||||
|  | 		SleepObserver *sleep_observer_; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified, | ||||||
|  | 			if one exists. | ||||||
|  |  | ||||||
|  | 			@c is_sleeping will be called only if there is an observer. | ||||||
|  | 		*/ | ||||||
|  | 		void update_sleep_observer() { | ||||||
|  | 			if(!sleep_observer_) return; | ||||||
|  | 			sleep_observer_->set_component_is_sleeping(this, is_sleeping()); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* Sleeper_h */ | ||||||
| @@ -25,29 +25,16 @@ WD1770::Status::Status() : | |||||||
| 		busy(false) {} | 		busy(false) {} | ||||||
|  |  | ||||||
| WD1770::WD1770(Personality p) : | WD1770::WD1770(Personality p) : | ||||||
| 		Storage::Disk::Controller(8000000, 16, 300), | 		Storage::Disk::MFMController(8000000, 16, 300), | ||||||
| 		crc_generator_(0x1021, 0xffff), | 		interesting_event_mask_((int)Event1770::Command), | ||||||
| 		interesting_event_mask_(Event::Command), |  | ||||||
| 		resume_point_(0), | 		resume_point_(0), | ||||||
| 		delay_time_(0), | 		delay_time_(0), | ||||||
| 		index_hole_count_target_(-1), | 		index_hole_count_target_(-1), | ||||||
| 		is_awaiting_marker_value_(false), |  | ||||||
| 		data_mode_(DataMode::Scanning), |  | ||||||
| 		delegate_(nullptr), | 		delegate_(nullptr), | ||||||
| 		personality_(p), | 		personality_(p), | ||||||
| 		head_is_loaded_(false) { | 		head_is_loaded_(false) { | ||||||
| 	set_is_double_density(false); | 	set_is_double_density(false); | ||||||
| 	posit_event(Event::Command); | 	posit_event((int)Event1770::Command); | ||||||
| } |  | ||||||
|  |  | ||||||
| void WD1770::set_is_double_density(bool is_double_density) { |  | ||||||
| 	is_double_density_ = is_double_density; |  | ||||||
| 	Storage::Time bit_length; |  | ||||||
| 	bit_length.length = 1; |  | ||||||
| 	bit_length.clock_rate = is_double_density ? 500000 : 250000; |  | ||||||
| 	set_expected_bit_length(bit_length); |  | ||||||
|  |  | ||||||
| 	if(!is_double_density) is_awaiting_marker_value_ = false; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void WD1770::set_register(int address, uint8_t value) { | void WD1770::set_register(int address, uint8_t value) { | ||||||
| @@ -60,7 +47,7 @@ void WD1770::set_register(int address, uint8_t value) { | |||||||
| 				}); | 				}); | ||||||
| 			} else { | 			} else { | ||||||
| 				command_ = value; | 				command_ = value; | ||||||
| 				posit_event(Event::Command); | 				posit_event((int)Event1770::Command); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		break; | 		break; | ||||||
| @@ -124,154 +111,31 @@ uint8_t WD1770::get_register(int address) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void WD1770::run_for_cycles(unsigned int number_of_cycles) { | void WD1770::run_for(const Cycles cycles) { | ||||||
| 	Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); | 	Storage::Disk::Controller::run_for(cycles); | ||||||
|  |  | ||||||
| 	if(delay_time_) { | 	if(delay_time_) { | ||||||
|  | 		unsigned int number_of_cycles = (unsigned int)cycles.as_int(); | ||||||
| 		if(delay_time_ <= number_of_cycles) { | 		if(delay_time_ <= number_of_cycles) { | ||||||
| 			delay_time_ = 0; | 			delay_time_ = 0; | ||||||
| 			posit_event(Event::Timer); | 			posit_event((int)Event1770::Timer); | ||||||
| 		} else { | 		} else { | ||||||
| 			delay_time_ -= number_of_cycles; | 			delay_time_ -= number_of_cycles; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) { | #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__: | ||||||
| 	if(data_mode_ == DataMode::Writing) return; | #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer); | ||||||
|  | #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = (int)Event::Token; return; } | ||||||
| 	shift_register_ = (shift_register_ << 1) | value; |  | ||||||
| 	bits_since_token_++; |  | ||||||
|  |  | ||||||
| 	if(data_mode_ == DataMode::Scanning) { |  | ||||||
| 		Token::Type token_type = Token::Byte; |  | ||||||
| 		if(!is_double_density_) { |  | ||||||
| 			switch(shift_register_ & 0xffff) { |  | ||||||
| 				case Storage::Encodings::MFM::FMIndexAddressMark: |  | ||||||
| 					token_type = Token::Index; |  | ||||||
| 					crc_generator_.reset(); |  | ||||||
| 					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte); |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::FMIDAddressMark: |  | ||||||
| 					token_type = Token::ID; |  | ||||||
| 					crc_generator_.reset(); |  | ||||||
| 					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte); |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::FMDataAddressMark: |  | ||||||
| 					token_type = Token::Data; |  | ||||||
| 					crc_generator_.reset(); |  | ||||||
| 					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte); |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::FMDeletedDataAddressMark: |  | ||||||
| 					token_type = Token::DeletedData; |  | ||||||
| 					crc_generator_.reset(); |  | ||||||
| 					crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte); |  | ||||||
| 				break; |  | ||||||
| 				default: |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			switch(shift_register_ & 0xffff) { |  | ||||||
| 				case Storage::Encodings::MFM::MFMIndexSync: |  | ||||||
| 					bits_since_token_ = 0; |  | ||||||
| 					is_awaiting_marker_value_ = true; |  | ||||||
|  |  | ||||||
| 					token_type = Token::Sync; |  | ||||||
| 					latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue; |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::MFMSync: |  | ||||||
| 					bits_since_token_ = 0; |  | ||||||
| 					is_awaiting_marker_value_ = true; |  | ||||||
| 					crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); |  | ||||||
|  |  | ||||||
| 					token_type = Token::Sync; |  | ||||||
| 					latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue; |  | ||||||
| 				break; |  | ||||||
| 				default: |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if(token_type != Token::Byte) { |  | ||||||
| 			latest_token_.type = token_type; |  | ||||||
| 			bits_since_token_ = 0; |  | ||||||
| 			posit_event(Event::Token); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(bits_since_token_ == 16) { |  | ||||||
| 		latest_token_.type = Token::Byte; |  | ||||||
| 		latest_token_.byte_value = (uint8_t)( |  | ||||||
| 			((shift_register_ & 0x0001) >> 0) | |  | ||||||
| 			((shift_register_ & 0x0004) >> 1) | |  | ||||||
| 			((shift_register_ & 0x0010) >> 2) | |  | ||||||
| 			((shift_register_ & 0x0040) >> 3) | |  | ||||||
| 			((shift_register_ & 0x0100) >> 4) | |  | ||||||
| 			((shift_register_ & 0x0400) >> 5) | |  | ||||||
| 			((shift_register_ & 0x1000) >> 6) | |  | ||||||
| 			((shift_register_ & 0x4000) >> 7)); |  | ||||||
| 		bits_since_token_ = 0; |  | ||||||
|  |  | ||||||
| 		if(is_awaiting_marker_value_ && is_double_density_) { |  | ||||||
| 			is_awaiting_marker_value_ = false; |  | ||||||
| 			switch(latest_token_.byte_value) { |  | ||||||
| 				case Storage::Encodings::MFM::IndexAddressByte: |  | ||||||
| 					latest_token_.type = Token::Index; |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::IDAddressByte: |  | ||||||
| 					latest_token_.type = Token::ID; |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::DataAddressByte: |  | ||||||
| 					latest_token_.type = Token::Data; |  | ||||||
| 				break; |  | ||||||
| 				case Storage::Encodings::MFM::DeletedDataAddressByte: |  | ||||||
| 					latest_token_.type = Token::DeletedData; |  | ||||||
| 				break; |  | ||||||
| 				default: break; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		crc_generator_.add(latest_token_.byte_value); |  | ||||||
| 		posit_event(Event::Token); |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void WD1770::process_index_hole() { |  | ||||||
| 	index_hole_count_++; |  | ||||||
| 	posit_event(Event::IndexHole); |  | ||||||
| 	if(index_hole_count_target_ == index_hole_count_) { |  | ||||||
| 		posit_event(Event::IndexHoleTarget); |  | ||||||
| 		index_hole_count_target_ = -1; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// motor power-down |  | ||||||
| 	if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) { |  | ||||||
| 		set_motor_on(false); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// head unload |  | ||||||
| 	if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) { |  | ||||||
| 		set_head_load_request(false); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void WD1770::process_write_completed() { |  | ||||||
| 	posit_event(Event::DataWritten); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__: |  | ||||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__: |  | ||||||
| #define WAIT_FOR_BYTES(count)	resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; } |  | ||||||
| #define BEGIN_SECTION()	switch(resume_point_) { default: | #define BEGIN_SECTION()	switch(resume_point_) { default: | ||||||
| #define END_SECTION()	0; } | #define END_SECTION()	0; } | ||||||
|  |  | ||||||
| #define READ_ID()	\ | #define READ_ID()	\ | ||||||
| 		if(new_event_type == Event::Token) {	\ | 		if(new_event_type == (int)Event::Token) {	\ | ||||||
| 			if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }	\ | 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }	\ | ||||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) {	\ | 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {	\ | ||||||
| 				header_[distance_into_section_ - 1] = latest_token_.byte_value;	\ | 				header_[distance_into_section_ - 1] = get_latest_token().byte_value;	\ | ||||||
| 				distance_into_section_++;	\ | 				distance_into_section_++;	\ | ||||||
| 			}	\ | 			}	\ | ||||||
| 		} | 		} | ||||||
| @@ -284,7 +148,7 @@ void WD1770::process_write_completed() { | |||||||
| 		set_motor_on(true);	\ | 		set_motor_on(true);	\ | ||||||
| 		index_hole_count_ = 0;	\ | 		index_hole_count_ = 0;	\ | ||||||
| 		index_hole_count_target_ = 6;	\ | 		index_hole_count_target_ = 6;	\ | ||||||
| 		WAIT_FOR_EVENT(Event::IndexHoleTarget);	\ | 		WAIT_FOR_EVENT(Event1770::IndexHoleTarget);	\ | ||||||
| 		status_.spin_up = true; | 		status_.spin_up = true; | ||||||
|  |  | ||||||
| //     +--------+----------+-------------------------+ | //     +--------+----------+-------------------------+ | ||||||
| @@ -304,7 +168,25 @@ void WD1770::process_write_completed() { | |||||||
| //     !	 4  ! Forc int !  1  1	0  1 i3 i2 i1 i0 ! | //     !	 4  ! Forc int !  1  1	0  1 i3 i2 i1 i0 ! | ||||||
| //     +--------+----------+-------------------------+ | //     +--------+----------+-------------------------+ | ||||||
|  |  | ||||||
| void WD1770::posit_event(Event new_event_type) { | void WD1770::posit_event(int new_event_type) { | ||||||
|  | 	if(new_event_type == (int)Event::IndexHole) { | ||||||
|  | 		index_hole_count_++; | ||||||
|  | 		if(index_hole_count_target_ == index_hole_count_) { | ||||||
|  | 			posit_event((int)Event1770::IndexHoleTarget); | ||||||
|  | 			index_hole_count_target_ = -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// motor power-down | ||||||
|  | 		if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) { | ||||||
|  | 			set_motor_on(false); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// head unload | ||||||
|  | 		if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) { | ||||||
|  | 			set_head_load_request(false); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if(!(interesting_event_mask_ & (int)new_event_type)) return; | 	if(!(interesting_event_mask_ & (int)new_event_type)) return; | ||||||
| 	interesting_event_mask_ &= ~new_event_type; | 	interesting_event_mask_ &= ~new_event_type; | ||||||
|  |  | ||||||
| @@ -314,7 +196,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 	// Wait for a new command, branch to the appropriate handler. | 	// Wait for a new command, branch to the appropriate handler. | ||||||
| 	wait_for_command: | 	wait_for_command: | ||||||
| 		printf("Idle...\n"); | 		printf("Idle...\n"); | ||||||
| 		data_mode_ = DataMode::Scanning; | 		set_data_mode(DataMode::Scanning); | ||||||
| 		index_hole_count_ = 0; | 		index_hole_count_ = 0; | ||||||
|  |  | ||||||
| 		update_status([] (Status &status) { | 		update_status([] (Status &status) { | ||||||
| @@ -322,7 +204,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 			status.interrupt_request = true; | 			status.interrupt_request = true; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		WAIT_FOR_EVENT(Event::Command); | 		WAIT_FOR_EVENT(Event1770::Command); | ||||||
|  |  | ||||||
| 		update_status([] (Status &status) { | 		update_status([] (Status &status) { | ||||||
| 			status.busy = true; | 			status.busy = true; | ||||||
| @@ -371,7 +253,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 		} | 		} | ||||||
| 		set_head_load_request(true); | 		set_head_load_request(true); | ||||||
| 		if(head_is_loaded_) goto test_type1_type; | 		if(head_is_loaded_) goto test_type1_type; | ||||||
| 		WAIT_FOR_EVENT(Event::HeadLoad); | 		WAIT_FOR_EVENT(Event1770::HeadLoad); | ||||||
| 		goto test_type1_type; | 		goto test_type1_type; | ||||||
|  |  | ||||||
| 	begin_type1_spin_up: | 	begin_type1_spin_up: | ||||||
| @@ -403,7 +285,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 			goto verify; | 			goto verify; | ||||||
| 		} | 		} | ||||||
| 		step(step_direction_ ? 1 : -1); | 		step(step_direction_ ? 1 : -1); | ||||||
| 		int time_to_wait; | 		unsigned int time_to_wait; | ||||||
| 		switch(command_ & 3) { | 		switch(command_ & 3) { | ||||||
| 			default: | 			default: | ||||||
| 			case 0: time_to_wait = 6;	break; | 			case 0: time_to_wait = 6;	break; | ||||||
| @@ -428,7 +310,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 		distance_into_section_ = 0; | 		distance_into_section_ = 0; | ||||||
|  |  | ||||||
| 	verify_read_data: | 	verify_read_data: | ||||||
| 		WAIT_FOR_EVENT(Event::IndexHole | Event::Token); | 		WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token); | ||||||
| 		READ_ID(); | 		READ_ID(); | ||||||
|  |  | ||||||
| 		if(index_hole_count_ == 6) { | 		if(index_hole_count_ == 6) { | ||||||
| @@ -438,8 +320,8 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		if(distance_into_section_ == 7) { | 		if(distance_into_section_ == 7) { | ||||||
| 			data_mode_ = DataMode::Scanning; | 			set_data_mode(DataMode::Scanning); | ||||||
| 			if(crc_generator_.get_value()) { | 			if(get_crc_generator().get_value()) { | ||||||
| 				update_status([] (Status &status) { | 				update_status([] (Status &status) { | ||||||
| 					status.crc_error = true; | 					status.crc_error = true; | ||||||
| 				}); | 				}); | ||||||
| @@ -490,7 +372,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 	begin_type2_load_head: | 	begin_type2_load_head: | ||||||
| 		set_head_load_request(true); | 		set_head_load_request(true); | ||||||
| 		if(head_is_loaded_) goto test_type2_delay; | 		if(head_is_loaded_) goto test_type2_delay; | ||||||
| 		WAIT_FOR_EVENT(Event::HeadLoad); | 		WAIT_FOR_EVENT(Event1770::HeadLoad); | ||||||
| 		goto test_type2_delay; | 		goto test_type2_delay; | ||||||
|  |  | ||||||
| 	begin_type2_spin_up: | 	begin_type2_spin_up: | ||||||
| @@ -512,7 +394,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	type2_get_header: | 	type2_get_header: | ||||||
| 		WAIT_FOR_EVENT(Event::IndexHole | Event::Token); | 		WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token); | ||||||
| 		READ_ID(); | 		READ_ID(); | ||||||
|  |  | ||||||
| 		if(index_hole_count_ == 5) { | 		if(index_hole_count_ == 5) { | ||||||
| @@ -524,11 +406,11 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 		} | 		} | ||||||
| 		if(distance_into_section_ == 7) { | 		if(distance_into_section_ == 7) { | ||||||
| 			printf("Considering %d/%d\n", header_[0], header_[2]); | 			printf("Considering %d/%d\n", header_[0], header_[2]); | ||||||
| 			data_mode_ = DataMode::Scanning; | 			set_data_mode(DataMode::Scanning); | ||||||
| 			if(		header_[0] == track_ && header_[2] == sector_ && | 			if(		header_[0] == track_ && header_[2] == sector_ && | ||||||
| 					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { | 					(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { | ||||||
| 				printf("Found %d/%d\n", header_[0], header_[2]); | 				printf("Found %d/%d\n", header_[0], header_[2]); | ||||||
| 				if(crc_generator_.get_value()) { | 				if(get_crc_generator().get_value()) { | ||||||
| 					printf("CRC error; back to searching\n"); | 					printf("CRC error; back to searching\n"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.crc_error = true; | 						status.crc_error = true; | ||||||
| @@ -553,20 +435,20 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 	type2_read_data: | 	type2_read_data: | ||||||
| 		WAIT_FOR_EVENT(Event::Token); | 		WAIT_FOR_EVENT(Event::Token); | ||||||
| 		// TODO: timeout | 		// TODO: timeout | ||||||
| 		if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) { | 		if(get_latest_token().type == Token::Data || get_latest_token().type == Token::DeletedData) { | ||||||
| 			update_status([this] (Status &status) { | 			update_status([this] (Status &status) { | ||||||
| 				status.record_type = (latest_token_.type == Token::DeletedData); | 				status.record_type = (get_latest_token().type == Token::DeletedData); | ||||||
| 			}); | 			}); | ||||||
| 			distance_into_section_ = 0; | 			distance_into_section_ = 0; | ||||||
| 			data_mode_ = DataMode::Reading; | 			set_data_mode(DataMode::Reading); | ||||||
| 			goto type2_read_byte; | 			goto type2_read_byte; | ||||||
| 		} | 		} | ||||||
| 		goto type2_read_data; | 		goto type2_read_data; | ||||||
|  |  | ||||||
| 	type2_read_byte: | 	type2_read_byte: | ||||||
| 		WAIT_FOR_EVENT(Event::Token); | 		WAIT_FOR_EVENT(Event::Token); | ||||||
| 		if(latest_token_.type != Token::Byte) goto type2_read_byte; | 		if(get_latest_token().type != Token::Byte) goto type2_read_byte; | ||||||
| 		data_ = latest_token_.byte_value; | 		data_ = get_latest_token().byte_value; | ||||||
| 		update_status([] (Status &status) { | 		update_status([] (Status &status) { | ||||||
| 			status.lost_data |= status.data_request; | 			status.lost_data |= status.data_request; | ||||||
| 			status.data_request = true; | 			status.data_request = true; | ||||||
| @@ -580,11 +462,11 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
|  |  | ||||||
| 	type2_check_crc: | 	type2_check_crc: | ||||||
| 		WAIT_FOR_EVENT(Event::Token); | 		WAIT_FOR_EVENT(Event::Token); | ||||||
| 		if(latest_token_.type != Token::Byte) goto type2_read_byte; | 		if(get_latest_token().type != Token::Byte) goto type2_read_byte; | ||||||
| 		header_[distance_into_section_] = latest_token_.byte_value; | 		header_[distance_into_section_] = get_latest_token().byte_value; | ||||||
| 		distance_into_section_++; | 		distance_into_section_++; | ||||||
| 		if(distance_into_section_ == 2) { | 		if(distance_into_section_ == 2) { | ||||||
| 			if(crc_generator_.get_value()) { | 			if(get_crc_generator().get_value()) { | ||||||
| 				printf("CRC error; terminating\n"); | 				printf("CRC error; terminating\n"); | ||||||
| 				update_status([this] (Status &status) { | 				update_status([this] (Status &status) { | ||||||
| 					status.crc_error = true; | 					status.crc_error = true; | ||||||
| @@ -615,24 +497,24 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		WAIT_FOR_BYTES(1); | 		WAIT_FOR_BYTES(1); | ||||||
| 		if(is_double_density_) { | 		if(get_is_double_density()) { | ||||||
| 			WAIT_FOR_BYTES(11); | 			WAIT_FOR_BYTES(11); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		data_mode_ = DataMode::Writing; | 		set_data_mode(DataMode::Writing); | ||||||
| 		begin_writing(); | 		begin_writing(false); | ||||||
| 		for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) { | 		for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) { | ||||||
| 			write_byte(0); | 			write_byte(0); | ||||||
| 		} | 		} | ||||||
| 		WAIT_FOR_EVENT(Event::DataWritten); | 		WAIT_FOR_EVENT(Event::DataWritten); | ||||||
|  |  | ||||||
| 		if(is_double_density_) { | 		if(get_is_double_density()) { | ||||||
| 			crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); | 			get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); | ||||||
| 			for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync); | 			for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync); | ||||||
| 			write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); | 			write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); | ||||||
| 		} else { | 		} else { | ||||||
| 			crc_generator_.reset(); | 			get_crc_generator().reset(); | ||||||
| 			crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); | 			get_crc_generator().add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); | ||||||
| 			write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark); | 			write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -668,11 +550,8 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
|  |  | ||||||
| 		goto type2_write_loop; | 		goto type2_write_loop; | ||||||
|  |  | ||||||
| 	type2_write_crc: { | 	type2_write_crc: | ||||||
| 			uint16_t crc = crc_generator_.get_value(); | 		write_crc(); | ||||||
| 			write_byte(crc >> 8); |  | ||||||
| 			write_byte(crc & 0xff); |  | ||||||
| 		} |  | ||||||
| 		write_byte(0xff); | 		write_byte(0xff); | ||||||
| 		WAIT_FOR_EVENT(Event::DataWritten); | 		WAIT_FOR_EVENT(Event::DataWritten); | ||||||
| 		end_writing(); | 		end_writing(); | ||||||
| @@ -711,7 +590,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 	begin_type3_load_head: | 	begin_type3_load_head: | ||||||
| 		set_head_load_request(true); | 		set_head_load_request(true); | ||||||
| 		if(head_is_loaded_) goto type3_test_delay; | 		if(head_is_loaded_) goto type3_test_delay; | ||||||
| 		WAIT_FOR_EVENT(Event::HeadLoad); | 		WAIT_FOR_EVENT(Event1770::HeadLoad); | ||||||
| 		goto type3_test_delay; | 		goto type3_test_delay; | ||||||
|  |  | ||||||
| 	begin_type3_spin_up: | 	begin_type3_spin_up: | ||||||
| @@ -732,17 +611,17 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 		distance_into_section_ = 0; | 		distance_into_section_ = 0; | ||||||
|  |  | ||||||
| 	read_address_get_header: | 	read_address_get_header: | ||||||
| 		WAIT_FOR_EVENT(Event::IndexHole | Event::Token); | 		WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token); | ||||||
| 		if(new_event_type == Event::Token) { | 		if(new_event_type == (int)Event::Token) { | ||||||
| 			if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } | 			if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } | ||||||
| 			else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { | 			else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { | ||||||
| 				if(status_.data_request) { | 				if(status_.data_request) { | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.lost_data = true; | 						status.lost_data = true; | ||||||
| 					}); | 					}); | ||||||
| 					goto wait_for_command; | 					goto wait_for_command; | ||||||
| 				} | 				} | ||||||
| 				header_[distance_into_section_ - 1] = data_ = latest_token_.byte_value; | 				header_[distance_into_section_ - 1] = data_ = get_latest_token().byte_value; | ||||||
| 				track_ = header_[0]; | 				track_ = header_[0]; | ||||||
| 				update_status([] (Status &status) { | 				update_status([] (Status &status) { | ||||||
| 					status.data_request = true; | 					status.data_request = true; | ||||||
| @@ -750,7 +629,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 				distance_into_section_++; | 				distance_into_section_++; | ||||||
|  |  | ||||||
| 				if(distance_into_section_ == 7) { | 				if(distance_into_section_ == 7) { | ||||||
| 					if(crc_generator_.get_value()) { | 					if(get_crc_generator().get_value()) { | ||||||
| 						update_status([] (Status &status) { | 						update_status([] (Status &status) { | ||||||
| 							status.crc_error = true; | 							status.crc_error = true; | ||||||
| 						}); | 						}); | ||||||
| @@ -773,7 +652,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 		index_hole_count_ = 0; | 		index_hole_count_ = 0; | ||||||
|  |  | ||||||
| 	read_track_read_byte: | 	read_track_read_byte: | ||||||
| 		WAIT_FOR_EVENT(Event::Token | Event::IndexHole); | 		WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); | ||||||
| 		if(index_hole_count_) { | 		if(index_hole_count_) { | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| @@ -783,7 +662,7 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 			}); | 			}); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		data_ = latest_token_.byte_value; | 		data_ = get_latest_token().byte_value; | ||||||
| 		update_status([] (Status &status) { | 		update_status([] (Status &status) { | ||||||
| 			status.data_request = true; | 			status.data_request = true; | ||||||
| 		}); | 		}); | ||||||
| @@ -814,25 +693,23 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		WAIT_FOR_EVENT(Event::IndexHoleTarget); | 		WAIT_FOR_EVENT(Event1770::IndexHoleTarget); | ||||||
| 		begin_writing(); | 		begin_writing(true); | ||||||
| 		index_hole_count_ = 0; | 		index_hole_count_ = 0; | ||||||
|  |  | ||||||
| 	write_track_write_loop: | 	write_track_write_loop: | ||||||
| 		if(is_double_density_) { | 		if(get_is_double_density()) { | ||||||
| 			switch(data_) { | 			switch(data_) { | ||||||
| 				case 0xf5: | 				case 0xf5: | ||||||
| 					write_raw_short(Storage::Encodings::MFM::MFMSync); | 					write_raw_short(Storage::Encodings::MFM::MFMSync); | ||||||
| 					crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); | 					get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xf6: | 				case 0xf6: | ||||||
| 					write_raw_short(Storage::Encodings::MFM::MFMIndexSync); | 					write_raw_short(Storage::Encodings::MFM::MFMIndexSync); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xff: { | 				case 0xff: | ||||||
| 					uint16_t crc = crc_generator_.get_value(); | 					write_crc(); | ||||||
| 					write_byte(crc >> 8); | 				break; | ||||||
| 					write_byte(crc & 0xff); |  | ||||||
| 				} break; |  | ||||||
| 				default: | 				default: | ||||||
| 					write_byte(data_); | 					write_byte(data_); | ||||||
| 				break; | 				break; | ||||||
| @@ -855,17 +732,15 @@ void WD1770::posit_event(Event new_event_type) { | |||||||
| 							(data_ & 0x01) | 							(data_ & 0x01) | ||||||
| 						) | 						) | ||||||
| 					); | 					); | ||||||
| 					crc_generator_.reset(); | 					get_crc_generator().reset(); | ||||||
| 					crc_generator_.add(data_); | 					get_crc_generator().add(data_); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xfc: | 				case 0xfc: | ||||||
| 					write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark); | 					write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xf7: { | 				case 0xf7: | ||||||
| 					uint16_t crc = crc_generator_.get_value(); | 					write_crc(); | ||||||
| 					write_byte(crc >> 8); | 				break; | ||||||
| 					write_byte(crc & 0xff); |  | ||||||
| 				} break; |  | ||||||
| 				default: | 				default: | ||||||
| 					write_byte(data_); | 					write_byte(data_); | ||||||
| 				break; | 				break; | ||||||
| @@ -909,27 +784,5 @@ void WD1770::set_head_load_request(bool head_load) {} | |||||||
|  |  | ||||||
| void WD1770::set_head_loaded(bool head_loaded) { | void WD1770::set_head_loaded(bool head_loaded) { | ||||||
| 	head_is_loaded_ = head_loaded; | 	head_is_loaded_ = head_loaded; | ||||||
| 	if(head_loaded) posit_event(Event::HeadLoad); | 	if(head_loaded) posit_event((int)Event1770::HeadLoad); | ||||||
| } |  | ||||||
|  |  | ||||||
| void WD1770::write_bit(int bit) { |  | ||||||
| 	if(is_double_density_) { |  | ||||||
| 		Controller::write_bit(!bit && !last_bit_); |  | ||||||
| 		Controller::write_bit(!!bit); |  | ||||||
| 		last_bit_ = bit; |  | ||||||
| 	} else { |  | ||||||
| 		Controller::write_bit(true); |  | ||||||
| 		Controller::write_bit(!!bit); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void WD1770::write_byte(uint8_t byte) { |  | ||||||
| 	for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80); |  | ||||||
| 	crc_generator_.add(byte); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void WD1770::write_raw_short(uint16_t value) { |  | ||||||
| 	for(int c = 0; c < 16; c++) { |  | ||||||
| 		Controller::write_bit(!!((value << c)&0x8000)); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,26 +9,41 @@ | |||||||
| #ifndef _770_hpp | #ifndef _770_hpp | ||||||
| #define _770_hpp | #define _770_hpp | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/DiskController.hpp" | #include "../../Storage/Disk/MFMDiskController.hpp" | ||||||
| #include "../../NumberTheory/CRC.hpp" |  | ||||||
|  |  | ||||||
| namespace WD { | namespace WD { | ||||||
|  |  | ||||||
| class WD1770: public Storage::Disk::Controller { | /*! | ||||||
|  | 	Provides an emulation of various Western Digital drive controllers, including the | ||||||
|  | 	WD1770, WD1772, FDC1773 and FDC1793. | ||||||
|  | */ | ||||||
|  | class WD1770: public Storage::Disk::MFMController { | ||||||
| 	public: | 	public: | ||||||
| 		enum Personality { | 		enum Personality { | ||||||
| 			P1770,	// implies automatic motor-on management with Type 2 commands offering a spin-up disable | 			P1770,	// implies automatic motor-on management, with Type 2 commands offering a spin-up disable | ||||||
| 			P1772,	// as per the 1770, with different stepping rates | 			P1772,	// as per the 1770, with different stepping rates | ||||||
| 			P1773,	// implements the side number-testing logic of the 1793; omits spin-up/loading logic | 			P1773,	// implements the side number-testing logic of the 1793; omits spin-up/loading logic | ||||||
| 			P1793	// implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT | 			P1793	// implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Constructs an instance of the drive controller that behaves according to personality @c p. | ||||||
|  | 			@param p The type of controller to emulate. | ||||||
|  | 		*/ | ||||||
| 		WD1770(Personality p); | 		WD1770(Personality p); | ||||||
|  |  | ||||||
| 		void set_is_double_density(bool is_double_density); | 		/// 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; | ||||||
|  |  | ||||||
|  | 		/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded. | ||||||
| 		void set_register(int address, uint8_t value); | 		void set_register(int address, uint8_t value); | ||||||
|  |  | ||||||
|  | 		/// Fetches the value of the register @c address. Only the low two bits of the address are decoded. | ||||||
| 		uint8_t get_register(int address); | 		uint8_t get_register(int address); | ||||||
|  |  | ||||||
| 		void run_for_cycles(unsigned int number_of_cycles); | 		/// Runs the controller for @c number_of_cycles cycles. | ||||||
|  | 		void run_for(const Cycles cycles); | ||||||
|  | 		using Storage::Disk::Controller::run_for; | ||||||
|  |  | ||||||
| 		enum Flag: uint8_t { | 		enum Flag: uint8_t { | ||||||
| 			NotReady		= 0x80, | 			NotReady		= 0x80, | ||||||
| @@ -47,8 +62,12 @@ class WD1770: public Storage::Disk::Controller { | |||||||
| 			Busy			= 0x01 | 			Busy			= 0x01 | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | 		/// @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()		{	return status_.interrupt_request;	} | ||||||
|  |  | ||||||
|  | 		/// @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()				{	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; | ||||||
| @@ -87,66 +106,31 @@ class WD1770: public Storage::Disk::Controller { | |||||||
|  |  | ||||||
| 		int index_hole_count_; | 		int index_hole_count_; | ||||||
| 		int index_hole_count_target_; | 		int index_hole_count_target_; | ||||||
| 		int bits_since_token_; |  | ||||||
| 		int distance_into_section_; | 		int distance_into_section_; | ||||||
| 		bool is_awaiting_marker_value_; |  | ||||||
|  |  | ||||||
| 		int step_direction_; | 		int step_direction_; | ||||||
| 		void update_status(std::function<void(Status &)> updater); | 		void update_status(std::function<void(Status &)> updater); | ||||||
|  |  | ||||||
| 		// Tokeniser |  | ||||||
| 		enum DataMode { |  | ||||||
| 			Scanning, |  | ||||||
| 			Reading, |  | ||||||
| 			Writing |  | ||||||
| 		} data_mode_; |  | ||||||
| 		bool is_double_density_; |  | ||||||
| 		int shift_register_; |  | ||||||
| 		struct Token { |  | ||||||
| 			enum Type { |  | ||||||
| 				Index, ID, Data, DeletedData, Sync, Byte |  | ||||||
| 			} type; |  | ||||||
| 			uint8_t byte_value; |  | ||||||
| 		} latest_token_; |  | ||||||
|  |  | ||||||
| 		// Events | 		// Events | ||||||
| 		enum Event: int { | 		enum Event1770: int { | ||||||
| 			Command			= (1 << 0),	// Indicates receipt of a new command. | 			Command			= (1 << 3),	// Indicates receipt of a new command. | ||||||
| 			Token			= (1 << 1),	// Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details. | 			HeadLoad		= (1 << 4),	// Indicates the head has been loaded (1973 only). | ||||||
| 			IndexHole		= (1 << 2),	// Indicates the passing of a physical index hole. |  | ||||||
| 			HeadLoad		= (1 << 3),	// Indicates the head has been loaded (1973 only). |  | ||||||
| 			DataWritten		= (1 << 4),	// Indicates that all queued bits have been written |  | ||||||
|  |  | ||||||
| 			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out. | 			Timer			= (1 << 5),	// Indicates that the delay_time_-powered timer has timed out. | ||||||
| 			IndexHoleTarget	= (1 << 6)	// Indicates that index_hole_count_ has reached index_hole_count_target_. | 			IndexHoleTarget	= (1 << 6)	// Indicates that index_hole_count_ has reached index_hole_count_target_. | ||||||
| 		}; | 		}; | ||||||
| 		void posit_event(Event type); | 		void posit_event(int type); | ||||||
| 		int interesting_event_mask_; | 		int interesting_event_mask_; | ||||||
| 		int resume_point_; | 		int resume_point_; | ||||||
| 		int delay_time_; | 		unsigned int delay_time_; | ||||||
|  |  | ||||||
| 		// Output |  | ||||||
| 		int last_bit_; |  | ||||||
| 		void write_bit(int bit); |  | ||||||
| 		void write_byte(uint8_t byte); |  | ||||||
| 		void write_raw_short(uint16_t value); |  | ||||||
|  |  | ||||||
| 		// ID buffer | 		// ID buffer | ||||||
| 		uint8_t header_[6]; | 		uint8_t header_[6]; | ||||||
|  |  | ||||||
| 		// CRC generator |  | ||||||
| 		NumberTheory::CRC16 crc_generator_; |  | ||||||
|  |  | ||||||
| 		// 1793 head-loading logic | 		// 1793 head-loading logic | ||||||
| 		bool head_is_loaded_; | 		bool head_is_loaded_; | ||||||
|  |  | ||||||
| 		// delegate | 		// delegate | ||||||
| 		Delegate *delegate_; | 		Delegate *delegate_; | ||||||
|  |  | ||||||
| 		// Storage::Disk::Controller |  | ||||||
| 		virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); |  | ||||||
| 		virtual void process_index_hole(); |  | ||||||
| 		virtual void process_write_completed(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ | |||||||
| #include <typeinfo> | #include <typeinfo> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -250,32 +252,22 @@ template <class T> class MOS6522 { | |||||||
| 			timer_is_running_[0] = false;\ | 			timer_is_running_[0] = false;\ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 		/*! | 		/*! Runs for a specified number of half cycles. */ | ||||||
| 			Runs for a specified number of half cycles. | 		inline void run_for(const HalfCycles half_cycles) { | ||||||
|  | 			int number_of_half_cycles = half_cycles.as_int(); | ||||||
|  |  | ||||||
| 			Although the original chip accepts only a phase-2 input, timer reloads are specified as occuring |  | ||||||
| 			1.5 cycles after the timer hits zero. It therefore may be necessary to emulate at half-cycle precision. |  | ||||||
|  |  | ||||||
| 			The first emulated half-cycle will be the period between the trailing edge of a phase-2 input and the |  | ||||||
| 			next rising edge. So it should align with a full system's phase-1. The next emulated half-cycle will be |  | ||||||
| 			that which occurs during phase-2. |  | ||||||
|  |  | ||||||
| 			Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not |  | ||||||
| 			intermingle usage. |  | ||||||
| 		*/ |  | ||||||
| 		inline void run_for_half_cycles(unsigned int number_of_cycles) { |  | ||||||
| 			if(is_phase2_) { | 			if(is_phase2_) { | ||||||
| 				phase2(); | 				phase2(); | ||||||
| 				number_of_cycles--; | 				number_of_half_cycles--; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			while(number_of_cycles >= 2) { | 			while(number_of_half_cycles >= 2) { | ||||||
| 				phase1(); | 				phase1(); | ||||||
| 				phase2(); | 				phase2(); | ||||||
| 				number_of_cycles -= 2; | 				number_of_half_cycles -= 2; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(number_of_cycles) { | 			if(number_of_half_cycles) { | ||||||
| 				phase1(); | 				phase1(); | ||||||
| 				is_phase2_ = true; | 				is_phase2_ = true; | ||||||
| 			} else { | 			} else { | ||||||
| @@ -283,13 +275,9 @@ template <class T> class MOS6522 { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! Runs for a specified number of cycles. */ | ||||||
| 			Runs for a specified number of cycles. | 		inline void run_for(const Cycles cycles) { | ||||||
|  | 			int number_of_cycles = cycles.as_int(); | ||||||
| 			Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not |  | ||||||
| 			intermingle usage. |  | ||||||
| 		*/ |  | ||||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) { |  | ||||||
| 			while(number_of_cycles--) { | 			while(number_of_cycles--) { | ||||||
| 				phase1(); | 				phase1(); | ||||||
| 				phase2(); | 				phase2(); | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ | |||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -104,7 +106,9 @@ template <class T> class MOS6532 { | |||||||
| 			return 0xff; | 			return 0xff; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) { | 		inline void run_for(const Cycles cycles) { | ||||||
|  | 			unsigned int number_of_cycles = (unsigned int)cycles.as_int(); | ||||||
|  |  | ||||||
| 			// 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) { | ||||||
| 				timer_.value -= number_of_cycles; | 				timer_.value -= number_of_cycles; | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" | #include "../../Outputs/CRT/CRT.hpp" | ||||||
| #include "../../Outputs/Speaker.hpp" | #include "../../Outputs/Speaker.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  |  | ||||||
| @@ -99,10 +100,10 @@ template <class T> class MOS6560 { | |||||||
| 				8,		88,		120,	56, | 				8,		88,		120,	56, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t ntsc_chrominances[16] = { | 			const uint8_t ntsc_chrominances[16] = { | ||||||
| 				255,	255,	40,		104, | 				255,	255,	8,		72, | ||||||
| 				64,		120,	80,		16, | 				32,		88,		48,		112, | ||||||
| 				32,		32,		40,		104, | 				0,		0,		8,		72, | ||||||
| 				64,		120,	80,		16, | 				32,		88,		48,		112, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t *chrominances; | 			const uint8_t *chrominances; | ||||||
| 			Outputs::CRT::DisplayType display_type; | 			Outputs::CRT::DisplayType display_type; | ||||||
| @@ -149,10 +150,11 @@ template <class T> class MOS6560 { | |||||||
| 		/*! | 		/*! | ||||||
| 			Runs for cycles. Derr. | 			Runs for cycles. Derr. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline void run_for_cycles(unsigned int number_of_cycles) { | 		inline void run_for(const Cycles cycles) { | ||||||
| 			// keep track of the amount of time since the speaker was updated; lazy updates are applied | 			// keep track of the amount of time since the speaker was updated; lazy updates are applied | ||||||
| 			cycles_since_speaker_update_ += number_of_cycles; | 			cycles_since_speaker_update_ += cycles; | ||||||
|  |  | ||||||
|  | 			int number_of_cycles = cycles.as_int(); | ||||||
| 			while(number_of_cycles--) { | 			while(number_of_cycles--) { | ||||||
| 				// keep an old copy of the vertical count because that test is a cycle later than the actual changes | 				// keep an old copy of the vertical count because that test is a cycle later than the actual changes | ||||||
| 				int previous_vertical_counter = vertical_counter_; | 				int previous_vertical_counter = vertical_counter_; | ||||||
| @@ -259,7 +261,7 @@ template <class T> class MOS6560 { | |||||||
| 				if(this_state_ != output_state_) { | 				if(this_state_ != output_state_) { | ||||||
| 					switch(output_state_) { | 					switch(output_state_) { | ||||||
| 						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break; | 						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break; | ||||||
| 						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0);	break; | 						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0);		break; | ||||||
| 						case State::Border:			output_border(cycles_in_state_ * 4);															break; | 						case State::Border:			output_border(cycles_in_state_ * 4);															break; | ||||||
| 						case State::Pixels:			crt_->output_data(cycles_in_state_ * 4, 1);														break; | 						case State::Pixels:			crt_->output_data(cycles_in_state_ * 4, 1);														break; | ||||||
| 					} | 					} | ||||||
| @@ -323,7 +325,7 @@ template <class T> class MOS6560 { | |||||||
| 		/*! | 		/*! | ||||||
| 			Causes the 6560 to flush as much pending CRT and speaker communications as possible. | 			Causes the 6560 to flush as much pending CRT and speaker communications as possible. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline void synchronise() { update_audio(); speaker_->flush(); } | 		inline void flush() { update_audio(); speaker_->flush(); } | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Writes to a 6560 register. | 			Writes to a 6560 register. | ||||||
| @@ -406,10 +408,9 @@ template <class T> class MOS6560 { | |||||||
| 		std::shared_ptr<Outputs::CRT::CRT> crt_; | 		std::shared_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
| 		std::shared_ptr<Speaker> speaker_; | 		std::shared_ptr<Speaker> speaker_; | ||||||
| 		unsigned int cycles_since_speaker_update_; | 		Cycles cycles_since_speaker_update_; | ||||||
| 		void update_audio() { | 		void update_audio() { | ||||||
| 			speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2); | 			speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); | ||||||
| 			cycles_since_speaker_update_ &= 3; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// register state | 		// register state | ||||||
|   | |||||||
							
								
								
									
										226
									
								
								Components/6845/CRTC6845.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								Components/6845/CRTC6845.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | |||||||
|  | // | ||||||
|  | //  CRTC6845.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 31/07/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef CRTC6845_hpp | ||||||
|  | #define CRTC6845_hpp | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <cstdio> | ||||||
|  |  | ||||||
|  | namespace Motorola { | ||||||
|  | namespace CRTC { | ||||||
|  |  | ||||||
|  | struct BusState { | ||||||
|  | 	bool display_enable; | ||||||
|  | 	bool hsync; | ||||||
|  | 	bool vsync; | ||||||
|  | 	bool cursor; | ||||||
|  | 	uint16_t refresh_address; | ||||||
|  | 	uint16_t row_address; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class BusHandler { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that | ||||||
|  | 			systems using the 6845 respect the bus state and produce pixels, sync or whatever they require. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_bus_cycle_phase1(const BusState &) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated | ||||||
|  | 			directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore | ||||||
|  | 			implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without | ||||||
|  | 			having to wait until the next cycle has begun. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_bus_cycle_phase2(const BusState &) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum Personality { | ||||||
|  | 	HD6845S,	// | ||||||
|  | 	UM6845R,	// | ||||||
|  | 	MC6845,		// | ||||||
|  | 	AMS40226	// | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template <class T> class CRTC6845 { | ||||||
|  | 	public: | ||||||
|  |  | ||||||
|  | 		CRTC6845(Personality p, T &bus_handler) noexcept : | ||||||
|  | 			personality_(p), bus_handler_(bus_handler) {} | ||||||
|  |  | ||||||
|  | 		void select_register(uint8_t r) { | ||||||
|  | 			selected_register_ = r; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t get_status() const { | ||||||
|  | 			return 0xff; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t get_register() const { | ||||||
|  | 			if(selected_register_ < 12 || selected_register_ > 17) return 0xff; | ||||||
|  | 			return registers_[selected_register_]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_register(uint8_t value) { | ||||||
|  | 			static uint8_t masks[] = { | ||||||
|  | 				0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, | ||||||
|  | 				0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			if(selected_register_ < 16) { | ||||||
|  | 				registers_[selected_register_] = value & masks[selected_register_]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void trigger_light_pen() { | ||||||
|  | 			registers_[17] = bus_state_.refresh_address & 0xff; | ||||||
|  | 			registers_[16] = bus_state_.refresh_address >> 8; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(Cycles cycles) { | ||||||
|  | 			int cyles_remaining = cycles.as_int(); | ||||||
|  | 			while(cyles_remaining--) { | ||||||
|  | 				// check for end of visible characters | ||||||
|  | 				if(character_counter_ == registers_[1]) { | ||||||
|  | 					// TODO: consider skew in character_is_visible_. Or maybe defer until perform_bus_cycle? | ||||||
|  | 					character_is_visible_ = false; | ||||||
|  | 					end_of_line_address_ = bus_state_.refresh_address; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				perform_bus_cycle_phase1(); | ||||||
|  | 				bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff; | ||||||
|  |  | ||||||
|  | 				// check for end-of-line | ||||||
|  | 				if(character_counter_ == registers_[0]) { | ||||||
|  | 					character_counter_ = 0; | ||||||
|  | 					do_end_of_line(); | ||||||
|  | 					character_is_visible_ = true; | ||||||
|  | 				} else { | ||||||
|  | 					// increment counter | ||||||
|  | 					character_counter_++; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// check for start of horizontal sync | ||||||
|  | 				if(character_counter_ == registers_[2]) { | ||||||
|  | 					hsync_counter_ = 0; | ||||||
|  | 					bus_state_.hsync = true; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// check for end of horizontal sync; note that a sync time of zero will result in an immediate | ||||||
|  | 				// cancellation of the plan to perform sync | ||||||
|  | 				if(bus_state_.hsync) { | ||||||
|  | 					bus_state_.hsync = hsync_counter_ != (registers_[3] & 15); | ||||||
|  | 					hsync_counter_ = (hsync_counter_ + 1) & 15; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				perform_bus_cycle_phase2(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const BusState &get_bus_state() const { | ||||||
|  | 			return bus_state_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		inline void perform_bus_cycle_phase1() { | ||||||
|  | 			bus_state_.display_enable = character_is_visible_ && line_is_visible_; | ||||||
|  | 			bus_handler_.perform_bus_cycle_phase1(bus_state_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void perform_bus_cycle_phase2() { | ||||||
|  | 			bus_state_.display_enable = character_is_visible_ && line_is_visible_; | ||||||
|  | 			bus_handler_.perform_bus_cycle_phase2(bus_state_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void do_end_of_line() { | ||||||
|  | 			// check for end of vertical sync | ||||||
|  | 			if(bus_state_.vsync) { | ||||||
|  | 				vsync_counter_ = (vsync_counter_ + 1) & 15; | ||||||
|  | 				if(vsync_counter_ == (registers_[3] >> 4)) { | ||||||
|  | 					bus_state_.vsync = false; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(is_in_adjustment_period_) { | ||||||
|  | 				line_counter_++; | ||||||
|  | 				if(line_counter_ == registers_[5]) { | ||||||
|  | 					is_in_adjustment_period_ = false; | ||||||
|  | 					do_end_of_frame(); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// advance vertical counter | ||||||
|  | 				if(bus_state_.row_address == registers_[9]) { | ||||||
|  | 					bus_state_.row_address = 0; | ||||||
|  | 					line_address_ = end_of_line_address_; | ||||||
|  |  | ||||||
|  | 					// check for entry into the overflow area | ||||||
|  | 					if(line_counter_ == registers_[4]) { | ||||||
|  | 						if(registers_[5]) { | ||||||
|  | 							line_counter_ = 0; | ||||||
|  | 							is_in_adjustment_period_ = true; | ||||||
|  | 						} else { | ||||||
|  | 							do_end_of_frame(); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						line_counter_ = (line_counter_ + 1) & 0x7f; | ||||||
|  |  | ||||||
|  | 						// check for start of vertical sync | ||||||
|  | 						if(line_counter_ == registers_[7]) { | ||||||
|  | 							bus_state_.vsync = true; | ||||||
|  | 							vsync_counter_ = 0; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// check for end of visible lines | ||||||
|  | 						if(line_counter_ == registers_[6]) { | ||||||
|  | 							line_is_visible_ = false; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bus_state_.refresh_address = line_address_; | ||||||
|  | 			character_counter_ = 0; | ||||||
|  | 			character_is_visible_ = (registers_[1] != 0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void do_end_of_frame() { | ||||||
|  | 			line_counter_ = 0; | ||||||
|  | 			line_is_visible_ = true; | ||||||
|  | 			line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]); | ||||||
|  | 			bus_state_.refresh_address = line_address_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Personality personality_; | ||||||
|  | 		T &bus_handler_; | ||||||
|  | 		BusState bus_state_; | ||||||
|  |  | ||||||
|  | 		uint8_t registers_[18]; | ||||||
|  | 		int selected_register_; | ||||||
|  |  | ||||||
|  | 		uint8_t character_counter_; | ||||||
|  | 		uint8_t line_counter_; | ||||||
|  |  | ||||||
|  | 		bool character_is_visible_, line_is_visible_; | ||||||
|  |  | ||||||
|  | 		int hsync_counter_; | ||||||
|  | 		int vsync_counter_; | ||||||
|  | 		bool is_in_adjustment_period_; | ||||||
|  |  | ||||||
|  | 		uint16_t line_address_; | ||||||
|  | 		uint16_t end_of_line_address_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* CRTC6845_hpp */ | ||||||
							
								
								
									
										92
									
								
								Components/8255/i8255.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Components/8255/i8255.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | // | ||||||
|  | //  i8255.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 01/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef i8255_hpp | ||||||
|  | #define i8255_hpp | ||||||
|  |  | ||||||
|  | namespace Intel { | ||||||
|  | namespace i8255 { | ||||||
|  |  | ||||||
|  | class PortHandler { | ||||||
|  | 	public: | ||||||
|  | 		void set_value(int port, uint8_t value) {} | ||||||
|  | 		uint8_t get_value(int port) { return 0xff; } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // TODO: Modes 1 and 2. | ||||||
|  | template <class T> class i8255 { | ||||||
|  | 	public: | ||||||
|  | 		i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Stores the value @c value to the register at @c address. If this causes a change in 8255 output | ||||||
|  | 			then the PortHandler will be informed. | ||||||
|  | 		*/ | ||||||
|  | 		void set_register(int address, uint8_t value) { | ||||||
|  | 			switch(address & 3) { | ||||||
|  | 				case 0: | ||||||
|  | 					if(!(control_ & 0x10)) { | ||||||
|  | 						// TODO: so what would output be when switching from input to output mode? | ||||||
|  | 						outputs_[0] = value; port_handler_.set_value(0, value); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 1: | ||||||
|  | 					if(!(control_ & 0x02)) { | ||||||
|  | 						outputs_[1] = value; port_handler_.set_value(1, value); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case 2:	outputs_[2] = value; port_handler_.set_value(2, value);	break; | ||||||
|  | 				case 3: | ||||||
|  | 					if(value & 0x80) { | ||||||
|  | 						control_ = value; | ||||||
|  | 					} else { | ||||||
|  | 						if(value & 1) { | ||||||
|  | 							outputs_[2] |= 1 << ((value >> 1)&7); | ||||||
|  | 						} else { | ||||||
|  | 							outputs_[2] &= ~(1 << ((value >> 1)&7)); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					update_outputs(); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Obtains the current value for the register at @c address. If this provides a reading | ||||||
|  | 			of input then the PortHandler will be queried. | ||||||
|  | 		*/ | ||||||
|  | 		uint8_t get_register(int address) { | ||||||
|  | 			switch(address & 3) { | ||||||
|  | 				case 0:	return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0]; | ||||||
|  | 				case 1:	return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1]; | ||||||
|  | 				case 2:	{ | ||||||
|  | 					if(!(control_ & 0x09)) return outputs_[2]; | ||||||
|  | 					uint8_t input = port_handler_.get_value(2); | ||||||
|  | 					return ((control_ & 0x01) ? (input & 0x0f) : (outputs_[2] & 0x0f)) | ((control_ & 0x08) ? (input & 0xf0) : (outputs_[2] & 0xf0)); | ||||||
|  | 				} | ||||||
|  | 				case 3:	return control_; | ||||||
|  | 			} | ||||||
|  | 			return 0xff; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void update_outputs() { | ||||||
|  | 			port_handler_.set_value(0, outputs_[0]); | ||||||
|  | 			port_handler_.set_value(1, outputs_[1]); | ||||||
|  | 			port_handler_.set_value(2, outputs_[2]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t control_; | ||||||
|  | 		uint8_t outputs_[3]; | ||||||
|  | 		T &port_handler_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* i8255_hpp */ | ||||||
							
								
								
									
										872
									
								
								Components/8272/i8272.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										872
									
								
								Components/8272/i8272.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,872 @@ | |||||||
|  | // | ||||||
|  | //  i8272.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 05/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "i8272.hpp" | ||||||
|  | #include "../../Storage/Disk/Encodings/MFM.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdio> | ||||||
|  |  | ||||||
|  | using namespace Intel::i8272; | ||||||
|  |  | ||||||
|  | #define SetDataRequest()				(main_status_ |= 0x80) | ||||||
|  | #define ResetDataRequest()				(main_status_ &= ~0x80) | ||||||
|  | #define DataRequest()					(main_status_ & 0x80) | ||||||
|  |  | ||||||
|  | #define SetDataDirectionToProcessor()	(main_status_ |= 0x40) | ||||||
|  | #define SetDataDirectionFromProcessor()	(main_status_ &= ~0x40) | ||||||
|  | #define DataDirectionToProcessor()		(main_status_ & 0x40) | ||||||
|  |  | ||||||
|  | #define SetNonDMAExecution()			(main_status_ |= 0x20) | ||||||
|  | #define ResetNonDMAExecution()			(main_status_ &= ~0x20) | ||||||
|  |  | ||||||
|  | #define SetBusy()						(main_status_ |= 0x10) | ||||||
|  | #define ResetBusy()						(main_status_ &= ~0x10) | ||||||
|  | #define Busy()							(main_status_ & 0x10) | ||||||
|  |  | ||||||
|  | #define SetAbnormalTermination()		(status_[0] |= 0x40) | ||||||
|  | #define SetInvalidCommand()				(status_[0] |= 0x80) | ||||||
|  | #define SetReadyChanged()				(status_[0] |= 0xc0) | ||||||
|  | #define SetSeekEnd()					(status_[0] |= 0x20) | ||||||
|  | #define SetEquipmentCheck()				(status_[0] |= 0x10) | ||||||
|  | #define SetNotReady()					(status_[0] |= 0x08) | ||||||
|  |  | ||||||
|  | #define SetEndOfCylinder()				(status_[1] |= 0x80) | ||||||
|  | #define SetDataError()					(status_[1] |= 0x20) | ||||||
|  | #define SetOverrun()					(status_[1] |= 0x10) | ||||||
|  | #define SetNoData()						(status_[1] |= 0x04) | ||||||
|  | #define SetNotWriteable()				(status_[1] |= 0x02) | ||||||
|  | #define SetMissingAddressMark()			(status_[1] |= 0x01) | ||||||
|  |  | ||||||
|  | #define SetControlMark()				(status_[2] |= 0x40) | ||||||
|  | #define ControlMark()					(status_[2] & 0x40) | ||||||
|  |  | ||||||
|  | #define SetDataFieldDataError()			(status_[2] |= 0x20) | ||||||
|  | #define SetWrongCyinder()				(status_[2] |= 0x10) | ||||||
|  | #define SetScanEqualHit()				(status_[2] |= 0x08) | ||||||
|  | #define SetScanNotSatisfied()			(status_[2] |= 0x04) | ||||||
|  | #define SetBadCylinder()				(status_[2] |= 0x02) | ||||||
|  | #define SetMissingDataAddressMark()		(status_[2] |= 0x01) | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  | 	const uint8_t CommandReadData = 0x06; | ||||||
|  | 	const uint8_t CommandReadDeletedData = 0x0c; | ||||||
|  |  | ||||||
|  | 	const uint8_t CommandWriteData = 0x05; | ||||||
|  | 	const uint8_t CommandWriteDeletedData = 0x09; | ||||||
|  |  | ||||||
|  | 	const uint8_t CommandReadTrack = 0x02; | ||||||
|  | 	const uint8_t CommandReadID = 0x0a; | ||||||
|  | 	const uint8_t CommandFormatTrack = 0x0d; | ||||||
|  |  | ||||||
|  | 	const uint8_t CommandScanLow = 0x11; | ||||||
|  | 	const uint8_t CommandScanLowOrEqual = 0x19; | ||||||
|  | 	const uint8_t CommandScanHighOrEqual = 0x1d; | ||||||
|  |  | ||||||
|  | 	const uint8_t CommandRecalibrate = 0x07; | ||||||
|  | 	const uint8_t CommandSeek = 0x0f; | ||||||
|  |  | ||||||
|  | 	const uint8_t CommandSenseInterruptStatus = 0x08; | ||||||
|  | 	const uint8_t CommandSpecify = 0x03; | ||||||
|  | 	const uint8_t CommandSenseDriveStatus = 0x04; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : | ||||||
|  | 	Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), | ||||||
|  | 	bus_handler_(bus_handler), | ||||||
|  | 	main_status_(0), | ||||||
|  | 	interesting_event_mask_((int)Event8272::CommandByte), | ||||||
|  | 	resume_point_(0), | ||||||
|  | 	delay_time_(0), | ||||||
|  | 	head_timers_running_(0), | ||||||
|  | 	expects_input_(false), | ||||||
|  | 	drives_seeking_(0) { | ||||||
|  | 	posit_event((int)Event8272::CommandByte); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool i8272::is_sleeping() { | ||||||
|  | 	return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void i8272::run_for(Cycles cycles) { | ||||||
|  | 	Storage::Disk::MFMController::run_for(cycles); | ||||||
|  |  | ||||||
|  | 	if(is_sleeping_) return; | ||||||
|  |  | ||||||
|  | 	// check for an expired timer | ||||||
|  | 	if(delay_time_ > 0) { | ||||||
|  | 		if(cycles.as_int() >= delay_time_) { | ||||||
|  | 			delay_time_ = 0; | ||||||
|  | 			posit_event((int)Event8272::Timer); | ||||||
|  | 		} else { | ||||||
|  | 			delay_time_ -= cycles.as_int(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// update seek status of any drives presently seeking | ||||||
|  | 	if(drives_seeking_) { | ||||||
|  | 		int drives_left = drives_seeking_; | ||||||
|  | 		for(int c = 0; c < 4; c++) { | ||||||
|  | 			if(drives_[c].phase == Drive::Seeking) { | ||||||
|  | 				drives_[c].step_rate_counter += cycles.as_int(); | ||||||
|  | 				int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_); | ||||||
|  | 				drives_[c].step_rate_counter %= (8000 * step_rate_time_); | ||||||
|  | 				while(steps--) { | ||||||
|  | 					// Perform a step. | ||||||
|  | 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | ||||||
|  | 					printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); | ||||||
|  | 					drives_[c].drive->step(direction); | ||||||
|  | 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | ||||||
|  |  | ||||||
|  | 					// Check for completion. | ||||||
|  | 					if(drives_[c].seek_is_satisfied()) { | ||||||
|  | 						drives_[c].phase = Drive::CompletedSeeking; | ||||||
|  | 						drives_seeking_--; | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				drives_left--; | ||||||
|  | 				if(!drives_left) break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// check for any head unloads | ||||||
|  | 	if(head_timers_running_) { | ||||||
|  | 		int timers_left = head_timers_running_; | ||||||
|  | 		for(int c = 0; c < 8; c++) { | ||||||
|  | 			int drive = (c >> 1); | ||||||
|  | 			int head = c&1; | ||||||
|  |  | ||||||
|  | 			if(drives_[drive].head_unload_delay[head] > 0) { | ||||||
|  | 				if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) { | ||||||
|  | 					drives_[drive].head_unload_delay[head] = 0; | ||||||
|  | 					drives_[drive].head_is_loaded[head] = false; | ||||||
|  | 					head_timers_running_--; | ||||||
|  | 				} else { | ||||||
|  | 					drives_[drive].head_unload_delay[head] -= cycles.as_int(); | ||||||
|  | 				} | ||||||
|  | 				timers_left--; | ||||||
|  | 				if(!timers_left) break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | ||||||
|  | 	if(is_sleeping_) update_sleep_observer(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void i8272::set_register(int address, uint8_t value) { | ||||||
|  | 	// don't consider attempted sets to the status register | ||||||
|  | 	if(!address) return; | ||||||
|  |  | ||||||
|  | 	// if not ready for commands, do nothing | ||||||
|  | 	if(!DataRequest() || DataDirectionToProcessor()) return; | ||||||
|  |  | ||||||
|  | 	if(expects_input_) { | ||||||
|  | 		input_ = value; | ||||||
|  | 		has_input_ = true; | ||||||
|  | 		ResetDataRequest(); | ||||||
|  | 	} else { | ||||||
|  | 		// accumulate latest byte in the command byte sequence | ||||||
|  | 		command_.push_back(value); | ||||||
|  | 		posit_event((int)Event8272::CommandByte); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t i8272::get_register(int address) { | ||||||
|  | 	if(address) { | ||||||
|  | 		if(result_stack_.empty()) return 0xff; | ||||||
|  | 		uint8_t result = result_stack_.back(); | ||||||
|  | 		result_stack_.pop_back(); | ||||||
|  | 		if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty); | ||||||
|  |  | ||||||
|  | 		return result; | ||||||
|  | 	} else { | ||||||
|  | 		return main_status_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) { | ||||||
|  | 	if(drive < 4 && drive >= 0) { | ||||||
|  | 		drives_[drive].drive->set_disk(disk); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #define BEGIN_SECTION()	switch(resume_point_) { default: | ||||||
|  | #define END_SECTION()	} | ||||||
|  |  | ||||||
|  | #define MS_TO_CYCLES(x)			x * 8000 | ||||||
|  | #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_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; | ||||||
|  |  | ||||||
|  | #define PASTE(x, y) x##y | ||||||
|  | #define CONCAT(x, y) PASTE(x, y) | ||||||
|  |  | ||||||
|  | #define FIND_HEADER()	\ | ||||||
|  | 	set_data_mode(DataMode::Scanning);	\ | ||||||
|  | 	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \ | ||||||
|  | 	if(event_type == (int)Event::IndexHole) { index_hole_limit_--; }	\ | ||||||
|  | 	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\ | ||||||
|  | 	\ | ||||||
|  | 	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\ | ||||||
|  | 	CONCAT(header_found, __LINE__):	0;\ | ||||||
|  |  | ||||||
|  | #define FIND_DATA()	\ | ||||||
|  | 	set_data_mode(DataMode::Scanning);	\ | ||||||
|  | 	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \ | ||||||
|  | 	if(event_type == (int)Event::Token) { \ | ||||||
|  | 		if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #define READ_HEADER()	\ | ||||||
|  | 	distance_into_section_ = 0;	\ | ||||||
|  | 	set_data_mode(DataMode::Reading);	\ | ||||||
|  | 	CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \ | ||||||
|  | 	header_[distance_into_section_] = get_latest_token().byte_value;	\ | ||||||
|  | 	distance_into_section_++; \ | ||||||
|  | 	if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__);	\ | ||||||
|  |  | ||||||
|  | #define SET_DRIVE_HEAD_MFM()	\ | ||||||
|  | 	active_drive_ = command_[1]&3;	\ | ||||||
|  | 	active_head_ = (command_[1] >> 2)&1;	\ | ||||||
|  | 	set_drive(drives_[active_drive_].drive);	\ | ||||||
|  | 	drives_[active_drive_].drive->set_head((unsigned int)active_head_);	\ | ||||||
|  | 	set_is_double_density(command_[0] & 0x40);	\ | ||||||
|  | 	invalidate_track(); | ||||||
|  |  | ||||||
|  | #define WAIT_FOR_BYTES(n) \ | ||||||
|  | 	distance_into_section_ = 0;	\ | ||||||
|  | 	CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token);	\ | ||||||
|  | 	if(get_latest_token().type == Token::Byte) distance_into_section_++;	\ | ||||||
|  | 	if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__); | ||||||
|  |  | ||||||
|  | #define LOAD_HEAD()	\ | ||||||
|  | 	if(!drives_[active_drive_].head_is_loaded[active_head_]) {	\ | ||||||
|  | 		drives_[active_drive_].head_is_loaded[active_head_] = true;	\ | ||||||
|  | 		WAIT_FOR_TIME(head_load_time_);	\ | ||||||
|  | 	} else {	\ | ||||||
|  | 		if(drives_[active_drive_].head_unload_delay[active_head_] > 0) {	\ | ||||||
|  | 			drives_[active_drive_].head_unload_delay[active_head_] = 0;	\ | ||||||
|  | 			head_timers_running_--;	\ | ||||||
|  | 		}	\ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #define SCHEDULE_HEAD_UNLOAD()	\ | ||||||
|  | 	if(drives_[active_drive_].head_is_loaded[active_head_]) {\ | ||||||
|  | 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | ||||||
|  | 			head_timers_running_++;	\ | ||||||
|  | 			is_sleeping_ = false;	\ | ||||||
|  | 			update_sleep_observer();	\ | ||||||
|  | 		}	\ | ||||||
|  | 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | void i8272::posit_event(int event_type) { | ||||||
|  | 	if(event_type == (int)Event::IndexHole) index_hole_count_++; | ||||||
|  | 	if(!(interesting_event_mask_ & event_type)) return; | ||||||
|  | 	interesting_event_mask_ &= ~event_type; | ||||||
|  |  | ||||||
|  | 	BEGIN_SECTION(); | ||||||
|  |  | ||||||
|  | 	// Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows | ||||||
|  | 	// into wait_for_complete_command_sequence. | ||||||
|  | 	wait_for_command: | ||||||
|  | 			expects_input_ = false; | ||||||
|  | 			set_data_mode(Storage::Disk::MFMController::DataMode::Scanning); | ||||||
|  | 			ResetBusy(); | ||||||
|  | 			ResetNonDMAExecution(); | ||||||
|  | 			command_.clear(); | ||||||
|  |  | ||||||
|  | 	// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes | ||||||
|  | 	// until it has a quantity that make up an entire command, then resets the data request bit and | ||||||
|  | 	// branches to that command. | ||||||
|  | 	wait_for_complete_command_sequence: | ||||||
|  | 			SetDataRequest(); | ||||||
|  | 			SetDataDirectionFromProcessor(); | ||||||
|  | 			WAIT_FOR_EVENT(Event8272::CommandByte) | ||||||
|  | 			SetBusy(); | ||||||
|  |  | ||||||
|  | 			static const size_t required_lengths[32] = { | ||||||
|  | 				0,	0,	9,	3,	2,	9,	9,	2, | ||||||
|  | 				1,	9,	2,	0,	9,	6,	0,	3, | ||||||
|  | 				0,	9,	0,	0,	0,	0,	0,	0, | ||||||
|  | 				0,	9,	0,	0,	0,	9,	0,	0, | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			if(command_.size() < required_lengths[command_[0] & 0x1f]) goto wait_for_complete_command_sequence; | ||||||
|  | 			if(command_.size() == 9) { | ||||||
|  | 				cylinder_ = command_[2]; | ||||||
|  | 				head_ = command_[3]; | ||||||
|  | 				sector_ = command_[4]; | ||||||
|  | 				size_ = command_[5]; | ||||||
|  | 			} | ||||||
|  | 			ResetDataRequest(); | ||||||
|  | 			status_[0] = status_[1] = status_[2] = 0; | ||||||
|  |  | ||||||
|  | 			// If this is not clearly a command that's safe to carry out in parallel to a seek, end all seeks. | ||||||
|  | 			switch(command_[0] & 0x1f) { | ||||||
|  | 				case CommandReadData: | ||||||
|  | 				case CommandReadDeletedData: | ||||||
|  | 				case CommandWriteData: | ||||||
|  | 				case CommandWriteDeletedData: | ||||||
|  | 				case CommandReadTrack: | ||||||
|  | 				case CommandReadID: | ||||||
|  | 				case CommandFormatTrack: | ||||||
|  | 				case CommandScanLow: | ||||||
|  | 				case CommandScanLowOrEqual: | ||||||
|  | 				case CommandScanHighOrEqual: | ||||||
|  | 					is_access_command_ = true; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				default: | ||||||
|  | 					is_access_command_ = false; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(is_access_command_) { | ||||||
|  | 				for(int c = 0; c < 4; c++) { | ||||||
|  | 					if(drives_[c].phase == Drive::Seeking) { | ||||||
|  | 						drives_[c].phase = Drive::NotSeeking; | ||||||
|  | 						drives_seeking_--; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				// Establishes the drive and head being addressed, and whether in double density mode; populates the internal | ||||||
|  | 				// cylinder, head, sector and size registers from the command stream. | ||||||
|  | 				if(!dma_mode_) SetNonDMAExecution(); | ||||||
|  | 				SET_DRIVE_HEAD_MFM(); | ||||||
|  | 				LOAD_HEAD(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Jump to the proper place. | ||||||
|  | 			switch(command_[0] & 0x1f) { | ||||||
|  | 				case CommandReadData: | ||||||
|  | 				case CommandReadDeletedData: | ||||||
|  | 					goto read_data; | ||||||
|  |  | ||||||
|  | 				case CommandWriteData: | ||||||
|  | 				case CommandWriteDeletedData: | ||||||
|  | 					goto write_data; | ||||||
|  |  | ||||||
|  | 				case CommandReadTrack:				goto read_track; | ||||||
|  | 				case CommandReadID:					goto read_id; | ||||||
|  | 				case CommandFormatTrack:			goto format_track; | ||||||
|  |  | ||||||
|  | 				case CommandScanLow:				goto scan_low; | ||||||
|  | 				case CommandScanLowOrEqual:			goto scan_low_or_equal; | ||||||
|  | 				case CommandScanHighOrEqual:		goto scan_high_or_equal; | ||||||
|  |  | ||||||
|  | 				case CommandRecalibrate:			goto recalibrate; | ||||||
|  | 				case CommandSeek:					goto seek; | ||||||
|  |  | ||||||
|  | 				case CommandSenseInterruptStatus:	goto sense_interrupt_status; | ||||||
|  | 				case CommandSpecify:				goto specify; | ||||||
|  | 				case CommandSenseDriveStatus:		goto sense_drive_status; | ||||||
|  |  | ||||||
|  | 				default:							goto invalid; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 	// Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers, | ||||||
|  | 	// and searches for a sector that meets those criteria. If one is found, inspects the instruction in use and | ||||||
|  | 	// jumps to an appropriate handler. | ||||||
|  | 	read_write_find_header: | ||||||
|  |  | ||||||
|  | 		// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until | ||||||
|  | 		// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the | ||||||
|  | 		// values in the internal registers. | ||||||
|  | 			index_hole_limit_ = 2; | ||||||
|  | //			printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_); | ||||||
|  | 		find_next_sector: | ||||||
|  | 			FIND_HEADER(); | ||||||
|  | 			if(!index_hole_limit_) { | ||||||
|  | 				// Two index holes have passed wihout finding the header sought. | ||||||
|  | //				printf("Not found\n"); | ||||||
|  | 				SetNoData(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  | 			index_hole_count_ = 0; | ||||||
|  | //			printf("Header\n"); | ||||||
|  | 			READ_HEADER(); | ||||||
|  | 			if(index_hole_count_) { | ||||||
|  | 				// This implies an index hole was sighted within the header. Error out. | ||||||
|  | 				SetEndOfCylinder(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  | 			if(get_crc_generator().get_value()) { | ||||||
|  | 				// This implies a CRC error in the header; mark as such but continue. | ||||||
|  | 				SetDataError(); | ||||||
|  | 			} | ||||||
|  | //			printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); | ||||||
|  | 			if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector; | ||||||
|  |  | ||||||
|  | 			// Branch to whatever is supposed to happen next | ||||||
|  | //			printf("Proceeding\n"); | ||||||
|  | 			switch(command_[0] & 0x1f) { | ||||||
|  | 				case CommandReadData: | ||||||
|  | 				case CommandReadDeletedData: | ||||||
|  | 				goto read_data_found_header; | ||||||
|  |  | ||||||
|  | 				case CommandWriteData:	// write data | ||||||
|  | 				case CommandWriteDeletedData:	// write deleted data | ||||||
|  | 				goto write_data_found_header; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	// Performs the read data or read deleted data command. | ||||||
|  | 	read_data: | ||||||
|  | 			printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | ||||||
|  | 		read_next_data: | ||||||
|  | 			goto read_write_find_header; | ||||||
|  |  | ||||||
|  | 		// Finds the next data block and sets data mode to reading, setting an error flag if the on-disk deleted | ||||||
|  | 		// flag doesn't match the sort the command was looking for. | ||||||
|  | 		read_data_found_header: | ||||||
|  | 			FIND_DATA(); | ||||||
|  | 			if(event_type == (int)Event::Token) { | ||||||
|  | 				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. | ||||||
|  | 					SetMissingAddressMark(); | ||||||
|  | 					SetMissingDataAddressMark(); | ||||||
|  | 					goto abort;	// TODO: or read_next_data? | ||||||
|  | 				} else { | ||||||
|  | 					if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) { | ||||||
|  | 						if(!(command_[0]&0x20)) { | ||||||
|  | 							// SK is not set; set the error flag but read this sector before finishing. | ||||||
|  | 							SetControlMark(); | ||||||
|  | 						} else { | ||||||
|  | 							// SK is set; skip this sector. | ||||||
|  | 							goto read_next_data; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// An index hole appeared before the data mark. | ||||||
|  | 				SetEndOfCylinder(); | ||||||
|  | 				goto abort;	// TODO: or read_next_data? | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			distance_into_section_ = 0; | ||||||
|  | 			set_data_mode(Reading); | ||||||
|  |  | ||||||
|  | 		// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting | ||||||
|  | 		// data request once the byte has been taken. Continues until all bytes have been read. | ||||||
|  | 		// | ||||||
|  | 		// TODO: consider DTL. | ||||||
|  | 		read_data_get_byte: | ||||||
|  | 			WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); | ||||||
|  | 			if(event_type == (int)Event::Token) { | ||||||
|  | 				result_stack_.push_back(get_latest_token().byte_value); | ||||||
|  | 				distance_into_section_++; | ||||||
|  | 				SetDataRequest(); | ||||||
|  | 				SetDataDirectionToProcessor(); | ||||||
|  | 				WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole); | ||||||
|  | 			} | ||||||
|  | 			switch(event_type) { | ||||||
|  | 				case (int)Event8272::ResultEmpty:	// The caller read the byte in time; proceed as normal. | ||||||
|  | 					ResetDataRequest(); | ||||||
|  | 					if(distance_into_section_ < (128 << size_)) goto read_data_get_byte; | ||||||
|  | 				break; | ||||||
|  | 				case (int)Event::Token:				// The caller hasn't read the old byte yet and a new one has arrived | ||||||
|  | 					SetOverrun(); | ||||||
|  | 					goto abort; | ||||||
|  | 				break; | ||||||
|  | 				case (int)Event::IndexHole: | ||||||
|  | 					SetEndOfCylinder(); | ||||||
|  | 					goto abort; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		// read CRC, without transferring it, then check it | ||||||
|  | 			WAIT_FOR_EVENT(Event::Token); | ||||||
|  | 			WAIT_FOR_EVENT(Event::Token); | ||||||
|  | 			if(get_crc_generator().get_value()) { | ||||||
|  | 				// This implies a CRC error in the sector; mark as such and temrinate. | ||||||
|  | 				SetDataError(); | ||||||
|  | 				SetDataFieldDataError(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		// check whether that's it: either the final requested sector has been read, or because | ||||||
|  | 		// a sector that was [/wasn't] marked as deleted when it shouldn't [/should] have been | ||||||
|  | 			if(sector_ != command_[6] && !ControlMark()) { | ||||||
|  | 				sector_++; | ||||||
|  | 				goto read_next_data; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N | ||||||
|  | 			goto post_st012chrn; | ||||||
|  |  | ||||||
|  | 	write_data: | ||||||
|  | 			printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | ||||||
|  |  | ||||||
|  | 			if(drives_[active_drive_].drive->get_is_read_only()) { | ||||||
|  | 				SetNotWriteable(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		write_next_data: | ||||||
|  | 			goto read_write_find_header; | ||||||
|  |  | ||||||
|  | 		write_data_found_header: | ||||||
|  | 			WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11); | ||||||
|  | 			begin_writing(true); | ||||||
|  |  | ||||||
|  | 			write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true); | ||||||
|  |  | ||||||
|  | 			SetDataDirectionFromProcessor(); | ||||||
|  | 			SetDataRequest(); | ||||||
|  | 			expects_input_ = true; | ||||||
|  | 			distance_into_section_ = 0; | ||||||
|  |  | ||||||
|  | 		write_loop: | ||||||
|  | 			WAIT_FOR_EVENT(Event::DataWritten); | ||||||
|  | 			if(!has_input_) { | ||||||
|  | 				SetOverrun(); | ||||||
|  | 				end_writing(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  | 			write_byte(input_); | ||||||
|  | 			has_input_ = false; | ||||||
|  | 			distance_into_section_++; | ||||||
|  | 			if(distance_into_section_ < (128 << size_)) { | ||||||
|  | 				SetDataRequest(); | ||||||
|  | 				goto write_loop; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			printf("Wrote %d bytes\n", distance_into_section_); | ||||||
|  | 			write_crc(); | ||||||
|  | 			expects_input_ = false; | ||||||
|  | 			WAIT_FOR_EVENT(Event::DataWritten); | ||||||
|  | 			end_writing(); | ||||||
|  |  | ||||||
|  | 			if(sector_ != command_[6]) { | ||||||
|  | 				sector_++; | ||||||
|  | 				goto write_next_data; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		goto post_st012chrn; | ||||||
|  |  | ||||||
|  | 	// Performs the read ID command. | ||||||
|  | 	read_id: | ||||||
|  | 		// Establishes the drive and head being addressed, and whether in double density mode. | ||||||
|  | 			printf("Read ID [%02x %02x]\n", command_[0], 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. | ||||||
|  | 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | ||||||
|  | 			index_hole_limit_ = 2; | ||||||
|  | 		read_id_find_next_sector: | ||||||
|  | 			FIND_HEADER(); | ||||||
|  | 			if(!index_hole_limit_) { | ||||||
|  | 				SetMissingAddressMark(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  | 			READ_HEADER(); | ||||||
|  |  | ||||||
|  | 		// Sets internal registers from the discovered header and posts the standard ST0, ST1, ST2, C, H, R, N. | ||||||
|  | 			cylinder_ = header_[0]; | ||||||
|  | 			head_ = header_[1]; | ||||||
|  | 			sector_ = header_[2]; | ||||||
|  | 			size_ = header_[3]; | ||||||
|  |  | ||||||
|  | 			goto post_st012chrn; | ||||||
|  |  | ||||||
|  | 	// Performs read track. | ||||||
|  | 	read_track: | ||||||
|  | 			printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]); | ||||||
|  |  | ||||||
|  | 			// Wait for the index hole. | ||||||
|  | 			WAIT_FOR_EVENT(Event::IndexHole); | ||||||
|  |  | ||||||
|  | 			sector_ = 0; | ||||||
|  | 			index_hole_limit_ = 2; | ||||||
|  |  | ||||||
|  | 		// While not index hole again, stream all sector contents until EOT sectors have been read. | ||||||
|  | 		read_track_next_sector: | ||||||
|  | 			FIND_HEADER(); | ||||||
|  | 			if(!index_hole_limit_) { | ||||||
|  | 				if(!sector_) { | ||||||
|  | 					SetMissingAddressMark(); | ||||||
|  | 					goto abort; | ||||||
|  | 				} else { | ||||||
|  | 					goto post_st012chrn; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			READ_HEADER(); | ||||||
|  |  | ||||||
|  | 			FIND_DATA(); | ||||||
|  | 			distance_into_section_ = 0; | ||||||
|  | 			SetDataDirectionToProcessor(); | ||||||
|  | 		read_track_get_byte: | ||||||
|  | 			WAIT_FOR_EVENT(Event::Token); | ||||||
|  | 			result_stack_.push_back(get_latest_token().byte_value); | ||||||
|  | 			distance_into_section_++; | ||||||
|  | 			SetDataRequest(); | ||||||
|  | 			// TODO: other possible exit conditions; find a way to merge with the read_data version of this. | ||||||
|  | 			WAIT_FOR_EVENT((int)Event8272::ResultEmpty); | ||||||
|  | 			ResetDataRequest(); | ||||||
|  | 			if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; | ||||||
|  |  | ||||||
|  | 			sector_++; | ||||||
|  | 			if(sector_ < command_[6]) goto read_track_next_sector; | ||||||
|  |  | ||||||
|  | 			goto post_st012chrn; | ||||||
|  |  | ||||||
|  | 	// Performs format [/write] track. | ||||||
|  | 	format_track: | ||||||
|  | 			printf("Format track\n"); | ||||||
|  | 			if(drives_[active_drive_].drive->get_is_read_only()) { | ||||||
|  | 				SetNotWriteable(); | ||||||
|  | 				goto abort; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Wait for the index hole. | ||||||
|  | 			WAIT_FOR_EVENT(Event::IndexHole); | ||||||
|  | 			index_hole_count_ = 0; | ||||||
|  | 			begin_writing(true); | ||||||
|  |  | ||||||
|  | 			// Write start-of-track. | ||||||
|  | 			write_start_of_track(); | ||||||
|  | 			WAIT_FOR_EVENT(Event::DataWritten); | ||||||
|  | 			sector_ = 0; | ||||||
|  |  | ||||||
|  | 		format_track_write_sector: | ||||||
|  | 			write_id_joiner(); | ||||||
|  |  | ||||||
|  | 			// Write the sector header, obtaining its contents | ||||||
|  | 			// from the processor. | ||||||
|  | 			SetDataDirectionFromProcessor(); | ||||||
|  | 			SetDataRequest(); | ||||||
|  | 			expects_input_ = true; | ||||||
|  | 			distance_into_section_ = 0; | ||||||
|  | 		format_track_write_header: | ||||||
|  | 			WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); | ||||||
|  | 			switch(event_type) { | ||||||
|  | 				case (int)Event::IndexHole: | ||||||
|  | 					SetOverrun(); | ||||||
|  | 					end_writing(); | ||||||
|  | 					goto abort; | ||||||
|  | 				break; | ||||||
|  | 				case (int)Event::DataWritten: | ||||||
|  | 					header_[distance_into_section_] = input_; | ||||||
|  | 					write_byte(input_); | ||||||
|  | 					has_input_ = false; | ||||||
|  | 					distance_into_section_++; | ||||||
|  | 					if(distance_into_section_ < 4) { | ||||||
|  | 						SetDataRequest(); | ||||||
|  | 						goto format_track_write_header; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); | ||||||
|  | 			write_crc(); | ||||||
|  |  | ||||||
|  | 			// Write the sector body. | ||||||
|  | 			write_id_data_joiner(false, false); | ||||||
|  | 			write_n_bytes(128 << command_[2], command_[5]); | ||||||
|  | 			write_crc(); | ||||||
|  |  | ||||||
|  | 			// Write the prescribed gap. | ||||||
|  | 			write_n_bytes(command_[4], get_is_double_density() ? 0x4e : 0xff); | ||||||
|  |  | ||||||
|  | 			// Consider repeating. | ||||||
|  | 			sector_++; | ||||||
|  | 			if(sector_ < command_[3] && !index_hole_count_) | ||||||
|  | 				goto format_track_write_sector; | ||||||
|  |  | ||||||
|  | 			// Otherwise, pad out to the index hole. | ||||||
|  | 		format_track_pad: | ||||||
|  | 			write_byte(get_is_double_density() ? 0x4e : 0xff); | ||||||
|  | 			WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); | ||||||
|  | 			if(event_type != (int)Event::IndexHole) goto format_track_pad; | ||||||
|  |  | ||||||
|  | 			end_writing(); | ||||||
|  |  | ||||||
|  | 			cylinder_ = header_[0]; | ||||||
|  | 			head_ = header_[1]; | ||||||
|  | 			sector_ = header_[2] + 1; | ||||||
|  | 			size_ = header_[3]; | ||||||
|  |  | ||||||
|  | 		goto post_st012chrn; | ||||||
|  |  | ||||||
|  | 	scan_low: | ||||||
|  | 		printf("Scan low unimplemented!!\n"); | ||||||
|  | 		goto wait_for_command; | ||||||
|  |  | ||||||
|  | 	scan_low_or_equal: | ||||||
|  | 		printf("Scan low or equal unimplemented!!\n"); | ||||||
|  | 		goto wait_for_command; | ||||||
|  |  | ||||||
|  | 	scan_high_or_equal: | ||||||
|  | 		printf("Scan high or equal unimplemented!!\n"); | ||||||
|  | 		goto wait_for_command; | ||||||
|  |  | ||||||
|  | 	// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work | ||||||
|  | 	// occurs in ::run_for; this merely establishes that seeking should be ongoing. | ||||||
|  | 	recalibrate: | ||||||
|  | 	seek: | ||||||
|  | 			{ | ||||||
|  | 				int drive = command_[1]&3; | ||||||
|  |  | ||||||
|  | 				// Increment the seeking count if this drive wasn't already seeking. | ||||||
|  | 				if(drives_[drive].phase != Drive::Seeking) { | ||||||
|  | 					drives_seeking_++; | ||||||
|  | 					is_sleeping_ = false; | ||||||
|  | 					update_sleep_observer(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | ||||||
|  | 				// in could damage your drive motor). | ||||||
|  | 				drives_[drive].phase = Drive::Seeking; | ||||||
|  | 				drives_[drive].step_rate_counter = 8000 * step_rate_time_; | ||||||
|  | 				drives_[drive].steps_taken = 0; | ||||||
|  | 				drives_[drive].seek_failed = false; | ||||||
|  | 				main_status_ |= 1 << (command_[1]&3); | ||||||
|  |  | ||||||
|  | 				// If this is a seek, set the processor-supplied target location; otherwise it is a recalibrate, | ||||||
|  | 				// which means resetting the current state now but aiming to hit '-1' (which the stepping code | ||||||
|  | 				// up in run_for understands to mean 'keep going until track 0 is active'). | ||||||
|  | 				if(command_.size() > 2) { | ||||||
|  | 					drives_[drive].target_head_position = command_[2]; | ||||||
|  | 					printf("Seek to %02x\n", command_[2]); | ||||||
|  | 				} else { | ||||||
|  | 					drives_[drive].target_head_position = -1; | ||||||
|  | 					drives_[drive].head_position = 0; | ||||||
|  | 					printf("Recalibrate\n"); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Check whether any steps are even needed; if not then mark as completed already. | ||||||
|  | 				if(drives_[drive].seek_is_satisfied()) { | ||||||
|  | 					drives_[drive].phase = Drive::CompletedSeeking; | ||||||
|  | 					drives_seeking_--; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			goto wait_for_command; | ||||||
|  |  | ||||||
|  | 	// Performs sense interrupt status. | ||||||
|  | 	sense_interrupt_status: | ||||||
|  | 			printf("Sense interrupt status\n"); | ||||||
|  | 			{ | ||||||
|  | 				// Find the first drive that is in the CompletedSeeking state. | ||||||
|  | 				int found_drive = -1; | ||||||
|  | 				for(int c = 0; c < 4; c++) { | ||||||
|  | 					if(drives_[c].phase == Drive::CompletedSeeking) { | ||||||
|  | 						found_drive = c; | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// If a drive was found, return its results. Otherwise return a single 0x80. | ||||||
|  | 				if(found_drive != -1) { | ||||||
|  | 					drives_[found_drive].phase = Drive::NotSeeking; | ||||||
|  | 					status_[0] = (uint8_t)found_drive; | ||||||
|  | 					main_status_ &= ~(1 << found_drive); | ||||||
|  | 					SetSeekEnd(); | ||||||
|  |  | ||||||
|  | 					result_stack_.push_back(drives_[found_drive].head_position); | ||||||
|  | 					result_stack_.push_back(status_[0]); | ||||||
|  | 				} else { | ||||||
|  | 					result_stack_.push_back(0x80); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			goto post_result; | ||||||
|  |  | ||||||
|  | 	// Performs specify. | ||||||
|  | 	specify: | ||||||
|  | 		// Just store the values, and terminate the command. | ||||||
|  | 			printf("Specify\n"); | ||||||
|  | 			step_rate_time_ = 16 - (command_[1] >> 4);			// i.e. 1 to 16ms | ||||||
|  | 			head_unload_time_ = (command_[1] & 0x0f) << 4;		// i.e. 16 to 240ms | ||||||
|  | 			head_load_time_ = command_[2] & ~1;					// i.e. 2 to 254 ms in increments of 2ms | ||||||
|  |  | ||||||
|  | 			if(!head_unload_time_) head_unload_time_ = 16; | ||||||
|  | 			if(!head_load_time_) head_load_time_ = 2; | ||||||
|  | 			dma_mode_ = !(command_[2] & 1); | ||||||
|  | 			goto wait_for_command; | ||||||
|  |  | ||||||
|  | 	sense_drive_status: | ||||||
|  | 			printf("Sense drive status\n"); | ||||||
|  | 			{ | ||||||
|  | 				int drive = command_[1] & 3; | ||||||
|  | 				result_stack_.push_back( | ||||||
|  | 					(command_[1] & 7) |	// drive and head number | ||||||
|  | 					0x08 |				// single sided | ||||||
|  | 					(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00)	| | ||||||
|  | 					(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00)		| | ||||||
|  | 					(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00) | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 			goto post_result; | ||||||
|  |  | ||||||
|  | 	// Performs any invalid command. | ||||||
|  | 	invalid: | ||||||
|  | 			// A no-op, but posts ST0 (but which ST0?) | ||||||
|  | 			result_stack_.push_back(0x80); | ||||||
|  | 			goto post_result; | ||||||
|  |  | ||||||
|  | 	// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase. | ||||||
|  | 	abort: | ||||||
|  | 		SetAbnormalTermination(); | ||||||
|  | 		goto post_st012chrn; | ||||||
|  |  | ||||||
|  | 	// Posts ST0, ST1, ST2, C, H, R and N as a result phase. | ||||||
|  | 	post_st012chrn: | ||||||
|  | 			SCHEDULE_HEAD_UNLOAD(); | ||||||
|  |  | ||||||
|  | 			result_stack_.push_back(size_); | ||||||
|  | 			result_stack_.push_back(sector_); | ||||||
|  | 			result_stack_.push_back(head_); | ||||||
|  | 			result_stack_.push_back(cylinder_); | ||||||
|  |  | ||||||
|  | 			result_stack_.push_back(status_[2]); | ||||||
|  | 			result_stack_.push_back(status_[1]); | ||||||
|  | 			result_stack_.push_back(status_[0]); | ||||||
|  |  | ||||||
|  | 			goto post_result; | ||||||
|  |  | ||||||
|  | 	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the | ||||||
|  | 	// last thing in it will be returned first. | ||||||
|  | 	post_result: | ||||||
|  | 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | ||||||
|  | 			for(size_t c = 0; c < result_stack_.size(); c++) { | ||||||
|  | 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | ||||||
|  | 			} | ||||||
|  | 			printf("\n"); | ||||||
|  |  | ||||||
|  | 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | ||||||
|  | 			ResetNonDMAExecution(); | ||||||
|  | 			SetDataRequest(); | ||||||
|  | 			SetDataDirectionToProcessor(); | ||||||
|  |  | ||||||
|  | 			// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait | ||||||
|  | 			// until the processor has read all result bytes. | ||||||
|  | 			WAIT_FOR_EVENT(Event8272::ResultEmpty); | ||||||
|  |  | ||||||
|  | 			// Reset data direction and end the command. | ||||||
|  | 			goto wait_for_command; | ||||||
|  |  | ||||||
|  | 	END_SECTION() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool i8272::Drive::seek_is_satisfied() { | ||||||
|  | 	return	(target_head_position == head_position) || | ||||||
|  | 			(target_head_position == -1 && drive->get_is_track_zero()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void i8272::set_dma_acknowledge(bool dack) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void i8272::set_terminal_count(bool tc) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void i8272::set_data_input(uint8_t value) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t i8272::get_data_output() { | ||||||
|  | 	return 0xff; | ||||||
|  | } | ||||||
							
								
								
									
										140
									
								
								Components/8272/i8272.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Components/8272/i8272.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | // | ||||||
|  | //  i8272.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 05/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef i8272_hpp | ||||||
|  | #define i8272_hpp | ||||||
|  |  | ||||||
|  | #include "../../Storage/Disk/MFMDiskController.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Intel { | ||||||
|  | namespace i8272 { | ||||||
|  |  | ||||||
|  | class BusHandler { | ||||||
|  | 	public: | ||||||
|  | 		virtual void set_dma_data_request(bool drq) {} | ||||||
|  | 		virtual void set_interrupt(bool irq) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class i8272: public Storage::Disk::MFMController { | ||||||
|  | 	public: | ||||||
|  | 		i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); | ||||||
|  |  | ||||||
|  | 		void run_for(Cycles); | ||||||
|  |  | ||||||
|  | 		void set_data_input(uint8_t value); | ||||||
|  | 		uint8_t get_data_output(); | ||||||
|  |  | ||||||
|  | 		void set_register(int address, uint8_t value); | ||||||
|  | 		uint8_t get_register(int address); | ||||||
|  |  | ||||||
|  | 		void set_dma_acknowledge(bool dack); | ||||||
|  | 		void set_terminal_count(bool tc); | ||||||
|  |  | ||||||
|  | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive); | ||||||
|  |  | ||||||
|  | 		bool is_sleeping(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// The bus handler, for interrupt and DMA-driven usage. | ||||||
|  | 		BusHandler &bus_handler_; | ||||||
|  | 		std::unique_ptr<BusHandler> allocated_bus_handler_; | ||||||
|  |  | ||||||
|  | 		// Status registers. | ||||||
|  | 		uint8_t main_status_; | ||||||
|  | 		uint8_t status_[3]; | ||||||
|  |  | ||||||
|  | 		// A buffer for accumulating the incoming command, and one for accumulating the result. | ||||||
|  | 		std::vector<uint8_t> command_; | ||||||
|  | 		std::vector<uint8_t> result_stack_; | ||||||
|  | 		uint8_t input_; | ||||||
|  | 		bool has_input_; | ||||||
|  | 		bool expects_input_; | ||||||
|  |  | ||||||
|  | 		// Event stream: the 8272-specific events, plus the current event state. | ||||||
|  | 		enum class Event8272: int { | ||||||
|  | 			CommandByte	= (1 << 3), | ||||||
|  | 			Timer = (1 << 4), | ||||||
|  | 			ResultEmpty = (1 << 5), | ||||||
|  | 		}; | ||||||
|  | 		void posit_event(int type); | ||||||
|  | 		int interesting_event_mask_; | ||||||
|  | 		int resume_point_; | ||||||
|  | 		bool is_access_command_; | ||||||
|  |  | ||||||
|  | 		// The counter used for ::Timer events. | ||||||
|  | 		int delay_time_; | ||||||
|  |  | ||||||
|  | 		// The connected drives. | ||||||
|  | 		struct Drive { | ||||||
|  | 			uint8_t head_position; | ||||||
|  |  | ||||||
|  | 			// Seeking: persistent state. | ||||||
|  | 			enum Phase { | ||||||
|  | 				NotSeeking, | ||||||
|  | 				Seeking, | ||||||
|  | 				CompletedSeeking | ||||||
|  | 			} phase; | ||||||
|  | 			bool did_seek; | ||||||
|  | 			bool seek_failed; | ||||||
|  |  | ||||||
|  | 			// Seeking: transient state. | ||||||
|  | 			int step_rate_counter; | ||||||
|  | 			int steps_taken; | ||||||
|  | 			int target_head_position;	// either an actual number, or -1 to indicate to step until track zero | ||||||
|  |  | ||||||
|  | 			/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be. | ||||||
|  | 			bool seek_is_satisfied(); | ||||||
|  |  | ||||||
|  | 			// Head state. | ||||||
|  | 			int head_unload_delay[2]; | ||||||
|  | 			bool head_is_loaded[2]; | ||||||
|  |  | ||||||
|  | 			// The connected drive. | ||||||
|  | 			std::shared_ptr<Storage::Disk::Drive> drive; | ||||||
|  |  | ||||||
|  | 			Drive() : | ||||||
|  | 				head_position(0), phase(NotSeeking), | ||||||
|  | 				drive(new Storage::Disk::Drive), | ||||||
|  | 				head_is_loaded{false, false}, | ||||||
|  | 				head_unload_delay{0, 0} {}; | ||||||
|  | 		} drives_[4]; | ||||||
|  | 		int drives_seeking_; | ||||||
|  |  | ||||||
|  | 		// User-supplied parameters; as per the specify command. | ||||||
|  | 		int step_rate_time_; | ||||||
|  | 		int head_unload_time_; | ||||||
|  | 		int head_load_time_; | ||||||
|  | 		bool dma_mode_; | ||||||
|  |  | ||||||
|  | 		// A count of head unload timers currently running. | ||||||
|  | 		int head_timers_running_; | ||||||
|  |  | ||||||
|  | 		// Transient storage and counters used while reading the disk. | ||||||
|  | 		uint8_t header_[6]; | ||||||
|  | 		int distance_into_section_; | ||||||
|  | 		int index_hole_count_, index_hole_limit_; | ||||||
|  |  | ||||||
|  | 		// Keeps track of the drive and head in use during commands. | ||||||
|  | 		int active_drive_; | ||||||
|  | 		int active_head_; | ||||||
|  |  | ||||||
|  | 		// Internal registers. | ||||||
|  | 		uint8_t cylinder_, head_, sector_, size_; | ||||||
|  |  | ||||||
|  | 		// Master switch on not performing any work. | ||||||
|  | 		bool is_sleeping_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* i8272_hpp */ | ||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| #include "AY38910.hpp" | #include "AY38910.hpp" | ||||||
|  |  | ||||||
| using namespace GI; | using namespace GI::AY38910; | ||||||
|  |  | ||||||
| AY38910::AY38910() : | AY38910::AY38910() : | ||||||
| 		selected_register_(0), | 		selected_register_(0), | ||||||
| @@ -16,7 +16,8 @@ AY38910::AY38910() : | |||||||
| 		noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0), | 		noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0), | ||||||
| 		envelope_divider_(0), envelope_period_(0), envelope_position_(0), | 		envelope_divider_(0), envelope_period_(0), envelope_position_(0), | ||||||
| 		master_divider_(0), | 		master_divider_(0), | ||||||
| 		output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} { | 		output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
|  | 		port_handler_(nullptr) { | ||||||
| 	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0; | 	output_registers_[8] = output_registers_[9] = output_registers_[10] = 0; | ||||||
|  |  | ||||||
| 	// set up envelope lookup tables | 	// set up envelope lookup tables | ||||||
| @@ -76,7 +77,7 @@ void AY38910::set_clock_rate(double clock_rate) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { | void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { | ||||||
| 	int c = 0; | 	unsigned int c = 0; | ||||||
| 	while((master_divider_&7) && c < number_of_samples) { | 	while((master_divider_&7) && c < number_of_samples) { | ||||||
| 		target[c] = output_volume_; | 		target[c] = output_volume_; | ||||||
| 		master_divider_++; | 		master_divider_++; | ||||||
| @@ -166,11 +167,14 @@ void AY38910::evaluate_output_volume() { | |||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #pragma mark - Register manipulation | ||||||
|  |  | ||||||
| void AY38910::select_register(uint8_t r) { | void AY38910::select_register(uint8_t r) { | ||||||
| 	selected_register_ = r & 0xf; | 	selected_register_ = r; | ||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::set_register_value(uint8_t value) { | void AY38910::set_register_value(uint8_t value) { | ||||||
|  | 	if(selected_register_ > 15) return; | ||||||
| 	registers_[selected_register_] = value; | 	registers_[selected_register_] = value; | ||||||
| 	if(selected_register_ < 14) { | 	if(selected_register_ < 14) { | ||||||
| 		int selected_register = selected_register_; | 		int selected_register = selected_register_; | ||||||
| @@ -185,23 +189,19 @@ void AY38910::set_register_value(uint8_t value) { | |||||||
| 						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (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; | ||||||
| 					tone_counters_[channel] = tone_periods_[channel]; |  | ||||||
| 				} | 				} | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 6: | 				case 6: | ||||||
| 					noise_period_ = value & 0x1f; | 					noise_period_ = value & 0x1f; | ||||||
| 					noise_counter_ = noise_period_; |  | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 11: | 				case 11: | ||||||
| 					envelope_period_ = (envelope_period_ & ~0xff) | value; | 					envelope_period_ = (envelope_period_ & ~0xff) | value; | ||||||
| 					envelope_divider_ = envelope_period_; |  | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 12: | 				case 12: | ||||||
| 					envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8); | 					envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8); | ||||||
| 					envelope_divider_ = envelope_period_; |  | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case 13: | 				case 13: | ||||||
| @@ -212,53 +212,75 @@ void AY38910::set_register_value(uint8_t value) { | |||||||
| 			output_registers_[selected_register] = masked_value; | 			output_registers_[selected_register] = masked_value; | ||||||
| 			evaluate_output_volume(); | 			evaluate_output_volume(); | ||||||
| 		}); | 		}); | ||||||
|  | 	} else { | ||||||
|  | 		if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t AY38910::get_register_value() { | uint8_t AY38910::get_register_value() { | ||||||
| 	// This table ensures that bits that aren't defined within the AY are returned as 1s | 	// This table ensures that bits that aren't defined within the AY are returned as 0s | ||||||
| 	// when read. I can't find documentation on this and don't have a machine to test, so | 	// when read, conforming to CPC-sourced unit tests. | ||||||
| 	// this is provisionally a guess. TODO: investigate. |  | ||||||
| 	const uint8_t register_masks[16] = { | 	const uint8_t register_masks[16] = { | ||||||
| 		0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0xe0, 0x00, | 		0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff, | ||||||
| 		0xe0, 0xe0, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x00 | 		0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	return registers_[selected_register_] | register_masks[selected_register_]; | 	if(selected_register_ > 15) return 0xff; | ||||||
|  | 	switch(selected_register_) { | ||||||
|  | 		default:	return registers_[selected_register_] & register_masks[selected_register_]; | ||||||
|  | 		case 14:	return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0]; | ||||||
|  | 		case 15:	return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1]; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #pragma mark - Port handling | ||||||
|  |  | ||||||
| uint8_t AY38910::get_port_output(bool port_b) { | uint8_t AY38910::get_port_output(bool port_b) { | ||||||
| 	return registers_[port_b ? 15 : 14]; | 	return registers_[port_b ? 15 : 14]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #pragma mark - Bus handling | ||||||
|  |  | ||||||
|  | void AY38910::set_port_handler(PortHandler *handler) { | ||||||
|  | 	port_handler_ = handler; | ||||||
|  | } | ||||||
|  |  | ||||||
| void AY38910::set_data_input(uint8_t r) { | void AY38910::set_data_input(uint8_t r) { | ||||||
| 	data_input_ = r; | 	data_input_ = r; | ||||||
|  | 	update_bus(); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t AY38910::get_data_output() { | uint8_t AY38910::get_data_output() { | ||||||
|  | 	if(control_state_ == Read && selected_register_ >= 14) { | ||||||
|  | 		if(port_handler_) { | ||||||
|  | 			return port_handler_->get_port_input(selected_register_ == 15); | ||||||
|  | 		} else { | ||||||
|  | 			return 0xff; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	return data_output_; | 	return data_output_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void AY38910::set_control_lines(ControlLines control_lines) { | void AY38910::set_control_lines(ControlLines control_lines) { | ||||||
| 	ControlState new_state; |  | ||||||
| 	switch((int)control_lines) { | 	switch((int)control_lines) { | ||||||
| 		default:					new_state = Inactive;		break; | 		default:					control_state_ = Inactive;		break; | ||||||
|  |  | ||||||
| 		case (int)(BCDIR | BC2 | BC1): | 		case (int)(BDIR | BC2 | BC1): | ||||||
| 		case BCDIR: | 		case BDIR: | ||||||
| 		case BC1:					new_state = LatchAddress;	break; | 		case BC1:					control_state_ = LatchAddress;	break; | ||||||
|  |  | ||||||
| 		case (int)(BC2 | BC1):		new_state = Read;			break; | 		case (int)(BC2 | BC1):		control_state_ = Read;			break; | ||||||
| 		case (int)(BCDIR | BC2):	new_state = Write;			break; | 		case (int)(BDIR | BC2):		control_state_ = Write;			break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(new_state != control_state_) { | 	update_bus(); | ||||||
| 		control_state_ = new_state; | } | ||||||
| 		switch(new_state) { |  | ||||||
| 			default: break; | void AY38910::update_bus() { | ||||||
| 			case LatchAddress:	select_register(data_input_);			break; | 	switch(control_state_) { | ||||||
| 			case Write:			set_register_value(data_input_);		break; | 		default: break; | ||||||
| 			case Read:			data_output_ = get_register_value();	break; | 		case LatchAddress:	select_register(data_input_);			break; | ||||||
| 		} | 		case Write:			set_register_value(data_input_);		break; | ||||||
|  | 		case Read:			data_output_ = get_register_value();	break; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,43 @@ | |||||||
| #include "../../Outputs/Speaker.hpp" | #include "../../Outputs/Speaker.hpp" | ||||||
|  |  | ||||||
| namespace GI { | namespace GI { | ||||||
|  | namespace AY38910 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A port handler provides all input for an AY's two 8-bit ports, and may optionally receive | ||||||
|  | 	active notification of changes in output. | ||||||
|  |  | ||||||
|  | 	Machines with an AY without ports or with nothing wired to them need not supply a port handler. | ||||||
|  | 	Machines that use the AY ports as output but for which polling for changes is acceptable can | ||||||
|  | 	instead use AY38910.get_port_output. | ||||||
|  | */ | ||||||
|  | class PortHandler { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Requests the current input on an AY port. | ||||||
|  | 			 | ||||||
|  | 			@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) { | ||||||
|  | 			return 0xff; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Requests the current input on an AY port. | ||||||
|  | 			 | ||||||
|  | 			@param port_b @c true if the input being queried is Port B. @c false if it is Port A. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_port_output(bool port_b, uint8_t value) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Names the control lines used as input to the AY, which uses CP1600 bus semantics. | ||||||
|  | */ | ||||||
|  | enum ControlLines { | ||||||
|  | 	BC1		= (1 << 0), | ||||||
|  | 	BC2		= (1 << 1), | ||||||
|  | 	BDIR	= (1 << 2) | ||||||
|  | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	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 | ||||||
| @@ -26,19 +63,13 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
| 		/// Sets the clock rate at which this AY38910 will be run. | 		/// Sets the clock rate at which this AY38910 will be run. | ||||||
| 		void set_clock_rate(double clock_rate); | 		void set_clock_rate(double clock_rate); | ||||||
|  |  | ||||||
| 		enum ControlLines { |  | ||||||
| 			BC1		= (1 << 0), |  | ||||||
| 			BC2		= (1 << 1), |  | ||||||
| 			BCDIR	= (1 << 2) |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		/// Sets the value the AY would read from its data lines if it were not outputting. | 		/// Sets the value the AY would read from its data lines if it were not outputting. | ||||||
| 		void set_data_input(uint8_t r); | 		void set_data_input(uint8_t r); | ||||||
|  |  | ||||||
| 		/// Gets the value that would appear on the data lines if only the AY is outputting. | 		/// Gets the value that would appear on the data lines if only the AY is outputting. | ||||||
| 		uint8_t get_data_output(); | 		uint8_t get_data_output(); | ||||||
|  |  | ||||||
| 		/// Sets the | 		/// Sets the current control line state, as a bit field. | ||||||
| 		void set_control_lines(ControlLines control_lines); | 		void set_control_lines(ControlLines control_lines); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -47,12 +78,20 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
| 		*/ | 		*/ | ||||||
| 		uint8_t get_port_output(bool port_b); | 		uint8_t get_port_output(bool port_b); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the port handler, which will receive a call every time the AY either wants to sample | ||||||
|  | 			input or else declare new output. As a convenience, current port output can be obtained | ||||||
|  | 			without installing a port handler via get_port_output. | ||||||
|  | 		*/ | ||||||
|  | 		void set_port_handler(PortHandler *); | ||||||
|  |  | ||||||
| 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | ||||||
| 		void get_samples(unsigned int number_of_samples, int16_t *target); | 		void get_samples(unsigned int number_of_samples, int16_t *target); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		int selected_register_; | 		int selected_register_; | ||||||
| 		uint8_t registers_[16], output_registers_[16]; | 		uint8_t registers_[16], output_registers_[16]; | ||||||
|  | 		uint8_t port_inputs_[2]; | ||||||
|  |  | ||||||
| 		int master_divider_; | 		int master_divider_; | ||||||
|  |  | ||||||
| @@ -88,8 +127,12 @@ class AY38910: public ::Outputs::Filter<AY38910> { | |||||||
|  |  | ||||||
| 		int16_t output_volume_; | 		int16_t output_volume_; | ||||||
| 		inline void evaluate_output_volume(); | 		inline void evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		inline void update_bus(); | ||||||
|  | 		PortHandler *port_handler_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }; | } | ||||||
|  | } | ||||||
|  |  | ||||||
| #endif /* AY_3_8910_hpp */ | #endif /* AY_3_8910_hpp */ | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue() | |||||||
| AsyncTaskQueue::~AsyncTaskQueue() { | AsyncTaskQueue::~AsyncTaskQueue() { | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
| 	dispatch_release(serial_dispatch_queue_); | 	dispatch_release(serial_dispatch_queue_); | ||||||
|  | 	serial_dispatch_queue_ = nullptr; | ||||||
| #else | #else | ||||||
| 	should_destruct_ = true; | 	should_destruct_ = true; | ||||||
| 	enqueue([](){}); | 	enqueue([](){}); | ||||||
|   | |||||||
| @@ -22,11 +22,10 @@ namespace Concurrency { | |||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed | 	An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed | ||||||
| 	to be performed serially and asynchronously from the caller. A caller may also request to synchronise, | 	to be performed serially and asynchronously from the caller. A caller may also request to flush, | ||||||
| 	causing it to block until all previously-enqueued functions are complete. | 	causing it to block until all previously-enqueued functions are complete. | ||||||
| */ | */ | ||||||
| class AsyncTaskQueue { | class AsyncTaskQueue { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		AsyncTaskQueue(); | 		AsyncTaskQueue(); | ||||||
| 		~AsyncTaskQueue(); | 		~AsyncTaskQueue(); | ||||||
|   | |||||||
							
								
								
									
										1045
									
								
								Machines/AmstradCPC/AmstradCPC.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1045
									
								
								Machines/AmstradCPC/AmstradCPC.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										66
									
								
								Machines/AmstradCPC/AmstradCPC.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Machines/AmstradCPC/AmstradCPC.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | // | ||||||
|  | //  AmstradCPC.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/07/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef AmstradCPC_hpp | ||||||
|  | #define AmstradCPC_hpp | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | enum ROMType: int { | ||||||
|  | 	OS464 = 0,	BASIC464, | ||||||
|  | 	OS664,		BASIC664, | ||||||
|  | 	OS6128,		BASIC6128, | ||||||
|  | 	AMSDOS | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum Key: uint16_t { | ||||||
|  | #define Line(l, k1, k2, k3, k4, k5, k6, k7, k8)	\ | ||||||
|  | 	k1 = (l << 4) | 0x07,	k2 = (l << 4) | 0x06,	k3 = (l << 4) | 0x05,	k4 = (l << 4) | 0x04,\ | ||||||
|  | 	k5 = (l << 4) | 0x03,	k6 = (l << 4) | 0x02,	k7 = (l << 4) | 0x01,	k8 = (l << 4) | 0x00, | ||||||
|  |  | ||||||
|  | 	Line(0, KeyFDot,		KeyEnter,			KeyF3,			KeyF6,			KeyF9,					KeyDown,		KeyRight,				KeyUp) | ||||||
|  | 	Line(1, KeyF0,			KeyF2,				KeyF1,			KeyF5,			KeyF8,					KeyF7,			KeyCopy,				KeyLeft) | ||||||
|  | 	Line(2, KeyControl,		KeyBackSlash,		KeyShift,		KeyF4,			KeyRightSquareBracket,	KeyReturn,		KeyLeftSquareBracket,	KeyClear) | ||||||
|  | 	Line(3, KeyFullStop,	KeyForwardSlash,	KeyColon,		KeySemicolon,	KeyP,					KeyAt,			KeyMinus,				KeyCaret) | ||||||
|  | 	Line(4, KeyComma,		KeyM,				KeyK,			KeyL,			KeyI,					KeyO,			Key9,					Key0) | ||||||
|  | 	Line(5, KeySpace,		KeyN,				KeyJ,			KeyH,			KeyY,					KeyU,			Key7,					Key8) | ||||||
|  | 	Line(6, KeyV,			KeyB,				KeyF,			KeyG,			KeyT,					KeyR,			Key5,					Key6) | ||||||
|  | 	Line(7, KeyX,			KeyC,				KeyD,			KeyS,			KeyW,					KeyE,			Key3,					Key4) | ||||||
|  | 	Line(8, KeyZ,			KeyCapsLock,		KeyA,			KeyTab,			KeyQ,					KeyEscape,		Key2,					Key1) | ||||||
|  | 	Line(9, KeyDelete,		KeyJoy1Fire3,		KeyJoy2Fire2,	KeyJoy1Fire1,	KeyJoy1Right,			KeyJoy1Left,	KeyJoy1Down,			KeyJoy1Up) | ||||||
|  |  | ||||||
|  | #undef Line | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models an Amstrad CPC. | ||||||
|  | */ | ||||||
|  | class Machine: | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|  | 		/// Creates and returns an Amstrad CPC. | ||||||
|  | 		static Machine *AmstradCPC(); | ||||||
|  |  | ||||||
|  | 		/// Sets the contents of rom @c type to @c data. Assumed to be a setup step; has no effect once a machine is running. | ||||||
|  | 		virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* AmstradCPC_hpp */ | ||||||
							
								
								
									
										89
									
								
								Machines/AmstradCPC/CharacterMapper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Machines/AmstradCPC/CharacterMapper.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
|  | #include "AmstradCPC.hpp" | ||||||
|  |  | ||||||
|  | using namespace AmstradCPC; | ||||||
|  |  | ||||||
|  | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
|  | #define KEYS(...)	{__VA_ARGS__, EndSequence} | ||||||
|  | #define SHIFT(...)	{KeyShift, __VA_ARGS__, EndSequence} | ||||||
|  | #define X			{NotMapped} | ||||||
|  | 	static KeySequence key_sequences[] = { | ||||||
|  | 		/* NUL */	X,							/* SOH */	X, | ||||||
|  | 		/* STX */	X,							/* ETX */	X, | ||||||
|  | 		/* EOT */	X,							/* ENQ */	X, | ||||||
|  | 		/* ACK */	X,							/* BEL */	X, | ||||||
|  | 		/* BS */	KEYS(KeyDelete),			/* HT */	X, | ||||||
|  | 		/* LF */	KEYS(KeyReturn),			/* VT */	X, | ||||||
|  | 		/* FF */	X,							/* CR */	X, | ||||||
|  | 		/* SO */	X,							/* SI */	X, | ||||||
|  | 		/* DLE */	X,							/* DC1 */	X, | ||||||
|  | 		/* DC2 */	X,							/* DC3 */	X, | ||||||
|  | 		/* DC4 */	X,							/* NAK */	X, | ||||||
|  | 		/* SYN */	X,							/* ETB */	X, | ||||||
|  | 		/* CAN */	X,							/* EM */	X, | ||||||
|  | 		/* SUB */	X,							/* ESC */	X, | ||||||
|  | 		/* FS */	X,							/* GS */	X, | ||||||
|  | 		/* RS */	X,							/* US */	X, | ||||||
|  | 		/* space */	KEYS(KeySpace),				/* ! */		SHIFT(Key1), | ||||||
|  | 		/* " */		SHIFT(Key2),				/* # */		SHIFT(Key3), | ||||||
|  | 		/* $ */		SHIFT(Key4),				/* % */		SHIFT(Key5), | ||||||
|  | 		/* & */		SHIFT(Key6),				/* ' */		SHIFT(Key7), | ||||||
|  | 		/* ( */		SHIFT(Key8),				/* ) */		SHIFT(Key9), | ||||||
|  | 		/* * */		SHIFT(KeyColon),			/* + */		SHIFT(KeySemicolon), | ||||||
|  | 		/* , */		KEYS(KeyComma),				/* - */		KEYS(KeyMinus), | ||||||
|  | 		/* . */		KEYS(KeyFullStop),			/* / */		KEYS(KeyForwardSlash), | ||||||
|  | 		/* 0 */		KEYS(Key0),					/* 1 */		KEYS(Key1), | ||||||
|  | 		/* 2 */		KEYS(Key2),					/* 3 */		KEYS(Key3), | ||||||
|  | 		/* 4 */		KEYS(Key4),					/* 5 */		KEYS(Key5), | ||||||
|  | 		/* 6 */		KEYS(Key6),					/* 7 */		KEYS(Key7), | ||||||
|  | 		/* 8 */		KEYS(Key8),					/* 9 */		KEYS(Key9), | ||||||
|  | 		/* : */		KEYS(KeyColon),				/* ; */		KEYS(KeySemicolon), | ||||||
|  | 		/* < */		SHIFT(KeyComma),			/* = */		SHIFT(KeyMinus), | ||||||
|  | 		/* > */		SHIFT(KeyFullStop),			/* ? */		SHIFT(KeyForwardSlash), | ||||||
|  | 		/* @ */		SHIFT(KeyAt),				/* A */		SHIFT(KeyA), | ||||||
|  | 		/* B */		SHIFT(KeyB),				/* C */		SHIFT(KeyC), | ||||||
|  | 		/* D */		SHIFT(KeyD),				/* E */		SHIFT(KeyE), | ||||||
|  | 		/* F */		SHIFT(KeyF),				/* G */		SHIFT(KeyG), | ||||||
|  | 		/* H */		SHIFT(KeyH),				/* I */		SHIFT(KeyI), | ||||||
|  | 		/* J */		SHIFT(KeyJ),				/* K */		SHIFT(KeyK), | ||||||
|  | 		/* L */		SHIFT(KeyL),				/* M */		SHIFT(KeyM), | ||||||
|  | 		/* N */		SHIFT(KeyN),				/* O */		SHIFT(KeyO), | ||||||
|  | 		/* P */		SHIFT(KeyP),				/* Q */		SHIFT(KeyQ), | ||||||
|  | 		/* R */		SHIFT(KeyR),				/* S */		SHIFT(KeyS), | ||||||
|  | 		/* T */		SHIFT(KeyT),				/* U */		SHIFT(KeyU), | ||||||
|  | 		/* V */		SHIFT(KeyV),				/* W */		SHIFT(KeyW), | ||||||
|  | 		/* X */		SHIFT(KeyX),				/* Y */		SHIFT(KeyY), | ||||||
|  | 		/* Z */		SHIFT(KeyZ),				/* [ */		KEYS(KeyLeftSquareBracket), | ||||||
|  | 		/* \ */		KEYS(KeyBackSlash),			/* ] */		KEYS(KeyRightSquareBracket), | ||||||
|  | 		/* ^ */		SHIFT(KeyCaret),			/* _ */		SHIFT(Key0), | ||||||
|  | 		/* ` */		X,							/* a */		KEYS(KeyA), | ||||||
|  | 		/* b */		KEYS(KeyB),					/* c */		KEYS(KeyC), | ||||||
|  | 		/* d */		KEYS(KeyD),					/* e */		KEYS(KeyE), | ||||||
|  | 		/* f */		KEYS(KeyF),					/* g */		KEYS(KeyG), | ||||||
|  | 		/* h */		KEYS(KeyH),					/* i */		KEYS(KeyI), | ||||||
|  | 		/* j */		KEYS(KeyJ),					/* k */		KEYS(KeyK), | ||||||
|  | 		/* l */		KEYS(KeyL),					/* m */		KEYS(KeyM), | ||||||
|  | 		/* n */		KEYS(KeyN),					/* o */		KEYS(KeyO), | ||||||
|  | 		/* p */		KEYS(KeyP),					/* q */		KEYS(KeyQ), | ||||||
|  | 		/* r */		KEYS(KeyR),					/* s */		KEYS(KeyS), | ||||||
|  | 		/* t */		KEYS(KeyT),					/* u */		KEYS(KeyU), | ||||||
|  | 		/* v */		KEYS(KeyV),					/* w */		KEYS(KeyW), | ||||||
|  | 		/* x */		KEYS(KeyX),					/* y */		KEYS(KeyY), | ||||||
|  | 		/* z */		KEYS(KeyZ),					/* { */		X, | ||||||
|  | 		/* | */		SHIFT(KeyAt),				/* } */		X, | ||||||
|  | 		/* ~ */		X | ||||||
|  | 	}; | ||||||
|  | #undef KEYS | ||||||
|  | #undef SHIFT | ||||||
|  | #undef X | ||||||
|  |  | ||||||
|  | 	return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								Machines/AmstradCPC/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Machines/AmstradCPC/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_AmstradCPC_CharacterMapper_hpp | ||||||
|  | #define Machines_AmstradCPC_CharacterMapper_hpp | ||||||
|  |  | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | class CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	public: | ||||||
|  | 		uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* CharacterMapper_hpp */ | ||||||
| @@ -10,148 +10,200 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  |  | ||||||
| #include "Cartridges/CartridgeAtari8k.hpp" | #include "Cartridges/Atari8k.hpp" | ||||||
| #include "Cartridges/CartridgeAtari16k.hpp" | #include "Cartridges/Atari16k.hpp" | ||||||
| #include "Cartridges/CartridgeAtari32k.hpp" | #include "Cartridges/Atari32k.hpp" | ||||||
| #include "Cartridges/CartridgeActivisionStack.hpp" | #include "Cartridges/ActivisionStack.hpp" | ||||||
| #include "Cartridges/CartridgeCBSRAMPlus.hpp" | #include "Cartridges/CBSRAMPlus.hpp" | ||||||
| #include "Cartridges/CartridgeCommaVid.hpp" | #include "Cartridges/CommaVid.hpp" | ||||||
| #include "Cartridges/CartridgeMegaBoy.hpp" | #include "Cartridges/MegaBoy.hpp" | ||||||
| #include "Cartridges/CartridgeMNetwork.hpp" | #include "Cartridges/MNetwork.hpp" | ||||||
| #include "Cartridges/CartridgeParkerBros.hpp" | #include "Cartridges/ParkerBros.hpp" | ||||||
| #include "Cartridges/CartridgePitfall2.hpp" | #include "Cartridges/Pitfall2.hpp" | ||||||
| #include "Cartridges/CartridgeTigervision.hpp" | #include "Cartridges/Tigervision.hpp" | ||||||
| #include "Cartridges/CartridgeUnpaged.hpp" | #include "Cartridges/Unpaged.hpp" | ||||||
|  |  | ||||||
| using namespace Atari2600; |  | ||||||
| namespace { | namespace { | ||||||
| 	static const double NTSC_clock_rate = 1194720; | 	static const double NTSC_clock_rate = 1194720; | ||||||
| 	static const double PAL_clock_rate = 1182298; | 	static const double PAL_clock_rate = 1182298; | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::Machine() : | namespace Atari2600 { | ||||||
| 	frame_record_pointer_(0), |  | ||||||
| 	is_ntsc_(true) { |  | ||||||
| 	set_clock_rate(NTSC_clock_rate); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::setup_output(float aspect_ratio) { | class ConcreteMachine: | ||||||
| 	bus_->tia_.reset(new TIA); | 	public Machine, | ||||||
| 	bus_->speaker_.reset(new Speaker); | 	public Outputs::CRT::Delegate { | ||||||
| 	bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick)); | 	public: | ||||||
| 	bus_->tia_->get_crt()->set_delegate(this); | 		ConcreteMachine() : | ||||||
| } | 			frame_record_pointer_(0), | ||||||
|  | 			is_ntsc_(true) { | ||||||
| void Machine::close_output() { | 			set_clock_rate(NTSC_clock_rate); | ||||||
| 	bus_.reset(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Machine::~Machine() { |  | ||||||
| 	close_output(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_digital_input(Atari2600DigitalInput input, bool state) { |  | ||||||
| 	switch (input) { |  | ||||||
| 		case Atari2600DigitalInputJoy1Up:		bus_->mos6532_.update_port_input(0, 0x10, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy1Down:		bus_->mos6532_.update_port_input(0, 0x20, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy1Left:		bus_->mos6532_.update_port_input(0, 0x40, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy1Right:	bus_->mos6532_.update_port_input(0, 0x80, state);	break; |  | ||||||
|  |  | ||||||
| 		case Atari2600DigitalInputJoy2Up:		bus_->mos6532_.update_port_input(0, 0x01, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Down:		bus_->mos6532_.update_port_input(0, 0x02, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Left:		bus_->mos6532_.update_port_input(0, 0x04, state);	break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Right:	bus_->mos6532_.update_port_input(0, 0x08, state);	break; |  | ||||||
|  |  | ||||||
| 		// TODO: latching |  | ||||||
| 		case Atari2600DigitalInputJoy1Fire:		if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break; |  | ||||||
| 		case Atari2600DigitalInputJoy2Fire:		if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break; |  | ||||||
|  |  | ||||||
| 		default: break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) { |  | ||||||
| 	switch(input) { |  | ||||||
| 		case Atari2600SwitchReset:					bus_->mos6532_.update_port_input(1, 0x01, state);	break; |  | ||||||
| 		case Atari2600SwitchSelect:					bus_->mos6532_.update_port_input(1, 0x02, state);	break; |  | ||||||
| 		case Atari2600SwitchColour:					bus_->mos6532_.update_port_input(1, 0x08, state);	break; |  | ||||||
| 		case Atari2600SwitchLeftPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x40, state);	break; |  | ||||||
| 		case Atari2600SwitchRightPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x80, state);	break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { |  | ||||||
| 	const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data; |  | ||||||
| 	switch(target.atari.paging_model) { |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::ActivisionStack:	bus_.reset(new CartridgeActivisionStack(rom));	break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new CartridgeCBSRAMPlus(rom));		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::CommaVid:		bus_.reset(new CartridgeCommaVid(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::MegaBoy:			bus_.reset(new CartridgeMegaBoy(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::MNetwork:		bus_.reset(new CartridgeMNetwork(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::None:			bus_.reset(new CartridgeUnpaged(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::ParkerBros:		bus_.reset(new CartridgeParkerBros(rom));		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Pitfall2:		bus_.reset(new CartridgePitfall2(rom));			break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Tigervision:		bus_.reset(new CartridgeTigervision(rom));		break; |  | ||||||
|  |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari8k: |  | ||||||
| 			if(target.atari.uses_superchip) { |  | ||||||
| 				bus_.reset(new CartridgeAtari8kSuperChip(rom)); |  | ||||||
| 			} else { |  | ||||||
| 				bus_.reset(new CartridgeAtari8k(rom)); |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari16k: |  | ||||||
| 			if(target.atari.uses_superchip) { |  | ||||||
| 				bus_.reset(new CartridgeAtari16kSuperChip(rom)); |  | ||||||
| 			} else { |  | ||||||
| 				bus_.reset(new CartridgeAtari16k(rom)); |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 		case StaticAnalyser::Atari2600PagingModel::Atari32k: |  | ||||||
| 			if(target.atari.uses_superchip) { |  | ||||||
| 				bus_.reset(new CartridgeAtari32kSuperChip(rom)); |  | ||||||
| 			} else { |  | ||||||
| 				bus_.reset(new CartridgeAtari32k(rom)); |  | ||||||
| 			} |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - CRT delegate |  | ||||||
|  |  | ||||||
| void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { |  | ||||||
| 	const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); |  | ||||||
| 	frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames; |  | ||||||
| 	frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; |  | ||||||
| 	frame_record_pointer_ ++; |  | ||||||
|  |  | ||||||
| 	if(frame_record_pointer_ >= 6) { |  | ||||||
| 		unsigned int total_number_of_frames = 0; |  | ||||||
| 		unsigned int total_number_of_unexpected_vertical_syncs = 0; |  | ||||||
| 		for(size_t c = 0; c < number_of_frame_records; c++) { |  | ||||||
| 			total_number_of_frames += frame_records_[c].number_of_frames; |  | ||||||
| 			total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { | 		~ConcreteMachine() { | ||||||
| 			for(size_t c = 0; c < number_of_frame_records; c++) { | 			close_output(); | ||||||
| 				frame_records_[c].number_of_frames = 0; |  | ||||||
| 				frame_records_[c].number_of_unexpected_vertical_syncs = 0; |  | ||||||
| 			} |  | ||||||
| 			is_ntsc_ ^= true; |  | ||||||
|  |  | ||||||
| 			double clock_rate; |  | ||||||
| 			if(is_ntsc_) { |  | ||||||
| 				clock_rate = NTSC_clock_rate; |  | ||||||
| 				bus_->tia_->set_output_mode(TIA::OutputMode::NTSC); |  | ||||||
| 			} else { |  | ||||||
| 				clock_rate = PAL_clock_rate; |  | ||||||
| 				bus_->tia_->set_output_mode(TIA::OutputMode::PAL); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick)); |  | ||||||
| 			bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0))); |  | ||||||
| 			set_clock_rate(clock_rate); |  | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) { | ||||||
|  | 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | ||||||
|  | 			switch(target.atari.paging_model) { | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::CommaVid:		bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::MNetwork:		bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::None:			bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Pitfall2:		bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||||
|  |  | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Atari8k: | ||||||
|  | 					if(target.atari.uses_superchip) { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | ||||||
|  | 					} else { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Atari16k: | ||||||
|  | 					if(target.atari.uses_superchip) { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | ||||||
|  | 					} else { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::Atari2600PagingModel::Atari32k: | ||||||
|  | 					if(target.atari.uses_superchip) { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | ||||||
|  | 					} else { | ||||||
|  | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(Atari2600DigitalInput input, bool state) { | ||||||
|  | 			switch (input) { | ||||||
|  | 				case Atari2600DigitalInputJoy1Up:		bus_->mos6532_.update_port_input(0, 0x10, state);	break; | ||||||
|  | 				case Atari2600DigitalInputJoy1Down:		bus_->mos6532_.update_port_input(0, 0x20, state);	break; | ||||||
|  | 				case Atari2600DigitalInputJoy1Left:		bus_->mos6532_.update_port_input(0, 0x40, state);	break; | ||||||
|  | 				case Atari2600DigitalInputJoy1Right:	bus_->mos6532_.update_port_input(0, 0x80, state);	break; | ||||||
|  |  | ||||||
|  | 				case Atari2600DigitalInputJoy2Up:		bus_->mos6532_.update_port_input(0, 0x01, state);	break; | ||||||
|  | 				case Atari2600DigitalInputJoy2Down:		bus_->mos6532_.update_port_input(0, 0x02, state);	break; | ||||||
|  | 				case Atari2600DigitalInputJoy2Left:		bus_->mos6532_.update_port_input(0, 0x04, state);	break; | ||||||
|  | 				case Atari2600DigitalInputJoy2Right:	bus_->mos6532_.update_port_input(0, 0x08, state);	break; | ||||||
|  |  | ||||||
|  | 				// TODO: latching | ||||||
|  | 				case Atari2600DigitalInputJoy1Fire:		if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break; | ||||||
|  | 				case Atari2600DigitalInputJoy2Fire:		if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break; | ||||||
|  |  | ||||||
|  | 				default: break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		void set_switch_is_enabled(Atari2600Switch input, bool state) { | ||||||
|  | 			switch(input) { | ||||||
|  | 				case Atari2600SwitchReset:					bus_->mos6532_.update_port_input(1, 0x01, state);	break; | ||||||
|  | 				case Atari2600SwitchSelect:					bus_->mos6532_.update_port_input(1, 0x02, state);	break; | ||||||
|  | 				case Atari2600SwitchColour:					bus_->mos6532_.update_port_input(1, 0x08, state);	break; | ||||||
|  | 				case Atari2600SwitchLeftPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x40, state);	break; | ||||||
|  | 				case Atari2600SwitchRightPlayerDifficulty:	bus_->mos6532_.update_port_input(1, 0x80, state);	break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_reset_switch(bool state) { | ||||||
|  | 			bus_->set_reset_line(state); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy CRTMachine::Machine | ||||||
|  | 		void setup_output(float aspect_ratio) { | ||||||
|  | 			bus_->tia_.reset(new TIA); | ||||||
|  | 			bus_->speaker_.reset(new Speaker); | ||||||
|  | 			bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick)); | ||||||
|  | 			bus_->tia_->get_crt()->set_delegate(this); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void close_output() { | ||||||
|  | 			bus_.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::CRT::CRT> get_crt() { | ||||||
|  | 			return bus_->tia_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::Speaker> get_speaker() { | ||||||
|  | 			return bus_->speaker_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) { | ||||||
|  | 			bus_->run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy Outputs::CRT::Delegate | ||||||
|  | 		void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { | ||||||
|  | 			const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); | ||||||
|  | 			frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames; | ||||||
|  | 			frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; | ||||||
|  | 			frame_record_pointer_ ++; | ||||||
|  |  | ||||||
|  | 			if(frame_record_pointer_ >= 6) { | ||||||
|  | 				unsigned int total_number_of_frames = 0; | ||||||
|  | 				unsigned int total_number_of_unexpected_vertical_syncs = 0; | ||||||
|  | 				for(size_t c = 0; c < number_of_frame_records; c++) { | ||||||
|  | 					total_number_of_frames += frame_records_[c].number_of_frames; | ||||||
|  | 					total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) { | ||||||
|  | 					for(size_t c = 0; c < number_of_frame_records; c++) { | ||||||
|  | 						frame_records_[c].number_of_frames = 0; | ||||||
|  | 						frame_records_[c].number_of_unexpected_vertical_syncs = 0; | ||||||
|  | 					} | ||||||
|  | 					is_ntsc_ ^= true; | ||||||
|  |  | ||||||
|  | 					double clock_rate; | ||||||
|  | 					if(is_ntsc_) { | ||||||
|  | 						clock_rate = NTSC_clock_rate; | ||||||
|  | 						bus_->tia_->set_output_mode(TIA::OutputMode::NTSC); | ||||||
|  | 					} else { | ||||||
|  | 						clock_rate = PAL_clock_rate; | ||||||
|  | 						bus_->tia_->set_output_mode(TIA::OutputMode::PAL); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick)); | ||||||
|  | 					bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0))); | ||||||
|  | 					set_clock_rate(clock_rate); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// the bus | ||||||
|  | 		std::unique_ptr<Bus> bus_; | ||||||
|  |  | ||||||
|  | 		// output frame rate tracker | ||||||
|  | 		struct FrameRecord { | ||||||
|  | 			unsigned int number_of_frames; | ||||||
|  | 			unsigned int number_of_unexpected_vertical_syncs; | ||||||
|  |  | ||||||
|  | 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} | ||||||
|  | 		} frame_records_[4]; | ||||||
|  | 		unsigned int frame_record_pointer_; | ||||||
|  | 		bool is_ntsc_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | using namespace Atari2600; | ||||||
|  |  | ||||||
|  | Machine *Machine::Atari2600() { | ||||||
|  | 	return new Atari2600::ConcreteMachine; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,59 +9,33 @@ | |||||||
| #ifndef Atari2600_cpp | #ifndef Atari2600_cpp | ||||||
| #define Atari2600_cpp | #define Atari2600_cpp | ||||||
|  |  | ||||||
| #include <stdint.h> |  | ||||||
|  |  | ||||||
| #include "../../Processors/6502/CPU6502.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "Bus.hpp" |  | ||||||
| #include "PIA.hpp" |  | ||||||
| #include "Speaker.hpp" |  | ||||||
| #include "TIA.hpp" |  | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  |  | ||||||
| #include "Atari2600Inputs.h" | #include "Atari2600Inputs.h" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models an Atari 2600. | ||||||
|  | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public ConfigurationTarget::Machine { | ||||||
| 	public Outputs::CRT::Delegate { |  | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
| 		~Machine(); |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); | 		/// Creates and returns an Atari 2600 on the heap. | ||||||
| 		void switch_region(); | 		static Machine *Atari2600(); | ||||||
|  |  | ||||||
| 		void set_digital_input(Atari2600DigitalInput input, bool state); | 		/// Sets @c input to @c state. | ||||||
| 		void set_switch_is_enabled(Atari2600Switch input, bool state); | 		virtual void set_digital_input(Atari2600DigitalInput input, bool state) = 0; | ||||||
| 		void set_reset_line(bool state) { bus_->set_reset_line(state); } |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine | 		/// Sets the switch @c input to @c state. | ||||||
| 		virtual void setup_output(float aspect_ratio); | 		virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); } |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; } |  | ||||||
| 		virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); } |  | ||||||
|  |  | ||||||
| 		// to satisfy Outputs::CRT::Delegate | 		// Presses or releases the reset button. | ||||||
| 		virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); | 		virtual void set_reset_switch(bool state) = 0; | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		// the bus |  | ||||||
| 		std::unique_ptr<Bus> bus_; |  | ||||||
|  |  | ||||||
| 		// output frame rate tracker |  | ||||||
| 		struct FrameRecord { |  | ||||||
| 			unsigned int number_of_frames; |  | ||||||
| 			unsigned int number_of_unexpected_vertical_syncs; |  | ||||||
|  |  | ||||||
| 			FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} |  | ||||||
| 		} frame_records_[4]; |  | ||||||
| 		unsigned int frame_record_pointer_; |  | ||||||
| 		bool is_ntsc_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,17 +14,17 @@ | |||||||
| #include "Speaker.hpp" | #include "Speaker.hpp" | ||||||
| #include "TIA.hpp" | #include "TIA.hpp" | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  |  | ||||||
| class Bus { | class Bus { | ||||||
| 	public: | 	public: | ||||||
| 		Bus() : | 		Bus() : | ||||||
| 			tia_input_value_{0xff, 0xff}, | 			tia_input_value_{0xff, 0xff}, | ||||||
| 			cycles_since_speaker_update_(0), | 			cycles_since_speaker_update_(0) {} | ||||||
| 			cycles_since_video_update_(0), |  | ||||||
| 			cycles_since_6532_update_(0) {} |  | ||||||
|  |  | ||||||
| 		virtual void run_for_cycles(int number_of_cycles) = 0; | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
| 		virtual void set_reset_line(bool state) = 0; | 		virtual void set_reset_line(bool state) = 0; | ||||||
|  |  | ||||||
| 		// the RIOT, TIA and speaker | 		// the RIOT, TIA and speaker | ||||||
| @@ -37,25 +37,21 @@ class Bus { | |||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		// speaker backlog accumlation counter | 		// speaker backlog accumlation counter | ||||||
| 		unsigned int cycles_since_speaker_update_; | 		Cycles cycles_since_speaker_update_; | ||||||
| 		inline void update_audio() { | 		inline void update_audio() { | ||||||
| 			unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3); | 			speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); | ||||||
| 			cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3); |  | ||||||
| 			speaker_->run_for_cycles(audio_cycles); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// video backlog accumulation counter | 		// video backlog accumulation counter | ||||||
| 		unsigned int cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
| 		inline void update_video() { | 		inline void update_video() { | ||||||
| 			tia_->run_for_cycles((int)cycles_since_video_update_); | 			tia_->run_for(cycles_since_video_update_.flush()); | ||||||
| 			cycles_since_video_update_ = 0; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// RIOT backlog accumulation counter | 		// RIOT backlog accumulation counter | ||||||
| 		unsigned int cycles_since_6532_update_; | 		Cycles cycles_since_6532_update_; | ||||||
| 		inline void update_6532() { | 		inline void update_6532() { | ||||||
| 			mos6532_.run_for_cycles(cycles_since_6532_update_); | 			mos6532_.run_for(cycles_since_6532_update_.flush()); | ||||||
| 			cycles_since_6532_update_ = 0; |  | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,30 +6,30 @@ | |||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #ifndef Atari2600_CartridgeActivisionStack_hpp | #ifndef Atari2600_ActivisionStack_hpp | ||||||
| #define Atari2600_CartridgeActivisionStack_hpp | #define Atari2600_ActivisionStack_hpp | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | class ActivisionStack: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeActivisionStack(const std::vector<uint8_t> &rom) : | 		ActivisionStack(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom), | 			BusExtender(rom_base, rom_size), | ||||||
| 			last_opcode_(0x00) { | 			rom_ptr_(rom_base), | ||||||
| 			rom_ptr_ = rom_.data(); | 			last_opcode_(0x00) {} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
 | 			// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
 | ||||||
| 			// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
 | 			// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
 | ||||||
| 			// RST or JSR.
 | 			// RST or JSR.
 | ||||||
| 			if(operation == CPU6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { | 			if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { | ||||||
| 				if(address & 0x2000) { | 				if(address & 0x2000) { | ||||||
| 					rom_ptr_ = rom_.data(); | 					rom_ptr_ = rom_base_; | ||||||
| 				} else { | 				} else { | ||||||
| 					rom_ptr_ = rom_.data() + 4096; | 					rom_ptr_ = rom_base_ + 4096; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| @@ -37,7 +37,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | |||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(operation == CPU6502::BusOperation::ReadOpcode) last_opcode_ = *value; | 			if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	private: | 	private: | ||||||
| @@ -45,6 +45,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> { | |||||||
| 		uint8_t last_opcode_; | 		uint8_t last_opcode_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeActivisionStack_hpp */ | #endif /* Atari2600_CartridgeActivisionStack_hpp */ | ||||||
| @@ -12,19 +12,19 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> { | class Atari16k: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari16k(const std::vector<uint8_t> &rom) : | 		Atari16k(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size), | ||||||
| 			rom_ptr_ = rom_.data(); | 			rom_ptr_(rom_base) {} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096; | 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -35,18 +35,17 @@ class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> { | |||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> { | class Atari16kSuperChip: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) : | 		Atari16kSuperChip(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size), | ||||||
| 			rom_ptr_ = rom_.data(); | 			rom_ptr_(rom_base) {} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096; | 			if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -61,6 +60,7 @@ class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> { | |||||||
| 		uint8_t ram_[128]; | 		uint8_t ram_[128]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeAtari16k_hpp */ | #endif /* Atari2600_CartridgeAtari16k_hpp */ | ||||||
| @@ -12,19 +12,17 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> { | class Atari32k: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari32k(const std::vector<uint8_t> &rom) : | 		Atari32k(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096; | 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -35,18 +33,15 @@ class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> { | |||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> { | class Atari32kSuperChip: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) : | 		Atari32kSuperChip(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096; | 			if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -61,6 +56,7 @@ class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> { | |||||||
| 		uint8_t ram_[128]; | 		uint8_t ram_[128]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeAtari32k_hpp */ | #endif /* Atari2600_CartridgeAtari32k_hpp */ | ||||||
| @@ -12,20 +12,18 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> { | class Atari8k: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari8k(const std::vector<uint8_t> &rom) : | 		Atari8k(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address == 0x1ff8) rom_ptr_ = rom_.data(); | 			if(address == 0x1ff8) rom_ptr_ = rom_base_; | ||||||
| 			else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096; | 			else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -36,19 +34,16 @@ class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> { | |||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> { | class Atari8kSuperChip: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) : | 		Atari8kSuperChip(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address == 0x1ff8) rom_ptr_ = rom_.data(); | 			if(address == 0x1ff8) rom_ptr_ = rom_base_; | ||||||
| 			if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096; | 			if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -63,6 +58,7 @@ class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> { | |||||||
| 		uint8_t ram_[128]; | 		uint8_t ram_[128]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeAtari8k_hpp */ | #endif /* Atari2600_CartridgeAtari8k_hpp */ | ||||||
| @@ -12,19 +12,17 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> { | class CBSRAMPlus: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) : | 		CBSRAMPlus(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} | ||||||
| 			Cartridge(rom) { |  | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096; | 			if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096; | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[address & 4095]; | 				*value = rom_ptr_[address & 4095]; | ||||||
| @@ -39,6 +37,7 @@ class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> { | |||||||
| 		uint8_t ram_[256]; | 		uint8_t ram_[256]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeCBSRAMPlus_hpp */ | #endif /* Atari2600_CartridgeCBSRAMPlus_hpp */ | ||||||
| @@ -9,43 +9,59 @@ | |||||||
| #ifndef Atari2600_Cartridge_hpp | #ifndef Atari2600_Cartridge_hpp | ||||||
| #define Atari2600_Cartridge_hpp | #define Atari2600_Cartridge_hpp | ||||||
|  |  | ||||||
| #include "../../../Processors/6502/CPU6502.hpp" | #include "../../../Processors/6502/6502.hpp" | ||||||
| #include "../Bus.hpp" | #include "../Bus.hpp" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
|  |  | ||||||
|  | class BusExtender: public CPU::MOS6502::BusHandler { | ||||||
|  | 	public: | ||||||
|  | 		BusExtender(uint8_t *rom_base, size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {} | ||||||
|  |  | ||||||
|  | 		void advance_cycles(int cycles) {} | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		uint8_t *rom_base_; | ||||||
|  | 		size_t rom_size_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<class T> class Cartridge: | template<class T> class Cartridge: | ||||||
| 	public CPU6502::Processor<Cartridge<T>>, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public Bus { | 	public Bus { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Cartridge(const std::vector<uint8_t> &rom) : | 		Cartridge(const std::vector<uint8_t> &rom) : | ||||||
| 			rom_(rom) {} | 			m6502_(*this), | ||||||
|  | 			rom_(rom), | ||||||
|  | 			bus_extender_(rom_.data(), rom.size()) { | ||||||
|  | 			// The above works because bus_extender_ is declared after rom_ in the instance storage list; | ||||||
|  | 			// consider doing something less fragile. | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); } | 		void run_for(const Cycles cycles)	{ m6502_.run_for(cycles);		} | ||||||
| 		void set_reset_line(bool state) { CPU6502::Processor<Cartridge<T>>::set_reset_line(state); } | 		void set_reset_line(bool state)		{ m6502_.set_reset_line(state);	} | ||||||
| 		void advance_cycles(unsigned int cycles) {} |  | ||||||
|  |  | ||||||
| 		// to satisfy CPU6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			uint8_t returnValue = 0xff; | 			uint8_t returnValue = 0xff; | ||||||
| 			unsigned int cycles_run_for = 3; | 			int cycles_run_for = 3; | ||||||
|  |  | ||||||
| 			// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for | 			// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for | ||||||
| 			// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take | 			// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take | ||||||
| 			// effect until the next read; therefore it isn't safe to assume that signalling ready immediately | 			// effect until the next read; therefore it isn't safe to assume that signalling ready immediately | ||||||
| 			// skips to the end of the line. | 			// skips to the end of the line. | ||||||
| 			if(operation == CPU6502::BusOperation::Ready) | 			if(operation == CPU::MOS6502::BusOperation::Ready) | ||||||
| 				cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); | 				cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); | ||||||
|  |  | ||||||
| 			cycles_since_speaker_update_ += cycles_run_for; | 			cycles_since_speaker_update_ += Cycles(cycles_run_for); | ||||||
| 			cycles_since_video_update_ += cycles_run_for; | 			cycles_since_video_update_ += Cycles(cycles_run_for); | ||||||
| 			cycles_since_6532_update_ += (cycles_run_for / 3); | 			cycles_since_6532_update_ += Cycles(cycles_run_for / 3); | ||||||
| 			static_cast<T *>(this)->advance_cycles(cycles_run_for / 3); | 			bus_extender_.advance_cycles(cycles_run_for / 3); | ||||||
|  |  | ||||||
| 			if(operation != CPU6502::BusOperation::Ready) { | 			if(operation != CPU::MOS6502::BusOperation::Ready) { | ||||||
| 				// give the cartridge a chance to respond to the bus access | 				// give the cartridge a chance to respond to the bus access | ||||||
| 				static_cast<T *>(this)->perform_bus_operation(operation, address, value); | 				bus_extender_.perform_bus_operation(operation, address, value); | ||||||
|  |  | ||||||
| 				// check for a RIOT RAM access | 				// check for a RIOT RAM access | ||||||
| 				if((address&0x1280) == 0x80) { | 				if((address&0x1280) == 0x80) { | ||||||
| @@ -91,7 +107,7 @@ template<class T> class Cartridge: | |||||||
| 							case 0x00:	update_video(); tia_->set_sync(*value & 0x02);		break; | 							case 0x00:	update_video(); tia_->set_sync(*value & 0x02);		break; | ||||||
| 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | 							case 0x01:	update_video();	tia_->set_blank(*value & 0x02);		break; | ||||||
|  |  | ||||||
| 							case 0x02:	CPU6502::Processor<Cartridge<T>>::set_ready_line(true);								break; | 							case 0x02:	m6502_.set_ready_line(true);						break; | ||||||
| 							case 0x03:	update_video();	tia_->reset_horizontal_counter();	break; | 							case 0x03:	update_video();	tia_->reset_horizontal_counter();	break; | ||||||
| 								// TODO: audio will now be out of synchronisation — fix | 								// TODO: audio will now be out of synchronisation — fix | ||||||
|  |  | ||||||
| @@ -156,21 +172,26 @@ template<class T> class Cartridge: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU6502::Processor<Cartridge<T>>::set_ready_line(false); | 			if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false); | ||||||
|  |  | ||||||
| 			return cycles_run_for / 3; | 			return Cycles(cycles_run_for / 3); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void synchronise() { | 		void flush() { | ||||||
| 			update_audio(); | 			update_audio(); | ||||||
| 			update_video(); | 			update_video(); | ||||||
| 			speaker_->flush(); | 			speaker_->flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
|  | 		CPU::MOS6502::Processor<Cartridge<T>, true> m6502_; | ||||||
| 		std::vector<uint8_t> rom_; | 		std::vector<uint8_t> rom_; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		T bus_extender_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* Atari2600_Cartridge_hpp */ | #endif /* Atari2600_Cartridge_hpp */ | ||||||
|   | |||||||
| @@ -9,14 +9,16 @@ | |||||||
| #ifndef Atari2600_CartridgeCommaVid_hpp | #ifndef Atari2600_CartridgeCommaVid_hpp | ||||||
| #define Atari2600_CartridgeCommaVid_hpp | #define Atari2600_CartridgeCommaVid_hpp | ||||||
| 
 | 
 | ||||||
|  | #include "Cartridge.hpp" | ||||||
|  | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> { | class CommaVid: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeCommaVid(const std::vector<uint8_t> &rom) : | 		CommaVid(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size) {} | ||||||
| 			Cartridge(rom) {} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 
 | 
 | ||||||
| @@ -30,13 +32,14 @@ class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> { | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) *value = rom_[address & 2047]; | 			if(isReadOperation(operation)) *value = rom_base_[address & 2047]; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	private: | 	private: | ||||||
| 		uint8_t ram_[1024]; | 		uint8_t ram_[1024]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeCommaVid_hpp */ | #endif /* Atari2600_CartridgeCommaVid_hpp */ | ||||||
| @@ -12,22 +12,23 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | class MNetwork: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeMNetwork(const std::vector<uint8_t> &rom) : | 		MNetwork(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size) { | ||||||
| 			rom_ptr_[0] = rom_.data() + rom_.size() - 4096; | 			rom_ptr_[0] = rom_base + rom_size_ - 4096; | ||||||
| 			rom_ptr_[1] = rom_ptr_[0] + 2048; | 			rom_ptr_[1] = rom_ptr_[0] + 2048; | ||||||
| 			high_ram_ptr_ = high_ram_; | 			high_ram_ptr_ = high_ram_; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1fe0 && address <= 0x1fe6) { | 			if(address >= 0x1fe0 && address <= 0x1fe6) { | ||||||
| 				rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048; | 				rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048; | ||||||
| 			} else if(address == 0x1fe7) { | 			} else if(address == 0x1fe7) { | ||||||
| 				rom_ptr_[0] = nullptr; | 				rom_ptr_[0] = nullptr; | ||||||
| 			} else if(address >= 0x1ff8 && address <= 0x1ffb) { | 			} else if(address >= 0x1ff8 && address <= 0x1ffb) { | ||||||
| @@ -54,7 +55,6 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	private: | 	private: | ||||||
| @@ -63,6 +63,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> { | |||||||
| 		uint8_t low_ram_[1024], high_ram_[1024]; | 		uint8_t low_ram_[1024], high_ram_[1024]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeMNetwork_hpp */ | #endif /* Atari2600_CartridgeMNetwork_hpp */ | ||||||
| @@ -12,22 +12,23 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> { | class MegaBoy: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeMegaBoy(const std::vector<uint8_t> &rom) : | 		MegaBoy(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom), | 			BusExtender(rom_base, rom_size), | ||||||
|  | 			rom_ptr_(rom_base), | ||||||
| 			current_page_(0) { | 			current_page_(0) { | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address == 0x1ff0) { | 			if(address == 0x1ff0) { | ||||||
| 				current_page_ = (current_page_ + 1) & 15; | 				current_page_ = (current_page_ + 1) & 15; | ||||||
| 				rom_ptr_ = rom_.data() + current_page_ * 4096; | 				rom_ptr_ = rom_base_ + current_page_ * 4096; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| @@ -40,6 +41,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> { | |||||||
| 		uint8_t current_page_; | 		uint8_t current_page_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* CartridgeMegaBoy_h */ | #endif /* CartridgeMegaBoy_h */ | ||||||
| @@ -12,24 +12,25 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> { | class ParkerBros: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeParkerBros(const std::vector<uint8_t> &rom) : | 		ParkerBros(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size) { | ||||||
| 			rom_ptr_[0] = rom_.data() + 4096; | 			rom_ptr_[0] = rom_base + 4096; | ||||||
| 			rom_ptr_[1] = rom_ptr_[0] + 1024; | 			rom_ptr_[1] = rom_ptr_[0] + 1024; | ||||||
| 			rom_ptr_[2] = rom_ptr_[1] + 1024; | 			rom_ptr_[2] = rom_ptr_[1] + 1024; | ||||||
| 			rom_ptr_[3] = rom_ptr_[2] + 1024; | 			rom_ptr_[3] = rom_ptr_[2] + 1024; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| 			if(address >= 0x1fe0 && address < 0x1ff8) { | 			if(address >= 0x1fe0 && address < 0x1ff8) { | ||||||
| 				int slot = (address >> 3)&3; | 				int slot = (address >> 3)&3; | ||||||
| 				rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024); | 				rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if(isReadOperation(operation)) { | 			if(isReadOperation(operation)) { | ||||||
| @@ -41,6 +42,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> { | |||||||
| 		uint8_t *rom_ptr_[4]; | 		uint8_t *rom_ptr_[4]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeParkerBros_hpp */ | #endif /* Atari2600_CartridgeParkerBros_hpp */ | ||||||
| @@ -10,23 +10,23 @@ | |||||||
| #define Atari2600_CartridgePitfall2_hpp | #define Atari2600_CartridgePitfall2_hpp | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | class Pitfall2: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgePitfall2(const std::vector<uint8_t> &rom) : | 		Pitfall2(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom), | 			BusExtender(rom_base, rom_size), | ||||||
|  | 			rom_ptr_(rom_base), | ||||||
| 			random_number_generator_(0), | 			random_number_generator_(0), | ||||||
| 			featcher_address_{0, 0, 0, 0, 0, 0, 0, 0}, | 			featcher_address_{0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
| 			mask_{0, 0, 0, 0, 0, 0, 0, 0}, | 			mask_{0, 0, 0, 0, 0, 0, 0, 0}, | ||||||
| 			cycles_since_audio_update_(0) { | 			cycles_since_audio_update_(0) {} | ||||||
| 			rom_ptr_ = rom_.data(); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		void advance_cycles(unsigned int cycles) { | 		void advance_cycles(int cycles) { | ||||||
| 			cycles_since_audio_update_ += cycles; | 			cycles_since_audio_update_ += cycles; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			address &= 0x1fff; | 			address &= 0x1fff; | ||||||
| 			if(!(address & 0x1000)) return; | 			if(!(address & 0x1000)) return; | ||||||
| 
 | 
 | ||||||
| @@ -53,11 +53,11 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 				case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: | 				case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: | ||||||
| 					*value = rom_[8192 + address_for_counter(address & 7)]; | 					*value = rom_base_[8192 + address_for_counter(address & 7)]; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 				case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: | 				case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: | ||||||
| 					*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; | 					*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| #pragma mark - Writes | #pragma mark - Writes | ||||||
| @@ -81,8 +81,8 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 
 | 
 | ||||||
| #pragma mark - Paging | #pragma mark - Paging | ||||||
| 
 | 
 | ||||||
| 				case 0x1ff8: rom_ptr_ = rom_.data();		break; | 				case 0x1ff8: rom_ptr_ = rom_base_;			break; | ||||||
| 				case 0x1ff9: rom_ptr_ = rom_.data() + 4096;	break; | 				case 0x1ff9: rom_ptr_ = rom_base_ + 4096;	break; | ||||||
| 
 | 
 | ||||||
| #pragma mark - Business as usual | #pragma mark - Business as usual | ||||||
| 
 | 
 | ||||||
| @@ -105,8 +105,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 
 | 
 | ||||||
| 		inline uint8_t update_audio() { | 		inline uint8_t update_audio() { | ||||||
| 			const unsigned int clock_divisor = 57; | 			const unsigned int clock_divisor = 57; | ||||||
| 			unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor; | 			int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int(); | ||||||
| 			cycles_since_audio_update_ %= clock_divisor; |  | ||||||
| 
 | 
 | ||||||
| 			int table_position = 0; | 			int table_position = 0; | ||||||
| 			for(int c = 0; c < 3; c++) { | 			for(int c = 0; c < 3; c++) { | ||||||
| @@ -126,9 +125,10 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> { | |||||||
| 		uint8_t random_number_generator_; | 		uint8_t random_number_generator_; | ||||||
| 		uint8_t *rom_ptr_; | 		uint8_t *rom_ptr_; | ||||||
| 		uint8_t audio_channel_[3]; | 		uint8_t audio_channel_[3]; | ||||||
| 		unsigned int cycles_since_audio_update_; | 		Cycles cycles_since_audio_update_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgePitfall2_hpp */ | #endif /* Atari2600_CartridgePitfall2_hpp */ | ||||||
| @@ -12,19 +12,20 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeTigervision: public Cartridge<CartridgeTigervision> { | class Tigervision: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeTigervision(const std::vector<uint8_t> &rom) : | 		Tigervision(uint8_t *rom_base, size_t rom_size) : | ||||||
| 			Cartridge(rom) { | 			BusExtender(rom_base, rom_size) { | ||||||
| 			rom_ptr_[0] = rom_.data() + rom_.size() - 4096; | 			rom_ptr_[0] = rom_base + rom_size - 4096; | ||||||
| 			rom_ptr_[1] = rom_ptr_[0] + 2048; | 			rom_ptr_[1] = rom_ptr_[0] + 2048; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if((address&0x1fff) == 0x3f) { | 			if((address&0x1fff) == 0x3f) { | ||||||
| 				int offset = ((*value) * 2048) & (rom_.size() - 1); | 				int offset = ((*value) * 2048) & (rom_size_ - 1); | ||||||
| 				rom_ptr_[0] = rom_.data() + offset; | 				rom_ptr_[0] = rom_base_ + offset; | ||||||
| 				return; | 				return; | ||||||
| 			} else if((address&0x1000) && isReadOperation(operation)) { | 			} else if((address&0x1000) && isReadOperation(operation)) { | ||||||
| 				*value = rom_ptr_[(address >> 11)&1][address & 2047]; | 				*value = rom_ptr_[(address >> 11)&1][address & 2047]; | ||||||
| @@ -35,6 +36,7 @@ class CartridgeTigervision: public Cartridge<CartridgeTigervision> { | |||||||
| 		uint8_t *rom_ptr_[2]; | 		uint8_t *rom_ptr_[2]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeTigervision_hpp */ | #endif /* Atari2600_CartridgeTigervision_hpp */ | ||||||
| @@ -12,19 +12,20 @@ | |||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
|  | namespace Cartridge { | ||||||
| 
 | 
 | ||||||
| class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> { | class Unpaged: public BusExtender { | ||||||
| 	public: | 	public: | ||||||
| 		CartridgeUnpaged(const std::vector<uint8_t> &rom) : | 		Unpaged(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size) {} | ||||||
| 			Cartridge(rom) {} |  | ||||||
| 
 | 
 | ||||||
| 		void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			if(isReadOperation(operation) && (address & 0x1000)) { | 			if(isReadOperation(operation) && (address & 0x1000)) { | ||||||
| 				*value = rom_[address & (rom_.size() - 1)]; | 				*value = rom_base_[address & (rom_size_ - 1)]; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* Atari2600_CartridgeUnpaged_hpp */ | #endif /* Atari2600_CartridgeUnpaged_hpp */ | ||||||
| @@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) { | |||||||
| /*	speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ | /*	speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::run_for_cycles(int number_of_cycles) | void TIA::run_for(const Cycles cycles) { | ||||||
| { | 	int number_of_cycles = cycles.as_int(); | ||||||
|  |  | ||||||
| 	// if part way through a line, definitely perform a partial, at most up to the end of the line | 	// if part way through a line, definitely perform a partial, at most up to the end of the line | ||||||
| 	if(horizontal_counter_) { | 	if(horizontal_counter_) { | ||||||
| 		int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); | 		int output_cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); | ||||||
| 		output_for_cycles(cycles); | 		output_for_cycles(output_cycles); | ||||||
| 		number_of_cycles -= cycles; | 		number_of_cycles -= output_cycles; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// output full lines for as long as possible | 	// output full lines for as long as possible | ||||||
| @@ -197,8 +198,8 @@ void TIA::set_blank(bool blank) { | |||||||
| void TIA::reset_horizontal_counter() { | void TIA::reset_horizontal_counter() { | ||||||
| } | } | ||||||
|  |  | ||||||
| int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) { | int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) { | ||||||
| 	return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line; | 	return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line; | ||||||
| } | } | ||||||
|  |  | ||||||
| void TIA::set_background_colour(uint8_t colour) { | void TIA::set_background_colour(uint8_t colour) { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #define TIA_hpp | #define TIA_hpp | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
|  |  | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
| @@ -27,10 +28,9 @@ class TIA { | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the | 			Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed. | ||||||
| 			first cycle performed. |  | ||||||
| 		*/ | 		*/ | ||||||
| 		void run_for_cycles(int number_of_cycles); | 		void run_for(const Cycles cycles); | ||||||
| 		void set_output_mode(OutputMode output_mode); | 		void set_output_mode(OutputMode output_mode); | ||||||
|  |  | ||||||
| 		void set_sync(bool sync); | 		void set_sync(bool sync); | ||||||
| @@ -41,7 +41,7 @@ class TIA { | |||||||
| 			@returns the number of cycles between (current TIA time) + from_offset to the current or | 			@returns the number of cycles between (current TIA time) + from_offset to the current or | ||||||
| 			next horizontal blanking period. Returns numbers in the range [0, 227]. | 			next horizontal blanking period. Returns numbers in the range [0, 227]. | ||||||
| 		*/ | 		*/ | ||||||
| 		int get_cycles_until_horizontal_blank(unsigned int from_offset); | 		int get_cycles_until_horizontal_blank(const Cycles from_offset); | ||||||
|  |  | ||||||
| 		void set_background_colour(uint8_t colour); | 		void set_background_colour(uint8_t colour); | ||||||
|  |  | ||||||
| @@ -229,14 +229,14 @@ class TIA { | |||||||
| 				} queue_[4]; | 				} queue_[4]; | ||||||
| 				int queue_read_pointer_, queue_write_pointer_; | 				int queue_read_pointer_, queue_write_pointer_; | ||||||
|  |  | ||||||
| 				inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) { | 				inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) { | ||||||
| 					if(pixel_position == 32 || !graphic[graphic_index]) return; | 					if(output_pixel_position == 32 || !graphic[graphic_index]) return; | ||||||
| 					int output_cursor = 0; | 					int output_cursor = 0; | ||||||
| 					while(pixel_position < 32 && output_cursor < count) { | 					while(output_pixel_position < 32 && output_cursor < count) { | ||||||
| 						int shift = (pixel_position >> 2) ^ reverse_mask; | 						int shift = (output_pixel_position >> 2) ^ output_reverse_mask; | ||||||
| 						target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity; | 						target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity; | ||||||
| 						output_cursor++; | 						output_cursor++; | ||||||
| 						pixel_position += adder; | 						output_pixel_position += output_adder; | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../Outputs/CRT/CRT.hpp" | #include "../Outputs/CRT/CRT.hpp" | ||||||
| #include "../Outputs/Speaker.hpp" | #include "../Outputs/Speaker.hpp" | ||||||
|  | #include "../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace CRTMachine { | namespace CRTMachine { | ||||||
|  |  | ||||||
| @@ -21,15 +22,28 @@ namespace CRTMachine { | |||||||
| */ | */ | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		Machine() : clock_is_unlimited_(false) {} | 		Machine() : clock_is_unlimited_(false), delegate_(nullptr) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | ||||||
|  | 			that an OpenGL context is bound. | ||||||
|  | 		*/ | ||||||
| 		virtual void setup_output(float aspect_ratio) = 0; | 		virtual void setup_output(float aspect_ratio) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Gives the machine a chance to release all owned resources. The caller guarantees that the | ||||||
|  | 			OpenGL context is bound. | ||||||
|  | 		*/ | ||||||
| 		virtual void close_output() = 0; | 		virtual void close_output() = 0; | ||||||
|  |  | ||||||
|  | 		/// @returns The CRT this machine is drawing to. Should not be @c nullptr. | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0; | 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0; | ||||||
|  |  | ||||||
|  | 		/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0; | 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0; | ||||||
|  |  | ||||||
| 		virtual void run_for_cycles(int number_of_cycles) = 0; | 		/// Runs the machine for @c cycles. | ||||||
|  | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
|  |  | ||||||
| 		// TODO: sever the clock-rate stuff. | 		// TODO: sever the clock-rate stuff. | ||||||
| 		double get_clock_rate() { | 		double get_clock_rate() { | ||||||
| @@ -43,7 +57,7 @@ class Machine { | |||||||
| 				virtual void machine_did_change_clock_rate(Machine *machine) = 0; | 				virtual void machine_did_change_clock_rate(Machine *machine) = 0; | ||||||
| 				virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0; | 				virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0; | ||||||
| 		}; | 		}; | ||||||
| 		void set_delegate(Delegate *delegate) { this->delegate_ = delegate; } | 		void set_delegate(Delegate *delegate) { delegate_ = delegate; } | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		void set_clock_rate(double clock_rate) { | 		void set_clock_rate(double clock_rate) { | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ | |||||||
| using namespace Commodore::C1540; | using namespace Commodore::C1540; | ||||||
|  |  | ||||||
| Machine::Machine() : | Machine::Machine() : | ||||||
|  | 		m6502_(*this), | ||||||
| 		shift_register_(0), | 		shift_register_(0), | ||||||
| 		Storage::Disk::Controller(1000000, 4, 300), | 		Storage::Disk::Controller(1000000, 4, 300), | ||||||
| 		serial_port_(new SerialPort), | 		serial_port_(new SerialPort), | ||||||
| @@ -34,7 +35,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu | |||||||
| 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | ||||||
| } | } | ||||||
|  |  | ||||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { | Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 	/* | 	/* | ||||||
| 		Memory map (given that I'm unsure yet on any potential mirroring): | 		Memory map (given that I'm unsure yet on any potential mirroring): | ||||||
|  |  | ||||||
| @@ -63,14 +64,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | |||||||
| 			drive_VIA_.set_register(address, *value); | 			drive_VIA_.set_register(address, *value); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	serial_port_VIA_->run_for_cycles(1); | 	serial_port_VIA_->run_for(Cycles(1)); | ||||||
| 	drive_VIA_.run_for_cycles(1); | 	drive_VIA_.run_for(Cycles(1)); | ||||||
|  |  | ||||||
| 	return 1; | 	return Cycles(1); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Machine::set_rom(const uint8_t *rom) { | void Machine::set_rom(const std::vector<uint8_t> &rom) { | ||||||
| 	memcpy(rom_, rom, sizeof(rom_)); | 	memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size())); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | ||||||
| @@ -79,18 +80,18 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | |||||||
| 	set_drive(drive); | 	set_drive(drive); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Machine::run_for_cycles(int number_of_cycles) { | void Machine::run_for(const Cycles cycles) { | ||||||
| 	CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); | 	m6502_.run_for(cycles); | ||||||
| 	set_motor_on(drive_VIA_.get_motor_enabled()); | 	set_motor_on(drive_VIA_.get_motor_enabled()); | ||||||
| 	if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down | 	if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down | ||||||
| 		Storage::Disk::Controller::run_for_cycles(number_of_cycles); | 		Storage::Disk::Controller::run_for(cycles); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - 6522 delegate | #pragma mark - 6522 delegate | ||||||
|  |  | ||||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { | void Machine::mos6522_did_change_interrupt_status(void *mos6522) { | ||||||
| 	// both VIAs are connected to the IRQ line | 	// both VIAs are connected to the IRQ line | ||||||
| 	set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line()); | 	m6502_.set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line()); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Disk drive | #pragma mark - Disk drive | ||||||
| @@ -108,10 +109,10 @@ void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) | |||||||
| 		drive_VIA_.set_data_input((uint8_t)shift_register_); | 		drive_VIA_.set_data_input((uint8_t)shift_register_); | ||||||
| 		bit_window_offset_ = 0; | 		bit_window_offset_ = 0; | ||||||
| 		if(drive_VIA_.get_should_set_overflow()) { | 		if(drive_VIA_.get_should_set_overflow()) { | ||||||
| 			set_overflow_line(true); | 			m6502_.set_overflow_line(true); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	else set_overflow_line(false); | 	else m6502_.set_overflow_line(false); | ||||||
| } | } | ||||||
|  |  | ||||||
| // the 1540 does not recognise index holes | // the 1540 does not recognise index holes | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #ifndef Commodore1540_hpp | #ifndef Commodore1540_hpp | ||||||
| #define Commodore1540_hpp | #define Commodore1540_hpp | ||||||
|  |  | ||||||
| #include "../../../Processors/6502/CPU6502.hpp" | #include "../../../Processors/6502/6502.hpp" | ||||||
| #include "../../../Components/6522/6522.hpp" | #include "../../../Components/6522/6522.hpp" | ||||||
|  |  | ||||||
| #include "../SerialBus.hpp" | #include "../SerialBus.hpp" | ||||||
| @@ -120,7 +120,7 @@ class SerialPort : public ::Commodore::Serial::Port { | |||||||
| 	Provides an emulation of the C1540. | 	Provides an emulation of the C1540. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CPU6502::Processor<Machine>, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public MOS::MOS6522IRQDelegate::Delegate, | 	public MOS::MOS6522IRQDelegate::Delegate, | ||||||
| 	public DriveVIA::Delegate, | 	public DriveVIA::Delegate, | ||||||
| 	public Storage::Disk::Controller { | 	public Storage::Disk::Controller { | ||||||
| @@ -131,18 +131,18 @@ class Machine: | |||||||
| 		/*! | 		/*! | ||||||
| 			Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size. | 			Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_rom(const uint8_t *rom); | 		void set_rom(const std::vector<uint8_t> &rom); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the serial bus to which this drive should attach itself. | 			Sets the serial bus to which this drive should attach itself. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); | 		void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); | ||||||
|  |  | ||||||
| 		void run_for_cycles(int number_of_cycles); | 		void run_for(const Cycles cycles); | ||||||
| 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | ||||||
|  |  | ||||||
| 		// to satisfy CPU6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); | 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522::Delegate | 		// to satisfy MOS::MOS6522::Delegate | ||||||
| 		virtual void mos6522_did_change_interrupt_status(void *mos6522); | 		virtual void mos6522_did_change_interrupt_status(void *mos6522); | ||||||
| @@ -152,6 +152,8 @@ class Machine: | |||||||
| 		void drive_via_did_set_data_density(void *driveVIA, int density); | 		void drive_via_did_set_data_density(void *driveVIA, int density); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		CPU::MOS6502::Processor<Machine, false> m6502_; | ||||||
|  |  | ||||||
| 		uint8_t ram_[0x800]; | 		uint8_t ram_[0x800]; | ||||||
| 		uint8_t rom_[0x4000]; | 		uint8_t rom_[0x4000]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,18 +1,21 @@ | |||||||
| //
 | //
 | ||||||
| //  Typer.cpp
 | //  CharacterMapper.cpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 05/11/2016.
 | //  Created by Thomas Harte on 03/08/2017.
 | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
| #include "Vic20.hpp" | #include "Vic20.hpp" | ||||||
| 
 | 
 | ||||||
| uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) { | using namespace Commodore::Vic20; | ||||||
| #define KEYS(...)	{__VA_ARGS__, TerminateSequence} | 
 | ||||||
| #define SHIFT(...)	{KeyLShift, __VA_ARGS__, TerminateSequence} | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
|  | #define KEYS(...)	{__VA_ARGS__, EndSequence} | ||||||
|  | #define SHIFT(...)	{KeyLShift, __VA_ARGS__, EndSequence} | ||||||
| #define X			{NotMapped} | #define X			{NotMapped} | ||||||
| 	static Key key_sequences[][3] = { | 	static KeySequence key_sequences[] = { | ||||||
| 		/* NUL */	X,							/* SOH */	X, | 		/* NUL */	X,							/* SOH */	X, | ||||||
| 		/* STX */	X,							/* ETX */	X, | 		/* STX */	X,							/* ETX */	X, | ||||||
| 		/* EOT */	X,							/* ENQ */	X, | 		/* EOT */	X,							/* ENQ */	X, | ||||||
| @@ -80,7 +83,5 @@ uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *type | |||||||
| #undef SHIFT | #undef SHIFT | ||||||
| #undef X | #undef X | ||||||
| 
 | 
 | ||||||
| 	if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr; | 	return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); | ||||||
| 	if(key_sequences[character][0] == NotMapped) return nullptr; |  | ||||||
| 	return (uint16_t *)key_sequences[character]; |  | ||||||
| } | } | ||||||
							
								
								
									
										25
									
								
								Machines/Commodore/Vic-20/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Machines/Commodore/Vic-20/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_Commodore_Vic20_CharacterMapper_hpp | ||||||
|  | #define Machines_Commodore_Vic20_CharacterMapper_hpp | ||||||
|  |  | ||||||
|  | #include "../../Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace Commodore { | ||||||
|  | namespace Vic20 { | ||||||
|  |  | ||||||
|  | class CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	public: | ||||||
|  | 		uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* CharacterMapper_hpp */ | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -13,15 +13,7 @@ | |||||||
| #include "../../CRTMachine.hpp" | #include "../../CRTMachine.hpp" | ||||||
| #include "../../Typer.hpp" | #include "../../Typer.hpp" | ||||||
|  |  | ||||||
| #include "../../../Processors/6502/CPU6502.hpp" | #include <cstdint> | ||||||
| #include "../../../Components/6560/6560.hpp" |  | ||||||
| #include "../../../Components/6522/6522.hpp" |  | ||||||
|  |  | ||||||
| #include "../SerialBus.hpp" |  | ||||||
| #include "../1540/C1540.hpp" |  | ||||||
|  |  | ||||||
| #include "../../../Storage/Tape/Tape.hpp" |  | ||||||
| #include "../../../Storage/Disk/Disk.hpp" |  | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace Vic20 { | namespace Vic20 { | ||||||
| @@ -44,9 +36,8 @@ enum Region { | |||||||
| 	PAL | 	PAL | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #define key(line, mask) (((mask) << 3) | (line)) |  | ||||||
|  |  | ||||||
| enum Key: uint16_t { | enum Key: uint16_t { | ||||||
|  | #define key(line, mask) (((mask) << 3) | (line)) | ||||||
| 	Key2		= key(7, 0x01),		Key4		= key(7, 0x02),		Key6			= key(7, 0x04),		Key8		= key(7, 0x08), | 	Key2		= key(7, 0x01),		Key4		= key(7, 0x02),		Key6			= key(7, 0x04),		Key8		= key(7, 0x08), | ||||||
| 	Key0		= key(7, 0x10),		KeyDash		= key(7, 0x20),		KeyHome			= key(7, 0x40),		KeyF7		= key(7, 0x80), | 	Key0		= key(7, 0x10),		KeyDash		= key(7, 0x20),		KeyHome			= key(7, 0x40),		KeyF7		= key(7, 0x80), | ||||||
| 	KeyQ		= key(6, 0x01),		KeyE		= key(6, 0x02),		KeyT			= key(6, 0x04),		KeyU		= key(6, 0x08), | 	KeyQ		= key(6, 0x01),		KeyE		= key(6, 0x02),		KeyT			= key(6, 0x04),		KeyU		= key(6, 0x08), | ||||||
| @@ -63,8 +54,7 @@ enum Key: uint16_t { | |||||||
| 	KeyI		= key(1, 0x10),		KeyP		= key(1, 0x20),		KeyAsterisk		= key(1, 0x40),		KeyReturn	= key(1, 0x80), | 	KeyI		= key(1, 0x10),		KeyP		= key(1, 0x20),		KeyAsterisk		= key(1, 0x40),		KeyReturn	= key(1, 0x80), | ||||||
| 	Key1		= key(0, 0x01),		Key3		= key(0, 0x02),		Key5			= key(0, 0x04),		Key7		= key(0, 0x08), | 	Key1		= key(0, 0x01),		Key3		= key(0, 0x02),		Key5			= key(0, 0x04),		Key7		= key(0, 0x08), | ||||||
| 	Key9		= key(0, 0x10),		KeyPlus		= key(0, 0x20),		KeyGBP			= key(0, 0x40),		KeyDelete	= key(0, 0x80), | 	Key9		= key(0, 0x10),		KeyPlus		= key(0, 0x20),		KeyGBP			= key(0, 0x40),		KeyDelete	= key(0, 0x80), | ||||||
|  | #undef key | ||||||
| 	TerminateSequence = 0xffff,	NotMapped = 0xfffe |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum JoystickInput { | enum JoystickInput { | ||||||
| @@ -75,152 +65,31 @@ enum JoystickInput { | |||||||
| 	Fire = 0x20 | 	Fire = 0x20 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		UserPortVIA(); |  | ||||||
| 		using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 		uint8_t get_port_input(Port port); |  | ||||||
| 		void set_control_line_output(Port port, Line line, bool value); |  | ||||||
| 		void set_serial_line_state(::Commodore::Serial::Line line, bool value); |  | ||||||
| 		void set_joystick_state(JoystickInput input, bool value); |  | ||||||
| 		void set_port_output(Port port, uint8_t value, uint8_t mask); |  | ||||||
|  |  | ||||||
| 		void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort); |  | ||||||
| 		void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t port_a_; |  | ||||||
| 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; |  | ||||||
| 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 	public: |  | ||||||
| 		KeyboardVIA(); |  | ||||||
| 		using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed); |  | ||||||
| 		void clear_all_keys(); |  | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522 |  | ||||||
| 		uint8_t get_port_input(Port port); |  | ||||||
|  |  | ||||||
| 		void set_port_output(Port port, uint8_t value, uint8_t mask); |  | ||||||
| 		void set_control_line_output(Port port, Line line, bool value); |  | ||||||
|  |  | ||||||
| 		void set_joystick_state(JoystickInput input, bool value); |  | ||||||
|  |  | ||||||
| 		void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t port_b_; |  | ||||||
| 		uint8_t columns_[8]; |  | ||||||
| 		uint8_t activation_mask_; |  | ||||||
| 		std::weak_ptr<::Commodore::Serial::Port> serial_port_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SerialPort : public ::Commodore::Serial::Port { |  | ||||||
| 	public: |  | ||||||
| 		void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level); |  | ||||||
| 		void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		std::weak_ptr<UserPortVIA> user_port_via_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class Vic6560: public MOS::MOS6560<Vic6560> { |  | ||||||
| 	public: |  | ||||||
| 		inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { |  | ||||||
| 			*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO |  | ||||||
| 			*colour_data = colour_memory[address & 0x03ff]; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		uint8_t *video_memory_map[16]; |  | ||||||
| 		uint8_t *colour_memory; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class Machine: | class Machine: | ||||||
| 	public CPU6502::Processor<Machine>, | 	public ConfigurationTarget::Machine, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public MOS::MOS6522IRQDelegate::Delegate, | 	public KeyboardMachine::Machine { | ||||||
| 	public Utility::TypeRecipient, |  | ||||||
| 	public Storage::Tape::BinaryTapePlayer::Delegate, |  | ||||||
| 	public ConfigurationTarget::Machine { |  | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
| 		~Machine(); |  | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, size_t length, const uint8_t *data); | 		/// Creates and returns a Vic-20. | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); | 		static Machine *Vic20(); | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); } | 		/// Sets the contents of the rom in @c slot to the buffer @c data of length @c length. | ||||||
| 		void clear_all_keys() { keyboard_via_->clear_all_keys(); } | 		virtual void set_rom(ROMSlot slot, size_t length, const uint8_t *data) = 0; | ||||||
| 		void set_joystick_state(JoystickInput input, bool isPressed) { | 		// TODO: take a std::vector<uint8_t> to collapse length and data. | ||||||
| 			user_port_via_->set_joystick_state(input, isPressed); |  | ||||||
| 			keyboard_via_->set_joystick_state(input, isPressed); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_memory_size(MemorySize size); | 		/// Sets the state of joystick input @c input. | ||||||
| 		void set_region(Region region); | 		virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0; | ||||||
|  |  | ||||||
| 		inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } | 		/// Sets the memory size of this Vic-20. | ||||||
|  | 		virtual void set_memory_size(MemorySize size) = 0; | ||||||
|  |  | ||||||
| 		// to satisfy CPU6502::Processor | 		/// Sets the region of this Vic-20. | ||||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); | 		virtual void set_region(Region region) = 0; | ||||||
| 		void synchronise() { mos6560_->synchronise(); } |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine | 		/// Enables or disables turbo-speed tape loading. | ||||||
| 		virtual void setup_output(float aspect_ratio); | 		virtual void set_use_fast_tape_hack(bool activate) = 0; | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); } |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); } |  | ||||||
| 		virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); } |  | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522::Delegate |  | ||||||
| 		virtual void mos6522_did_change_interrupt_status(void *mos6522); |  | ||||||
|  |  | ||||||
| 		// for Utility::TypeRecipient |  | ||||||
| 		uint16_t *sequence_for_character(Utility::Typer *typer, char character); |  | ||||||
|  |  | ||||||
| 		// for Tape::Delegate |  | ||||||
| 		virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		uint8_t character_rom_[0x1000]; |  | ||||||
| 		uint8_t basic_rom_[0x2000]; |  | ||||||
| 		uint8_t kernel_rom_[0x2000]; |  | ||||||
| 		uint8_t expansion_ram_[0x8000]; |  | ||||||
|  |  | ||||||
| 		uint8_t *rom_; |  | ||||||
| 		uint16_t rom_address_, rom_length_; |  | ||||||
|  |  | ||||||
| 		uint8_t user_basic_memory_[0x0400]; |  | ||||||
| 		uint8_t screen_memory_[0x1000]; |  | ||||||
| 		uint8_t colour_memory_[0x0400]; |  | ||||||
| 		std::unique_ptr<uint8_t> drive_rom_; |  | ||||||
|  |  | ||||||
| 		uint8_t *processor_read_memory_map_[64]; |  | ||||||
| 		uint8_t *processor_write_memory_map_[64]; |  | ||||||
| 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length); |  | ||||||
|  |  | ||||||
| 		Region region_; |  | ||||||
|  |  | ||||||
| 		std::unique_ptr<Vic6560> mos6560_; |  | ||||||
| 		std::shared_ptr<UserPortVIA> user_port_via_; |  | ||||||
| 		std::shared_ptr<KeyboardVIA> keyboard_via_; |  | ||||||
| 		std::shared_ptr<SerialPort> serial_port_; |  | ||||||
| 		std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; |  | ||||||
|  |  | ||||||
| 		// Tape |  | ||||||
| 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; |  | ||||||
| 		bool use_fast_tape_hack_; |  | ||||||
| 		bool is_running_at_zero_cost_; |  | ||||||
|  |  | ||||||
| 		// Disk |  | ||||||
| 		std::shared_ptr<::Commodore::C1540::Machine> c1540_; |  | ||||||
| 		void install_disk_rom(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,11 +15,19 @@ namespace ConfigurationTarget { | |||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target | 	A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target | ||||||
| 	and configure itself appropriately. | 	and configure itself appropriately, or accept a list of media subsequently to insert. | ||||||
| */ | */ | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
|  | 		/// Instructs the machine to configure itself as described by @c target and insert the included media. | ||||||
| 		virtual void configure_as_target(const StaticAnalyser::Target &target) = 0; | 		virtual void configure_as_target(const StaticAnalyser::Target &target) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Requests that the machine insert @c media as a modification to current state | ||||||
|  |  | ||||||
|  | 			@returns @c true if any media was inserted; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		virtual bool insert_media(const StaticAnalyser::Media &media) = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,27 +1,22 @@ | |||||||
| //
 | //
 | ||||||
| //  Typer.cpp
 | //  CharacterMapper.cpp
 | ||||||
| //  Clock Signal
 | //  Clock Signal
 | ||||||
| //
 | //
 | ||||||
| //  Created by Thomas Harte on 05/11/2016.
 | //  Created by Thomas Harte on 03/08/2017.
 | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved.
 | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
| #include "Electron.hpp" | #include "Electron.hpp" | ||||||
| 
 | 
 | ||||||
| int Electron::Machine::get_typer_delay() { | using namespace Electron; | ||||||
| 	return get_is_resetting() ? 625*25*128 : 0;	// wait one second if resetting
 |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| int Electron::Machine::get_typer_frequency() { | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
| 	return 625*128*2;	// accept a new character every two frames
 | #define KEYS(...)	{__VA_ARGS__, EndSequence} | ||||||
| } | #define SHIFT(...)	{KeyShift, __VA_ARGS__, EndSequence} | ||||||
| 
 | #define CTRL(...)	{KeyControl, __VA_ARGS__, EndSequence} | ||||||
| uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) { |  | ||||||
| #define KEYS(...)	{__VA_ARGS__, TerminateSequence} |  | ||||||
| #define SHIFT(...)	{KeyShift, __VA_ARGS__, TerminateSequence} |  | ||||||
| #define CTRL(...)	{KeyControl, __VA_ARGS__, TerminateSequence} |  | ||||||
| #define X			{NotMapped} | #define X			{NotMapped} | ||||||
| 	static Key key_sequences[][3] = { | 	static KeySequence key_sequences[] = { | ||||||
| 		/* NUL */	X,							/* SOH */	X, | 		/* NUL */	X,							/* SOH */	X, | ||||||
| 		/* STX */	X,							/* ETX */	X, | 		/* STX */	X,							/* ETX */	X, | ||||||
| 		/* EOT */	X,							/* ENQ */	X, | 		/* EOT */	X,							/* ENQ */	X, | ||||||
| @@ -91,7 +86,5 @@ uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char | |||||||
| #undef SHIFT | #undef SHIFT | ||||||
| #undef X | #undef X | ||||||
| 
 | 
 | ||||||
| 	if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr; | 	return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); | ||||||
| 	if(key_sequences[character][0] == NotMapped) return nullptr; |  | ||||||
| 	return (uint16_t *)key_sequences[character]; |  | ||||||
| } | } | ||||||
							
								
								
									
										23
									
								
								Machines/Electron/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Machines/Electron/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_Electron_CharacterMapper_hpp | ||||||
|  | #define Machines_Electron_CharacterMapper_hpp | ||||||
|  |  | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace Electron { | ||||||
|  |  | ||||||
|  | class CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	public: | ||||||
|  | 		uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Machines_Electron_CharacterMapper_hpp */ | ||||||
| @@ -8,394 +8,474 @@ | |||||||
|  |  | ||||||
| #include "Electron.hpp" | #include "Electron.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Processors/6502/6502.hpp" | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
|  | #include "Interrupts.hpp" | ||||||
|  | #include "Plus3.hpp" | ||||||
|  | #include "Speaker.hpp" | ||||||
|  | #include "Tape.hpp" | ||||||
|  | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | namespace Electron { | ||||||
|  |  | ||||||
|  | class ConcreteMachine: | ||||||
|  | 	public Machine, | ||||||
|  | 	public CPU::MOS6502::BusHandler, | ||||||
|  | 	public Tape::Delegate, | ||||||
|  | 	public Utility::TypeRecipient { | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
|  | 				m6502_(*this), | ||||||
|  | 				interrupt_control_(0), | ||||||
|  | 				interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80), | ||||||
|  | 				cycles_since_audio_update_(0), | ||||||
|  | 				use_fast_tape_hack_(false), | ||||||
|  | 				cycles_until_display_interrupt_(0) { | ||||||
|  | 			memset(key_states_, 0, sizeof(key_states_)); | ||||||
|  | 			for(int c = 0; c < 16; c++) | ||||||
|  | 				memset(roms_[c], 0xff, 16384); | ||||||
|  |  | ||||||
|  | 			tape_.set_delegate(this); | ||||||
|  | 			set_clock_rate(2000000); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) override final { | ||||||
|  | 			uint8_t *target = nullptr; | ||||||
|  | 			switch(slot) { | ||||||
|  | 				case ROMSlotDFS:	dfs_ = data;			return; | ||||||
|  | 				case ROMSlotADFS:	adfs_ = data;			return; | ||||||
|  |  | ||||||
|  | 				case ROMSlotOS:		target = os_;			break; | ||||||
|  | 				default: | ||||||
|  | 					target = roms_[slot]; | ||||||
|  | 					rom_write_masks_[slot] = is_writeable; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			memcpy(target, &data[0], std::min((size_t)16384, data.size())); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
|  | 			if(key == KeyBreak) { | ||||||
|  | 				m6502_.set_reset_line(isPressed); | ||||||
|  | 			} else { | ||||||
|  | 				if(isPressed) | ||||||
|  | 					key_states_[key >> 4] |= key&0xf; | ||||||
|  | 				else | ||||||
|  | 					key_states_[key >> 4] &= ~(key&0xf); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			memset(key_states_, 0, sizeof(key_states_)); | ||||||
|  | 			if(is_holding_shift_) set_key_state(KeyShift, true); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_use_fast_tape_hack(bool activate) override final { | ||||||
|  | 			use_fast_tape_hack_ = activate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) override final { | ||||||
|  | 			if(target.loadingCommand.length()) { | ||||||
|  | 				set_typer_for_string(target.loadingCommand.c_str()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.acorn.should_shift_restart) { | ||||||
|  | 				shift_restart_counter_ = 1000000; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.acorn.has_dfs || target.acorn.has_adfs) { | ||||||
|  | 				plus3_.reset(new Plus3); | ||||||
|  |  | ||||||
|  | 				if(target.acorn.has_dfs) { | ||||||
|  | 					set_rom(ROMSlot0, dfs_, true); | ||||||
|  | 				} | ||||||
|  | 				if(target.acorn.has_adfs) { | ||||||
|  | 					set_rom(ROMSlot4, adfs_, true); | ||||||
|  | 					set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override final { | ||||||
|  | 			if(!media.tapes.empty()) { | ||||||
|  | 				tape_.set_tape(media.tapes.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(!media.disks.empty() && plus3_) { | ||||||
|  | 				plus3_->set_disk(media.disks.front(), 0); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			ROMSlot slot = ROMSlot12; | ||||||
|  | 			for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) { | ||||||
|  | 				set_rom(slot, cartridge->get_segments().front().data, false); | ||||||
|  | 				slot = (ROMSlot)(((int)slot + 1)&15); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
|  | 			unsigned int cycles = 1; | ||||||
|  |  | ||||||
|  | 			if(address < 0x8000) { | ||||||
|  | 				if(isReadOperation(operation)) { | ||||||
|  | 					*value = ram_[address]; | ||||||
|  | 				} else { | ||||||
|  | 					if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display(); | ||||||
|  | 					ram_[address] = *value; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// for the entire frame, RAM is accessible only on odd cycles; in modes below 4 | ||||||
|  | 				// it's also accessible only outside of the pixel regions | ||||||
|  | 				cycles += video_output_->get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1); | ||||||
|  | 			} else { | ||||||
|  | 				switch(address & 0xff0f) { | ||||||
|  | 					case 0xfe00: | ||||||
|  | 						if(isReadOperation(operation)) { | ||||||
|  | 							*value = interrupt_status_; | ||||||
|  | 							interrupt_status_ &= ~PowerOnReset; | ||||||
|  | 						} else { | ||||||
|  | 							interrupt_control_ = (*value) & ~1; | ||||||
|  | 							evaluate_interrupts(); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 0xfe07: | ||||||
|  | 						if(!isReadOperation(operation)) { | ||||||
|  | 							// update speaker mode | ||||||
|  | 							bool new_speaker_is_enabled = (*value & 6) == 2; | ||||||
|  | 							if(new_speaker_is_enabled != speaker_is_enabled_) { | ||||||
|  | 								update_audio(); | ||||||
|  | 								speaker_->set_is_enabled(new_speaker_is_enabled); | ||||||
|  | 								speaker_is_enabled_ = new_speaker_is_enabled; | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							tape_.set_is_enabled((*value & 6) != 6); | ||||||
|  | 							tape_.set_is_in_input_mode((*value & 6) == 0); | ||||||
|  | 							tape_.set_is_running(((*value)&0x40) ? true : false); | ||||||
|  |  | ||||||
|  | 							// TODO: caps lock LED | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 					// deliberate fallthrough | ||||||
|  | 					case 0xfe02: case 0xfe03: | ||||||
|  | 					case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: | ||||||
|  | 					case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: | ||||||
|  | 						if(!isReadOperation(operation)) { | ||||||
|  | 							update_display(); | ||||||
|  | 							video_output_->set_register(address, *value); | ||||||
|  | 							video_access_range_ = video_output_->get_memory_access_range(); | ||||||
|  | 							queue_next_display_interrupt(); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 0xfe04: | ||||||
|  | 						if(isReadOperation(operation)) { | ||||||
|  | 							*value = tape_.get_data_register(); | ||||||
|  | 							tape_.clear_interrupts(Interrupt::ReceiveDataFull); | ||||||
|  | 						} else { | ||||||
|  | 							tape_.set_data_register(*value); | ||||||
|  | 							tape_.clear_interrupts(Interrupt::TransmitDataEmpty); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 0xfe05: | ||||||
|  | 						if(!isReadOperation(operation)) { | ||||||
|  | 							const uint8_t interruptDisable = (*value)&0xf0; | ||||||
|  | 							if( interruptDisable ) { | ||||||
|  | 								if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd; | ||||||
|  | 								if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock; | ||||||
|  | 								if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect; | ||||||
|  | 								evaluate_interrupts(); | ||||||
|  |  | ||||||
|  | 								// TODO: NMI | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							// latch the paged ROM in case external hardware is being emulated | ||||||
|  | 							active_rom_ = (Electron::ROMSlot)(*value & 0xf); | ||||||
|  |  | ||||||
|  | 							// apply the ULA's test | ||||||
|  | 							if(*value & 0x08) { | ||||||
|  | 								if(*value & 0x04) { | ||||||
|  | 									keyboard_is_active_ = false; | ||||||
|  | 									basic_is_active_ = false; | ||||||
|  | 								} else { | ||||||
|  | 									keyboard_is_active_ = !(*value & 0x02); | ||||||
|  | 									basic_is_active_ = !keyboard_is_active_; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 0xfe06: | ||||||
|  | 						if(!isReadOperation(operation)) { | ||||||
|  | 							update_audio(); | ||||||
|  | 							speaker_->set_divider(*value); | ||||||
|  | 							tape_.set_counter(*value); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: | ||||||
|  | 						if(plus3_ && (address&0x00f0) == 0x00c0) { | ||||||
|  | 							if(is_holding_shift_ && address == 0xfcc4) { | ||||||
|  | 								is_holding_shift_ = false; | ||||||
|  | 								set_key_state(KeyShift, false); | ||||||
|  | 							} | ||||||
|  | 							if(isReadOperation(operation)) | ||||||
|  | 								*value = plus3_->get_register(address); | ||||||
|  | 							else | ||||||
|  | 								plus3_->set_register(address, *value); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 					case 0xfc00: | ||||||
|  | 						if(plus3_ && (address&0x00f0) == 0x00c0) { | ||||||
|  | 							if(!isReadOperation(operation)) { | ||||||
|  | 								plus3_->set_control_register(*value); | ||||||
|  | 							} else *value = 1; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					default: | ||||||
|  | 						if(address >= 0xc000) { | ||||||
|  | 							if(isReadOperation(operation)) { | ||||||
|  | 								if( | ||||||
|  | 									use_fast_tape_hack_ && | ||||||
|  | 									tape_.has_tape() && | ||||||
|  | 									(operation == CPU::MOS6502::BusOperation::ReadOpcode) && | ||||||
|  | 									( | ||||||
|  | 										(address == 0xf4e5) || (address == 0xf4e6) ||	// double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 | ||||||
|  | 										(address == 0xf6de) || (address == 0xf6df) ||	// act to disable the normal branch into tape-handling | ||||||
|  | 										(address == 0xf6fa) || (address == 0xf6fb) ||	// code, forcing the OS along the serially-accessed ROM | ||||||
|  | 										(address == 0xfa51) || (address == 0xfa52) ||	// pathway. | ||||||
|  |  | ||||||
|  | 										(address == 0xf0a8)								// 0xf0a8 is from where a service call would normally be | ||||||
|  | 																						// dispatched; we can check whether it would be call 14 | ||||||
|  | 																						// (i.e. read byte) and, if so, whether the OS was about to | ||||||
|  | 																						// issue a read byte call to a ROM despite being the tape | ||||||
|  | 																						// FS being selected. If so then this is a get byte that | ||||||
|  | 																						// we should service synthetically. Put the byte into Y | ||||||
|  | 																						// and set A to zero to report that action was taken, then | ||||||
|  | 																						// allow the PC read to return an RTS. | ||||||
|  | 									) | ||||||
|  | 								) { | ||||||
|  | 									uint8_t service_call = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X); | ||||||
|  | 									if(address == 0xf0a8) { | ||||||
|  | 										if(!ram_[0x247] && service_call == 14) { | ||||||
|  | 											tape_.set_delegate(nullptr); | ||||||
|  |  | ||||||
|  | 											// TODO: handle tape wrap around. | ||||||
|  |  | ||||||
|  | 											int cycles_left_while_plausibly_in_data = 50; | ||||||
|  | 											tape_.clear_interrupts(Interrupt::ReceiveDataFull); | ||||||
|  | 											while(!tape_.get_tape()->is_at_end()) { | ||||||
|  | 												tape_.run_for_input_pulse(); | ||||||
|  | 												cycles_left_while_plausibly_in_data--; | ||||||
|  | 												if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false; | ||||||
|  | 												if(	(tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) && | ||||||
|  | 													(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a) | ||||||
|  | 												) break; | ||||||
|  | 											} | ||||||
|  | 											tape_.set_delegate(this); | ||||||
|  | 											tape_.clear_interrupts(Interrupt::ReceiveDataFull); | ||||||
|  | 											interrupt_status_ |= tape_.get_interrupt_status(); | ||||||
|  |  | ||||||
|  | 											fast_load_is_in_data_ = true; | ||||||
|  | 											m6502_.set_value_of_register(CPU::MOS6502::Register::A, 0); | ||||||
|  | 											m6502_.set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register()); | ||||||
|  | 											*value = 0x60; // 0x60 is RTS | ||||||
|  | 										} | ||||||
|  | 										else *value = os_[address & 16383]; | ||||||
|  | 									} | ||||||
|  | 									else *value = 0xea; | ||||||
|  | 								} else { | ||||||
|  | 									*value = os_[address & 16383]; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							if(isReadOperation(operation)) { | ||||||
|  | 								*value = roms_[active_rom_][address & 16383]; | ||||||
|  | 								if(keyboard_is_active_) { | ||||||
|  | 									*value &= 0xf0; | ||||||
|  | 									for(int address_line = 0; address_line < 14; address_line++) { | ||||||
|  | 										if(!(address&(1 << address_line))) *value |= key_states_[address_line]; | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 								if(basic_is_active_) { | ||||||
|  | 									*value &= roms_[ROMSlotBASIC][address & 16383]; | ||||||
|  | 								} | ||||||
|  | 							} else if(rom_write_masks_[active_rom_]) { | ||||||
|  | 								roms_[active_rom_][address & 16383] = *value; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			cycles_since_display_update_ += Cycles((int)cycles); | ||||||
|  | 			cycles_since_audio_update_ += Cycles((int)cycles); | ||||||
|  | 			if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); | ||||||
|  | 			tape_.run_for(Cycles((int)cycles)); | ||||||
|  |  | ||||||
|  | 			cycles_until_display_interrupt_ -= cycles; | ||||||
|  | 			if(cycles_until_display_interrupt_ < 0) { | ||||||
|  | 				signal_interrupt(next_display_interrupt_); | ||||||
|  | 				update_display(); | ||||||
|  | 				queue_next_display_interrupt(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(typer_) typer_->run_for(Cycles((int)cycles)); | ||||||
|  | 			if(plus3_) plus3_->run_for(Cycles(4*(int)cycles)); | ||||||
|  | 			if(shift_restart_counter_) { | ||||||
|  | 				shift_restart_counter_ -= cycles; | ||||||
|  | 				if(shift_restart_counter_ <= 0) { | ||||||
|  | 					shift_restart_counter_ = 0; | ||||||
|  | 					m6502_.set_power_on(true); | ||||||
|  | 					set_key_state(KeyShift, true); | ||||||
|  | 					is_holding_shift_ = true; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return Cycles((int)cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline void flush() { | ||||||
|  | 			update_display(); | ||||||
|  | 			update_audio(); | ||||||
|  | 			speaker_->flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void setup_output(float aspect_ratio) override final { | ||||||
|  | 			video_output_.reset(new VideoOutput(ram_)); | ||||||
|  |  | ||||||
|  | 			// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; | ||||||
|  | 			// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So | ||||||
|  | 			// run the speaker at a 2000000Hz input rate, at least for the time being. | ||||||
|  | 			speaker_.reset(new Speaker); | ||||||
|  | 			speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void close_output() override final { | ||||||
|  | 			video_output_.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::CRT::CRT> get_crt() override final { | ||||||
|  | 			return video_output_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::Speaker> get_speaker() override final { | ||||||
|  | 			return speaker_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override final { | ||||||
|  | 			m6502_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void tape_did_change_interrupt_status(Tape *tape) override final { | ||||||
|  | 			interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); | ||||||
|  | 			evaluate_interrupts(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		HalfCycles get_typer_delay() override final { | ||||||
|  | 			return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0);	// wait one second if resetting | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		HalfCycles get_typer_frequency() override final { | ||||||
|  | 			return Cycles(625*128*2);	// accept a new character every two frames | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_typer_for_string(const char *string) override final { | ||||||
|  | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper()); | ||||||
|  | 			Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		inline void update_display() { | ||||||
|  | 			if(cycles_since_display_update_ > 0) { | ||||||
|  | 				video_output_->run_for(cycles_since_display_update_.flush()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void queue_next_display_interrupt() { | ||||||
|  | 			VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt(); | ||||||
|  | 			cycles_until_display_interrupt_ = next_interrupt.cycles; | ||||||
|  | 			next_display_interrupt_ = next_interrupt.interrupt; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void update_audio() { | ||||||
|  | 			if(cycles_since_audio_update_ > 0) { | ||||||
|  | 				speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider))); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void signal_interrupt(Interrupt interrupt) { | ||||||
|  | 			interrupt_status_ |= interrupt; | ||||||
|  | 			evaluate_interrupts(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void clear_interrupt(Interrupt interrupt) { | ||||||
|  | 			interrupt_status_ &= ~interrupt; | ||||||
|  | 			evaluate_interrupts(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void evaluate_interrupts() { | ||||||
|  | 			if(interrupt_status_ & interrupt_control_) { | ||||||
|  | 				interrupt_status_ |= 1; | ||||||
|  | 			} else { | ||||||
|  | 				interrupt_status_ &= ~1; | ||||||
|  | 			} | ||||||
|  | 			m6502_.set_irq_line(interrupt_status_ & 1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
|  | 		// Things that directly constitute the memory map. | ||||||
|  | 		uint8_t roms_[16][16384]; | ||||||
|  | 		bool rom_write_masks_[16]; | ||||||
|  | 		uint8_t os_[16384], ram_[32768]; | ||||||
|  | 		std::vector<uint8_t> dfs_, adfs_; | ||||||
|  |  | ||||||
|  | 		// Paging | ||||||
|  | 		ROMSlot active_rom_; | ||||||
|  | 		bool keyboard_is_active_, basic_is_active_; | ||||||
|  |  | ||||||
|  | 		// Interrupt and keyboard state | ||||||
|  | 		uint8_t interrupt_status_, interrupt_control_; | ||||||
|  | 		uint8_t key_states_[14]; | ||||||
|  |  | ||||||
|  | 		// Counters related to simultaneous subsystems | ||||||
|  | 		Cycles cycles_since_display_update_; | ||||||
|  | 		Cycles cycles_since_audio_update_; | ||||||
|  | 		int cycles_until_display_interrupt_; | ||||||
|  | 		Interrupt next_display_interrupt_; | ||||||
|  | 		VideoOutput::Range video_access_range_; | ||||||
|  |  | ||||||
|  | 		// Tape | ||||||
|  | 		Tape tape_; | ||||||
|  | 		bool use_fast_tape_hack_; | ||||||
|  | 		bool fast_load_is_in_data_; | ||||||
|  |  | ||||||
|  | 		// Disk | ||||||
|  | 		std::unique_ptr<Plus3> plus3_; | ||||||
|  | 		bool is_holding_shift_; | ||||||
|  | 		int shift_restart_counter_; | ||||||
|  |  | ||||||
|  | 		// Outputs | ||||||
|  | 		std::unique_ptr<VideoOutput> video_output_; | ||||||
|  | 		std::shared_ptr<Speaker> speaker_; | ||||||
|  | 		bool speaker_is_enabled_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| #pragma mark - Lifecycle | Machine *Machine::Electron() { | ||||||
|  | 	return new Electron::ConcreteMachine; | ||||||
| Machine::Machine() : |  | ||||||
| 		interrupt_control_(0), |  | ||||||
| 		interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80), |  | ||||||
| 		cycles_since_display_update_(0), |  | ||||||
| 		cycles_since_audio_update_(0), |  | ||||||
| 		use_fast_tape_hack_(false), |  | ||||||
| 		cycles_until_display_interrupt_(0) { |  | ||||||
| 	memset(key_states_, 0, sizeof(key_states_)); |  | ||||||
| 	for(int c = 0; c < 16; c++) |  | ||||||
| 		memset(roms_[c], 0xff, 16384); |  | ||||||
|  |  | ||||||
| 	tape_.set_delegate(this); |  | ||||||
| 	set_clock_rate(2000000); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Output | Machine::~Machine() {} | ||||||
|  |  | ||||||
| void Machine::setup_output(float aspect_ratio) { |  | ||||||
| 	video_output_.reset(new VideoOutput(ram_)); |  | ||||||
|  |  | ||||||
| 	// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; |  | ||||||
| 	// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So |  | ||||||
| 	// run the speaker at a 2000000Hz input rate, at least for the time being. |  | ||||||
| 	speaker_.reset(new Speaker); |  | ||||||
| 	speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::close_output() { |  | ||||||
| 	video_output_.reset(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() { |  | ||||||
| 	return video_output_->get_crt(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() { |  | ||||||
| 	return speaker_; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - The keyboard |  | ||||||
|  |  | ||||||
| void Machine::clear_all_keys() { |  | ||||||
| 	memset(key_states_, 0, sizeof(key_states_)); |  | ||||||
| 	if(is_holding_shift_) set_key_state(KeyShift, true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_key_state(uint16_t key, bool isPressed) { |  | ||||||
| 	if(key == KeyBreak) { |  | ||||||
| 		set_reset_line(isPressed); |  | ||||||
| 	} else { |  | ||||||
| 		if(isPressed) |  | ||||||
| 			key_states_[key >> 4] |= key&0xf; |  | ||||||
| 		else |  | ||||||
| 			key_states_[key >> 4] &= ~(key&0xf); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Machine configuration |  | ||||||
|  |  | ||||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { |  | ||||||
| 	if(target.tapes.size()) { |  | ||||||
| 		tape_.set_tape(target.tapes.front()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.disks.size()) { |  | ||||||
| 		plus3_.reset(new Plus3); |  | ||||||
|  |  | ||||||
| 		if(target.acorn.has_dfs) { |  | ||||||
| 			set_rom(ROMSlot0, dfs_, true); |  | ||||||
| 		} |  | ||||||
| 		if(target.acorn.has_adfs) { |  | ||||||
| 			set_rom(ROMSlot4, adfs_, true); |  | ||||||
| 			set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		plus3_->set_disk(target.disks.front(), 0); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ROMSlot slot = ROMSlot12; |  | ||||||
| 	for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) { |  | ||||||
| 		set_rom(slot, cartridge->get_segments().front().data, false); |  | ||||||
| 		slot = (ROMSlot)(((int)slot + 1)&15); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.loadingCommand.length()) {	// TODO: and automatic loading option enabled |  | ||||||
| 		set_typer_for_string(target.loadingCommand.c_str()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.acorn.should_shift_restart) { |  | ||||||
| 		shift_restart_counter_ = 1000000; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) { |  | ||||||
| 	uint8_t *target = nullptr; |  | ||||||
| 	switch(slot) { |  | ||||||
| 		case ROMSlotDFS:	dfs_ = data;			return; |  | ||||||
| 		case ROMSlotADFS:	adfs_ = data;			return; |  | ||||||
|  |  | ||||||
| 		case ROMSlotOS:		target = os_;			break; |  | ||||||
| 		default: |  | ||||||
| 			target = roms_[slot]; |  | ||||||
| 			rom_write_masks_[slot] = is_writeable; |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	memcpy(target, &data[0], std::min((size_t)16384, data.size())); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - The bus |  | ||||||
|  |  | ||||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { |  | ||||||
| 	unsigned int cycles = 1; |  | ||||||
|  |  | ||||||
| 	if(address < 0x8000) { |  | ||||||
| 		if(isReadOperation(operation)) { |  | ||||||
| 			*value = ram_[address]; |  | ||||||
| 		} else { |  | ||||||
| 			if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display(); |  | ||||||
| 			ram_[address] = *value; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// for the entire frame, RAM is accessible only on odd cycles; in modes below 4 |  | ||||||
| 		// it's also accessible only outside of the pixel regions |  | ||||||
| 		cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1)); |  | ||||||
| 	} else { |  | ||||||
| 		switch(address & 0xff0f) { |  | ||||||
| 			case 0xfe00: |  | ||||||
| 				if(isReadOperation(operation)) { |  | ||||||
| 					*value = interrupt_status_; |  | ||||||
| 					interrupt_status_ &= ~PowerOnReset; |  | ||||||
| 				} else { |  | ||||||
| 					interrupt_control_ = (*value) & ~1; |  | ||||||
| 					evaluate_interrupts(); |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
| 			case 0xfe07: |  | ||||||
| 				if(!isReadOperation(operation)) { |  | ||||||
| 					// update speaker mode |  | ||||||
| 					bool new_speaker_is_enabled = (*value & 6) == 2; |  | ||||||
| 					if(new_speaker_is_enabled != speaker_is_enabled_) { |  | ||||||
| 						update_audio(); |  | ||||||
| 						speaker_->set_is_enabled(new_speaker_is_enabled); |  | ||||||
| 						speaker_is_enabled_ = new_speaker_is_enabled; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					tape_.set_is_enabled((*value & 6) != 6); |  | ||||||
| 					tape_.set_is_in_input_mode((*value & 6) == 0); |  | ||||||
| 					tape_.set_is_running(((*value)&0x40) ? true : false); |  | ||||||
|  |  | ||||||
| 					// TODO: caps lock LED |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 			// deliberate fallthrough |  | ||||||
| 			case 0xfe02: case 0xfe03: |  | ||||||
| 			case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: |  | ||||||
| 			case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: |  | ||||||
| 				if(!isReadOperation(operation)) { |  | ||||||
| 					update_display(); |  | ||||||
| 					video_output_->set_register(address, *value); |  | ||||||
| 					video_access_range_ = video_output_->get_memory_access_range(); |  | ||||||
| 					queue_next_display_interrupt(); |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
| 			case 0xfe04: |  | ||||||
| 				if(isReadOperation(operation)) { |  | ||||||
| 					*value = tape_.get_data_register(); |  | ||||||
| 					tape_.clear_interrupts(Interrupt::ReceiveDataFull); |  | ||||||
| 				} else { |  | ||||||
| 					tape_.set_data_register(*value); |  | ||||||
| 					tape_.clear_interrupts(Interrupt::TransmitDataEmpty); |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
| 			case 0xfe05: |  | ||||||
| 				if(!isReadOperation(operation)) { |  | ||||||
| 					const uint8_t interruptDisable = (*value)&0xf0; |  | ||||||
| 					if( interruptDisable ) { |  | ||||||
| 						if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd; |  | ||||||
| 						if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock; |  | ||||||
| 						if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect; |  | ||||||
| 						evaluate_interrupts(); |  | ||||||
|  |  | ||||||
| 						// TODO: NMI |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					// latch the paged ROM in case external hardware is being emulated |  | ||||||
| 					active_rom_ = (Electron::ROMSlot)(*value & 0xf); |  | ||||||
|  |  | ||||||
| 					// apply the ULA's test |  | ||||||
| 					if(*value & 0x08) { |  | ||||||
| 						if(*value & 0x04) { |  | ||||||
| 							keyboard_is_active_ = false; |  | ||||||
| 							basic_is_active_ = false; |  | ||||||
| 						} else { |  | ||||||
| 							keyboard_is_active_ = !(*value & 0x02); |  | ||||||
| 							basic_is_active_ = !keyboard_is_active_; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
| 			case 0xfe06: |  | ||||||
| 				if(!isReadOperation(operation)) { |  | ||||||
| 					update_audio(); |  | ||||||
| 					speaker_->set_divider(*value); |  | ||||||
| 					tape_.set_counter(*value); |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
|  |  | ||||||
| 			case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: |  | ||||||
| 				if(plus3_ && (address&0x00f0) == 0x00c0) { |  | ||||||
| 					if(is_holding_shift_ && address == 0xfcc4) { |  | ||||||
| 						is_holding_shift_ = false; |  | ||||||
| 						set_key_state(KeyShift, false); |  | ||||||
| 					} |  | ||||||
| 					if(isReadOperation(operation)) |  | ||||||
| 						*value = plus3_->get_register(address); |  | ||||||
| 					else |  | ||||||
| 						plus3_->set_register(address, *value); |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
| 			case 0xfc00: |  | ||||||
| 				if(plus3_ && (address&0x00f0) == 0x00c0) { |  | ||||||
| 					if(!isReadOperation(operation)) { |  | ||||||
| 						plus3_->set_control_register(*value); |  | ||||||
| 					} else *value = 1; |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
|  |  | ||||||
| 			default: |  | ||||||
| 				if(address >= 0xc000) { |  | ||||||
| 					if(isReadOperation(operation)) { |  | ||||||
| 						if( |  | ||||||
| 							use_fast_tape_hack_ && |  | ||||||
| 							tape_.has_tape() && |  | ||||||
| 							(operation == CPU6502::BusOperation::ReadOpcode) && |  | ||||||
| 							( |  | ||||||
| 								(address == 0xf4e5) || (address == 0xf4e6) ||	// double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 |  | ||||||
| 								(address == 0xf6de) || (address == 0xf6df) ||	// act to disable the normal branch into tape-handling |  | ||||||
| 								(address == 0xf6fa) || (address == 0xf6fb) ||	// code, forcing the OS along the serially-accessed ROM |  | ||||||
| 								(address == 0xfa51) || (address == 0xfa52) ||	// pathway. |  | ||||||
|  |  | ||||||
| 								(address == 0xf0a8)								// 0xf0a8 is from where a service call would normally be |  | ||||||
| 																				// dispatched; we can check whether it would be call 14 |  | ||||||
| 																				// (i.e. read byte) and, if so, whether the OS was about to |  | ||||||
| 																				// issue a read byte call to a ROM despite being the tape |  | ||||||
| 																				// FS being selected. If so then this is a get byte that |  | ||||||
| 																				// we should service synthetically. Put the byte into Y |  | ||||||
| 																				// and set A to zero to report that action was taken, then |  | ||||||
| 																				// allow the PC read to return an RTS. |  | ||||||
| 							) |  | ||||||
| 						) { |  | ||||||
| 							uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); |  | ||||||
| 							if(address == 0xf0a8) { |  | ||||||
| 								if(!ram_[0x247] && service_call == 14) { |  | ||||||
| 									tape_.set_delegate(nullptr); |  | ||||||
|  |  | ||||||
| 									// TODO: handle tape wrap around. |  | ||||||
|  |  | ||||||
| 									int cycles_left_while_plausibly_in_data = 50; |  | ||||||
| 									tape_.clear_interrupts(Interrupt::ReceiveDataFull); |  | ||||||
| 									while(!tape_.get_tape()->is_at_end()) { |  | ||||||
| 										tape_.run_for_input_pulse(); |  | ||||||
| 										cycles_left_while_plausibly_in_data--; |  | ||||||
| 										if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false; |  | ||||||
| 										if(	(tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) && |  | ||||||
| 											(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a) |  | ||||||
| 										) break; |  | ||||||
| 									} |  | ||||||
| 									tape_.set_delegate(this); |  | ||||||
| 									tape_.clear_interrupts(Interrupt::ReceiveDataFull); |  | ||||||
| 									interrupt_status_ |= tape_.get_interrupt_status(); |  | ||||||
|  |  | ||||||
| 									fast_load_is_in_data_ = true; |  | ||||||
| 									set_value_of_register(CPU6502::Register::A, 0); |  | ||||||
| 									set_value_of_register(CPU6502::Register::Y, tape_.get_data_register()); |  | ||||||
| 									*value = 0x60; // 0x60 is RTS |  | ||||||
| 								} |  | ||||||
| 								else *value = os_[address & 16383]; |  | ||||||
| 							} |  | ||||||
| 							else *value = 0xea; |  | ||||||
| 						} else { |  | ||||||
| 							*value = os_[address & 16383]; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					if(isReadOperation(operation)) { |  | ||||||
| 						*value = roms_[active_rom_][address & 16383]; |  | ||||||
| 						if(keyboard_is_active_) { |  | ||||||
| 							*value &= 0xf0; |  | ||||||
| 							for(int address_line = 0; address_line < 14; address_line++) { |  | ||||||
| 								if(!(address&(1 << address_line))) *value |= key_states_[address_line]; |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 						if(basic_is_active_) { |  | ||||||
| 							*value &= roms_[ROMSlotBASIC][address & 16383]; |  | ||||||
| 						} |  | ||||||
| 					} else if(rom_write_masks_[active_rom_]) { |  | ||||||
| 						roms_[active_rom_][address & 16383] = *value; |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			break; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cycles_since_display_update_ += cycles; |  | ||||||
| 	cycles_since_audio_update_ += cycles; |  | ||||||
| 	if(cycles_since_audio_update_ > 16384) update_audio(); |  | ||||||
| 	tape_.run_for_cycles(cycles); |  | ||||||
|  |  | ||||||
| 	cycles_until_display_interrupt_ -= cycles; |  | ||||||
| 	if(cycles_until_display_interrupt_ < 0) { |  | ||||||
| 		signal_interrupt(next_display_interrupt_); |  | ||||||
| 		update_display(); |  | ||||||
| 		queue_next_display_interrupt(); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(typer_) typer_->update((int)cycles); |  | ||||||
| 	if(plus3_) plus3_->run_for_cycles(4*cycles); |  | ||||||
| 	if(shift_restart_counter_) { |  | ||||||
| 		shift_restart_counter_ -= cycles; |  | ||||||
| 		if(shift_restart_counter_ <= 0) { |  | ||||||
| 			shift_restart_counter_ = 0; |  | ||||||
| 			set_power_on(true); |  | ||||||
| 			set_key_state(KeyShift, true); |  | ||||||
| 			is_holding_shift_ = true; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return cycles; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::synchronise() { |  | ||||||
| 	update_display(); |  | ||||||
| 	update_audio(); |  | ||||||
| 	speaker_->flush(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Deferred scheduling |  | ||||||
|  |  | ||||||
| inline void Machine::update_display() { |  | ||||||
| 	if(cycles_since_display_update_) { |  | ||||||
| 		video_output_->run_for_cycles((int)cycles_since_display_update_); |  | ||||||
| 		cycles_since_display_update_ = 0; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline void Machine::queue_next_display_interrupt() { |  | ||||||
| 	VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt(); |  | ||||||
| 	cycles_until_display_interrupt_ = next_interrupt.cycles; |  | ||||||
| 	next_display_interrupt_ = next_interrupt.interrupt; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline void Machine::update_audio() { |  | ||||||
| 	if(cycles_since_audio_update_) { |  | ||||||
| 		unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider; |  | ||||||
| 		cycles_since_audio_update_ %= Speaker::clock_rate_divider; |  | ||||||
| 		speaker_->run_for_cycles(difference); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Interrupts |  | ||||||
|  |  | ||||||
| inline void Machine::signal_interrupt(Electron::Interrupt interrupt) { |  | ||||||
| 	interrupt_status_ |= interrupt; |  | ||||||
| 	evaluate_interrupts(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline void Machine::clear_interrupt(Electron::Interrupt interrupt) { |  | ||||||
| 	interrupt_status_ &= ~interrupt; |  | ||||||
| 	evaluate_interrupts(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline void Machine::evaluate_interrupts() { |  | ||||||
| 	if(interrupt_status_ & interrupt_control_) { |  | ||||||
| 		interrupt_status_ |= 1; |  | ||||||
| 	} else { |  | ||||||
| 		interrupt_status_ &= ~1; |  | ||||||
| 	} |  | ||||||
| 	set_irq_line(interrupt_status_ & 1); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Tape::Delegate |  | ||||||
|  |  | ||||||
| void Machine::tape_did_change_interrupt_status(Tape *tape) { |  | ||||||
| 	interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); |  | ||||||
| 	evaluate_interrupts(); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -9,18 +9,9 @@ | |||||||
| #ifndef Electron_hpp | #ifndef Electron_hpp | ||||||
| #define Electron_hpp | #define Electron_hpp | ||||||
|  |  | ||||||
| #include "../../Processors/6502/CPU6502.hpp" |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" |  | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../ConfigurationTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../Typer.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "Interrupts.hpp" |  | ||||||
| #include "Plus3.hpp" |  | ||||||
| #include "Speaker.hpp" |  | ||||||
| #include "Tape.hpp" |  | ||||||
| #include "Video.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -57,8 +48,6 @@ enum Key: uint16_t { | |||||||
| 	KeyShift		= 0x00d0 | 0x08,	KeyControl		= 0x00d0 | 0x04,	KeyFunc			= 0x00d0 | 0x02,	KeyEscape		= 0x00d0 | 0x01, | 	KeyShift		= 0x00d0 | 0x08,	KeyControl		= 0x00d0 | 0x04,	KeyFunc			= 0x00d0 | 0x02,	KeyEscape		= 0x00d0 | 0x01, | ||||||
|  |  | ||||||
| 	KeyBreak		= 0xfffd, | 	KeyBreak		= 0xfffd, | ||||||
|  |  | ||||||
| 	TerminateSequence = 0xffff, NotMapped		= 0xfffe, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -68,88 +57,23 @@ enum Key: uint16_t { | |||||||
| 	Acorn Electron. | 	Acorn Electron. | ||||||
| */ | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CPU6502::Processor<Machine>, |  | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public Tape::Delegate, | 	public ConfigurationTarget::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public KeyboardMachine::Machine { | ||||||
| 	public ConfigurationTarget::Machine { |  | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable); | 		/// Creates and returns an Electron. | ||||||
|  | 		static Machine *Electron(); | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed); | 		/*! | ||||||
| 		void clear_all_keys(); | 			Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot | ||||||
|  | 			is enabled — it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) = 0; | ||||||
|  |  | ||||||
| 		inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } | 		/// Enables or disables turbo-speed tape loading. | ||||||
|  | 		virtual void set_use_fast_tape_hack(bool activate) = 0; | ||||||
| 		// to satisfy ConfigurationTarget::Machine |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); |  | ||||||
|  |  | ||||||
| 		// to satisfy CPU6502::Processor |  | ||||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); |  | ||||||
| 		void synchronise(); |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine |  | ||||||
| 		virtual void setup_output(float aspect_ratio); |  | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker(); |  | ||||||
| 		virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); } |  | ||||||
|  |  | ||||||
| 		// to satisfy Tape::Delegate |  | ||||||
| 		virtual void tape_did_change_interrupt_status(Tape *tape); |  | ||||||
|  |  | ||||||
| 		// for Utility::TypeRecipient |  | ||||||
| 		virtual int get_typer_delay(); |  | ||||||
| 		virtual int get_typer_frequency(); |  | ||||||
| 		uint16_t *sequence_for_character(Utility::Typer *typer, char character); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		inline void update_display(); |  | ||||||
| 		inline void queue_next_display_interrupt(); |  | ||||||
| 		inline void update_audio(); |  | ||||||
|  |  | ||||||
| 		inline void signal_interrupt(Interrupt interrupt); |  | ||||||
| 		inline void clear_interrupt(Interrupt interrupt); |  | ||||||
| 		inline void evaluate_interrupts(); |  | ||||||
|  |  | ||||||
| 		// Things that directly constitute the memory map. |  | ||||||
| 		uint8_t roms_[16][16384]; |  | ||||||
| 		bool rom_write_masks_[16]; |  | ||||||
| 		uint8_t os_[16384], ram_[32768]; |  | ||||||
| 		std::vector<uint8_t> dfs_, adfs_; |  | ||||||
|  |  | ||||||
| 		// Paging |  | ||||||
| 		ROMSlot active_rom_; |  | ||||||
| 		bool keyboard_is_active_, basic_is_active_; |  | ||||||
|  |  | ||||||
| 		// Interrupt and keyboard state |  | ||||||
| 		uint8_t interrupt_status_, interrupt_control_; |  | ||||||
| 		uint8_t key_states_[14]; |  | ||||||
|  |  | ||||||
| 		// Counters related to simultaneous subsystems |  | ||||||
| 		unsigned int cycles_since_display_update_; |  | ||||||
| 		unsigned int cycles_since_audio_update_; |  | ||||||
| 		int cycles_until_display_interrupt_; |  | ||||||
| 		Interrupt next_display_interrupt_; |  | ||||||
| 		VideoOutput::Range video_access_range_; |  | ||||||
|  |  | ||||||
| 		// Tape |  | ||||||
| 		Tape tape_; |  | ||||||
| 		bool use_fast_tape_hack_; |  | ||||||
| 		bool fast_load_is_in_data_; |  | ||||||
|  |  | ||||||
| 		// Disk |  | ||||||
| 		std::unique_ptr<Plus3> plus3_; |  | ||||||
| 		bool is_holding_shift_; |  | ||||||
| 		int shift_restart_counter_; |  | ||||||
|  |  | ||||||
| 		// Outputs |  | ||||||
| 		std::unique_ptr<VideoOutput> video_output_; |  | ||||||
| 		std::shared_ptr<Speaker> speaker_; |  | ||||||
| 		bool speaker_is_enabled_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,7 +17,9 @@ Tape::Tape() : | |||||||
| 		delegate_(nullptr), | 		delegate_(nullptr), | ||||||
| 		output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}), | 		output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}), | ||||||
| 		last_posted_interrupt_status_(0), | 		last_posted_interrupt_status_(0), | ||||||
| 		interrupt_status_(0) {} | 		interrupt_status_(0) { | ||||||
|  | 	shifter_.set_delegate(this); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Tape::push_tape_bit(uint16_t bit) { | void Tape::push_tape_bit(uint16_t bit) { | ||||||
| 	data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10)); | 	data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10)); | ||||||
| @@ -70,38 +72,22 @@ uint8_t Tape::get_data_register() { | |||||||
| 	return (uint8_t)(data_register_ >> 2); | 	return (uint8_t)(data_register_ >> 2); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) { | void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) { | ||||||
| 	crossings_[0] = crossings_[1]; | 	shifter_.process_pulse(pulse); | ||||||
| 	crossings_[1] = crossings_[2]; |  | ||||||
| 	crossings_[2] = crossings_[3]; |  | ||||||
|  |  | ||||||
| 	crossings_[3] = Tape::Unrecognised; |  | ||||||
| 	if(pulse.type != Storage::Tape::Tape::Pulse::Zero) { |  | ||||||
| 		float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate; |  | ||||||
| 		if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short; |  | ||||||
| 		if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) { |  | ||||||
| 		push_tape_bit(0); |  | ||||||
| 		crossings_[0] = crossings_[1] = Tape::Recognised; |  | ||||||
| 	} else { |  | ||||||
| 		if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) { |  | ||||||
| 			push_tape_bit(1); |  | ||||||
| 			crossings_[0] = crossings_[1] = |  | ||||||
| 			crossings_[2] = crossings_[3] = Tape::Recognised; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tape::run_for_cycles(unsigned int number_of_cycles) { | void Tape::acorn_shifter_output_bit(int value) { | ||||||
|  | 	push_tape_bit((uint16_t)value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tape::run_for(const Cycles cycles) { | ||||||
| 	if(is_enabled_) { | 	if(is_enabled_) { | ||||||
| 		if(is_in_input_mode_) { | 		if(is_in_input_mode_) { | ||||||
| 			if(is_running_) { | 			if(is_running_) { | ||||||
| 				TapePlayer::run_for_cycles((int)number_of_cycles); | 				TapePlayer::run_for(cycles); | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			output_.cycles_into_pulse += number_of_cycles; | 			output_.cycles_into_pulse += (unsigned int)cycles.as_int(); | ||||||
| 			while(output_.cycles_into_pulse > 1664) {	// 1664 = the closest you can get to 1200 baud if you're looking for something | 			while(output_.cycles_into_pulse > 1664) {	// 1664 = the closest you can get to 1200 baud if you're looking for something | ||||||
| 				output_.cycles_into_pulse -= 1664;		// that divides the 125,000Hz clock that the sound divider runs off. | 				output_.cycles_into_pulse -= 1664;		// that divides the 125,000Hz clock that the sound divider runs off. | ||||||
| 				push_tape_bit(1); | 				push_tape_bit(1); | ||||||
|   | |||||||
| @@ -9,18 +9,23 @@ | |||||||
| #ifndef Electron_Tape_h | #ifndef Electron_Tape_h | ||||||
| #define Electron_Tape_h | #define Electron_Tape_h | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" |  | ||||||
| #include "Interrupts.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  | #include "../../Storage/Tape/Parsers/Acorn.hpp" | ||||||
|  | #include "Interrupts.hpp" | ||||||
|  |  | ||||||
| namespace Electron { | namespace Electron { | ||||||
|  |  | ||||||
| class Tape: public Storage::Tape::TapePlayer { | class Tape: | ||||||
|  | 	public Storage::Tape::TapePlayer, | ||||||
|  | 	public Storage::Tape::Acorn::Shifter::Delegate { | ||||||
| 	public: | 	public: | ||||||
| 		Tape(); | 		Tape(); | ||||||
|  |  | ||||||
| 		void run_for_cycles(unsigned int number_of_cycles); | 		void run_for(const Cycles cycles); | ||||||
|  | 		using Storage::Tape::TapePlayer::run_for; | ||||||
|  |  | ||||||
| 		uint8_t get_data_register(); | 		uint8_t get_data_register(); | ||||||
| 		void set_data_register(uint8_t value); | 		void set_data_register(uint8_t value); | ||||||
| @@ -39,8 +44,10 @@ class Tape: public Storage::Tape::TapePlayer { | |||||||
| 		inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } | 		inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } | ||||||
| 		void set_is_in_input_mode(bool is_in_input_mode); | 		void set_is_in_input_mode(bool is_in_input_mode); | ||||||
|  |  | ||||||
|  | 		void acorn_shifter_output_bit(int value); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void process_input_pulse(Storage::Tape::Tape::Pulse pulse); | 		void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); | ||||||
| 		inline void push_tape_bit(uint16_t bit); | 		inline void push_tape_bit(uint16_t bit); | ||||||
| 		inline void get_next_tape_pulse(); | 		inline void get_next_tape_pulse(); | ||||||
|  |  | ||||||
| @@ -62,9 +69,7 @@ class Tape: public Storage::Tape::TapePlayer { | |||||||
| 		uint8_t interrupt_status_, last_posted_interrupt_status_; | 		uint8_t interrupt_status_, last_posted_interrupt_status_; | ||||||
| 		Delegate *delegate_; | 		Delegate *delegate_; | ||||||
|  |  | ||||||
| 		enum { | 		::Storage::Tape::Acorn::Shifter shifter_; | ||||||
| 			Long, Short, Unrecognised, Recognised |  | ||||||
| 		} crossings_[4]; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -223,7 +223,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoOutput::run_for_cycles(int number_of_cycles) { | void VideoOutput::run_for(const Cycles cycles) { | ||||||
|  | 	int number_of_cycles = cycles.as_int(); | ||||||
| 	output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame; | 	output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame; | ||||||
| 	while(number_of_cycles) { | 	while(number_of_cycles) { | ||||||
| 		int draw_action_length = screen_map_[screen_map_pointer_].length; | 		int draw_action_length = screen_map_[screen_map_pointer_].length; | ||||||
| @@ -425,9 +426,9 @@ void VideoOutput::setup_screen_map() { | |||||||
| 			screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1); | 			screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1); | ||||||
| 			screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1); | 			screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1); | ||||||
| 		} | 		} | ||||||
| 		for(int c = 0; c < first_graphics_line - 3; c++) emplace_blank_line(); | 		for(int l = 0; l < first_graphics_line - 3; l++) emplace_blank_line(); | ||||||
| 		for(int c = 0; c < 256; c++) emplace_pixel_line(); | 		for(int l = 0; l < 256; l++) emplace_pixel_line(); | ||||||
| 		for(int c = 256 + first_graphics_line; c < 312; c++) emplace_blank_line(); | 		for(int l = 256 + first_graphics_line; l < 312; l++) emplace_blank_line(); | ||||||
| 		if(c&1) emplace_blank_line(); | 		if(c&1) emplace_blank_line(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #define Machines_Electron_Video_hpp | #define Machines_Electron_Video_hpp | ||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "Interrupts.hpp" | #include "Interrupts.hpp" | ||||||
|  |  | ||||||
| namespace Electron { | namespace Electron { | ||||||
| @@ -32,8 +33,8 @@ class VideoOutput { | |||||||
| 		/// @returns the CRT to which output is being painted. | 		/// @returns the CRT to which output is being painted. | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt(); | 		std::shared_ptr<Outputs::CRT::CRT> get_crt(); | ||||||
|  |  | ||||||
| 		/// Produces the next @c number_of_cycles cycles of video output. | 		/// Produces the next @c cycles of video output. | ||||||
| 		void run_for_cycles(int number_of_cycles); | 		void run_for(const Cycles cycles); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, | 			Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, | ||||||
| @@ -53,14 +54,14 @@ class VideoOutput { | |||||||
| 		/*! | 		/*! | ||||||
| 			@returns the next interrupt that should be generated as a result of the video hardware. | 			@returns the next interrupt that should be generated as a result of the video hardware. | ||||||
| 			The time until signalling returned is the number of cycles after the final one triggered | 			The time until signalling returned is the number of cycles after the final one triggered | ||||||
| 			by the most recent call to @c run_for_cycles. | 			by the most recent call to @c run_for. | ||||||
|  |  | ||||||
| 			This result may be mutated by calls to @c set_register. | 			This result may be mutated by calls to @c set_register. | ||||||
| 		*/ | 		*/ | ||||||
| 		Interrupt get_next_interrupt(); | 		Interrupt get_next_interrupt(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			@returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time) | 			@returns the number of cycles after (final cycle of last run_for batch + @c from_time) | ||||||
| 			before the video circuits will allow the CPU to access RAM. | 			before the video circuits will allow the CPU to access RAM. | ||||||
| 		*/ | 		*/ | ||||||
| 		unsigned int get_cycles_until_next_ram_availability(int from_time); | 		unsigned int get_cycles_until_next_ram_availability(int from_time); | ||||||
|   | |||||||
| @@ -13,7 +13,15 @@ namespace KeyboardMachine { | |||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Indicates that the key @c key has been either pressed or released, according to | ||||||
|  | 			the state of @c isPressed. | ||||||
|  | 		*/ | ||||||
| 		virtual void set_key_state(uint16_t key, bool isPressed) = 0; | 		virtual void set_key_state(uint16_t key, bool isPressed) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Instructs that all keys should now be treated as released. | ||||||
|  | 		*/ | ||||||
| 		virtual void clear_all_keys() = 0; | 		virtual void clear_all_keys() = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,3 +22,7 @@ void Memory::Fuzz(uint8_t *buffer, size_t size) { | |||||||
| 		buffer[c] = (uint8_t)(std::rand() >> shift); | 		buffer[c] = (uint8_t)(std::rand() >> shift); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Memory::Fuzz(std::vector<uint8_t> &buffer) { | ||||||
|  | 	Fuzz(buffer.data(), buffer.size()); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,11 +11,16 @@ | |||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace Memory { | namespace Memory { | ||||||
|  |  | ||||||
|  | /// Stores @c size random bytes from @c buffer onwards. | ||||||
| void Fuzz(uint8_t *buffer, size_t size); | void Fuzz(uint8_t *buffer, size_t size); | ||||||
|  |  | ||||||
|  | // Replaces all existing vector contents with random bytes. | ||||||
|  | void Fuzz(std::vector<uint8_t> &buffer); | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* MemoryFuzzer_hpp */ | #endif /* MemoryFuzzer_hpp */ | ||||||
|   | |||||||
| @@ -1,10 +1,21 @@ | |||||||
|  | //
 | ||||||
|  | //  CharacterMapper.cpp
 | ||||||
|  | //  Clock Signal
 | ||||||
|  | //
 | ||||||
|  | //  Created by Thomas Harte on 03/08/2017.
 | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
| #include "Oric.hpp" | #include "Oric.hpp" | ||||||
| 
 | 
 | ||||||
| uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) { | using namespace Oric; | ||||||
| #define KEYS(...)	{__VA_ARGS__, TerminateSequence} | 
 | ||||||
| #define SHIFT(...)	{KeyLeftShift, __VA_ARGS__, TerminateSequence} | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
|  | #define KEYS(...)	{__VA_ARGS__, EndSequence} | ||||||
|  | #define SHIFT(...)	{KeyLeftShift, __VA_ARGS__, EndSequence} | ||||||
| #define X			{NotMapped} | #define X			{NotMapped} | ||||||
| 	static Key key_sequences[][3] = { | 	static KeySequence key_sequences[] = { | ||||||
| 		/* NUL */	X,							/* SOH */	X, | 		/* NUL */	X,							/* SOH */	X, | ||||||
| 		/* STX */	X,							/* ETX */	X, | 		/* STX */	X,							/* ETX */	X, | ||||||
| 		/* EOT */	X,							/* ENQ */	X, | 		/* EOT */	X,							/* ENQ */	X, | ||||||
| @@ -73,7 +84,5 @@ uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char char | |||||||
| #undef SHIFT | #undef SHIFT | ||||||
| #undef X | #undef X | ||||||
| 
 | 
 | ||||||
| 	if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr; | 	return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); | ||||||
| 	if(key_sequences[character][0] == NotMapped) return nullptr; |  | ||||||
| 	return (uint16_t *)key_sequences[character]; |  | ||||||
| } | } | ||||||
							
								
								
									
										23
									
								
								Machines/Oric/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Machines/Oric/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_Oric_CharacterMapper_hpp | ||||||
|  | #define Machines_Oric_CharacterMapper_hpp | ||||||
|  |  | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace Oric { | ||||||
|  |  | ||||||
|  | class CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	public: | ||||||
|  | 		uint16_t *sequence_for_character(char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Machines_Oric_CharacterMapper_hpp */ | ||||||
| @@ -104,12 +104,12 @@ void Microdisc::set_head_load_request(bool head_load) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void Microdisc::run_for_cycles(unsigned int number_of_cycles) { | void Microdisc::run_for(const Cycles cycles) { | ||||||
| 	if(head_load_request_counter_ < head_load_request_counter_target) { | 	if(head_load_request_counter_ < head_load_request_counter_target) { | ||||||
| 		head_load_request_counter_ += number_of_cycles; | 		head_load_request_counter_ += cycles.as_int(); | ||||||
| 		if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true); | 		if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true); | ||||||
| 	} | 	} | ||||||
| 	WD::WD1770::run_for_cycles(number_of_cycles); | 	WD::WD1770::run_for(cycles); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Microdisc::get_drive_is_ready() { | bool Microdisc::get_drive_is_ready() { | ||||||
|   | |||||||
| @@ -24,7 +24,8 @@ class Microdisc: public WD::WD1770 { | |||||||
|  |  | ||||||
| 		bool get_interrupt_request_line(); | 		bool get_interrupt_request_line(); | ||||||
|  |  | ||||||
| 		void run_for_cycles(unsigned int number_of_cycles); | 		void run_for(const Cycles cycles); | ||||||
|  | 		using WD::WD1770::run_for; | ||||||
|  |  | ||||||
| 		enum PagingFlags { | 		enum PagingFlags { | ||||||
| 			BASICDisable	=	(1 << 0), | 			BASICDisable	=	(1 << 0), | ||||||
|   | |||||||
| @@ -7,283 +7,381 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "Oric.hpp" | #include "Oric.hpp" | ||||||
|  |  | ||||||
|  | #include "Video.hpp" | ||||||
|  | #include "Microdisc.hpp" | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
|  |  | ||||||
| #include "../MemoryFuzzer.hpp" | #include "../MemoryFuzzer.hpp" | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Processors/6502/6502.hpp" | ||||||
|  | #include "../../Components/6522/6522.hpp" | ||||||
|  | #include "../../Components/AY38910/AY38910.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  | #include "../../Storage/Tape/Parsers/Oric.hpp" | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Oric { | ||||||
|  |  | ||||||
|  | class ConcreteMachine: | ||||||
|  | 	public CPU::MOS6502::BusHandler, | ||||||
|  | 	public MOS::MOS6522IRQDelegate::Delegate, | ||||||
|  | 	public Utility::TypeRecipient, | ||||||
|  | 	public Storage::Tape::BinaryTapePlayer::Delegate, | ||||||
|  | 	public Microdisc::Delegate, | ||||||
|  | 	public Machine { | ||||||
|  |  | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
|  | 				m6502_(*this), | ||||||
|  | 				use_fast_tape_hack_(false), | ||||||
|  | 				typer_delay_(2500000), | ||||||
|  | 				keyboard_read_count_(0), | ||||||
|  | 				keyboard_(new Keyboard), | ||||||
|  | 				ram_top_(0xbfff), | ||||||
|  | 				paged_rom_(rom_), | ||||||
|  | 				microdisc_is_enabled_(false) { | ||||||
|  | 			set_clock_rate(1000000); | ||||||
|  | 			via_.set_interrupt_delegate(this); | ||||||
|  | 			via_.keyboard = keyboard_; | ||||||
|  | 			clear_all_keys(); | ||||||
|  | 			via_.tape->set_delegate(this); | ||||||
|  | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_rom(ROM rom, const std::vector<uint8_t> &data) override final { | ||||||
|  | 			switch(rom) { | ||||||
|  | 				case BASIC11:	basic11_rom_ = std::move(data);		break; | ||||||
|  | 				case BASIC10:	basic10_rom_ = std::move(data);		break; | ||||||
|  | 				case Microdisc:	microdisc_rom_ = std::move(data);	break; | ||||||
|  | 				case Colour: | ||||||
|  | 					colour_rom_ = std::move(data); | ||||||
|  | 					if(video_output_) video_output_->set_colour_rom(colour_rom_); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
|  | 			if(key == KeyNMI) { | ||||||
|  | 				m6502_.set_nmi_line(isPressed); | ||||||
|  | 			} else { | ||||||
|  | 				if(isPressed) | ||||||
|  | 					keyboard_->rows[key >> 8] |= (key & 0xff); | ||||||
|  | 				else | ||||||
|  | 					keyboard_->rows[key >> 8] &= ~(key & 0xff); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			memset(keyboard_->rows, 0, sizeof(keyboard_->rows)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_use_fast_tape_hack(bool activate) override final { | ||||||
|  | 			use_fast_tape_hack_ = activate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_output_device(Outputs::CRT::OutputDevice output_device) override final { | ||||||
|  | 			video_output_->set_output_device(output_device); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy ConfigurationTarget::Machine | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) override final { | ||||||
|  | 			if(target.oric.has_microdisc) { | ||||||
|  | 				microdisc_is_enabled_ = true; | ||||||
|  | 				microdisc_did_change_paging_flags(µdisc_); | ||||||
|  | 				microdisc_.set_delegate(this); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.loadingCommand.length()) { | ||||||
|  | 				set_typer_for_string(target.loadingCommand.c_str()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.oric.use_atmos_rom) { | ||||||
|  | 				memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_))); | ||||||
|  |  | ||||||
|  | 				is_using_basic11_ = true; | ||||||
|  | 				tape_get_byte_address_ = 0xe6c9; | ||||||
|  | 				scan_keyboard_address_ = 0xf495; | ||||||
|  | 				tape_speed_address_ = 0x024d; | ||||||
|  | 			} else { | ||||||
|  | 				memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_))); | ||||||
|  |  | ||||||
|  | 				is_using_basic11_ = false; | ||||||
|  | 				tape_get_byte_address_ = 0xe630; | ||||||
|  | 				scan_keyboard_address_ = 0xf43c; | ||||||
|  | 				tape_speed_address_ = 0x67; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override final { | ||||||
|  | 			if(media.tapes.size()) { | ||||||
|  | 				via_.tape->set_tape(media.tapes.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			int drive_index = 0; | ||||||
|  | 			for(auto disk : media.disks) { | ||||||
|  | 				if(drive_index < 4) microdisc_.set_disk(disk, drive_index); | ||||||
|  | 				drive_index++; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty() || (!media.disks.empty() && microdisc_is_enabled_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy CPU::MOS6502::BusHandler | ||||||
|  | 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
|  | 			if(address > ram_top_) { | ||||||
|  | 				if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; | ||||||
|  |  | ||||||
|  | 				// 024D = 0 => fast; otherwise slow | ||||||
|  | 				// E6C9 = read byte: return byte in A | ||||||
|  | 				if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) { | ||||||
|  | 					uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]); | ||||||
|  | 					m6502_.set_value_of_register(CPU::MOS6502::A, next_byte); | ||||||
|  | 					m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero); | ||||||
|  | 					*value = 0x60; // i.e. RTS | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if((address & 0xff00) == 0x0300) { | ||||||
|  | 					if(microdisc_is_enabled_ && address >= 0x0310) { | ||||||
|  | 						switch(address) { | ||||||
|  | 							case 0x0310: case 0x0311: case 0x0312: case 0x0313: | ||||||
|  | 								if(isReadOperation(operation)) *value = microdisc_.get_register(address); | ||||||
|  | 								else microdisc_.set_register(address, *value); | ||||||
|  | 							break; | ||||||
|  | 							case 0x314: case 0x315: case 0x316: case 0x317: | ||||||
|  | 								if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register(); | ||||||
|  | 								else microdisc_.set_control_register(*value); | ||||||
|  | 							break; | ||||||
|  | 							case 0x318: case 0x319: case 0x31a: case 0x31b: | ||||||
|  | 								if(isReadOperation(operation)) *value = microdisc_.get_data_request_register(); | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						if(isReadOperation(operation)) *value = via_.get_register(address); | ||||||
|  | 						else via_.set_register(address, *value); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					if(isReadOperation(operation)) | ||||||
|  | 						*value = ram_[address]; | ||||||
|  | 					else { | ||||||
|  | 						if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; } | ||||||
|  | 						ram_[address] = *value; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(typer_ && address == scan_keyboard_address_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { | ||||||
|  | 				// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't | ||||||
|  | 				// do anything until at least the second, regardless of machine | ||||||
|  | 				if(!keyboard_read_count_) keyboard_read_count_++; | ||||||
|  | 				else if(!typer_->type_next_character()) { | ||||||
|  | 					clear_all_keys(); | ||||||
|  | 					typer_.reset(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			via_.run_for(Cycles(1)); | ||||||
|  | 			if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8)); | ||||||
|  | 			cycles_since_video_update_++; | ||||||
|  | 			return Cycles(1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline void flush() { | ||||||
|  | 			update_video(); | ||||||
|  | 			via_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy CRTMachine::Machine | ||||||
|  | 		void setup_output(float aspect_ratio) override final { | ||||||
|  | 			via_.ay8910.reset(new GI::AY38910::AY38910()); | ||||||
|  | 			via_.ay8910->set_clock_rate(1000000); | ||||||
|  | 			video_output_.reset(new VideoOutput(ram_)); | ||||||
|  | 			if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void close_output() override final { | ||||||
|  | 			video_output_.reset(); | ||||||
|  | 			via_.ay8910.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::CRT::CRT> get_crt() override final { | ||||||
|  | 			return video_output_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::Speaker> get_speaker() override final { | ||||||
|  | 			return via_.ay8910; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override final { | ||||||
|  | 			m6502_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy MOS::MOS6522IRQDelegate::Delegate | ||||||
|  | 		void mos6522_did_change_interrupt_status(void *mos6522) override final { | ||||||
|  | 			set_interrupt_line(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// to satisfy Storage::Tape::BinaryTapePlayer::Delegate | ||||||
|  | 		void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final { | ||||||
|  | 			// set CB1 | ||||||
|  | 			via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// for Utility::TypeRecipient::Delegate | ||||||
|  | 		void set_typer_for_string(const char *string) override final { | ||||||
|  | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper); | ||||||
|  | 			Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// for Microdisc::Delegate | ||||||
|  | 		void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final { | ||||||
|  | 			int flags = microdisc->get_paging_flags(); | ||||||
|  | 			if(!(flags&Microdisc::PagingFlags::BASICDisable)) { | ||||||
|  | 				ram_top_ = 0xbfff; | ||||||
|  | 				paged_rom_ = rom_; | ||||||
|  | 			} else { | ||||||
|  | 				if(flags&Microdisc::PagingFlags::MicrodscDisable) { | ||||||
|  | 					ram_top_ = 0xffff; | ||||||
|  | 				} else { | ||||||
|  | 					ram_top_ = 0xdfff; | ||||||
|  | 					paged_rom_ = microdisc_rom_.data(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void wd1770_did_change_output(WD::WD1770 *wd1770) override final { | ||||||
|  | 			set_interrupt_line(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
|  | 		// RAM and ROM | ||||||
|  | 		std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_; | ||||||
|  | 		uint8_t ram_[65536], rom_[16384]; | ||||||
|  | 		Cycles cycles_since_video_update_; | ||||||
|  | 		inline void update_video() { | ||||||
|  | 			video_output_->run_for(cycles_since_video_update_.flush()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// ROM bookkeeping | ||||||
|  | 		bool is_using_basic11_; | ||||||
|  | 		uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_; | ||||||
|  | 		int keyboard_read_count_; | ||||||
|  |  | ||||||
|  | 		// Outputs | ||||||
|  | 		std::unique_ptr<VideoOutput> video_output_; | ||||||
|  |  | ||||||
|  | 		// Keyboard | ||||||
|  | 		class Keyboard { | ||||||
|  | 			public: | ||||||
|  | 				uint8_t row; | ||||||
|  | 				uint8_t rows[8]; | ||||||
|  | 		}; | ||||||
|  | 		int typer_delay_; | ||||||
|  |  | ||||||
|  | 		// The tape | ||||||
|  | 		class TapePlayer: public Storage::Tape::BinaryTapePlayer { | ||||||
|  | 			public: | ||||||
|  | 				TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {} | ||||||
|  |  | ||||||
|  | 				inline uint8_t get_next_byte(bool fast) { | ||||||
|  | 					return (uint8_t)parser_.get_next_byte(get_tape(), fast); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				Storage::Tape::Oric::Parser parser_; | ||||||
|  | 		}; | ||||||
|  | 		bool use_fast_tape_hack_; | ||||||
|  |  | ||||||
|  | 		// VIA (which owns the tape and the AY) | ||||||
|  | 		class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate { | ||||||
|  | 			public: | ||||||
|  | 				VIA() : | ||||||
|  | 					MOS::MOS6522<VIA>(), | ||||||
|  | 					tape(new TapePlayer) {} | ||||||
|  |  | ||||||
|  | 				using MOS6522IRQDelegate::set_interrupt_status; | ||||||
|  |  | ||||||
|  | 				void set_control_line_output(Port port, Line line, bool value) { | ||||||
|  | 					if(line) { | ||||||
|  | 						if(port) ay_bdir_ = value; else ay_bc1_ = value; | ||||||
|  | 						update_ay(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				void set_port_output(Port port, uint8_t value, uint8_t direction_mask)  { | ||||||
|  | 					if(port) { | ||||||
|  | 						keyboard->row = value; | ||||||
|  | 						tape->set_motor_control(value & 0x40); | ||||||
|  | 					} else { | ||||||
|  | 						ay8910->set_data_input(value); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				uint8_t get_port_input(Port port) { | ||||||
|  | 					if(port) { | ||||||
|  | 						uint8_t column = ay8910->get_port_output(false) ^ 0xff; | ||||||
|  | 						return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; | ||||||
|  | 					} else { | ||||||
|  | 						return ay8910->get_data_output(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				inline void run_for(const Cycles cycles) { | ||||||
|  | 					cycles_since_ay_update_ += cycles; | ||||||
|  | 					MOS::MOS6522<VIA>::run_for(cycles); | ||||||
|  | 					tape->run_for(cycles); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				void flush() { | ||||||
|  | 					ay8910->run_for(cycles_since_ay_update_.flush()); | ||||||
|  | 					ay8910->flush(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				std::shared_ptr<GI::AY38910::AY38910> ay8910; | ||||||
|  | 				std::unique_ptr<TapePlayer> tape; | ||||||
|  | 				std::shared_ptr<Keyboard> keyboard; | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				void update_ay() { | ||||||
|  | 					ay8910->run_for(cycles_since_ay_update_.flush()); | ||||||
|  | 					ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); | ||||||
|  | 				} | ||||||
|  | 				bool ay_bdir_, ay_bc1_; | ||||||
|  | 				Cycles cycles_since_ay_update_; | ||||||
|  | 		}; | ||||||
|  | 		VIA via_; | ||||||
|  | 		std::shared_ptr<Keyboard> keyboard_; | ||||||
|  |  | ||||||
|  | 		// the Microdisc, if in use | ||||||
|  | 		class Microdisc microdisc_; | ||||||
|  | 		bool microdisc_is_enabled_; | ||||||
|  | 		uint16_t ram_top_; | ||||||
|  | 		uint8_t *paged_rom_; | ||||||
|  |  | ||||||
|  | 		inline void set_interrupt_line() { | ||||||
|  | 			m6502_.set_irq_line( | ||||||
|  | 				via_.get_interrupt_line() || | ||||||
|  | 				(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line())); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| using namespace Oric; | using namespace Oric; | ||||||
|  |  | ||||||
| Machine::Machine() : | Machine *Machine::Oric() { | ||||||
| 		cycles_since_video_update_(0), | 	return new ConcreteMachine; | ||||||
| 		use_fast_tape_hack_(false), |  | ||||||
| 		typer_delay_(2500000), |  | ||||||
| 		keyboard_read_count_(0), |  | ||||||
| 		keyboard_(new Keyboard), |  | ||||||
| 		ram_top_(0xbfff), |  | ||||||
| 		paged_rom_(rom_), |  | ||||||
| 		microdisc_is_enabled_(false) { |  | ||||||
| 	set_clock_rate(1000000); |  | ||||||
| 	via_.set_interrupt_delegate(this); |  | ||||||
| 	via_.keyboard = keyboard_; |  | ||||||
| 	clear_all_keys(); |  | ||||||
| 	via_.tape->set_delegate(this); |  | ||||||
| 	Memory::Fuzz(ram_, sizeof(ram_)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Machine::configure_as_target(const StaticAnalyser::Target &target) { | Machine::~Machine() {} | ||||||
| 	if(target.tapes.size()) { |  | ||||||
| 		via_.tape->set_tape(target.tapes.front()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.loadingCommand.length()) {	// TODO: and automatic loading option enabled |  | ||||||
| 		set_typer_for_string(target.loadingCommand.c_str()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.oric.has_microdisc) { |  | ||||||
| 		microdisc_is_enabled_ = true; |  | ||||||
| 		microdisc_did_change_paging_flags(µdisc_); |  | ||||||
| 		microdisc_.set_delegate(this); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	int drive_index = 0; |  | ||||||
| 	for(auto disk : target.disks) { |  | ||||||
| 		if(drive_index < 4) microdisc_.set_disk(disk, drive_index); |  | ||||||
| 		drive_index++; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(target.oric.use_atmos_rom) { |  | ||||||
| 		memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_))); |  | ||||||
|  |  | ||||||
| 		is_using_basic11_ = true; |  | ||||||
| 		tape_get_byte_address_ = 0xe6c9; |  | ||||||
| 		scan_keyboard_address_ = 0xf495; |  | ||||||
| 		tape_speed_address_ = 0x024d; |  | ||||||
| 	} else { |  | ||||||
| 		memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_))); |  | ||||||
|  |  | ||||||
| 		is_using_basic11_ = false; |  | ||||||
| 		tape_get_byte_address_ = 0xe630; |  | ||||||
| 		scan_keyboard_address_ = 0xf43c; |  | ||||||
| 		tape_speed_address_ = 0x67; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) { |  | ||||||
| 	switch(rom) { |  | ||||||
| 		case BASIC11:	basic11_rom_ = std::move(data);		break; |  | ||||||
| 		case BASIC10:	basic10_rom_ = std::move(data);		break; |  | ||||||
| 		case Microdisc:	microdisc_rom_ = std::move(data);	break; |  | ||||||
| 		case Colour: |  | ||||||
| 			colour_rom_ = std::move(data); |  | ||||||
| 			if(video_output_) video_output_->set_colour_rom(colour_rom_); |  | ||||||
| 		break; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { |  | ||||||
| 	if(address > ram_top_) { |  | ||||||
| 		if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1]; |  | ||||||
|  |  | ||||||
| 		// 024D = 0 => fast; otherwise slow |  | ||||||
| 		// E6C9 = read byte: return byte in A |  | ||||||
| 		if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) { |  | ||||||
| 			uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]); |  | ||||||
| 			set_value_of_register(CPU6502::A, next_byte); |  | ||||||
| 			set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero); |  | ||||||
| 			*value = 0x60; // i.e. RTS |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		if((address & 0xff00) == 0x0300) { |  | ||||||
| 			if(microdisc_is_enabled_ && address >= 0x0310) { |  | ||||||
| 				switch(address) { |  | ||||||
| 					case 0x0310: case 0x0311: case 0x0312: case 0x0313: |  | ||||||
| 						if(isReadOperation(operation)) *value = microdisc_.get_register(address); |  | ||||||
| 						else microdisc_.set_register(address, *value); |  | ||||||
| 					break; |  | ||||||
| 					case 0x314: case 0x315: case 0x316: case 0x317: |  | ||||||
| 						if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register(); |  | ||||||
| 						else microdisc_.set_control_register(*value); |  | ||||||
| 					break; |  | ||||||
| 					case 0x318: case 0x319: case 0x31a: case 0x31b: |  | ||||||
| 						if(isReadOperation(operation)) *value = microdisc_.get_data_request_register(); |  | ||||||
| 					break; |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				if(isReadOperation(operation)) *value = via_.get_register(address); |  | ||||||
| 				else via_.set_register(address, *value); |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			if(isReadOperation(operation)) |  | ||||||
| 				*value = ram_[address]; |  | ||||||
| 			else { |  | ||||||
| 				if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; } |  | ||||||
| 				ram_[address] = *value; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) { |  | ||||||
| 		// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't |  | ||||||
| 		// do anything until at least the second, regardless of machine |  | ||||||
| 		if(!keyboard_read_count_) keyboard_read_count_++; |  | ||||||
| 		else if(!typer_->type_next_character()) { |  | ||||||
| 			clear_all_keys(); |  | ||||||
| 			typer_.reset(); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	via_.run_for_cycles(1); |  | ||||||
| 	if(microdisc_is_enabled_) microdisc_.run_for_cycles(8); |  | ||||||
| 	cycles_since_video_update_++; |  | ||||||
| 	return 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::synchronise() { |  | ||||||
| 	update_video(); |  | ||||||
| 	via_.synchronise(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::update_video() { |  | ||||||
| 	video_output_->run_for_cycles(cycles_since_video_update_); |  | ||||||
| 	cycles_since_video_update_ = 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::setup_output(float aspect_ratio) { |  | ||||||
| 	via_.ay8910.reset(new GI::AY38910()); |  | ||||||
| 	via_.ay8910->set_clock_rate(1000000); |  | ||||||
| 	video_output_.reset(new VideoOutput(ram_)); |  | ||||||
| 	if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::close_output() { |  | ||||||
| 	video_output_.reset(); |  | ||||||
| 	via_.ay8910.reset(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::mos6522_did_change_interrupt_status(void *mos6522) { |  | ||||||
| 	set_interrupt_line(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_key_state(uint16_t key, bool isPressed) { |  | ||||||
| 	if(key == KeyNMI) { |  | ||||||
| 		set_nmi_line(isPressed); |  | ||||||
| 	} else { |  | ||||||
| 		if(isPressed) |  | ||||||
| 			keyboard_->rows[key >> 8] |= (key & 0xff); |  | ||||||
| 		else |  | ||||||
| 			keyboard_->rows[key >> 8] &= ~(key & 0xff); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::clear_all_keys() { |  | ||||||
| 	memset(keyboard_->rows, 0, sizeof(keyboard_->rows)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_use_fast_tape_hack(bool activate) { |  | ||||||
| 	use_fast_tape_hack_ = activate; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) { |  | ||||||
| 	video_output_->set_output_device(output_device); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) { |  | ||||||
| 	// set CB1 |  | ||||||
| 	via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() { |  | ||||||
| 	return video_output_->get_crt(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::shared_ptr<Outputs::Speaker> Machine::get_speaker() { |  | ||||||
| 	return via_.ay8910; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::run_for_cycles(int number_of_cycles) { |  | ||||||
| 	CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - The 6522 |  | ||||||
|  |  | ||||||
| Machine::VIA::VIA() : |  | ||||||
| 		MOS::MOS6522<Machine::VIA>(), |  | ||||||
| 		cycles_since_ay_update_(0), |  | ||||||
| 		tape(new TapePlayer) {} |  | ||||||
|  |  | ||||||
| void Machine::VIA::set_control_line_output(Port port, Line line, bool value) { |  | ||||||
| 	if(line) { |  | ||||||
| 		if(port) ay_bdir_ = value; else ay_bc1_ = value; |  | ||||||
| 		update_ay(); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) { |  | ||||||
| 	if(port) { |  | ||||||
| 		keyboard->row = value; |  | ||||||
| 		tape->set_motor_control(value & 0x40); |  | ||||||
| 	} else { |  | ||||||
| 		ay8910->set_data_input(value); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| uint8_t Machine::VIA::get_port_input(Port port) { |  | ||||||
| 	if(port) { |  | ||||||
| 		uint8_t column = ay8910->get_port_output(false) ^ 0xff; |  | ||||||
| 		return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; |  | ||||||
| 	} else { |  | ||||||
| 		return ay8910->get_data_output(); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::VIA::synchronise() { |  | ||||||
| 	ay8910->run_for_cycles(cycles_since_ay_update_); |  | ||||||
| 	ay8910->flush(); |  | ||||||
| 	cycles_since_ay_update_ = 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) { |  | ||||||
| 	cycles_since_ay_update_ += number_of_cycles; |  | ||||||
| 	MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles); |  | ||||||
| 	tape->run_for_cycles((int)number_of_cycles); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::VIA::update_ay() { |  | ||||||
| 	ay8910->run_for_cycles(cycles_since_ay_update_); |  | ||||||
| 	cycles_since_ay_update_ = 0; |  | ||||||
| 	ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - TapePlayer |  | ||||||
|  |  | ||||||
| Machine::TapePlayer::TapePlayer() : |  | ||||||
| 		Storage::Tape::BinaryTapePlayer(1000000) {} |  | ||||||
|  |  | ||||||
| uint8_t Machine::TapePlayer::get_next_byte(bool fast) { |  | ||||||
| 	return (uint8_t)parser_.get_next_byte(get_tape(), fast); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Microdisc |  | ||||||
|  |  | ||||||
| void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) { |  | ||||||
| 	int flags = microdisc->get_paging_flags(); |  | ||||||
| 	if(!(flags&Microdisc::PagingFlags::BASICDisable)) { |  | ||||||
| 		ram_top_ = 0xbfff; |  | ||||||
| 		paged_rom_ = rom_; |  | ||||||
| 	} else { |  | ||||||
| 		if(flags&Microdisc::PagingFlags::MicrodscDisable) { |  | ||||||
| 			ram_top_ = 0xffff; |  | ||||||
| 		} else { |  | ||||||
| 			ram_top_ = 0xdfff; |  | ||||||
| 			paged_rom_ = microdisc_rom_.data(); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) { |  | ||||||
| 	set_interrupt_line(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_interrupt_line() { |  | ||||||
| 	set_irq_line( |  | ||||||
| 		via_.get_interrupt_line() || |  | ||||||
| 		(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line())); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -11,24 +11,17 @@ | |||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../ConfigurationTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../Typer.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Processors/6502/CPU6502.hpp" |  | ||||||
| #include "../../Components/6522/6522.hpp" |  | ||||||
| #include "../../Components/AY38910/AY38910.hpp" |  | ||||||
| #include "../../Storage/Tape/Parsers/Oric.hpp" |  | ||||||
|  |  | ||||||
| #include "Video.hpp" |  | ||||||
| #include "Microdisc.hpp" |  | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <memory> |  | ||||||
|  |  | ||||||
| namespace Oric { | namespace Oric { | ||||||
|  |  | ||||||
|  | enum ROM { | ||||||
|  | 	BASIC10, BASIC11, Microdisc, Colour | ||||||
|  | }; | ||||||
|  |  | ||||||
| enum Key: uint16_t { | enum Key: uint16_t { | ||||||
| 	Key3			= 0x0000 | 0x80,	KeyX			= 0x0000 | 0x40,	Key1			= 0x0000 | 0x20, | 	Key3			= 0x0000 | 0x80,	KeyX			= 0x0000 | 0x40,	Key1			= 0x0000 | 0x20, | ||||||
| 	KeyV			= 0x0000 | 0x08,	Key5			= 0x0000 | 0x04,	KeyN			= 0x0000 | 0x02,	Key7			= 0x0000 | 0x01, | 	KeyV			= 0x0000 | 0x08,	Key5			= 0x0000 | 0x04,	KeyN			= 0x0000 | 0x02,	Key7			= 0x0000 | 0x01, | ||||||
| @@ -48,126 +41,29 @@ enum Key: uint16_t { | |||||||
| 	KeyForwardSlash	= 0x0700 | 0x08,	Key0			= 0x0700 | 0x04,	KeyL			= 0x0700 | 0x02,	Key8			= 0x0700 | 0x01, | 	KeyForwardSlash	= 0x0700 | 0x08,	Key0			= 0x0700 | 0x04,	KeyL			= 0x0700 | 0x02,	Key8			= 0x0700 | 0x01, | ||||||
|  |  | ||||||
| 	KeyNMI			= 0xfffd, | 	KeyNMI			= 0xfffd, | ||||||
|  |  | ||||||
| 	TerminateSequence = 0xffff, NotMapped = 0xfffe |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum ROM { |  | ||||||
| 	BASIC10, BASIC11, Microdisc, Colour |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models an Oric 1/Atmos with or without a Microdisc. | ||||||
|  | */ | ||||||
| class Machine: | class Machine: | ||||||
| 	public CPU6502::Processor<Machine>, |  | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public ConfigurationTarget::Machine, | ||||||
| 	public MOS::MOS6522IRQDelegate::Delegate, | 	public KeyboardMachine::Machine { | ||||||
| 	public Utility::TypeRecipient, |  | ||||||
| 	public Storage::Tape::BinaryTapePlayer::Delegate, |  | ||||||
| 	public Microdisc::Delegate { |  | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		void set_rom(ROM rom, const std::vector<uint8_t> &data); | 		/// Creates and returns an Oric. | ||||||
| 		void set_key_state(uint16_t key, bool isPressed); | 		static Machine *Oric(); | ||||||
| 		void clear_all_keys(); |  | ||||||
|  |  | ||||||
| 		void set_use_fast_tape_hack(bool activate); | 		/// Sets the contents of @c rom to @c data. Assumed to be a setup step; has no effect once a machine is running. | ||||||
| 		void set_output_device(Outputs::CRT::OutputDevice output_device); | 		virtual void set_rom(ROM rom, const std::vector<uint8_t> &data) = 0; | ||||||
|  |  | ||||||
| 		// to satisfy ConfigurationTarget::Machine | 		/// Enables or disables turbo-speed tape loading. | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target); | 		virtual void set_use_fast_tape_hack(bool activate) = 0; | ||||||
|  |  | ||||||
| 		// to satisfy CPU6502::Processor | 		/// Sets the type of display the Oric is connected to. | ||||||
| 		unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); | 		virtual void set_output_device(Outputs::CRT::OutputDevice output_device) = 0; | ||||||
| 		void synchronise(); |  | ||||||
|  |  | ||||||
| 		// to satisfy CRTMachine::Machine |  | ||||||
| 		virtual void setup_output(float aspect_ratio); |  | ||||||
| 		virtual void close_output(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::CRT::CRT> get_crt(); |  | ||||||
| 		virtual std::shared_ptr<Outputs::Speaker> get_speaker(); |  | ||||||
| 		virtual void run_for_cycles(int number_of_cycles); |  | ||||||
|  |  | ||||||
| 		// to satisfy MOS::MOS6522IRQDelegate::Delegate |  | ||||||
| 		void mos6522_did_change_interrupt_status(void *mos6522); |  | ||||||
|  |  | ||||||
| 		// to satisfy Storage::Tape::BinaryTapePlayer::Delegate |  | ||||||
| 		void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player); |  | ||||||
|  |  | ||||||
| 		// for Utility::TypeRecipient::Delegate |  | ||||||
| 		uint16_t *sequence_for_character(Utility::Typer *typer, char character); |  | ||||||
|  |  | ||||||
| 		// for Microdisc::Delegate |  | ||||||
| 		void microdisc_did_change_paging_flags(class Microdisc *microdisc); |  | ||||||
| 		void wd1770_did_change_output(WD::WD1770 *wd1770); |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		// RAM and ROM |  | ||||||
| 		std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_; |  | ||||||
| 		uint8_t ram_[65536], rom_[16384]; |  | ||||||
| 		int cycles_since_video_update_; |  | ||||||
| 		inline void update_video(); |  | ||||||
|  |  | ||||||
| 		// ROM bookkeeping |  | ||||||
| 		bool is_using_basic11_; |  | ||||||
| 		uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_; |  | ||||||
| 		int keyboard_read_count_; |  | ||||||
|  |  | ||||||
| 		// Outputs |  | ||||||
| 		std::unique_ptr<VideoOutput> video_output_; |  | ||||||
|  |  | ||||||
| 		// Keyboard |  | ||||||
| 		class Keyboard { |  | ||||||
| 			public: |  | ||||||
| 				uint8_t row; |  | ||||||
| 				uint8_t rows[8]; |  | ||||||
| 		}; |  | ||||||
| 		int typer_delay_; |  | ||||||
|  |  | ||||||
| 		// The tape |  | ||||||
| 		class TapePlayer: public Storage::Tape::BinaryTapePlayer { |  | ||||||
| 			public: |  | ||||||
| 				TapePlayer(); |  | ||||||
| 				uint8_t get_next_byte(bool fast); |  | ||||||
|  |  | ||||||
| 			private: |  | ||||||
| 				Storage::Tape::Oric::Parser parser_; |  | ||||||
| 		}; |  | ||||||
| 		bool use_fast_tape_hack_; |  | ||||||
|  |  | ||||||
| 		// VIA (which owns the tape and the AY) |  | ||||||
| 		class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate { |  | ||||||
| 			public: |  | ||||||
| 				VIA(); |  | ||||||
| 				using MOS6522IRQDelegate::set_interrupt_status; |  | ||||||
|  |  | ||||||
| 				void set_control_line_output(Port port, Line line, bool value); |  | ||||||
| 				void set_port_output(Port port, uint8_t value, uint8_t direction_mask); |  | ||||||
| 				uint8_t get_port_input(Port port); |  | ||||||
| 				inline void run_for_cycles(unsigned int number_of_cycles); |  | ||||||
|  |  | ||||||
| 				std::shared_ptr<GI::AY38910> ay8910; |  | ||||||
| 				std::unique_ptr<TapePlayer> tape; |  | ||||||
| 				std::shared_ptr<Keyboard> keyboard; |  | ||||||
|  |  | ||||||
| 				void synchronise(); |  | ||||||
|  |  | ||||||
| 			private: |  | ||||||
| 				void update_ay(); |  | ||||||
| 				bool ay_bdir_, ay_bc1_; |  | ||||||
| 				unsigned int cycles_since_ay_update_; |  | ||||||
| 		}; |  | ||||||
| 		VIA via_; |  | ||||||
| 		std::shared_ptr<Keyboard> keyboard_; |  | ||||||
|  |  | ||||||
| 		// the Microdisc, if in use |  | ||||||
| 		class Microdisc microdisc_; |  | ||||||
| 		bool microdisc_is_enabled_; |  | ||||||
| 		uint16_t ram_top_; |  | ||||||
| 		uint8_t *paged_rom_; |  | ||||||
|  |  | ||||||
| 		inline void set_interrupt_line(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -74,13 +74,14 @@ std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() { | |||||||
| 	return crt_; | 	return crt_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoOutput::run_for_cycles(int number_of_cycles) { | void VideoOutput::run_for(const Cycles cycles) { | ||||||
| 	// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst | 	// Vertical: 0–39: pixels; otherwise blank; 48–53 sync, 54–56 colour burst | ||||||
| 	// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync | 	// Horizontal: 0–223: pixels; otherwise blank; 256–259 sync | ||||||
|  |  | ||||||
| #define clamp(action)	\ | #define clamp(action)	\ | ||||||
| 	if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles; | 	if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles; | ||||||
|  |  | ||||||
|  | 	int number_of_cycles = cycles.as_int(); | ||||||
| 	while(number_of_cycles) { | 	while(number_of_cycles) { | ||||||
| 		int h_counter = counter_ & 63; | 		int h_counter = counter_ & 63; | ||||||
| 		int cycles_run_for = 0; | 		int cycles_run_for = 0; | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #define Machines_Oric_Video_hpp | #define Machines_Oric_Video_hpp | ||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace Oric { | namespace Oric { | ||||||
|  |  | ||||||
| @@ -17,7 +18,7 @@ class VideoOutput { | |||||||
| 	public: | 	public: | ||||||
| 		VideoOutput(uint8_t *memory); | 		VideoOutput(uint8_t *memory); | ||||||
| 		std::shared_ptr<Outputs::CRT::CRT> get_crt(); | 		std::shared_ptr<Outputs::CRT::CRT> get_crt(); | ||||||
| 		void run_for_cycles(int number_of_cycles); | 		void run_for(const Cycles cycles); | ||||||
| 		void set_colour_rom(const std::vector<uint8_t> &rom); | 		void set_colour_rom(const std::vector<uint8_t> &rom); | ||||||
| 		void set_output_device(Outputs::CRT::OutputDevice output_device); | 		void set_output_device(Outputs::CRT::OutputDevice output_device); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,14 +11,19 @@ | |||||||
|  |  | ||||||
| using namespace Utility; | using namespace Utility; | ||||||
|  |  | ||||||
| Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) : | Typer::Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate) : | ||||||
| 		counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) { | 		counter_(-delay), | ||||||
|  | 		frequency_(frequency), | ||||||
|  | 		string_pointer_(0), | ||||||
|  | 		delegate_(delegate), | ||||||
|  | 		phase_(0), | ||||||
|  | 		character_mapper_(std::move(character_mapper)) { | ||||||
| 	size_t string_size = strlen(string) + 3; | 	size_t string_size = strlen(string) + 3; | ||||||
| 	string_ = (char *)malloc(string_size); | 	string_ = (char *)malloc(string_size); | ||||||
| 	snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString); | 	snprintf(string_, string_size, "%c%s%c", Typer::BeginString, string, Typer::EndString); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Typer::update(int duration) { | void Typer::run_for(const HalfCycles duration) { | ||||||
| 	if(string_) { | 	if(string_) { | ||||||
| 		if(counter_ < 0 && counter_ + duration >= 0) { | 		if(counter_ < 0 && counter_ + duration >= 0) { | ||||||
| 			if(!type_next_character()) { | 			if(!type_next_character()) { | ||||||
| @@ -36,10 +41,26 @@ void Typer::update(int duration) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool Typer::try_type_next_character() { | ||||||
|  | 	uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]); | ||||||
|  |  | ||||||
|  | 	if(!sequence || sequence[0] == CharacterMapper::NotMapped) { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(!phase_) delegate_->clear_all_keys(); | ||||||
|  | 	else { | ||||||
|  | 		delegate_->set_key_state(sequence[phase_ - 1], true); | ||||||
|  | 		return sequence[phase_] != CharacterMapper::EndSequence; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| bool Typer::type_next_character() { | bool Typer::type_next_character() { | ||||||
| 	if(string_ == nullptr) return false; | 	if(string_ == nullptr) return false; | ||||||
|  |  | ||||||
| 	if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) { | 	if(!try_type_next_character()) { | ||||||
| 		phase_ = 0; | 		phase_ = 0; | ||||||
| 		if(!string_[string_pointer_]) { | 		if(!string_[string_pointer_]) { | ||||||
| 			free(string_); | 			free(string_); | ||||||
| @@ -59,21 +80,11 @@ Typer::~Typer() { | |||||||
| 	free(string_); | 	free(string_); | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Delegate | #pragma mark - Character mapper | ||||||
|  |  | ||||||
| bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) { | uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) { | ||||||
| 	uint16_t *sequence = sequence_for_character(typer, character); | 	size_t ucharacter = (size_t)((unsigned char)character); | ||||||
| 	if(!sequence) return true; | 	if(ucharacter > (length / sizeof(KeySequence))) return nullptr; | ||||||
|  | 	if(sequences[ucharacter][0] == NotMapped) return nullptr; | ||||||
| 	if(!phase) clear_all_keys(); | 	return sequences[ucharacter]; | ||||||
| 	else { |  | ||||||
| 		set_key_state(sequence[phase - 1], true); |  | ||||||
| 		return sequence[phase] == Typer::Delegate::EndSequence; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) { |  | ||||||
| 	return nullptr; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,53 +11,117 @@ | |||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "KeyboardMachine.hpp" | #include "KeyboardMachine.hpp" | ||||||
|  | #include "../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| namespace Utility { | namespace Utility { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	An interface that provides a mapping from logical characters to the sequence of keys | ||||||
|  | 	necessary to type that character on a given machine. | ||||||
|  | */ | ||||||
|  | class CharacterMapper { | ||||||
|  | 	public: | ||||||
|  | 		/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. | ||||||
|  | 		virtual uint16_t *sequence_for_character(char character) = 0; | ||||||
|  |  | ||||||
|  | 		/// Terminates a key sequence. | ||||||
|  | 		static const uint16_t EndSequence = 0xffff; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			If returned as the first entry in a key sequence, indicates that the requested character | ||||||
|  | 			cannot be mapped. | ||||||
|  | 		*/ | ||||||
|  | 		static const uint16_t NotMapped = 0xfffe; | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		typedef uint16_t KeySequence[16]; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provided in the base class as a convenience: given the lookup table of key sequences @c sequences, | ||||||
|  | 			with @c length entries, returns the sequence for character @c character if it exists; otherwise | ||||||
|  | 			returns @c nullptr. | ||||||
|  | 		*/ | ||||||
|  | 		uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a stateful mechanism for typing a sequence of characters. Each character is mapped to a key sequence | ||||||
|  | 	by a character mapper. That key sequence is then replayed to a delegate. | ||||||
|  |  | ||||||
|  | 	Being given a delay and frequency at construction, the run_for interface can be used to produce time-based | ||||||
|  | 	typing. Alternatively, an owner may decline to use run_for and simply call type_next_character each time a | ||||||
|  | 	fresh key transition is ready to be consumed. | ||||||
|  | */ | ||||||
| class Typer { | class Typer { | ||||||
| 	public: | 	public: | ||||||
| 		class Delegate: public KeyboardMachine::Machine { | 		class Delegate: public KeyboardMachine::Machine { | ||||||
| 			public: | 			public: | ||||||
| 				virtual bool typer_set_next_character(Typer *typer, char character, int phase); |  | ||||||
| 				virtual void typer_reset(Typer *typer) = 0; | 				virtual void typer_reset(Typer *typer) = 0; | ||||||
|  |  | ||||||
| 				virtual uint16_t *sequence_for_character(Typer *typer, char character); |  | ||||||
|  |  | ||||||
| 				const uint16_t EndSequence = 0xffff; |  | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		Typer(const char *string, int delay, int frequency, Delegate *delegate); | 		Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate); | ||||||
| 		~Typer(); | 		~Typer(); | ||||||
| 		void update(int duration); |  | ||||||
|  | 		void run_for(const HalfCycles duration); | ||||||
| 		bool type_next_character(); | 		bool type_next_character(); | ||||||
|  |  | ||||||
|  | 		bool is_completed(); | ||||||
|  |  | ||||||
| 		const char BeginString = 0x02;	// i.e. ASCII start of text | 		const char BeginString = 0x02;	// i.e. ASCII start of text | ||||||
| 		const char EndString = 0x03;	// i.e. ASCII end of text | 		const char EndString = 0x03;	// i.e. ASCII end of text | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		char *string_; | 		char *string_; | ||||||
| 		int frequency_; |  | ||||||
| 		int counter_; |  | ||||||
| 		int phase_; |  | ||||||
| 		Delegate *delegate_; |  | ||||||
| 		size_t string_pointer_; | 		size_t string_pointer_; | ||||||
|  |  | ||||||
|  | 		HalfCycles frequency_; | ||||||
|  | 		HalfCycles counter_; | ||||||
|  | 		int phase_; | ||||||
|  |  | ||||||
|  | 		Delegate *delegate_; | ||||||
|  | 		std::unique_ptr<CharacterMapper> character_mapper_; | ||||||
|  |  | ||||||
|  | 		bool try_type_next_character(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a default base class for type recipients: classes that want to attach a single typer at a time and | ||||||
|  | 	which may or may not want to nominate an initial delay and typing frequency. | ||||||
|  | */ | ||||||
| class TypeRecipient: public Typer::Delegate { | class TypeRecipient: public Typer::Delegate { | ||||||
| 	public: | 	public: | ||||||
| 		void set_typer_for_string(const char *string) { | 		/// Attaches a typer to this class that will type @c string using @c character_mapper as a source. | ||||||
| 			typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this)); | 		void set_typer_for_string(const char *string, std::unique_ptr<CharacterMapper> character_mapper) { | ||||||
|  | 			typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provided as a hook for subclasses to implement so that external callers can install a typer | ||||||
|  | 			without needing inside knowledge as to where the character mapper comes from. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_typer_for_string(const char *string) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provided in order to conform to that part of the Typer::Delegate interface that goes above and | ||||||
|  | 			beyond KeyboardMachine::Machine; responds to the end of typing by clearing all keys. | ||||||
|  | 		*/ | ||||||
| 		void typer_reset(Typer *typer) { | 		void typer_reset(Typer *typer) { | ||||||
| 			clear_all_keys(); | 			clear_all_keys(); | ||||||
| 			typer_.reset(); |  | ||||||
|  | 			// It's unsafe to deallocate typer right now, since it is the caller, but also it has a small | ||||||
|  | 			// memory footprint and it's desireable not to imply that the subclass need call it any more. | ||||||
|  | 			// So shuffle it off into a siding. | ||||||
|  | 			previous_typer_ = std::move(typer_); | ||||||
|  | 			typer_ = nullptr; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		virtual int get_typer_delay() { return 0; } | 		virtual HalfCycles get_typer_delay() { return HalfCycles(0); } | ||||||
| 		virtual int get_typer_frequency() { return 0; } | 		virtual HalfCycles get_typer_frequency() { return HalfCycles(0); } | ||||||
| 		std::unique_ptr<Typer> typer_; | 		std::unique_ptr<Typer> typer_; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::unique_ptr<Typer> previous_typer_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										159
									
								
								Machines/ZX8081/CharacterMapper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								Machines/ZX8081/CharacterMapper.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
|  | #include "ZX8081.hpp" | ||||||
|  |  | ||||||
|  | using namespace ZX8081; | ||||||
|  |  | ||||||
|  | CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {} | ||||||
|  |  | ||||||
|  | uint16_t *CharacterMapper::sequence_for_character(char character) { | ||||||
|  | #define KEYS(...)	{__VA_ARGS__, EndSequence} | ||||||
|  | #define SHIFT(...)	{KeyShift, __VA_ARGS__, EndSequence} | ||||||
|  | #define X			{NotMapped} | ||||||
|  | 	static KeySequence zx81_key_sequences[] = { | ||||||
|  | 		/* NUL */	X,							/* SOH */	X, | ||||||
|  | 		/* STX */	X,							/* ETX */	X, | ||||||
|  | 		/* EOT */	X,							/* ENQ */	X, | ||||||
|  | 		/* ACK */	X,							/* BEL */	X, | ||||||
|  | 		/* BS */	SHIFT(Key0),				/* HT */	X, | ||||||
|  | 		/* LF */	KEYS(KeyEnter),				/* VT */	X, | ||||||
|  | 		/* FF */	X,							/* CR */	X, | ||||||
|  | 		/* SO */	X,							/* SI */	X, | ||||||
|  | 		/* DLE */	X,							/* DC1 */	X, | ||||||
|  | 		/* DC2 */	X,							/* DC3 */	X, | ||||||
|  | 		/* DC4 */	X,							/* NAK */	X, | ||||||
|  | 		/* SYN */	X,							/* ETB */	X, | ||||||
|  | 		/* CAN */	X,							/* EM */	X, | ||||||
|  | 		/* SUB */	X,							/* ESC */	X, | ||||||
|  | 		/* FS */	X,							/* GS */	X, | ||||||
|  | 		/* RS */	X,							/* US */	X, | ||||||
|  | 		/* space */	KEYS(KeySpace),				/* ! */		X, | ||||||
|  | 		/* " */		SHIFT(KeyP),				/* # */		X, | ||||||
|  | 		/* $ */		SHIFT(KeyU),				/* % */		X, | ||||||
|  | 		/* & */		X,							/* ' */		X, | ||||||
|  | 		/* ( */		SHIFT(KeyI),				/* ) */		SHIFT(KeyO), | ||||||
|  | 		/* * */		SHIFT(KeyB),				/* + */		SHIFT(KeyK), | ||||||
|  | 		/* , */		SHIFT(KeyDot),				/* - */		SHIFT(KeyJ), | ||||||
|  | 		/* . */		KEYS(KeyDot),				/* / */		SHIFT(KeyV), | ||||||
|  | 		/* 0 */		KEYS(Key0),					/* 1 */		KEYS(Key1), | ||||||
|  | 		/* 2 */		KEYS(Key2),					/* 3 */		KEYS(Key3), | ||||||
|  | 		/* 4 */		KEYS(Key4),					/* 5 */		KEYS(Key5), | ||||||
|  | 		/* 6 */		KEYS(Key6),					/* 7 */		KEYS(Key7), | ||||||
|  | 		/* 8 */		KEYS(Key8),					/* 9 */		KEYS(Key9), | ||||||
|  | 		/* : */		SHIFT(KeyZ),				/* ; */		SHIFT(KeyX), | ||||||
|  | 		/* < */		SHIFT(KeyN),				/* = */		SHIFT(KeyL), | ||||||
|  | 		/* > */		SHIFT(KeyM),				/* ? */		SHIFT(KeyC), | ||||||
|  | 		/* @ */		X,							/* A */		KEYS(KeyA), | ||||||
|  | 		/* B */		KEYS(KeyB),					/* C */		KEYS(KeyC), | ||||||
|  | 		/* D */		KEYS(KeyD),					/* E */		KEYS(KeyE), | ||||||
|  | 		/* F */		KEYS(KeyF),					/* G */		KEYS(KeyG), | ||||||
|  | 		/* H */		KEYS(KeyH),					/* I */		KEYS(KeyI), | ||||||
|  | 		/* J */		KEYS(KeyJ),					/* K */		KEYS(KeyK), | ||||||
|  | 		/* L */		KEYS(KeyL),					/* M */		KEYS(KeyM), | ||||||
|  | 		/* N */		KEYS(KeyN),					/* O */		KEYS(KeyO), | ||||||
|  | 		/* P */		KEYS(KeyP),					/* Q */		KEYS(KeyQ), | ||||||
|  | 		/* R */		KEYS(KeyR),					/* S */		KEYS(KeyS), | ||||||
|  | 		/* T */		KEYS(KeyT),					/* U */		KEYS(KeyU), | ||||||
|  | 		/* V */		KEYS(KeyV),					/* W */		KEYS(KeyW), | ||||||
|  | 		/* X */		KEYS(KeyX),					/* Y */		KEYS(KeyY), | ||||||
|  | 		/* Z */		KEYS(KeyZ),					/* [ */		X, | ||||||
|  | 		/* \ */		X,							/* ] */		X, | ||||||
|  | 		/* ^ */		X,							/* _ */		X, | ||||||
|  | 		/* ` */		X,							/* a */		KEYS(KeyA), | ||||||
|  | 		/* b */		KEYS(KeyB),					/* c */		KEYS(KeyC), | ||||||
|  | 		/* d */		KEYS(KeyD),					/* e */		KEYS(KeyE), | ||||||
|  | 		/* f */		KEYS(KeyF),					/* g */		KEYS(KeyG), | ||||||
|  | 		/* h */		KEYS(KeyH),					/* i */		KEYS(KeyI), | ||||||
|  | 		/* j */		KEYS(KeyJ),					/* k */		KEYS(KeyK), | ||||||
|  | 		/* l */		KEYS(KeyL),					/* m */		KEYS(KeyM), | ||||||
|  | 		/* n */		KEYS(KeyN),					/* o */		KEYS(KeyO), | ||||||
|  | 		/* p */		KEYS(KeyP),					/* q */		KEYS(KeyQ), | ||||||
|  | 		/* r */		KEYS(KeyR),					/* s */		KEYS(KeyS), | ||||||
|  | 		/* t */		KEYS(KeyT),					/* u */		KEYS(KeyU), | ||||||
|  | 		/* v */		KEYS(KeyV),					/* w */		KEYS(KeyW), | ||||||
|  | 		/* x */		KEYS(KeyX),					/* y */		KEYS(KeyY), | ||||||
|  | 		/* z */		KEYS(KeyZ),					/* { */		X, | ||||||
|  | 		/* | */		X,							/* } */		X, | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	static KeySequence zx80_key_sequences[] = { | ||||||
|  | 		/* NUL */	X,							/* SOH */	X, | ||||||
|  | 		/* STX */	X,							/* ETX */	X, | ||||||
|  | 		/* EOT */	X,							/* ENQ */	X, | ||||||
|  | 		/* ACK */	X,							/* BEL */	X, | ||||||
|  | 		/* BS */	SHIFT(Key0),				/* HT */	X, | ||||||
|  | 		/* LF */	KEYS(KeyEnter),				/* VT */	X, | ||||||
|  | 		/* FF */	X,							/* CR */	X, | ||||||
|  | 		/* SO */	X,							/* SI */	X, | ||||||
|  | 		/* DLE */	X,							/* DC1 */	X, | ||||||
|  | 		/* DC2 */	X,							/* DC3 */	X, | ||||||
|  | 		/* DC4 */	X,							/* NAK */	X, | ||||||
|  | 		/* SYN */	X,							/* ETB */	X, | ||||||
|  | 		/* CAN */	X,							/* EM */	X, | ||||||
|  | 		/* SUB */	X,							/* ESC */	X, | ||||||
|  | 		/* FS */	X,							/* GS */	X, | ||||||
|  | 		/* RS */	X,							/* US */	X, | ||||||
|  | 		/* space */	KEYS(KeySpace),				/* ! */		X, | ||||||
|  | 		/* " */		SHIFT(KeyY),				/* # */		X, | ||||||
|  | 		/* $ */		SHIFT(KeyU),				/* % */		X, | ||||||
|  | 		/* & */		X,							/* ' */		X, | ||||||
|  | 		/* ( */		SHIFT(KeyI),				/* ) */		SHIFT(KeyO), | ||||||
|  | 		/* * */		SHIFT(KeyP),				/* + */		SHIFT(KeyK), | ||||||
|  | 		/* , */		SHIFT(KeyDot),				/* - */		SHIFT(KeyJ), | ||||||
|  | 		/* . */		KEYS(KeyDot),				/* / */		SHIFT(KeyV), | ||||||
|  | 		/* 0 */		KEYS(Key0),					/* 1 */		KEYS(Key1), | ||||||
|  | 		/* 2 */		KEYS(Key2),					/* 3 */		KEYS(Key3), | ||||||
|  | 		/* 4 */		KEYS(Key4),					/* 5 */		KEYS(Key5), | ||||||
|  | 		/* 6 */		KEYS(Key6),					/* 7 */		KEYS(Key7), | ||||||
|  | 		/* 8 */		KEYS(Key8),					/* 9 */		KEYS(Key9), | ||||||
|  | 		/* : */		SHIFT(KeyZ),				/* ; */		SHIFT(KeyX), | ||||||
|  | 		/* < */		SHIFT(KeyN),				/* = */		SHIFT(KeyL), | ||||||
|  | 		/* > */		SHIFT(KeyM),				/* ? */		SHIFT(KeyC), | ||||||
|  | 		/* @ */		X,							/* A */		KEYS(KeyA), | ||||||
|  | 		/* B */		KEYS(KeyB),					/* C */		KEYS(KeyC), | ||||||
|  | 		/* D */		KEYS(KeyD),					/* E */		KEYS(KeyE), | ||||||
|  | 		/* F */		KEYS(KeyF),					/* G */		KEYS(KeyG), | ||||||
|  | 		/* H */		KEYS(KeyH),					/* I */		KEYS(KeyI), | ||||||
|  | 		/* J */		KEYS(KeyJ),					/* K */		KEYS(KeyK), | ||||||
|  | 		/* L */		KEYS(KeyL),					/* M */		KEYS(KeyM), | ||||||
|  | 		/* N */		KEYS(KeyN),					/* O */		KEYS(KeyO), | ||||||
|  | 		/* P */		KEYS(KeyP),					/* Q */		KEYS(KeyQ), | ||||||
|  | 		/* R */		KEYS(KeyR),					/* S */		KEYS(KeyS), | ||||||
|  | 		/* T */		KEYS(KeyT),					/* U */		KEYS(KeyU), | ||||||
|  | 		/* V */		KEYS(KeyV),					/* W */		KEYS(KeyW), | ||||||
|  | 		/* X */		KEYS(KeyX),					/* Y */		KEYS(KeyY), | ||||||
|  | 		/* Z */		KEYS(KeyZ),					/* [ */		X, | ||||||
|  | 		/* \ */		X,							/* ] */		X, | ||||||
|  | 		/* ^ */		X,							/* _ */		X, | ||||||
|  | 		/* ` */		X,							/* a */		KEYS(KeyA), | ||||||
|  | 		/* b */		KEYS(KeyB),					/* c */		KEYS(KeyC), | ||||||
|  | 		/* d */		KEYS(KeyD),					/* e */		KEYS(KeyE), | ||||||
|  | 		/* f */		KEYS(KeyF),					/* g */		KEYS(KeyG), | ||||||
|  | 		/* h */		KEYS(KeyH),					/* i */		KEYS(KeyI), | ||||||
|  | 		/* j */		KEYS(KeyJ),					/* k */		KEYS(KeyK), | ||||||
|  | 		/* l */		KEYS(KeyL),					/* m */		KEYS(KeyM), | ||||||
|  | 		/* n */		KEYS(KeyN),					/* o */		KEYS(KeyO), | ||||||
|  | 		/* p */		KEYS(KeyP),					/* q */		KEYS(KeyQ), | ||||||
|  | 		/* r */		KEYS(KeyR),					/* s */		KEYS(KeyS), | ||||||
|  | 		/* t */		KEYS(KeyT),					/* u */		KEYS(KeyU), | ||||||
|  | 		/* v */		KEYS(KeyV),					/* w */		KEYS(KeyW), | ||||||
|  | 		/* x */		KEYS(KeyX),					/* y */		KEYS(KeyY), | ||||||
|  | 		/* z */		KEYS(KeyZ),					/* { */		X, | ||||||
|  | 		/* | */		X,							/* } */		X, | ||||||
|  | 	}; | ||||||
|  | #undef KEYS | ||||||
|  | #undef SHIFT | ||||||
|  | #undef X | ||||||
|  |  | ||||||
|  | 	if(is_zx81_) | ||||||
|  | 		return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character); | ||||||
|  | 	else | ||||||
|  | 		return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character); | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								Machines/ZX8081/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Machines/ZX8081/CharacterMapper.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  CharacterMapper.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_ZX8081_CharacterMapper_hpp | ||||||
|  | #define Machines_ZX8081_CharacterMapper_hpp | ||||||
|  |  | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | class CharacterMapper: public ::Utility::CharacterMapper { | ||||||
|  | 	public: | ||||||
|  | 		CharacterMapper(bool is_zx81); | ||||||
|  | 		uint16_t *sequence_for_character(char character); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		bool is_zx81_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* CharacterMapper_hpp */ | ||||||
							
								
								
									
										109
									
								
								Machines/ZX8081/Video.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Machines/ZX8081/Video.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | // | ||||||
|  | //  Video.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 06/06/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | using namespace ZX8081; | ||||||
|  |  | ||||||
|  | Video::Video() : | ||||||
|  | 	crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)), | ||||||
|  | 	line_data_(nullptr), | ||||||
|  | 	line_data_pointer_(nullptr), | ||||||
|  | 	cycles_since_update_(0), | ||||||
|  | 	sync_(false) { | ||||||
|  |  | ||||||
|  | 	// Set a composite sampling function that assumes 8bpp input grayscale. | ||||||
|  | 	// TODO: lessen this to 1bpp. | ||||||
|  | 	crt_->set_composite_sampling_function( | ||||||
|  | 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | ||||||
|  | 		"{" | ||||||
|  | 			"return float(texture(texID, coordinate).r) / 255.0;" | ||||||
|  | 		"}"); | ||||||
|  |  | ||||||
|  | 	// Show only the centre 80% of the TV frame. | ||||||
|  | 	crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::run_for(const HalfCycles half_cycles) { | ||||||
|  | 	// Just keep a running total of the amount of time that remains owed to the CRT. | ||||||
|  | 	cycles_since_update_ += (unsigned int)half_cycles.as_int(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::flush() { | ||||||
|  | 	flush(sync_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::flush(bool next_sync) { | ||||||
|  | 	if(sync_) { | ||||||
|  | 		// If in sync, that takes priority. Output the proper amount of sync. | ||||||
|  | 		crt_->output_sync(cycles_since_update_); | ||||||
|  | 	} else { | ||||||
|  | 		// If not presently in sync, then... | ||||||
|  |  | ||||||
|  | 		if(line_data_) { | ||||||
|  | 			// If there is output data queued, output it either if it's being interrupted by | ||||||
|  | 			// sync, or if we're past its end anyway. Otherwise let it be. | ||||||
|  | 			unsigned int data_length = (unsigned int)(line_data_pointer_ - line_data_); | ||||||
|  | 			if(data_length < cycles_since_update_ || next_sync) { | ||||||
|  | 				unsigned int output_length = std::min(data_length, cycles_since_update_); | ||||||
|  | 				crt_->output_data(output_length, 1); | ||||||
|  | 				line_data_pointer_ = line_data_ = nullptr; | ||||||
|  | 				cycles_since_update_ -= output_length; | ||||||
|  | 			} else return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Any pending pixels being dealt with, pad with the white level. | ||||||
|  | 		uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1); | ||||||
|  | 		if(colour_pointer) *colour_pointer = 0xff; | ||||||
|  | 		crt_->output_level(cycles_since_update_); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cycles_since_update_ = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::set_sync(bool sync) { | ||||||
|  | 	// Do nothing if sync hasn't changed. | ||||||
|  | 	if(sync_ == sync) return; | ||||||
|  |  | ||||||
|  | 	// Complete whatever was being drawn, and update sync. | ||||||
|  | 	flush(sync); | ||||||
|  | 	sync_ = sync; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Video::output_byte(uint8_t byte) { | ||||||
|  | 	// Complete whatever was going on. | ||||||
|  | 	if(sync_) return; | ||||||
|  | 	flush(); | ||||||
|  |  | ||||||
|  | 	// Grab a buffer if one isn't already available. | ||||||
|  | 	if(!line_data_) { | ||||||
|  | 		line_data_pointer_ = line_data_ = crt_->allocate_write_area(320); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If a buffer was obtained, serialise the new pixels. | ||||||
|  | 	if(line_data_) { | ||||||
|  | 		// If the buffer is full, output it now and obtain a new one | ||||||
|  | 		if(line_data_pointer_ - line_data_ == 320) { | ||||||
|  | 			crt_->output_data(320, 1); | ||||||
|  | 			cycles_since_update_ -= 320; | ||||||
|  | 			line_data_pointer_ = line_data_ = crt_->allocate_write_area(320); | ||||||
|  | 			if(!line_data_) return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t mask = 0x80; | ||||||
|  | 		for(int c = 0; c < 8; c++) { | ||||||
|  | 			line_data_pointer_[c] = (byte & mask) ? 0xff : 0x00; | ||||||
|  | 			mask >>= 1; | ||||||
|  | 		} | ||||||
|  | 		line_data_pointer_ += 8; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::shared_ptr<Outputs::CRT::CRT> Video::get_crt() { | ||||||
|  | 	return crt_; | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								Machines/ZX8081/Video.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								Machines/ZX8081/Video.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | // | ||||||
|  | //  Video.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 06/06/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_ZX8081_Video_hpp | ||||||
|  | #define Machines_ZX8081_Video_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/CRT/CRT.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Packages a ZX80/81-style video feed into a CRT-compatible waveform. | ||||||
|  |  | ||||||
|  | 	While sync is active, this feed will output the sync level. | ||||||
|  |  | ||||||
|  | 	While sync is inactive, this feed will output the white level unless it is supplied | ||||||
|  | 	with a byte to output. When a byte is supplied for output, it will be interpreted as | ||||||
|  | 	a 1-bit graphic and output over the next 4 cycles, picking between the white level | ||||||
|  | 	and the black level. | ||||||
|  | */ | ||||||
|  | class Video { | ||||||
|  | 	public: | ||||||
|  | 		/// Constructs an instance of the video feed; a CRT is also created. | ||||||
|  | 		Video(); | ||||||
|  | 		/// @returns The CRT this video feed is feeding. | ||||||
|  | 		std::shared_ptr<Outputs::CRT::CRT> get_crt(); | ||||||
|  |  | ||||||
|  | 		/// Advances time by @c cycles. | ||||||
|  | 		void run_for(const HalfCycles); | ||||||
|  | 		/// Forces output to catch up to the current output position. | ||||||
|  | 		void flush(); | ||||||
|  |  | ||||||
|  | 		/// Sets the current sync output. | ||||||
|  | 		void set_sync(bool sync); | ||||||
|  | 		/// Causes @c byte to be serialised into pixels and output over the next four cycles. | ||||||
|  | 		void output_byte(uint8_t byte); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		bool sync_; | ||||||
|  | 		uint8_t *line_data_, *line_data_pointer_; | ||||||
|  | 		unsigned int cycles_since_update_; | ||||||
|  | 		std::shared_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
|  | 		void flush(bool next_sync); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Video_hpp */ | ||||||
							
								
								
									
										400
									
								
								Machines/ZX8081/ZX8081.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								Machines/ZX8081/ZX8081.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,400 @@ | |||||||
|  | // | ||||||
|  | //  ZX8081.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/06/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ZX8081.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Processors/Z80/Z80.hpp" | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  | #include "../../Storage/Tape/Parsers/ZX8081.hpp" | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
|  | #include "../MemoryFuzzer.hpp" | ||||||
|  | #include "../Typer.hpp" | ||||||
|  |  | ||||||
|  | #include "CharacterMapper.hpp" | ||||||
|  | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  | 	// The clock rate is 3.25Mhz. | ||||||
|  | 	const unsigned int ZX8081ClockRate = 3250000; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | template<bool is_zx81> class ConcreteMachine: | ||||||
|  | 	public Utility::TypeRecipient, | ||||||
|  | 	public CPU::Z80::BusHandler, | ||||||
|  | 	public Machine { | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
|  | 			z80_(*this), | ||||||
|  | 			vsync_(false), | ||||||
|  | 			hsync_(false), | ||||||
|  | 			nmi_is_enabled_(false), | ||||||
|  | 			tape_player_(ZX8081ClockRate), | ||||||
|  | 			use_fast_tape_hack_(false), | ||||||
|  | 			tape_advance_delay_(0), | ||||||
|  | 			has_latched_video_byte_(false) { | ||||||
|  | 			set_clock_rate(ZX8081ClockRate); | ||||||
|  | 			clear_all_keys(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
|  | 			HalfCycles previous_counter = horizontal_counter_; | ||||||
|  | 			horizontal_counter_ += cycle.length; | ||||||
|  |  | ||||||
|  | 			if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) { | ||||||
|  | 				video_->run_for(vsync_start_ - previous_counter); | ||||||
|  | 				set_hsync(true); | ||||||
|  | 				line_counter_ = (line_counter_ + 1) & 7; | ||||||
|  | 				if(nmi_is_enabled_) { | ||||||
|  | 					z80_.set_non_maskable_interrupt_line(true); | ||||||
|  | 				} | ||||||
|  | 				video_->run_for(horizontal_counter_ - vsync_start_); | ||||||
|  | 			} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) { | ||||||
|  | 				video_->run_for(vsync_end_ - previous_counter); | ||||||
|  | 				set_hsync(false); | ||||||
|  | 				if(nmi_is_enabled_) { | ||||||
|  | 					z80_.set_non_maskable_interrupt_line(false); | ||||||
|  | 					z80_.set_wait_line(false); | ||||||
|  | 				} | ||||||
|  | 				video_->run_for(horizontal_counter_ - vsync_end_); | ||||||
|  | 			} else { | ||||||
|  | 				video_->run_for(cycle.length); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207)); | ||||||
|  | 			if(!tape_advance_delay_) { | ||||||
|  | 				tape_player_.run_for(cycle.length); | ||||||
|  | 			} else { | ||||||
|  | 				tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) { | ||||||
|  | 				z80_.set_wait_line(true); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(!cycle.is_terminal()) { | ||||||
|  | 				return Cycles(0); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			uint16_t address = cycle.address ? *cycle.address : 0; | ||||||
|  | 			bool is_opcode_read = false; | ||||||
|  | 			switch(cycle.operation) { | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Output: | ||||||
|  | 					if(!(address & 2)) nmi_is_enabled_ = false; | ||||||
|  | 					if(!(address & 1)) nmi_is_enabled_ = is_zx81_; | ||||||
|  | 					if(!nmi_is_enabled_) { | ||||||
|  | 						// Line counter reset is held low while vsync is active; simulate that lazily by performing | ||||||
|  | 						// an instant reset upon the transition from active to inactive. | ||||||
|  | 						if(vsync_) line_counter_ = 0; | ||||||
|  | 						set_vsync(false); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Input: { | ||||||
|  | 					uint8_t value = 0xff; | ||||||
|  | 					if(!(address&1)) { | ||||||
|  | 						if(!nmi_is_enabled_) set_vsync(true); | ||||||
|  |  | ||||||
|  | 						uint16_t mask = 0x100; | ||||||
|  | 						for(int c = 0; c < 8; c++) { | ||||||
|  | 							if(!(address & mask)) value &= key_states_[c]; | ||||||
|  | 							mask <<= 1; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						value &= ~(tape_player_.get_input() ? 0x00 : 0x80); | ||||||
|  | 					} | ||||||
|  | 					*cycle.value = value; | ||||||
|  | 				} break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Interrupt: | ||||||
|  | 					// resetting event is M1 and IOREQ both simultaneously having leading edges; | ||||||
|  | 					// that happens 2 cycles before the end of INTACK. So the timer was reset and | ||||||
|  | 					// now has advanced twice. | ||||||
|  | 					horizontal_counter_ = HalfCycles(2); | ||||||
|  |  | ||||||
|  | 					*cycle.value = 0xff; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Refresh: | ||||||
|  | 					// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh | ||||||
|  | 					// address is low. The Z80 signals a refresh, providing the refresh address during the | ||||||
|  | 					// final two cycles of an opcode fetch. Therefore communicate a transient signalling | ||||||
|  | 					// of the IRQ line if necessary. | ||||||
|  | 					if(!(address & 0x40)) { | ||||||
|  | 						z80_.set_interrupt_line(true, Cycles(-2)); | ||||||
|  | 						z80_.set_interrupt_line(false); | ||||||
|  | 					} | ||||||
|  | 					if(has_latched_video_byte_) { | ||||||
|  | 						size_t char_address = (size_t)((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_); | ||||||
|  | 						uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff; | ||||||
|  | 						if(char_address < ram_base_) { | ||||||
|  | 							latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask; | ||||||
|  | 						} else { | ||||||
|  | 							latched_video_byte_ = ram_[address & ram_mask_] ^ mask; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						video_->output_byte(latched_video_byte_); | ||||||
|  | 						has_latched_video_byte_ = false; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
|  | 					// Check for use of the fast tape hack. | ||||||
|  | 					if(use_fast_tape_hack_ && address == tape_trap_address_ && tape_player_.has_tape()) { | ||||||
|  | 						uint64_t prior_offset = tape_player_.get_tape()->get_offset(); | ||||||
|  | 						int next_byte = parser_.get_next_byte(tape_player_.get_tape()); | ||||||
|  | 						if(next_byte != -1) { | ||||||
|  | 							uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL); | ||||||
|  | 							ram_[hl & ram_mask_] = (uint8_t)next_byte; | ||||||
|  | 							*cycle.value = 0x00; | ||||||
|  | 							z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1); | ||||||
|  |  | ||||||
|  | 							// Assume that having read one byte quickly, we're probably going to be asked to read | ||||||
|  | 							// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order | ||||||
|  | 							// to avoid fighting with real time. This is a stop-gap fix. | ||||||
|  | 							tape_advance_delay_ = 1000; | ||||||
|  | 							return 0; | ||||||
|  | 						} else { | ||||||
|  | 							tape_player_.get_tape()->set_offset(prior_offset); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Check for automatic tape control. | ||||||
|  | 					if(use_automatic_tape_motor_control_) { | ||||||
|  | 						tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_)); | ||||||
|  | 					} | ||||||
|  | 					is_opcode_read = true; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Read: | ||||||
|  | 					if(address < ram_base_) { | ||||||
|  | 						*cycle.value = rom_[address & rom_mask_]; | ||||||
|  | 					} else { | ||||||
|  | 						uint8_t value = ram_[address & ram_mask_]; | ||||||
|  |  | ||||||
|  | 						// If this is an M1 cycle reading from above the 32kb mark and HALT is not | ||||||
|  | 						// currently active, latch for video output and return a NOP. Otherwise, | ||||||
|  | 						// just return the value as read. | ||||||
|  | 						if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) { | ||||||
|  | 							latched_video_byte_ = value; | ||||||
|  | 							has_latched_video_byte_ = true; | ||||||
|  | 							*cycle.value = 0; | ||||||
|  | 						} else *cycle.value = value; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Write: | ||||||
|  | 					if(address >= ram_base_) { | ||||||
|  | 						ram_[address & ram_mask_] = *cycle.value; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				default: break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(typer_) typer_->run_for(cycle.length); | ||||||
|  |  | ||||||
|  | 			return HalfCycles(0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline void flush() { | ||||||
|  | 			video_->flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void setup_output(float aspect_ratio) override final { | ||||||
|  | 			video_.reset(new Video); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void close_output() override final { | ||||||
|  | 			video_.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::CRT::CRT> get_crt() override final { | ||||||
|  | 			return video_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Outputs::Speaker> get_speaker() override final { | ||||||
|  | 			return nullptr; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override final { | ||||||
|  | 			z80_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const StaticAnalyser::Target &target) override final { | ||||||
|  | 			is_zx81_ = target.zx8081.isZX81; | ||||||
|  | 			if(is_zx81_) { | ||||||
|  | 				rom_ = zx81_rom_; | ||||||
|  | 				tape_trap_address_ = 0x37c; | ||||||
|  | 				tape_return_address_ = 0x380; | ||||||
|  | 				vsync_start_ = HalfCycles(32); | ||||||
|  | 				vsync_end_ = HalfCycles(64); | ||||||
|  | 				automatic_tape_motor_start_address_ = 0x0340; | ||||||
|  | 				automatic_tape_motor_end_address_ = 0x03c3; | ||||||
|  | 			} else { | ||||||
|  | 				rom_ = zx80_rom_; | ||||||
|  | 				tape_trap_address_ = 0x220; | ||||||
|  | 				tape_return_address_ = 0x248; | ||||||
|  | 				vsync_start_ = HalfCycles(26); | ||||||
|  | 				vsync_end_ = HalfCycles(66); | ||||||
|  | 				automatic_tape_motor_start_address_ = 0x0206; | ||||||
|  | 				automatic_tape_motor_end_address_ = 0x024d; | ||||||
|  | 			} | ||||||
|  | 			rom_mask_ = (uint16_t)(rom_.size() - 1); | ||||||
|  |  | ||||||
|  | 			switch(target.zx8081.memory_model) { | ||||||
|  | 				case StaticAnalyser::ZX8081MemoryModel::Unexpanded: | ||||||
|  | 					ram_.resize(1024); | ||||||
|  | 					ram_base_ = 16384; | ||||||
|  | 					ram_mask_ = 1023; | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::ZX8081MemoryModel::SixteenKB: | ||||||
|  | 					ram_.resize(16384); | ||||||
|  | 					ram_base_ = 16384; | ||||||
|  | 					ram_mask_ = 16383; | ||||||
|  | 				break; | ||||||
|  | 				case StaticAnalyser::ZX8081MemoryModel::SixtyFourKB: | ||||||
|  | 					ram_.resize(65536); | ||||||
|  | 					ram_base_ = 8192; | ||||||
|  | 					ram_mask_ = 65535; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			Memory::Fuzz(ram_); | ||||||
|  |  | ||||||
|  | 			if(target.loadingCommand.length()) { | ||||||
|  | 				set_typer_for_string(target.loadingCommand.c_str()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const StaticAnalyser::Media &media) override final { | ||||||
|  | 			if(!media.tapes.empty()) { | ||||||
|  | 				tape_player_.set_tape(media.tapes.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_typer_for_string(const char *string) override final { | ||||||
|  | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_)); | ||||||
|  | 			Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_rom(ROMType type, std::vector<uint8_t> data) override final { | ||||||
|  | 			switch(type) { | ||||||
|  | 				case ZX80: zx80_rom_ = data; break; | ||||||
|  | 				case ZX81: zx81_rom_ = data; break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | #pragma mark - Keyboard | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
|  | 			if(isPressed) | ||||||
|  | 				key_states_[key >> 8] &= (uint8_t)(~key); | ||||||
|  | 			else | ||||||
|  | 				key_states_[key >> 8] |= (uint8_t)key; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			memset(key_states_, 0xff, 8); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | #pragma mark - Tape control | ||||||
|  |  | ||||||
|  | 		void set_use_fast_tape_hack(bool activate) override final { | ||||||
|  | 			use_fast_tape_hack_ = activate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_use_automatic_tape_motor_control(bool enabled) override final { | ||||||
|  | 			use_automatic_tape_motor_control_ = enabled; | ||||||
|  | 			if(!enabled) { | ||||||
|  | 				tape_player_.set_motor_control(false); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		void set_tape_is_playing(bool is_playing) override final { | ||||||
|  | 			tape_player_.set_motor_control(is_playing); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | #pragma mark - Typer timing | ||||||
|  |  | ||||||
|  | 		HalfCycles get_typer_delay() override final { return Cycles(7000000); } | ||||||
|  | 		HalfCycles get_typer_frequency() override final { return Cycles(390000); } | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_; | ||||||
|  |  | ||||||
|  | 		std::shared_ptr<Video> video_; | ||||||
|  | 		std::vector<uint8_t> zx81_rom_, zx80_rom_; | ||||||
|  |  | ||||||
|  | 		uint16_t tape_trap_address_, tape_return_address_; | ||||||
|  | 		uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_; | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> ram_; | ||||||
|  | 		uint16_t ram_mask_, ram_base_; | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> rom_; | ||||||
|  | 		uint16_t rom_mask_; | ||||||
|  |  | ||||||
|  | 		bool vsync_, hsync_; | ||||||
|  | 		int line_counter_; | ||||||
|  |  | ||||||
|  | 		uint8_t key_states_[8]; | ||||||
|  |  | ||||||
|  | 		HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_; | ||||||
|  | 		Storage::Tape::ZX8081::Parser parser_; | ||||||
|  |  | ||||||
|  | 		bool is_zx81_; | ||||||
|  | 		bool nmi_is_enabled_; | ||||||
|  |  | ||||||
|  | 		HalfCycles vsync_start_, vsync_end_; | ||||||
|  | 		HalfCycles horizontal_counter_; | ||||||
|  |  | ||||||
|  | 		uint8_t latched_video_byte_; | ||||||
|  | 		bool has_latched_video_byte_; | ||||||
|  |  | ||||||
|  | 		bool use_fast_tape_hack_; | ||||||
|  | 		bool use_automatic_tape_motor_control_; | ||||||
|  | 		HalfCycles tape_advance_delay_; | ||||||
|  |  | ||||||
|  | #pragma mark - Video | ||||||
|  |  | ||||||
|  | 		inline void set_vsync(bool sync) { | ||||||
|  | 			vsync_ = sync; | ||||||
|  | 			update_sync(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void set_hsync(bool sync) { | ||||||
|  | 			hsync_ = sync; | ||||||
|  | 			update_sync(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		inline void update_sync() { | ||||||
|  | 			video_->set_sync(vsync_ || hsync_); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | using namespace ZX8081; | ||||||
|  |  | ||||||
|  | // See header; constructs and returns an instance of the ZX80 or 81. | ||||||
|  | Machine *Machine::ZX8081(const StaticAnalyser::Target &target_hint) { | ||||||
|  | 	// Instantiate the correct type of machine. | ||||||
|  | 	if(target_hint.zx8081.isZX81) | ||||||
|  | 		return new ZX8081::ConcreteMachine<true>(); | ||||||
|  | 	else | ||||||
|  | 		return new ZX8081::ConcreteMachine<false>(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Machine::~Machine() {} | ||||||
							
								
								
									
										53
									
								
								Machines/ZX8081/ZX8081.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Machines/ZX8081/ZX8081.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // | ||||||
|  | //  ZX8081.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 04/06/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ZX8081_hpp | ||||||
|  | #define ZX8081_hpp | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | enum ROMType: uint8_t { | ||||||
|  | 	ZX80, ZX81 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum Key: uint16_t { | ||||||
|  | 	KeyShift	= 0x0000 | 0x01,	KeyZ	= 0x0000 | 0x02,	KeyX = 0x0000 | 0x04,	KeyC = 0x0000 | 0x08,	KeyV = 0x0000 | 0x10, | ||||||
|  | 	KeyA		= 0x0100 | 0x01,	KeyS	= 0x0100 | 0x02,	KeyD = 0x0100 | 0x04,	KeyF = 0x0100 | 0x08,	KeyG = 0x0100 | 0x10, | ||||||
|  | 	KeyQ		= 0x0200 | 0x01,	KeyW	= 0x0200 | 0x02,	KeyE = 0x0200 | 0x04,	KeyR = 0x0200 | 0x08,	KeyT = 0x0200 | 0x10, | ||||||
|  | 	Key1		= 0x0300 | 0x01,	Key2	= 0x0300 | 0x02,	Key3 = 0x0300 | 0x04,	Key4 = 0x0300 | 0x08,	Key5 = 0x0300 | 0x10, | ||||||
|  | 	Key0		= 0x0400 | 0x01,	Key9	= 0x0400 | 0x02,	Key8 = 0x0400 | 0x04,	Key7 = 0x0400 | 0x08,	Key6 = 0x0400 | 0x10, | ||||||
|  | 	KeyP		= 0x0500 | 0x01,	KeyO	= 0x0500 | 0x02,	KeyI = 0x0500 | 0x04,	KeyU = 0x0500 | 0x08,	KeyY = 0x0500 | 0x10, | ||||||
|  | 	KeyEnter	= 0x0600 | 0x01,	KeyL	= 0x0600 | 0x02,	KeyK = 0x0600 | 0x04,	KeyJ = 0x0600 | 0x08,	KeyH = 0x0600 | 0x10, | ||||||
|  | 	KeySpace	= 0x0700 | 0x01,	KeyDot	= 0x0700 | 0x02,	KeyM = 0x0700 | 0x04,	KeyN = 0x0700 | 0x08,	KeyB = 0x0700 | 0x10, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Machine: | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public KeyboardMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		static Machine *ZX8081(const StaticAnalyser::Target &target_hint); | ||||||
|  | 		virtual ~Machine(); | ||||||
|  |  | ||||||
|  | 		virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0; | ||||||
|  |  | ||||||
|  | 		virtual void set_use_fast_tape_hack(bool activate) = 0; | ||||||
|  | 		virtual void set_tape_is_playing(bool is_playing) = 0; | ||||||
|  | 		virtual void set_use_automatic_tape_motor_control(bool enabled) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ZX8081_hpp */ | ||||||
| @@ -13,8 +13,13 @@ | |||||||
|  |  | ||||||
| namespace NumberTheory { | namespace NumberTheory { | ||||||
|  |  | ||||||
|  | /*! Provides a class capable of accumulating a CRC16 from source data. */ | ||||||
| class CRC16 { | class CRC16 { | ||||||
| 	public: | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Instantiates a CRC16 that will compute the CRC16 specified by the supplied | ||||||
|  | 			@c polynomial and @c reset_value. | ||||||
|  | 		*/ | ||||||
| 		CRC16(uint16_t polynomial, uint16_t reset_value) : | 		CRC16(uint16_t polynomial, uint16_t reset_value) : | ||||||
| 				reset_value_(reset_value), value_(reset_value) { | 				reset_value_(reset_value), value_(reset_value) { | ||||||
| 			for(int c = 0; c < 256; c++) { | 			for(int c = 0; c < 256; c++) { | ||||||
| @@ -27,11 +32,18 @@ class CRC16 { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/// Resets the CRC to the reset value. | ||||||
| 		inline void reset() { value_ = reset_value_; } | 		inline void reset() { value_ = reset_value_; } | ||||||
|  |  | ||||||
|  | 		/// Updates the CRC to include @c byte. | ||||||
| 		inline void add(uint8_t byte) { | 		inline void add(uint8_t byte) { | ||||||
| 			value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]); | 			value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/// @returns The current value of the CRC. | ||||||
| 		inline uint16_t get_value() const {	return value_; } | 		inline uint16_t get_value() const {	return value_; } | ||||||
|  |  | ||||||
|  | 		/// Sets the current value of the CRC. | ||||||
| 		inline void set_value(uint16_t value) { value_ = value; } | 		inline void set_value(uint16_t value) { value_ = value; } | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -9,15 +9,19 @@ | |||||||
| #ifndef Factors_hpp | #ifndef Factors_hpp | ||||||
| #define Factors_hpp | #define Factors_hpp | ||||||
|  |  | ||||||
|  | #include <numeric> | ||||||
|  | #include <utility> | ||||||
|  |  | ||||||
| namespace NumberTheory { | namespace NumberTheory { | ||||||
| 	/*! | 	/*! | ||||||
| 		@returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm. | 		@returns The greatest common divisor of @c a and @c b. | ||||||
| 	*/ | 	*/ | ||||||
| 	template<class T> T greatest_common_divisor(T a, T b) { | 	template<class T> T greatest_common_divisor(T a, T b) { | ||||||
|  | #if __cplusplus > 201402L | ||||||
|  | 		return std::gcd(a, b); | ||||||
|  | #else | ||||||
| 		if(a < b) { | 		if(a < b) { | ||||||
| 			T swap = b; | 			std::swap(a, b); | ||||||
| 			b = a; |  | ||||||
| 			a = swap; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		while(1) { | 		while(1) { | ||||||
| @@ -28,11 +32,12 @@ namespace NumberTheory { | |||||||
| 			a = b; | 			a = b; | ||||||
| 			b = remainder; | 			b = remainder; | ||||||
| 		} | 		} | ||||||
|  | #endif | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/*! | 	/*! | ||||||
| 		@returns The least common multiple of @c a and @c b computed indirectly via Euclid's greatest | 		@returns The least common multiple of @c a and @c b computed indirectly via the greatest | ||||||
| 		common divisor algorithm. | 		common divisor. | ||||||
| 	*/ | 	*/ | ||||||
| 	template<class T> T least_common_multiple(T a, T b) { | 	template<class T> T least_common_multiple(T a, T b) { | ||||||
| 		if(a == b) return a; | 		if(a == b) return a; | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -38,6 +38,11 @@ | |||||||
|                BlueprintName = "Clock SignalTests" |                BlueprintName = "Clock SignalTests" | ||||||
|                ReferencedContainer = "container:Clock Signal.xcodeproj"> |                ReferencedContainer = "container:Clock Signal.xcodeproj"> | ||||||
|             </BuildableReference> |             </BuildableReference> | ||||||
|  |             <SkippedTests> | ||||||
|  |                <Test | ||||||
|  |                   Identifier = "ZexallTests"> | ||||||
|  |                </Test> | ||||||
|  |             </SkippedTests> | ||||||
|          </TestableReference> |          </TestableReference> | ||||||
|          <TestableReference |          <TestableReference | ||||||
|             skipped = "YES"> |             skipped = "YES"> | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/AmstradCPCOptions.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/> | ||||||
|  |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|  |     </dependencies> | ||||||
|  |     <objects> | ||||||
|  |         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/> | ||||||
|  |             </connections> | ||||||
|  |         </customObject> | ||||||
|  |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|  |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|  |         <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|  |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|  |             <rect key="contentRect" x="83" y="102" width="200" height="54"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> | ||||||
|  |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|  |                 <rect key="frame" x="0.0" y="0.0" width="200" height="54"/> | ||||||
|  |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|  |                 <subviews> | ||||||
|  |                     <button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw"> | ||||||
|  |                         <rect key="frame" x="18" y="18" width="164" height="18"/> | ||||||
|  |                         <buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB"> | ||||||
|  |                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                         </buttonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </button> | ||||||
|  |                 </subviews> | ||||||
|  |                 <constraints> | ||||||
|  |                     <constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/> | ||||||
|  |                     <constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/> | ||||||
|  |                     <constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/> | ||||||
|  |                 </constraints> | ||||||
|  |             </view> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/> | ||||||
|  |             </connections> | ||||||
|  |             <point key="canvasLocation" x="175" y="30"/> | ||||||
|  |         </window> | ||||||
|  |     </objects> | ||||||
|  | </document> | ||||||
							
								
								
									
										76
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/ZX8081Options.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/ZX8081Options.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/> | ||||||
|  |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|  |     </dependencies> | ||||||
|  |     <objects> | ||||||
|  |         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/> | ||||||
|  |             </connections> | ||||||
|  |         </customObject> | ||||||
|  |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|  |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|  |         <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|  |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|  |             <rect key="contentRect" x="83" y="102" width="261" height="100"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> | ||||||
|  |             <view key="contentView" id="7Pv-WL-2Rq"> | ||||||
|  |                 <rect key="frame" x="0.0" y="0.0" width="261" height="100"/> | ||||||
|  |                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|  |                 <subviews> | ||||||
|  |                     <button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s"> | ||||||
|  |                         <rect key="frame" x="18" y="64" width="225" height="18"/> | ||||||
|  |                         <buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm"> | ||||||
|  |                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                         </buttonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </button> | ||||||
|  |                     <button translatesAutoresizingMaskIntoConstraints="NO" id="qSb-72-6Os"> | ||||||
|  |                         <rect key="frame" x="18" y="44" width="225" height="18"/> | ||||||
|  |                         <buttonCell key="cell" type="check" title="Control Tape Motor Automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CzC-YT-lgA"> | ||||||
|  |                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                         </buttonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="setAutomaticTapeMotorConrol:" target="ota-g7-hOL" id="bpF-1P-tga"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </button> | ||||||
|  |                     <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tkN-gI-RmT"> | ||||||
|  |                         <rect key="frame" x="20" y="19" width="221" height="19"/> | ||||||
|  |                         <buttonCell key="cell" type="roundRect" title="Play Tape" bezelStyle="roundedRect" alignment="center" enabled="NO" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cTq-f9-Gzx"> | ||||||
|  |                             <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> | ||||||
|  |                             <font key="font" metaFont="cellTitle"/> | ||||||
|  |                         </buttonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="playOrPauseTape:" target="ota-g7-hOL" id="O0K-pL-nOr"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </button> | ||||||
|  |                 </subviews> | ||||||
|  |                 <constraints> | ||||||
|  |                     <constraint firstItem="qSb-72-6Os" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="05p-Jn-ueX"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/> | ||||||
|  |                     <constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/> | ||||||
|  |                     <constraint firstItem="qSb-72-6Os" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="WxD-kP-vwf"/> | ||||||
|  |                     <constraint firstAttribute="bottom" secondItem="tkN-gI-RmT" secondAttribute="bottom" constant="20" id="Xnu-On-nOA"/> | ||||||
|  |                     <constraint firstItem="tkN-gI-RmT" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="fHf-K0-PsU"/> | ||||||
|  |                     <constraint firstItem="tkN-gI-RmT" firstAttribute="top" secondItem="qSb-72-6Os" secondAttribute="bottom" constant="8" id="gLh-vE-Cqk"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="qSb-72-6Os" secondAttribute="trailing" constant="20" id="mQz-p8-aYf"/> | ||||||
|  |                     <constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="tkN-gI-RmT" secondAttribute="trailing" constant="20" id="vgD-A3-m6T"/> | ||||||
|  |                 </constraints> | ||||||
|  |             </view> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="automaticTapeMotorControlButton" destination="qSb-72-6Os" id="bB6-FP-TKM"/> | ||||||
|  |                 <outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/> | ||||||
|  |                 <outlet property="playOrPauseTapeButton" destination="tkN-gI-RmT" id="UnJ-nb-3mv"/> | ||||||
|  |             </connections> | ||||||
|  |             <point key="canvasLocation" x="28.5" y="15"/> | ||||||
|  |         </window> | ||||||
|  |     </objects> | ||||||
|  | </document> | ||||||
| @@ -10,6 +10,7 @@ | |||||||
| #import "CSElectron.h" | #import "CSElectron.h" | ||||||
| #import "CSOric.h" | #import "CSOric.h" | ||||||
| #import "CSVic20.h" | #import "CSVic20.h" | ||||||
|  | #import "CSZX8081.h" | ||||||
|  |  | ||||||
| #import "CSStaticAnalyser.h" | #import "CSStaticAnalyser.h" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -164,6 +164,13 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | ||||||
|  | 		let mediaSet = CSMediaSet(fileAt: URL) | ||||||
|  | 		if let mediaSet = mediaSet { | ||||||
|  | 			mediaSet.apply(to: self.machine) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// MARK: NSDocument overrides | 	// MARK: NSDocument overrides | ||||||
| 	override func data(ofType typeName: String) throws -> Data { | 	override func data(ofType typeName: String) throws -> Data { | ||||||
| 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | ||||||
|   | |||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | // | ||||||
|  | //  ZX8081OptionsPanel.swift | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 08/07/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | class ZX8081OptionsPanel: MachinePanel { | ||||||
|  | 	var zx8081: CSZX8081! { | ||||||
|  | 		get { | ||||||
|  | 			return self.machine as! CSZX8081 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@IBOutlet var automaticTapeMotorControlButton: NSButton! | ||||||
|  | 	var automaticTapeMotorControlDefaultsKey: String { | ||||||
|  | 		get { return prefixedUserDefaultsKey("automaticTapeMotorControl") } | ||||||
|  | 	} | ||||||
|  | 	@IBAction func setAutomaticTapeMotorConrol(_ sender: NSButton!) { | ||||||
|  | 		let isEnabled = sender.state == NSOnState | ||||||
|  | 		UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey) | ||||||
|  | 		self.playOrPauseTapeButton.isEnabled = !isEnabled | ||||||
|  | 		self.zx8081.useAutomaticTapeMotorControl = isEnabled | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@IBOutlet var playOrPauseTapeButton: NSButton! | ||||||
|  | 	@IBAction func playOrPauseTape(_ sender: NSButton!) { | ||||||
|  | 		self.zx8081.tapeIsPlaying = !self.zx8081.tapeIsPlaying | ||||||
|  | 		self.playOrPauseTapeButton.title = self.zx8081.tapeIsPlaying | ||||||
|  | 			? NSLocalizedString("Stop Tape", comment: "Text for a button that will stop tape playback") | ||||||
|  | 			: NSLocalizedString("Play Tape", comment: "Text for a button that will start tape playback") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: option restoration | ||||||
|  | 	override func establishStoredOptions() { | ||||||
|  | 		super.establishStoredOptions() | ||||||
|  |  | ||||||
|  | 		let standardUserDefaults = UserDefaults.standard | ||||||
|  | 		standardUserDefaults.register(defaults: [ | ||||||
|  | 			self.automaticTapeMotorControlDefaultsKey: true | ||||||
|  | 		]) | ||||||
|  |  | ||||||
|  | 		let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey) | ||||||
|  | 		self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? NSOnState : NSOffState | ||||||
|  | 		self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled | ||||||
|  | 		self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -104,7 +104,7 @@ | |||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Disk</string> | 			<string>Commodore Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Editor</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<integer>0</integer> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| @@ -120,7 +120,7 @@ | |||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore 1540/1 Disk</string> | 			<string>Commodore 1540/1 Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Editor</string> | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<integer>0</integer> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| @@ -140,7 +140,9 @@ | |||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC Disk Image</string> | 			<string>Electron/BBC Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Editor</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| @@ -154,7 +156,108 @@ | |||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Editor</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 		</dict> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>o</string> | ||||||
|  | 				<string>80</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>ZX80 Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 		</dict> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>p</string> | ||||||
|  | 				<string>81</string> | ||||||
|  | 				<string>p81</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>ZX81 Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 		</dict> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>csw</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 		</dict> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>tzx</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 		</dict> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>cdt</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>Amstrad CPC Tape Image</string> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
|  | 			<key>NSDocumentClass</key> | ||||||
|  | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 		</dict> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>CFBundleTypeExtensions</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>hfe</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy35</string> | ||||||
|  | 			<key>CFBundleTypeName</key> | ||||||
|  | 			<string>HxC Disk Image</string> | ||||||
|  | 			<key>CFBundleTypeRole</key> | ||||||
|  | 			<string>Viewer</string> | ||||||
|  | 			<key>LSTypeIsPackage</key> | ||||||
|  | 			<integer>0</integer> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 		</dict> | 		</dict> | ||||||
|   | |||||||
| @@ -11,7 +11,5 @@ | |||||||
|  |  | ||||||
| @interface CSMachine (Subclassing) | @interface CSMachine (Subclassing) | ||||||
|  |  | ||||||
| - (CRTMachine::Machine * const)machine; |  | ||||||
| - (void)setupOutputWithAspectRatio:(float)aspectRatio; | - (void)setupOutputWithAspectRatio:(float)aspectRatio; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ | |||||||
|  |  | ||||||
| @interface CSMachine(Target) | @interface CSMachine(Target) | ||||||
|  |  | ||||||
| - (void)applyTarget:(StaticAnalyser::Target)target; | - (void)applyTarget:(const StaticAnalyser::Target &)target; | ||||||
|  | - (void)applyMedia:(const StaticAnalyser::Media &)media; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -18,6 +18,15 @@ | |||||||
|  |  | ||||||
| @interface CSMachine : NSObject | @interface CSMachine : NSObject | ||||||
|  |  | ||||||
|  | - (instancetype)init NS_UNAVAILABLE; | ||||||
|  | /*! | ||||||
|  | 	Initialises an instance of CSMachine. | ||||||
|  | 	 | ||||||
|  | 	@param machine The pointer to an instance of @c CRTMachine::Machine* . C++ type is omitted because | ||||||
|  | 	this header is visible to Swift, and the designated initialiser cannot be placed into a category. | ||||||
|  | */ | ||||||
|  | - (instancetype)initWithMachine:(void *)machine NS_DESIGNATED_INITIALIZER; | ||||||
|  |  | ||||||
| - (void)runForNumberOfCycles:(int)numberOfCycles; | - (void)runForNumberOfCycles:(int)numberOfCycles; | ||||||
|  |  | ||||||
| - (float)idealSamplingRateFromRange:(NSRange)range; | - (float)idealSamplingRateFromRange:(NSRange)range; | ||||||
|   | |||||||
| @@ -21,8 +21,8 @@ | |||||||
|  |  | ||||||
| struct SpeakerDelegate: public Outputs::Speaker::Delegate { | struct SpeakerDelegate: public Outputs::Speaker::Delegate { | ||||||
| 	__weak CSMachine *machine; | 	__weak CSMachine *machine; | ||||||
| 	void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) { | 	void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) { | ||||||
| 		[machine speaker:speaker didCompleteSamples:buffer length:buffer_size]; | 		[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -39,15 +39,17 @@ struct MachineDelegate: CRTMachine::Machine::Delegate { | |||||||
| @implementation CSMachine { | @implementation CSMachine { | ||||||
| 	SpeakerDelegate _speakerDelegate; | 	SpeakerDelegate _speakerDelegate; | ||||||
| 	MachineDelegate _machineDelegate; | 	MachineDelegate _machineDelegate; | ||||||
|  | 	CRTMachine::Machine *_machine; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (instancetype)init { | - (instancetype)initWithMachine:(void *)machine { | ||||||
| 	self = [super init]; | 	self = [super init]; | ||||||
| 	if(self) | 	if(self) { | ||||||
| 	{ | 		_machine = (CRTMachine::Machine *)machine; | ||||||
| 		_machineDelegate.machine = self; | 		_machineDelegate.machine = self; | ||||||
| 		self.machine->set_delegate(&_machineDelegate); |  | ||||||
| 		_speakerDelegate.machine = self; | 		_speakerDelegate.machine = self; | ||||||
|  |  | ||||||
|  | 		_machine->set_delegate(&_machineDelegate); | ||||||
| 	} | 	} | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
| @@ -67,14 +69,14 @@ struct MachineDelegate: CRTMachine::Machine::Delegate { | |||||||
| - (void)dealloc { | - (void)dealloc { | ||||||
| 	[_view performWithGLContext:^{ | 	[_view performWithGLContext:^{ | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			self.machine->close_output(); | 			_machine->close_output(); | ||||||
| 		} | 		} | ||||||
| 	}]; | 	}]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (float)idealSamplingRateFromRange:(NSRange)range { | - (float)idealSamplingRateFromRange:(NSRange)range { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		std::shared_ptr<Outputs::Speaker> speaker = self.machine->get_speaker(); | 		std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker(); | ||||||
| 		if(speaker) | 		if(speaker) | ||||||
| 		{ | 		{ | ||||||
| 			return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length)); | 			return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length)); | ||||||
| @@ -91,7 +93,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate { | |||||||
|  |  | ||||||
| - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize { | - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		std::shared_ptr<Outputs::Speaker> speaker = self.machine->get_speaker(); | 		std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker(); | ||||||
| 		if(speaker) | 		if(speaker) | ||||||
| 		{ | 		{ | ||||||
| 			speaker->set_output_rate(sampleRate, (int)bufferSize); | 			speaker->set_output_rate(sampleRate, (int)bufferSize); | ||||||
| @@ -104,7 +106,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate { | |||||||
|  |  | ||||||
| - (void)runForNumberOfCycles:(int)numberOfCycles { | - (void)runForNumberOfCycles:(int)numberOfCycles { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		self.machine->run_for_cycles(numberOfCycles); | 		_machine->run_for(Cycles(numberOfCycles)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -116,33 +118,44 @@ struct MachineDelegate: CRTMachine::Machine::Delegate { | |||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setupOutputWithAspectRatio:(float)aspectRatio { | - (void)setupOutputWithAspectRatio:(float)aspectRatio { | ||||||
| 	self.machine->setup_output(aspectRatio); | 	_machine->setup_output(aspectRatio); | ||||||
|  |  | ||||||
|  | 	// Since OS X v10.6, Macs have had a gamma of 2.2. | ||||||
|  | 	_machine->get_crt()->set_output_gamma(2.2f); | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { | - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { | ||||||
| 	self.machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); | 	_machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); | ||||||
| } | } | ||||||
|  |  | ||||||
| - (double)clockRate { | - (double)clockRate { | ||||||
| 	return self.machine->get_clock_rate(); | 	return _machine->get_clock_rate(); | ||||||
| } | } | ||||||
|  |  | ||||||
| - (BOOL)clockIsUnlimited { | - (BOOL)clockIsUnlimited { | ||||||
| 	return self.machine->get_clock_is_unlimited() ? YES : NO; | 	return _machine->get_clock_is_unlimited() ? YES : NO; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)paste:(NSString *)paste { | - (void)paste:(NSString *)paste { | ||||||
| 	Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(self.machine); | 	Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(_machine); | ||||||
| 	if(typeRecipient) | 	if(typeRecipient) | ||||||
| 		typeRecipient->set_typer_for_string([paste UTF8String]); | 		typeRecipient->set_typer_for_string([paste UTF8String]); | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)applyTarget:(StaticAnalyser::Target)target { | - (void)applyTarget:(const StaticAnalyser::Target &)target { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		ConfigurationTarget::Machine *const configurationTarget = | 		ConfigurationTarget::Machine *const configurationTarget = | ||||||
| 			dynamic_cast<ConfigurationTarget::Machine *>(self.machine); | 			dynamic_cast<ConfigurationTarget::Machine *>(_machine); | ||||||
| 		if(configurationTarget) configurationTarget->configure_as_target(target); | 		if(configurationTarget) configurationTarget->configure_as_target(target); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (void)applyMedia:(const StaticAnalyser::Media &)media { | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		ConfigurationTarget::Machine *const configurationTarget = | ||||||
|  | 			dynamic_cast<ConfigurationTarget::Machine *>(_machine); | ||||||
|  | 		if(configurationTarget) configurationTarget->insert_media(media); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -22,3 +22,10 @@ | |||||||
| - (void)applyToMachine:(CSMachine *)machine; | - (void)applyToMachine:(CSMachine *)machine; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|  |  | ||||||
|  | @interface CSMediaSet : NSObject | ||||||
|  |  | ||||||
|  | - (instancetype)initWithFileAtURL:(NSURL *)url; | ||||||
|  | - (void)applyToMachine:(CSMachine *)machine; | ||||||
|  |  | ||||||
|  | @end | ||||||
|   | |||||||
| @@ -14,23 +14,22 @@ | |||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #import "CSAmstradCPC.h" | ||||||
| #import "CSAtari2600.h" | #import "CSAtari2600.h" | ||||||
| #import "CSElectron.h" | #import "CSElectron.h" | ||||||
| #import "CSOric.h" | #import "CSOric.h" | ||||||
| #import "CSVic20.h" | #import "CSVic20.h" | ||||||
|  | #import "CSZX8081+Instantiation.h" | ||||||
|  |  | ||||||
| #import "Clock_Signal-Swift.h" | #import "Clock_Signal-Swift.h" | ||||||
|  |  | ||||||
| @implementation CSStaticAnalyser | @implementation CSStaticAnalyser { | ||||||
| { |  | ||||||
| 	StaticAnalyser::Target _target; | 	StaticAnalyser::Target _target; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (instancetype)initWithFileAtURL:(NSURL *)url | - (instancetype)initWithFileAtURL:(NSURL *)url { | ||||||
| { |  | ||||||
| 	self = [super init]; | 	self = [super init]; | ||||||
| 	if(self) | 	if(self) { | ||||||
| 	{ |  | ||||||
| 		std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([url fileSystemRepresentation]); | 		std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([url fileSystemRepresentation]); | ||||||
| 		if(!targets.size()) return nil; | 		if(!targets.size()) return nil; | ||||||
| 		_target = targets.front(); | 		_target = targets.front(); | ||||||
| @@ -41,33 +40,50 @@ | |||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (NSString *)optionsPanelNibName | - (NSString *)optionsPanelNibName { | ||||||
| { | 	switch(_target.machine) { | ||||||
| 	switch(_target.machine) | 		case StaticAnalyser::Target::AmstradCPC:	return nil; | ||||||
| 	{ | 		case StaticAnalyser::Target::Atari2600:		return @"Atari2600Options"; | ||||||
| 		case StaticAnalyser::Target::Atari2600:	return @"Atari2600Options"; | 		case StaticAnalyser::Target::Electron:		return @"ElectronOptions"; | ||||||
| 		case StaticAnalyser::Target::Electron:	return @"ElectronOptions"; | 		case StaticAnalyser::Target::Oric:			return @"OricOptions"; | ||||||
| 		case StaticAnalyser::Target::Oric:		return @"OricOptions"; | 		case StaticAnalyser::Target::Vic20:			return @"Vic20Options"; | ||||||
| 		case StaticAnalyser::Target::Vic20:		return @"Vic20Options"; | 		case StaticAnalyser::Target::ZX8081:		return @"ZX8081Options"; | ||||||
| 		default: return nil; | 		default: return nil; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (CSMachine *)newMachine | - (CSMachine *)newMachine { | ||||||
| { | 	switch(_target.machine) { | ||||||
| 	switch(_target.machine) | 		case StaticAnalyser::Target::AmstradCPC:	return [[CSAmstradCPC alloc] init]; | ||||||
| 	{ | 		case StaticAnalyser::Target::Atari2600:		return [[CSAtari2600 alloc] init]; | ||||||
| 		case StaticAnalyser::Target::Atari2600:	return [[CSAtari2600 alloc] init]; | 		case StaticAnalyser::Target::Electron:		return [[CSElectron alloc] init]; | ||||||
| 		case StaticAnalyser::Target::Electron:	return [[CSElectron alloc] init]; | 		case StaticAnalyser::Target::Oric:			return [[CSOric alloc] init]; | ||||||
| 		case StaticAnalyser::Target::Oric:		return [[CSOric alloc] init]; | 		case StaticAnalyser::Target::Vic20:			return [[CSVic20 alloc] init]; | ||||||
| 		case StaticAnalyser::Target::Vic20:		return [[CSVic20 alloc] init]; | 		case StaticAnalyser::Target::ZX8081:		return [[CSZX8081 alloc] initWithIntendedTarget:_target]; | ||||||
| 		default: return nil; | 		default: return nil; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)applyToMachine:(CSMachine *)machine | - (void)applyToMachine:(CSMachine *)machine { | ||||||
| { |  | ||||||
| 	[machine applyTarget:_target]; | 	[machine applyTarget:_target]; | ||||||
| } | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
|  |  | ||||||
|  | @implementation CSMediaSet { | ||||||
|  | 	StaticAnalyser::Media _media; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithFileAtURL:(NSURL *)url { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_media = StaticAnalyser::GetMedia([url fileSystemRepresentation]); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)applyToMachine:(CSMachine *)machine { | ||||||
|  | 	[machine applyMedia:_media]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | // | ||||||
|  | //  CSAmstradCPC.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/07/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "CSMachine.h" | ||||||
|  | #import "CSKeyboardMachine.h" | ||||||
|  |  | ||||||
|  | @interface CSAmstradCPC : CSMachine <CSKeyboardMachine> | ||||||
|  |  | ||||||
|  | - (instancetype)init; | ||||||
|  |  | ||||||
|  | @end | ||||||
							
								
								
									
										152
									
								
								OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAmstradCPC.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | // | ||||||
|  | //  CSAmstradCPC.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 30/07/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "CSAmstradCPC.h" | ||||||
|  |  | ||||||
|  | #include "AmstradCPC.hpp" | ||||||
|  |  | ||||||
|  | #import "CSMachine+Subclassing.h" | ||||||
|  | #import "NSData+StdVector.h" | ||||||
|  | #import "NSBundle+DataResource.h" | ||||||
|  |  | ||||||
|  | @implementation CSAmstradCPC { | ||||||
|  | 	std::unique_ptr<AmstradCPC::Machine> _amstradCPC; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)init { | ||||||
|  | 	AmstradCPC::Machine *machine = AmstradCPC::Machine::AmstradCPC(); | ||||||
|  |  | ||||||
|  | 	self = [super initWithMachine:machine]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_amstradCPC.reset(machine); | ||||||
|  |  | ||||||
|  | 		NSDictionary *roms = @{ | ||||||
|  | 			@(AmstradCPC::ROMType::OS464) : @"os464", | ||||||
|  | 			@(AmstradCPC::ROMType::OS664) : @"os664", | ||||||
|  | 			@(AmstradCPC::ROMType::OS6128) : @"os6128", | ||||||
|  | 			@(AmstradCPC::ROMType::BASIC464) : @"basic464", | ||||||
|  | 			@(AmstradCPC::ROMType::BASIC664) : @"basic664", | ||||||
|  | 			@(AmstradCPC::ROMType::BASIC6128) : @"basic6128", | ||||||
|  | 			@(AmstradCPC::ROMType::AMSDOS) : @"amsdos", | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		for(NSNumber *key in roms.allKeys) { | ||||||
|  | 			AmstradCPC::ROMType type = (AmstradCPC::ROMType)key.integerValue; | ||||||
|  | 			NSString *name = roms[key]; | ||||||
|  | 			NSData *data = [self rom:name]; | ||||||
|  | 			if(data) { | ||||||
|  | 				_amstradCPC->set_rom(type, data.stdVector8); | ||||||
|  | 			} else { | ||||||
|  | 				NSLog(@"Amstrad CPC ROM missing: %@", name); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSData *)rom:(NSString *)name { | ||||||
|  | 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/AmstradCPC"]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)userDefaultsPrefix {	return @"amstradCPC";	} | ||||||
|  |  | ||||||
|  | #pragma mark - Keyboard Mapping | ||||||
|  |  | ||||||
|  | - (void)clearAllKeys { | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		_amstradCPC->clear_all_keys(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		switch(key) { | ||||||
|  | 			case VK_ANSI_0:		_amstradCPC->set_key_state(AmstradCPC::Key::Key0, isPressed);	break; | ||||||
|  | 			case VK_ANSI_1:		_amstradCPC->set_key_state(AmstradCPC::Key::Key1, isPressed);	break; | ||||||
|  | 			case VK_ANSI_2:		_amstradCPC->set_key_state(AmstradCPC::Key::Key2, isPressed);	break; | ||||||
|  | 			case VK_ANSI_3:		_amstradCPC->set_key_state(AmstradCPC::Key::Key3, isPressed);	break; | ||||||
|  | 			case VK_ANSI_4:		_amstradCPC->set_key_state(AmstradCPC::Key::Key4, isPressed);	break; | ||||||
|  | 			case VK_ANSI_5:		_amstradCPC->set_key_state(AmstradCPC::Key::Key5, isPressed);	break; | ||||||
|  | 			case VK_ANSI_6:		_amstradCPC->set_key_state(AmstradCPC::Key::Key6, isPressed);	break; | ||||||
|  | 			case VK_ANSI_7:		_amstradCPC->set_key_state(AmstradCPC::Key::Key7, isPressed);	break; | ||||||
|  | 			case VK_ANSI_8:		_amstradCPC->set_key_state(AmstradCPC::Key::Key8, isPressed);	break; | ||||||
|  | 			case VK_ANSI_9:		_amstradCPC->set_key_state(AmstradCPC::Key::Key9, isPressed);	break; | ||||||
|  |  | ||||||
|  | 			case VK_ANSI_Q:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyQ, isPressed);	break; | ||||||
|  | 			case VK_ANSI_W:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyW, isPressed);	break; | ||||||
|  | 			case VK_ANSI_E:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyE, isPressed);	break; | ||||||
|  | 			case VK_ANSI_R:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyR, isPressed);	break; | ||||||
|  | 			case VK_ANSI_T:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyT, isPressed);	break; | ||||||
|  | 			case VK_ANSI_Y:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyY, isPressed);	break; | ||||||
|  | 			case VK_ANSI_U:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyU, isPressed);	break; | ||||||
|  | 			case VK_ANSI_I:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyI, isPressed);	break; | ||||||
|  | 			case VK_ANSI_O:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyO, isPressed);	break; | ||||||
|  | 			case VK_ANSI_P:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyP, isPressed);	break; | ||||||
|  | 			case VK_ANSI_A:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyA, isPressed);	break; | ||||||
|  | 			case VK_ANSI_S:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyS, isPressed);	break; | ||||||
|  | 			case VK_ANSI_D:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyD, isPressed);	break; | ||||||
|  | 			case VK_ANSI_F:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyF, isPressed);	break; | ||||||
|  | 			case VK_ANSI_G:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyG, isPressed);	break; | ||||||
|  | 			case VK_ANSI_H:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyH, isPressed);	break; | ||||||
|  | 			case VK_ANSI_J:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyJ, isPressed);	break; | ||||||
|  | 			case VK_ANSI_K:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyK, isPressed);	break; | ||||||
|  | 			case VK_ANSI_L:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyL, isPressed);	break; | ||||||
|  | 			case VK_ANSI_Z:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyZ, isPressed);	break; | ||||||
|  | 			case VK_ANSI_X:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyX, isPressed);	break; | ||||||
|  | 			case VK_ANSI_C:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyC, isPressed);	break; | ||||||
|  | 			case VK_ANSI_V:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyV, isPressed);	break; | ||||||
|  | 			case VK_ANSI_B:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyB, isPressed);	break; | ||||||
|  | 			case VK_ANSI_N:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyN, isPressed);	break; | ||||||
|  | 			case VK_ANSI_M:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyM, isPressed);	break; | ||||||
|  |  | ||||||
|  | 			case VK_Space:			_amstradCPC->set_key_state(AmstradCPC::Key::KeySpace, isPressed);		break; | ||||||
|  | 			case VK_ANSI_Grave:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed);		break; | ||||||
|  | 			case VK_Return:			_amstradCPC->set_key_state(AmstradCPC::Key::KeyReturn, isPressed);		break; | ||||||
|  | 			case VK_ANSI_Minus:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyMinus, isPressed);		break; | ||||||
|  |  | ||||||
|  | 			case VK_RightArrow:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyRight, isPressed);		break; | ||||||
|  | 			case VK_LeftArrow:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed);		break; | ||||||
|  | 			case VK_DownArrow:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed);		break; | ||||||
|  | 			case VK_UpArrow:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyUp, isPressed);			break; | ||||||
|  |  | ||||||
|  | 			case VK_Delete:			_amstradCPC->set_key_state(AmstradCPC::Key::KeyDelete, isPressed);		break; | ||||||
|  | 			case VK_Escape:			_amstradCPC->set_key_state(AmstradCPC::Key::KeyEscape, isPressed);		break; | ||||||
|  |  | ||||||
|  | 			case VK_ANSI_Comma:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyComma, isPressed);		break; | ||||||
|  | 			case VK_ANSI_Period:	_amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed);	break; | ||||||
|  |  | ||||||
|  | 			case VK_ANSI_Semicolon: | ||||||
|  | 									_amstradCPC->set_key_state(AmstradCPC::Key::KeySemicolon, isPressed);	break; | ||||||
|  | 			case VK_ANSI_Quote:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyColon, isPressed);		break; | ||||||
|  |  | ||||||
|  | 			case VK_ANSI_Slash:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed);	break; | ||||||
|  | 			case VK_ANSI_Backslash:	_amstradCPC->set_key_state(AmstradCPC::Key::KeyBackSlash, isPressed);		break; | ||||||
|  |  | ||||||
|  | 			case VK_Shift:			_amstradCPC->set_key_state(AmstradCPC::Key::KeyShift, isPressed);		break; | ||||||
|  | 			case VK_Control:		_amstradCPC->set_key_state(AmstradCPC::Key::KeyControl, isPressed);		break; | ||||||
|  |  | ||||||
|  | 			case VK_F1:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF1, isPressed);			break; | ||||||
|  | 			case VK_F2:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF2, isPressed);			break; | ||||||
|  | 			case VK_F3:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF3, isPressed);			break; | ||||||
|  | 			case VK_F4:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF4, isPressed);			break; | ||||||
|  | 			case VK_F5:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF5, isPressed);			break; | ||||||
|  | 			case VK_F6:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF6, isPressed);			break; | ||||||
|  | 			case VK_F7:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF7, isPressed);			break; | ||||||
|  | 			case VK_F8:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF8, isPressed);			break; | ||||||
|  | 			case VK_F9:				_amstradCPC->set_key_state(AmstradCPC::Key::KeyF9, isPressed);			break; | ||||||
|  | 			case VK_F10:			_amstradCPC->set_key_state(AmstradCPC::Key::KeyF0, isPressed);			break; | ||||||
|  | 			case VK_F12:			_amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed);		break; | ||||||
|  |  | ||||||
|  | 			default: | ||||||
|  | //				printf("%02x\n", key); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -12,6 +12,8 @@ | |||||||
|  |  | ||||||
| @interface CSAtari2600 : CSMachine <CSJoystickMachine> | @interface CSAtari2600 : CSMachine <CSJoystickMachine> | ||||||
|  |  | ||||||
|  | - (instancetype)init; | ||||||
|  |  | ||||||
| - (void)setResetLineEnabled:(BOOL)enabled; | - (void)setResetLineEnabled:(BOOL)enabled; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL colourButton; | @property (nonatomic, assign) BOOL colourButton; | ||||||
|   | |||||||
| @@ -12,7 +12,16 @@ | |||||||
| #import "CSMachine+Subclassing.h" | #import "CSMachine+Subclassing.h" | ||||||
|  |  | ||||||
| @implementation CSAtari2600 { | @implementation CSAtari2600 { | ||||||
| 	Atari2600::Machine _atari2600; | 	std::unique_ptr<Atari2600::Machine> _atari2600; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)init { | ||||||
|  | 	Atari2600::Machine *machine = Atari2600::Machine::Atari2600(); | ||||||
|  | 	self = [super initWithMachine:machine]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_atari2600.reset(machine); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed { | - (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed { | ||||||
| @@ -25,19 +34,19 @@ | |||||||
| 		case CSJoystickDirectionRight:	input = pad ? Atari2600DigitalInputJoy2Right : Atari2600DigitalInputJoy1Right;	break; | 		case CSJoystickDirectionRight:	input = pad ? Atari2600DigitalInputJoy2Right : Atari2600DigitalInputJoy1Right;	break; | ||||||
| 	} | 	} | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_digital_input(input, isPressed ? true : false); | 		_atari2600->set_digital_input(input, isPressed ? true : false); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed { | - (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false); | 		_atari2600->set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setResetLineEnabled:(BOOL)enabled { | - (void)setResetLineEnabled:(BOOL)enabled { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_reset_line(enabled ? true : false); | 		_atari2600->set_reset_switch(enabled ? true : false); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -47,40 +56,36 @@ | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (CRTMachine::Machine * const)machine { |  | ||||||
| 	return &_atari2600; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - Switches | #pragma mark - Switches | ||||||
|  |  | ||||||
| - (void)setColourButton:(BOOL)colourButton { | - (void)setColourButton:(BOOL)colourButton { | ||||||
| 	_colourButton = colourButton; | 	_colourButton = colourButton; | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_switch_is_enabled(Atari2600SwitchColour, colourButton); | 		_atari2600->set_switch_is_enabled(Atari2600SwitchColour, colourButton); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setLeftPlayerDifficultyButton:(BOOL)leftPlayerDifficultyButton { | - (void)setLeftPlayerDifficultyButton:(BOOL)leftPlayerDifficultyButton { | ||||||
| 	_leftPlayerDifficultyButton = leftPlayerDifficultyButton; | 	_leftPlayerDifficultyButton = leftPlayerDifficultyButton; | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton); | 		_atari2600->set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setRightPlayerDifficultyButton:(BOOL)rightPlayerDifficultyButton { | - (void)setRightPlayerDifficultyButton:(BOOL)rightPlayerDifficultyButton { | ||||||
| 	_rightPlayerDifficultyButton = rightPlayerDifficultyButton; | 	_rightPlayerDifficultyButton = rightPlayerDifficultyButton; | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton); | 		_atari2600->set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)toggleSwitch:(Atari2600Switch)toggleSwitch { | - (void)toggleSwitch:(Atari2600Switch)toggleSwitch { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_atari2600.set_switch_is_enabled(toggleSwitch, true); | 		_atari2600->set_switch_is_enabled(toggleSwitch, true); | ||||||
| 	} | 	} | ||||||
| 	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | 	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			_atari2600.set_switch_is_enabled(toggleSwitch, false); | 			_atari2600->set_switch_is_enabled(toggleSwitch, false); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ | |||||||
|  |  | ||||||
| @interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading> | @interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading> | ||||||
|  |  | ||||||
|  | - (instancetype)init; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) BOOL useTelevisionOutput; | @property (nonatomic, assign) BOOL useTelevisionOutput; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,17 +16,16 @@ | |||||||
| #import "NSBundle+DataResource.h" | #import "NSBundle+DataResource.h" | ||||||
|  |  | ||||||
| @implementation CSElectron { | @implementation CSElectron { | ||||||
| 	Electron::Machine _electron; | 	std::unique_ptr<Electron::Machine> _electron; | ||||||
| } |  | ||||||
|  |  | ||||||
| - (CRTMachine::Machine * const)machine { |  | ||||||
| 	return &_electron; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| - (instancetype)init { | - (instancetype)init { | ||||||
| 	self = [super init]; | 	Electron::Machine *machine = Electron::Machine::Electron(); | ||||||
| 	if(self) |  | ||||||
| 	{ | 	self = [super initWithMachine:machine]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_electron.reset(machine); | ||||||
|  |  | ||||||
| 		[self setOSROM:[self rom:@"os"]]; | 		[self setOSROM:[self rom:@"os"]]; | ||||||
| 		[self setBASICROM:[self rom:@"basic"]]; | 		[self setBASICROM:[self rom:@"basic"]]; | ||||||
| 		[self setDFSROM:[self rom:@"DFS-1770-2.20"]]; | 		[self setDFSROM:[self rom:@"DFS-1770-2.20"]]; | ||||||
| @@ -38,8 +37,7 @@ | |||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (NSData *)rom:(NSString *)name | - (NSData *)rom:(NSString *)name { | ||||||
| { |  | ||||||
| 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"]; | 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"]; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -54,7 +52,7 @@ | |||||||
| 	if(rom) | 	if(rom) | ||||||
| 	{ | 	{ | ||||||
| 		@synchronized(self) { | 		@synchronized(self) { | ||||||
| 			_electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8, false); | 			_electron->set_rom((Electron::ROMSlot)slot, rom.stdVector8, false); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -63,7 +61,7 @@ | |||||||
|  |  | ||||||
| - (void)clearAllKeys { | - (void)clearAllKeys { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_electron.clear_all_keys(); | 		_electron->clear_all_keys(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -71,74 +69,74 @@ | |||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		switch(key) | 		switch(key) | ||||||
| 		{ | 		{ | ||||||
| 			case VK_ANSI_0:		_electron.set_key_state(Electron::Key::Key0, isPressed);	break; | 			case VK_ANSI_0:		_electron->set_key_state(Electron::Key::Key0, isPressed);	break; | ||||||
| 			case VK_ANSI_1:		_electron.set_key_state(Electron::Key::Key1, isPressed);	break; | 			case VK_ANSI_1:		_electron->set_key_state(Electron::Key::Key1, isPressed);	break; | ||||||
| 			case VK_ANSI_2:		_electron.set_key_state(Electron::Key::Key2, isPressed);	break; | 			case VK_ANSI_2:		_electron->set_key_state(Electron::Key::Key2, isPressed);	break; | ||||||
| 			case VK_ANSI_3:		_electron.set_key_state(Electron::Key::Key3, isPressed);	break; | 			case VK_ANSI_3:		_electron->set_key_state(Electron::Key::Key3, isPressed);	break; | ||||||
| 			case VK_ANSI_4:		_electron.set_key_state(Electron::Key::Key4, isPressed);	break; | 			case VK_ANSI_4:		_electron->set_key_state(Electron::Key::Key4, isPressed);	break; | ||||||
| 			case VK_ANSI_5:		_electron.set_key_state(Electron::Key::Key5, isPressed);	break; | 			case VK_ANSI_5:		_electron->set_key_state(Electron::Key::Key5, isPressed);	break; | ||||||
| 			case VK_ANSI_6:		_electron.set_key_state(Electron::Key::Key6, isPressed);	break; | 			case VK_ANSI_6:		_electron->set_key_state(Electron::Key::Key6, isPressed);	break; | ||||||
| 			case VK_ANSI_7:		_electron.set_key_state(Electron::Key::Key7, isPressed);	break; | 			case VK_ANSI_7:		_electron->set_key_state(Electron::Key::Key7, isPressed);	break; | ||||||
| 			case VK_ANSI_8:		_electron.set_key_state(Electron::Key::Key8, isPressed);	break; | 			case VK_ANSI_8:		_electron->set_key_state(Electron::Key::Key8, isPressed);	break; | ||||||
| 			case VK_ANSI_9:		_electron.set_key_state(Electron::Key::Key9, isPressed);	break; | 			case VK_ANSI_9:		_electron->set_key_state(Electron::Key::Key9, isPressed);	break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Q:		_electron.set_key_state(Electron::Key::KeyQ, isPressed);	break; | 			case VK_ANSI_Q:		_electron->set_key_state(Electron::Key::KeyQ, isPressed);	break; | ||||||
| 			case VK_ANSI_W:		_electron.set_key_state(Electron::Key::KeyW, isPressed);	break; | 			case VK_ANSI_W:		_electron->set_key_state(Electron::Key::KeyW, isPressed);	break; | ||||||
| 			case VK_ANSI_E:		_electron.set_key_state(Electron::Key::KeyE, isPressed);	break; | 			case VK_ANSI_E:		_electron->set_key_state(Electron::Key::KeyE, isPressed);	break; | ||||||
| 			case VK_ANSI_R:		_electron.set_key_state(Electron::Key::KeyR, isPressed);	break; | 			case VK_ANSI_R:		_electron->set_key_state(Electron::Key::KeyR, isPressed);	break; | ||||||
| 			case VK_ANSI_T:		_electron.set_key_state(Electron::Key::KeyT, isPressed);	break; | 			case VK_ANSI_T:		_electron->set_key_state(Electron::Key::KeyT, isPressed);	break; | ||||||
| 			case VK_ANSI_Y:		_electron.set_key_state(Electron::Key::KeyY, isPressed);	break; | 			case VK_ANSI_Y:		_electron->set_key_state(Electron::Key::KeyY, isPressed);	break; | ||||||
| 			case VK_ANSI_U:		_electron.set_key_state(Electron::Key::KeyU, isPressed);	break; | 			case VK_ANSI_U:		_electron->set_key_state(Electron::Key::KeyU, isPressed);	break; | ||||||
| 			case VK_ANSI_I:		_electron.set_key_state(Electron::Key::KeyI, isPressed);	break; | 			case VK_ANSI_I:		_electron->set_key_state(Electron::Key::KeyI, isPressed);	break; | ||||||
| 			case VK_ANSI_O:		_electron.set_key_state(Electron::Key::KeyO, isPressed);	break; | 			case VK_ANSI_O:		_electron->set_key_state(Electron::Key::KeyO, isPressed);	break; | ||||||
| 			case VK_ANSI_P:		_electron.set_key_state(Electron::Key::KeyP, isPressed);	break; | 			case VK_ANSI_P:		_electron->set_key_state(Electron::Key::KeyP, isPressed);	break; | ||||||
| 			case VK_ANSI_A:		_electron.set_key_state(Electron::Key::KeyA, isPressed);	break; | 			case VK_ANSI_A:		_electron->set_key_state(Electron::Key::KeyA, isPressed);	break; | ||||||
| 			case VK_ANSI_S:		_electron.set_key_state(Electron::Key::KeyS, isPressed);	break; | 			case VK_ANSI_S:		_electron->set_key_state(Electron::Key::KeyS, isPressed);	break; | ||||||
| 			case VK_ANSI_D:		_electron.set_key_state(Electron::Key::KeyD, isPressed);	break; | 			case VK_ANSI_D:		_electron->set_key_state(Electron::Key::KeyD, isPressed);	break; | ||||||
| 			case VK_ANSI_F:		_electron.set_key_state(Electron::Key::KeyF, isPressed);	break; | 			case VK_ANSI_F:		_electron->set_key_state(Electron::Key::KeyF, isPressed);	break; | ||||||
| 			case VK_ANSI_G:		_electron.set_key_state(Electron::Key::KeyG, isPressed);	break; | 			case VK_ANSI_G:		_electron->set_key_state(Electron::Key::KeyG, isPressed);	break; | ||||||
| 			case VK_ANSI_H:		_electron.set_key_state(Electron::Key::KeyH, isPressed);	break; | 			case VK_ANSI_H:		_electron->set_key_state(Electron::Key::KeyH, isPressed);	break; | ||||||
| 			case VK_ANSI_J:		_electron.set_key_state(Electron::Key::KeyJ, isPressed);	break; | 			case VK_ANSI_J:		_electron->set_key_state(Electron::Key::KeyJ, isPressed);	break; | ||||||
| 			case VK_ANSI_K:		_electron.set_key_state(Electron::Key::KeyK, isPressed);	break; | 			case VK_ANSI_K:		_electron->set_key_state(Electron::Key::KeyK, isPressed);	break; | ||||||
| 			case VK_ANSI_L:		_electron.set_key_state(Electron::Key::KeyL, isPressed);	break; | 			case VK_ANSI_L:		_electron->set_key_state(Electron::Key::KeyL, isPressed);	break; | ||||||
| 			case VK_ANSI_Z:		_electron.set_key_state(Electron::Key::KeyZ, isPressed);	break; | 			case VK_ANSI_Z:		_electron->set_key_state(Electron::Key::KeyZ, isPressed);	break; | ||||||
| 			case VK_ANSI_X:		_electron.set_key_state(Electron::Key::KeyX, isPressed);	break; | 			case VK_ANSI_X:		_electron->set_key_state(Electron::Key::KeyX, isPressed);	break; | ||||||
| 			case VK_ANSI_C:		_electron.set_key_state(Electron::Key::KeyC, isPressed);	break; | 			case VK_ANSI_C:		_electron->set_key_state(Electron::Key::KeyC, isPressed);	break; | ||||||
| 			case VK_ANSI_V:		_electron.set_key_state(Electron::Key::KeyV, isPressed);	break; | 			case VK_ANSI_V:		_electron->set_key_state(Electron::Key::KeyV, isPressed);	break; | ||||||
| 			case VK_ANSI_B:		_electron.set_key_state(Electron::Key::KeyB, isPressed);	break; | 			case VK_ANSI_B:		_electron->set_key_state(Electron::Key::KeyB, isPressed);	break; | ||||||
| 			case VK_ANSI_N:		_electron.set_key_state(Electron::Key::KeyN, isPressed);	break; | 			case VK_ANSI_N:		_electron->set_key_state(Electron::Key::KeyN, isPressed);	break; | ||||||
| 			case VK_ANSI_M:		_electron.set_key_state(Electron::Key::KeyM, isPressed);	break; | 			case VK_ANSI_M:		_electron->set_key_state(Electron::Key::KeyM, isPressed);	break; | ||||||
|  |  | ||||||
| 			case VK_Space:			_electron.set_key_state(Electron::Key::KeySpace, isPressed);		break; | 			case VK_Space:			_electron->set_key_state(Electron::Key::KeySpace, isPressed);		break; | ||||||
| 			case VK_ANSI_Grave: | 			case VK_ANSI_Grave: | ||||||
| 			case VK_ANSI_Backslash: | 			case VK_ANSI_Backslash: | ||||||
| 									_electron.set_key_state(Electron::Key::KeyCopy, isPressed);			break; | 									_electron->set_key_state(Electron::Key::KeyCopy, isPressed);		break; | ||||||
| 			case VK_Return:			_electron.set_key_state(Electron::Key::KeyReturn, isPressed);		break; | 			case VK_Return:			_electron->set_key_state(Electron::Key::KeyReturn, isPressed);		break; | ||||||
| 			case VK_ANSI_Minus:		_electron.set_key_state(Electron::Key::KeyMinus, isPressed);		break; | 			case VK_ANSI_Minus:		_electron->set_key_state(Electron::Key::KeyMinus, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_RightArrow:		_electron.set_key_state(Electron::Key::KeyRight, isPressed);		break; | 			case VK_RightArrow:		_electron->set_key_state(Electron::Key::KeyRight, isPressed);		break; | ||||||
| 			case VK_LeftArrow:		_electron.set_key_state(Electron::Key::KeyLeft, isPressed);			break; | 			case VK_LeftArrow:		_electron->set_key_state(Electron::Key::KeyLeft, isPressed);		break; | ||||||
| 			case VK_DownArrow:		_electron.set_key_state(Electron::Key::KeyDown, isPressed);			break; | 			case VK_DownArrow:		_electron->set_key_state(Electron::Key::KeyDown, isPressed);		break; | ||||||
| 			case VK_UpArrow:		_electron.set_key_state(Electron::Key::KeyUp, isPressed);			break; | 			case VK_UpArrow:		_electron->set_key_state(Electron::Key::KeyUp, isPressed);			break; | ||||||
|  |  | ||||||
| 			case VK_Delete:			_electron.set_key_state(Electron::Key::KeyDelete, isPressed);		break; | 			case VK_Delete:			_electron->set_key_state(Electron::Key::KeyDelete, isPressed);		break; | ||||||
| 			case VK_Escape:			_electron.set_key_state(Electron::Key::KeyEscape, isPressed);		break; | 			case VK_Escape:			_electron->set_key_state(Electron::Key::KeyEscape, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Comma:		_electron.set_key_state(Electron::Key::KeyComma, isPressed);		break; | 			case VK_ANSI_Comma:		_electron->set_key_state(Electron::Key::KeyComma, isPressed);		break; | ||||||
| 			case VK_ANSI_Period:	_electron.set_key_state(Electron::Key::KeyFullStop, isPressed);		break; | 			case VK_ANSI_Period:	_electron->set_key_state(Electron::Key::KeyFullStop, isPressed);	break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Semicolon: | 			case VK_ANSI_Semicolon: | ||||||
| 									_electron.set_key_state(Electron::Key::KeySemiColon, isPressed);	break; | 									_electron->set_key_state(Electron::Key::KeySemiColon, isPressed);	break; | ||||||
| 			case VK_ANSI_Quote:		_electron.set_key_state(Electron::Key::KeyColon, isPressed);		break; | 			case VK_ANSI_Quote:		_electron->set_key_state(Electron::Key::KeyColon, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Slash:		_electron.set_key_state(Electron::Key::KeySlash, isPressed);		break; | 			case VK_ANSI_Slash:		_electron->set_key_state(Electron::Key::KeySlash, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_Shift:			_electron.set_key_state(Electron::Key::KeyShift, isPressed);		break; | 			case VK_Shift:			_electron->set_key_state(Electron::Key::KeyShift, isPressed);		break; | ||||||
| 			case VK_Control:		_electron.set_key_state(Electron::Key::KeyControl, isPressed);		break; | 			case VK_Control:		_electron->set_key_state(Electron::Key::KeyControl, isPressed);		break; | ||||||
| 			case VK_Command: | 			case VK_Command: | ||||||
| 			case VK_Option:			_electron.set_key_state(Electron::Key::KeyFunc, isPressed);			break; | 			case VK_Option:			_electron->set_key_state(Electron::Key::KeyFunc, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_F12:			_electron.set_key_state(Electron::Key::KeyBreak, isPressed);		break; | 			case VK_F12:			_electron->set_key_state(Electron::Key::KeyBreak, isPressed);		break; | ||||||
|  |  | ||||||
| 			default: | 			default: | ||||||
| //				printf("%02x\n", key); | //				printf("%02x\n", key); | ||||||
| @@ -154,19 +152,14 @@ | |||||||
| - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { | - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_useFastLoadingHack = useFastLoadingHack; | 		_useFastLoadingHack = useFastLoadingHack; | ||||||
| 		_electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); | 		_electron->set_use_fast_tape_hack(useFastLoadingHack ? true : false); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput { | - (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_useTelevisionOutput = useTelevisionOutput; | 		_useTelevisionOutput = useTelevisionOutput; | ||||||
| 		_electron.get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); | 		_electron->get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| //override func aspectRatio() -> NSSize { |  | ||||||
| //		return NSSize(width: 11.0, height: 10.0) |  | ||||||
| //	} |  | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ | |||||||
|  |  | ||||||
| @interface CSOric : CSMachine <CSKeyboardMachine, CSFastLoading> | @interface CSOric : CSMachine <CSKeyboardMachine, CSFastLoading> | ||||||
|  |  | ||||||
|  | - (instancetype)init; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) BOOL useCompositeOutput; | @property (nonatomic, assign) BOOL useCompositeOutput; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,118 +15,113 @@ | |||||||
| #import "NSData+StdVector.h" | #import "NSData+StdVector.h" | ||||||
| #import "NSBundle+DataResource.h" | #import "NSBundle+DataResource.h" | ||||||
|  |  | ||||||
| @implementation CSOric | @implementation CSOric { | ||||||
| { | 	std::unique_ptr<Oric::Machine> _oric; | ||||||
| 	Oric::Machine _oric; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| - (instancetype)init | - (instancetype)init { | ||||||
| { | 	Oric::Machine *machine = Oric::Machine::Oric(); | ||||||
| 	self = [super init]; |  | ||||||
| 	if(self) | 	self = [super initWithMachine:machine]; | ||||||
| 	{ | 	if(self) { | ||||||
|  | 		_oric.reset(machine); | ||||||
|  |  | ||||||
| 		NSData *basic10 = [self rom:@"basic10"]; | 		NSData *basic10 = [self rom:@"basic10"]; | ||||||
| 		NSData *basic11 = [self rom:@"basic11"]; | 		NSData *basic11 = [self rom:@"basic11"]; | ||||||
| 		NSData *colour = [self rom:@"colour"]; | 		NSData *colour = [self rom:@"colour"]; | ||||||
| 		NSData *microdisc = [self rom:@"microdisc"]; | 		NSData *microdisc = [self rom:@"microdisc"]; | ||||||
|  |  | ||||||
| 		if(basic10)		_oric.set_rom(Oric::BASIC10, basic10.stdVector8); | 		if(basic10)		_oric->set_rom(Oric::BASIC10, basic10.stdVector8); | ||||||
| 		if(basic11)		_oric.set_rom(Oric::BASIC11, basic11.stdVector8); | 		if(basic11)		_oric->set_rom(Oric::BASIC11, basic11.stdVector8); | ||||||
| 		if(colour)		_oric.set_rom(Oric::Colour, colour.stdVector8); | 		if(colour)		_oric->set_rom(Oric::Colour, colour.stdVector8); | ||||||
| 		if(microdisc)	_oric.set_rom(Oric::Microdisc, microdisc.stdVector8); | 		if(microdisc)	_oric->set_rom(Oric::Microdisc, microdisc.stdVector8); | ||||||
| 	} | 	} | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (NSData *)rom:(NSString *)name | - (NSData *)rom:(NSString *)name { | ||||||
| { |  | ||||||
| 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"]; | 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (CRTMachine::Machine * const)machine |  | ||||||
| { |  | ||||||
| 	return &_oric; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #pragma mark - CSKeyboardMachine | #pragma mark - CSKeyboardMachine | ||||||
|  |  | ||||||
| - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed | - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { | ||||||
| { |  | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		switch(key) | 		switch(key) { | ||||||
| 		{ | 			case VK_ANSI_0:		_oric->set_key_state(Oric::Key::Key0, isPressed);	break; | ||||||
| 			case VK_ANSI_0:		_oric.set_key_state(Oric::Key::Key0, isPressed);	break; | 			case VK_ANSI_1:		_oric->set_key_state(Oric::Key::Key1, isPressed);	break; | ||||||
| 			case VK_ANSI_1:		_oric.set_key_state(Oric::Key::Key1, isPressed);	break; | 			case VK_ANSI_2:		_oric->set_key_state(Oric::Key::Key2, isPressed);	break; | ||||||
| 			case VK_ANSI_2:		_oric.set_key_state(Oric::Key::Key2, isPressed);	break; | 			case VK_ANSI_3:		_oric->set_key_state(Oric::Key::Key3, isPressed);	break; | ||||||
| 			case VK_ANSI_3:		_oric.set_key_state(Oric::Key::Key3, isPressed);	break; | 			case VK_ANSI_4:		_oric->set_key_state(Oric::Key::Key4, isPressed);	break; | ||||||
| 			case VK_ANSI_4:		_oric.set_key_state(Oric::Key::Key4, isPressed);	break; | 			case VK_ANSI_5:		_oric->set_key_state(Oric::Key::Key5, isPressed);	break; | ||||||
| 			case VK_ANSI_5:		_oric.set_key_state(Oric::Key::Key5, isPressed);	break; | 			case VK_ANSI_6:		_oric->set_key_state(Oric::Key::Key6, isPressed);	break; | ||||||
| 			case VK_ANSI_6:		_oric.set_key_state(Oric::Key::Key6, isPressed);	break; | 			case VK_ANSI_7:		_oric->set_key_state(Oric::Key::Key7, isPressed);	break; | ||||||
| 			case VK_ANSI_7:		_oric.set_key_state(Oric::Key::Key7, isPressed);	break; | 			case VK_ANSI_8:		_oric->set_key_state(Oric::Key::Key8, isPressed);	break; | ||||||
| 			case VK_ANSI_8:		_oric.set_key_state(Oric::Key::Key8, isPressed);	break; | 			case VK_ANSI_9:		_oric->set_key_state(Oric::Key::Key9, isPressed);	break; | ||||||
| 			case VK_ANSI_9:		_oric.set_key_state(Oric::Key::Key9, isPressed);	break; |  | ||||||
|  |  | ||||||
| 			case VK_ANSI_Q:		_oric.set_key_state(Oric::Key::KeyQ, isPressed);	break; | 			case VK_ANSI_Q:		_oric->set_key_state(Oric::Key::KeyQ, isPressed);	break; | ||||||
| 			case VK_ANSI_W:		_oric.set_key_state(Oric::Key::KeyW, isPressed);	break; | 			case VK_ANSI_W:		_oric->set_key_state(Oric::Key::KeyW, isPressed);	break; | ||||||
| 			case VK_ANSI_E:		_oric.set_key_state(Oric::Key::KeyE, isPressed);	break; | 			case VK_ANSI_E:		_oric->set_key_state(Oric::Key::KeyE, isPressed);	break; | ||||||
| 			case VK_ANSI_R:		_oric.set_key_state(Oric::Key::KeyR, isPressed);	break; | 			case VK_ANSI_R:		_oric->set_key_state(Oric::Key::KeyR, isPressed);	break; | ||||||
| 			case VK_ANSI_T:		_oric.set_key_state(Oric::Key::KeyT, isPressed);	break; | 			case VK_ANSI_T:		_oric->set_key_state(Oric::Key::KeyT, isPressed);	break; | ||||||
| 			case VK_ANSI_Y:		_oric.set_key_state(Oric::Key::KeyY, isPressed);	break; | 			case VK_ANSI_Y:		_oric->set_key_state(Oric::Key::KeyY, isPressed);	break; | ||||||
| 			case VK_ANSI_U:		_oric.set_key_state(Oric::Key::KeyU, isPressed);	break; | 			case VK_ANSI_U:		_oric->set_key_state(Oric::Key::KeyU, isPressed);	break; | ||||||
| 			case VK_ANSI_I:		_oric.set_key_state(Oric::Key::KeyI, isPressed);	break; | 			case VK_ANSI_I:		_oric->set_key_state(Oric::Key::KeyI, isPressed);	break; | ||||||
| 			case VK_ANSI_O:		_oric.set_key_state(Oric::Key::KeyO, isPressed);	break; | 			case VK_ANSI_O:		_oric->set_key_state(Oric::Key::KeyO, isPressed);	break; | ||||||
| 			case VK_ANSI_P:		_oric.set_key_state(Oric::Key::KeyP, isPressed);	break; | 			case VK_ANSI_P:		_oric->set_key_state(Oric::Key::KeyP, isPressed);	break; | ||||||
| 			case VK_ANSI_A:		_oric.set_key_state(Oric::Key::KeyA, isPressed);	break; | 			case VK_ANSI_A:		_oric->set_key_state(Oric::Key::KeyA, isPressed);	break; | ||||||
| 			case VK_ANSI_S:		_oric.set_key_state(Oric::Key::KeyS, isPressed);	break; | 			case VK_ANSI_S:		_oric->set_key_state(Oric::Key::KeyS, isPressed);	break; | ||||||
| 			case VK_ANSI_D:		_oric.set_key_state(Oric::Key::KeyD, isPressed);	break; | 			case VK_ANSI_D:		_oric->set_key_state(Oric::Key::KeyD, isPressed);	break; | ||||||
| 			case VK_ANSI_F:		_oric.set_key_state(Oric::Key::KeyF, isPressed);	break; | 			case VK_ANSI_F:		_oric->set_key_state(Oric::Key::KeyF, isPressed);	break; | ||||||
| 			case VK_ANSI_G:		_oric.set_key_state(Oric::Key::KeyG, isPressed);	break; | 			case VK_ANSI_G:		_oric->set_key_state(Oric::Key::KeyG, isPressed);	break; | ||||||
| 			case VK_ANSI_H:		_oric.set_key_state(Oric::Key::KeyH, isPressed);	break; | 			case VK_ANSI_H:		_oric->set_key_state(Oric::Key::KeyH, isPressed);	break; | ||||||
| 			case VK_ANSI_J:		_oric.set_key_state(Oric::Key::KeyJ, isPressed);	break; | 			case VK_ANSI_J:		_oric->set_key_state(Oric::Key::KeyJ, isPressed);	break; | ||||||
| 			case VK_ANSI_K:		_oric.set_key_state(Oric::Key::KeyK, isPressed);	break; | 			case VK_ANSI_K:		_oric->set_key_state(Oric::Key::KeyK, isPressed);	break; | ||||||
| 			case VK_ANSI_L:		_oric.set_key_state(Oric::Key::KeyL, isPressed);	break; | 			case VK_ANSI_L:		_oric->set_key_state(Oric::Key::KeyL, isPressed);	break; | ||||||
| 			case VK_ANSI_Z:		_oric.set_key_state(Oric::Key::KeyZ, isPressed);	break; | 			case VK_ANSI_Z:		_oric->set_key_state(Oric::Key::KeyZ, isPressed);	break; | ||||||
| 			case VK_ANSI_X:		_oric.set_key_state(Oric::Key::KeyX, isPressed);	break; | 			case VK_ANSI_X:		_oric->set_key_state(Oric::Key::KeyX, isPressed);	break; | ||||||
| 			case VK_ANSI_C:		_oric.set_key_state(Oric::Key::KeyC, isPressed);	break; | 			case VK_ANSI_C:		_oric->set_key_state(Oric::Key::KeyC, isPressed);	break; | ||||||
| 			case VK_ANSI_V:		_oric.set_key_state(Oric::Key::KeyV, isPressed);	break; | 			case VK_ANSI_V:		_oric->set_key_state(Oric::Key::KeyV, isPressed);	break; | ||||||
| 			case VK_ANSI_B:		_oric.set_key_state(Oric::Key::KeyB, isPressed);	break; | 			case VK_ANSI_B:		_oric->set_key_state(Oric::Key::KeyB, isPressed);	break; | ||||||
| 			case VK_ANSI_N:		_oric.set_key_state(Oric::Key::KeyN, isPressed);	break; | 			case VK_ANSI_N:		_oric->set_key_state(Oric::Key::KeyN, isPressed);	break; | ||||||
| 			case VK_ANSI_M:		_oric.set_key_state(Oric::Key::KeyM, isPressed);	break; | 			case VK_ANSI_M:		_oric->set_key_state(Oric::Key::KeyM, isPressed);	break; | ||||||
|  |  | ||||||
| 			case VK_Space:			_oric.set_key_state(Oric::Key::KeySpace, isPressed);		break; | 			case VK_Space:			_oric->set_key_state(Oric::Key::KeySpace, isPressed);		break; | ||||||
| 			case VK_Return:			_oric.set_key_state(Oric::Key::KeyReturn, isPressed);		break; | 			case VK_Return:			_oric->set_key_state(Oric::Key::KeyReturn, isPressed);		break; | ||||||
| 			case VK_ANSI_Minus:		_oric.set_key_state(Oric::Key::KeyMinus, isPressed);		break; | 			case VK_ANSI_Minus:		_oric->set_key_state(Oric::Key::KeyMinus, isPressed);		break; | ||||||
| 			case VK_ANSI_Equal:		_oric.set_key_state(Oric::Key::KeyEquals, isPressed);		break; | 			case VK_ANSI_Equal:		_oric->set_key_state(Oric::Key::KeyEquals, isPressed);		break; | ||||||
| 			case VK_ANSI_Backslash: | 			case VK_ANSI_Backslash: | ||||||
| 									_oric.set_key_state(Oric::Key::KeyBackSlash, isPressed);	break; | 									_oric->set_key_state(Oric::Key::KeyBackSlash, isPressed);		break; | ||||||
| 			case VK_ANSI_Slash:		_oric.set_key_state(Oric::Key::KeyForwardSlash, isPressed);	break; | 			case VK_ANSI_Slash:		_oric->set_key_state(Oric::Key::KeyForwardSlash, isPressed);	break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_LeftBracket: | 			case VK_ANSI_LeftBracket: | ||||||
| 									_oric.set_key_state(Oric::Key::KeyOpenSquare, isPressed);	break; | 									_oric->set_key_state(Oric::Key::KeyOpenSquare, isPressed);	break; | ||||||
| 			case VK_ANSI_RightBracket: | 			case VK_ANSI_RightBracket: | ||||||
| 									_oric.set_key_state(Oric::Key::KeyCloseSquare, isPressed);	break; | 									_oric->set_key_state(Oric::Key::KeyCloseSquare, isPressed);	break; | ||||||
| 			case VK_ANSI_Quote:		_oric.set_key_state(Oric::Key::KeyQuote, isPressed);		break; | 			case VK_ANSI_Quote:		_oric->set_key_state(Oric::Key::KeyQuote, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_RightArrow:		_oric.set_key_state(Oric::Key::KeyRight, isPressed);		break; | 			case VK_RightArrow:		_oric->set_key_state(Oric::Key::KeyRight, isPressed);		break; | ||||||
| 			case VK_LeftArrow:		_oric.set_key_state(Oric::Key::KeyLeft, isPressed);			break; | 			case VK_LeftArrow:		_oric->set_key_state(Oric::Key::KeyLeft, isPressed);		break; | ||||||
| 			case VK_DownArrow:		_oric.set_key_state(Oric::Key::KeyDown, isPressed);			break; | 			case VK_DownArrow:		_oric->set_key_state(Oric::Key::KeyDown, isPressed);		break; | ||||||
| 			case VK_UpArrow:		_oric.set_key_state(Oric::Key::KeyUp, isPressed);			break; | 			case VK_UpArrow:		_oric->set_key_state(Oric::Key::KeyUp, isPressed);			break; | ||||||
|  |  | ||||||
| 			case VK_Delete:			_oric.set_key_state(Oric::Key::KeyDelete, isPressed);		break; | 			case VK_Delete:			_oric->set_key_state(Oric::Key::KeyDelete, isPressed);		break; | ||||||
| 			case VK_Escape:			_oric.set_key_state(Oric::Key::KeyEscape, isPressed);		break; | 			case VK_Escape:			_oric->set_key_state(Oric::Key::KeyEscape, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Comma:		_oric.set_key_state(Oric::Key::KeyComma, isPressed);		break; | 			case VK_ANSI_Comma:		_oric->set_key_state(Oric::Key::KeyComma, isPressed);		break; | ||||||
| 			case VK_ANSI_Period:	_oric.set_key_state(Oric::Key::KeyFullStop, isPressed);		break; | 			case VK_ANSI_Period:	_oric->set_key_state(Oric::Key::KeyFullStop, isPressed);	break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Semicolon: | 			case VK_ANSI_Semicolon:	_oric->set_key_state(Oric::Key::KeySemiColon, isPressed);	break; | ||||||
| 									_oric.set_key_state(Oric::Key::KeySemiColon, isPressed);	break; |  | ||||||
|  |  | ||||||
| 			case VK_Shift:			_oric.set_key_state(Oric::Key::KeyLeftShift, isPressed);	break; | 			case VK_Shift: | ||||||
| 			case VK_RightShift:		_oric.set_key_state(Oric::Key::KeyRightShift, isPressed);	break; | 				_oric->set_key_state(Oric::Key::KeyLeftShift, isPressed); | ||||||
| 			case VK_Control:		_oric.set_key_state(Oric::Key::KeyControl, isPressed);		break; | 				_oric->set_key_state(Oric::Key::KeyRightShift, isPressed); | ||||||
|  | 			break; | ||||||
|  | 			case VK_RightShift:		_oric->set_key_state(Oric::Key::KeyRightShift, isPressed);	break; | ||||||
|  | 			case VK_Control:		_oric->set_key_state(Oric::Key::KeyControl, isPressed);		break; | ||||||
|  |  | ||||||
| 			case VK_ANSI_Grave: | 			case VK_ANSI_Grave: | ||||||
| 			case VK_F12:			_oric.set_key_state(Oric::Key::KeyNMI, isPressed);		break; | 			case VK_F12:			_oric->set_key_state(Oric::Key::KeyNMI, isPressed);			break; | ||||||
|  |  | ||||||
| 			default: | 			default: | ||||||
| 				printf("%02x\n", key); | 				printf("%02x\n", key); | ||||||
| @@ -135,9 +130,10 @@ | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)clearAllKeys | - (void)clearAllKeys { | ||||||
| { | 	@synchronized(self) { | ||||||
| 	_oric.clear_all_keys(); | 		_oric->clear_all_keys(); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Options | #pragma mark - Options | ||||||
| @@ -145,14 +141,14 @@ | |||||||
| - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { | - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_useFastLoadingHack = useFastLoadingHack; | 		_useFastLoadingHack = useFastLoadingHack; | ||||||
| 		_oric.set_use_fast_tape_hack(useFastLoadingHack ? true : false); | 		_oric->set_use_fast_tape_hack(useFastLoadingHack ? true : false); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)setUseCompositeOutput:(BOOL)useCompositeOutput { | - (void)setUseCompositeOutput:(BOOL)useCompositeOutput { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_useCompositeOutput = useCompositeOutput; | 		_useCompositeOutput = useCompositeOutput; | ||||||
| 		_oric.set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); | 		_oric->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,6 +28,8 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize) | |||||||
|  |  | ||||||
| @interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading> | @interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading> | ||||||
|  |  | ||||||
|  | - (instancetype)init; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) CSVic20Country country; | @property (nonatomic, assign) CSVic20Country country; | ||||||
| @property (nonatomic, assign) CSVic20MemorySize memorySize; | @property (nonatomic, assign) CSVic20MemorySize memorySize; | ||||||
|   | |||||||
| @@ -13,22 +13,24 @@ | |||||||
| #include "G64.hpp" | #include "G64.hpp" | ||||||
| #include "D64.hpp" | #include "D64.hpp" | ||||||
|  |  | ||||||
|  | #import "CSmachine+Subclassing.h" | ||||||
| #import "NSBundle+DataResource.h" | #import "NSBundle+DataResource.h" | ||||||
|  |  | ||||||
| using namespace Commodore::Vic20; | using namespace Commodore::Vic20; | ||||||
|  |  | ||||||
| @implementation CSVic20 { | @implementation CSVic20 { | ||||||
| 	Machine _vic20; | 	std::unique_ptr<Machine> _vic20; | ||||||
| 	BOOL _joystickMode; | 	BOOL _joystickMode; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (CRTMachine::Machine * const)machine	{	return &_vic20;		} |  | ||||||
| - (NSString *)userDefaultsPrefix		{	return @"vic20";	} | - (NSString *)userDefaultsPrefix		{	return @"vic20";	} | ||||||
|  |  | ||||||
| - (instancetype)init { | - (instancetype)init { | ||||||
| 	self = [super init]; | 	Machine *machine = Commodore::Vic20::Machine::Vic20(); | ||||||
| 	if(self) |  | ||||||
| 	{ | 	self = [super initWithMachine:machine]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_vic20.reset(machine); | ||||||
| 		[self setDriveROM:[[NSBundle mainBundle] dataForResource:@"1540" withExtension:@"bin" subdirectory:@"ROMImages/Commodore1540"]]; | 		[self setDriveROM:[[NSBundle mainBundle] dataForResource:@"1540" withExtension:@"bin" subdirectory:@"ROMImages/Commodore1540"]]; | ||||||
| 		[self setBASICROM:[self rom:@"basic"]]; | 		[self setBASICROM:[self rom:@"basic"]]; | ||||||
| 		[self setCountry:CSVic20CountryEuropean]; | 		[self setCountry:CSVic20CountryEuropean]; | ||||||
| @@ -36,8 +38,7 @@ using namespace Commodore::Vic20; | |||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (NSData *)rom:(NSString *)name | - (NSData *)rom:(NSString *)name { | ||||||
| { |  | ||||||
| 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"bin" subdirectory:@"ROMImages/Vic20"]; | 	return [[NSBundle mainBundle] dataForResource:name withExtension:@"bin" subdirectory:@"ROMImages/Vic20"]; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -45,7 +46,7 @@ using namespace Commodore::Vic20; | |||||||
|  |  | ||||||
| - (void)setROM:(nonnull NSData *)rom slot:(ROMSlot)slot { | - (void)setROM:(nonnull NSData *)rom slot:(ROMSlot)slot { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes); | 		_vic20->set_rom(slot, rom.length, (const uint8_t *)rom.bytes); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -120,32 +121,26 @@ using namespace Commodore::Vic20; | |||||||
| 	//	KeyPlus | 	//	KeyPlus | ||||||
| 	//	KeyGBP | 	//	KeyGBP | ||||||
|  |  | ||||||
| 	if(key == VK_Tab && isPressed) | 	if(key == VK_Tab && isPressed) { | ||||||
| 	{ |  | ||||||
| 		_joystickMode ^= YES; | 		_joystickMode ^= YES; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		if(_joystickMode) | 		if(_joystickMode) { | ||||||
| 		{ | 			switch(key) { | ||||||
| 			switch(key) | 				case VK_UpArrow:	_vic20->set_joystick_state(JoystickInput::Up, isPressed);	break; | ||||||
| 			{ | 				case VK_DownArrow:	_vic20->set_joystick_state(JoystickInput::Down, isPressed);	break; | ||||||
| 				case VK_UpArrow:	_vic20.set_joystick_state(JoystickInput::Up, isPressed);	break; | 				case VK_LeftArrow:	_vic20->set_joystick_state(JoystickInput::Left, isPressed);	break; | ||||||
| 				case VK_DownArrow:	_vic20.set_joystick_state(JoystickInput::Down, isPressed);	break; | 				case VK_RightArrow:	_vic20->set_joystick_state(JoystickInput::Right, isPressed);	break; | ||||||
| 				case VK_LeftArrow:	_vic20.set_joystick_state(JoystickInput::Left, isPressed);	break; | 				case VK_ANSI_A:		_vic20->set_joystick_state(JoystickInput::Fire, isPressed);	break; | ||||||
| 				case VK_RightArrow:	_vic20.set_joystick_state(JoystickInput::Right, isPressed);	break; |  | ||||||
| 				case VK_ANSI_A:		_vic20.set_joystick_state(JoystickInput::Fire, isPressed);	break; |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} else { | ||||||
| 		else | 			switch(key) { | ||||||
| 		{ |  | ||||||
| 			switch(key) |  | ||||||
| 			{ |  | ||||||
| 				default: { | 				default: { | ||||||
| 					NSNumber *targetKey = vicKeysByKeys[@(key)]; | 					NSNumber *targetKey = vicKeysByKeys[@(key)]; | ||||||
| 					if(targetKey) | 					if(targetKey) | ||||||
| 					{ | 					{ | ||||||
| 						_vic20.set_key_state((Key)targetKey.integerValue, isPressed); | 						_vic20->set_key_state((Key)targetKey.integerValue, isPressed); | ||||||
| 					} | 					} | ||||||
| 					else | 					else | ||||||
| 					{ | 					{ | ||||||
| @@ -155,8 +150,8 @@ using namespace Commodore::Vic20; | |||||||
|  |  | ||||||
| 				case VK_Shift: | 				case VK_Shift: | ||||||
| 					// Yuck | 					// Yuck | ||||||
| 					_vic20.set_key_state(Key::KeyLShift, isPressed); | 					_vic20->set_key_state(Key::KeyLShift, isPressed); | ||||||
| 					_vic20.set_key_state(Key::KeyRShift, isPressed); | 					_vic20->set_key_state(Key::KeyRShift, isPressed); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -165,7 +160,7 @@ using namespace Commodore::Vic20; | |||||||
|  |  | ||||||
| - (void)clearAllKeys { | - (void)clearAllKeys { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_vic20.clear_all_keys(); | 		_vic20->clear_all_keys(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -174,7 +169,7 @@ using namespace Commodore::Vic20; | |||||||
| - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { | - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { | ||||||
| 	_useFastLoadingHack = useFastLoadingHack; | 	_useFastLoadingHack = useFastLoadingHack; | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_vic20.set_use_fast_tape_hack(useFastLoadingHack ? true : false); | 		_vic20->set_use_fast_tape_hack(useFastLoadingHack ? true : false); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -182,8 +177,7 @@ using namespace Commodore::Vic20; | |||||||
| 	_country = country; | 	_country = country; | ||||||
| 	NSString *charactersROM, *kernelROM; | 	NSString *charactersROM, *kernelROM; | ||||||
| 	Commodore::Vic20::Region region; | 	Commodore::Vic20::Region region; | ||||||
| 	switch(country) | 	switch(country) { | ||||||
| 	{ |  | ||||||
| 		case CSVic20CountryDanish: | 		case CSVic20CountryDanish: | ||||||
| 			region = Commodore::Vic20::Region::PAL; | 			region = Commodore::Vic20::Region::PAL; | ||||||
| 			charactersROM = @"characters-danish"; | 			charactersROM = @"characters-danish"; | ||||||
| @@ -212,7 +206,7 @@ using namespace Commodore::Vic20; | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		_vic20.set_region(region); | 		_vic20->set_region(region); | ||||||
| 		[self setCharactersROM:[self rom:charactersROM]]; | 		[self setCharactersROM:[self rom:charactersROM]]; | ||||||
| 		[self setKernelROM:[self rom:kernelROM]]; | 		[self setKernelROM:[self rom:kernelROM]]; | ||||||
| 	} | 	} | ||||||
| @@ -222,9 +216,9 @@ using namespace Commodore::Vic20; | |||||||
| 	_memorySize = memorySize; | 	_memorySize = memorySize; | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		switch(memorySize) { | 		switch(memorySize) { | ||||||
| 			case CSVic20MemorySize5Kb: _vic20.set_memory_size(Commodore::Vic20::Default);	break; | 			case CSVic20MemorySize5Kb: _vic20->set_memory_size(Commodore::Vic20::Default);	break; | ||||||
| 			case CSVic20MemorySize8Kb: _vic20.set_memory_size(Commodore::Vic20::ThreeKB);	break; | 			case CSVic20MemorySize8Kb: _vic20->set_memory_size(Commodore::Vic20::ThreeKB);	break; | ||||||
| 			case CSVic20MemorySize32Kb: _vic20.set_memory_size(Commodore::Vic20::ThirtyTwoKB);	break; | 			case CSVic20MemorySize32Kb: _vic20->set_memory_size(Commodore::Vic20::ThirtyTwoKB);	break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | // | ||||||
|  | //  CSZX8081+Instantiation.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 27/08/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  | #import "CSZX8081.h" | ||||||
|  |  | ||||||
|  | @interface CSZX8081 (Instantiation) | ||||||
|  |  | ||||||
|  | - (instancetype)initWithIntendedTarget:(const StaticAnalyser::Target &)target; | ||||||
|  |  | ||||||
|  | @end | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user