mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-30 14:16:04 +00:00 
			
		
		
		
	Compare commits
	
		
			667 Commits
		
	
	
		
			2018-03-04
			...
			2018-09-09
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ab02f82470 | ||
|  | 1e3318816c | ||
|  | 3a3dec92c7 | ||
|  | 5a5fc1ae1a | ||
|  | 8d79a1e381 | ||
|  | d70f5da94e | ||
|  | 05d4274019 | ||
|  | afeec09902 | ||
|  | 0526ac2ee2 | ||
|  | 6725ee2190 | ||
|  | 8b661fb90f | ||
|  | dab7d3db1b | ||
|  | 1cba3d48d9 | ||
|  | d53b38ec7e | ||
|  | 5d0f47eda2 | ||
|  | 2e04c4442c | ||
|  | f639cdc8ad | ||
|  | 71ec7624ca | ||
|  | 0599d9602e | ||
|  | 234bef2a88 | ||
|  | adb574e1cd | ||
|  | 1f491e764e | ||
|  | 114a43a662 | ||
|  | 5547c39c91 | ||
|  | 97a89aaf4d | ||
|  | 61e46399dc | ||
|  | e802f6ecc2 | ||
|  | 4209f0e044 | ||
|  | 33576aa2c4 | ||
|  | 17bf1a64bf | ||
|  | f8d46f8f3d | ||
|  | 8787d85e64 | ||
|  | 7f0f17f435 | ||
|  | 0e7f54f375 | ||
|  | b3bdfa9f46 | ||
|  | 592ec69d36 | ||
|  | 60e00ddd02 | ||
|  | 6806193dc2 | ||
|  | c35dca783f | ||
|  | 901e0d65b9 | ||
|  | ddf45a0010 | ||
|  | 1eca4463b3 | ||
|  | be01203cc1 | ||
|  | 4d1d19a464 | ||
|  | 760817eb3b | ||
|  | cb47575860 | ||
|  | 434d184503 | ||
|  | 7374c665e8 | ||
|  | 10c930a59d | ||
|  | 60ab6f0c2a | ||
|  | a13eb351da | ||
|  | 4b91910fab | ||
|  | f46d52364c | ||
|  | 878c63dcd2 | ||
|  | 261fb3d4f8 | ||
|  | b63e0cff72 | ||
|  | 5d6e479338 | ||
|  | 90094529a5 | ||
|  | aed4c0539e | ||
|  | 8b50ab2593 | ||
|  | 95164b79c9 | ||
|  | 6f838fe190 | ||
|  | bb680b40d8 | ||
|  | e3f6da6994 | ||
|  | e46bde35f5 | ||
|  | 32338bea4d | ||
|  | 5c881bd19d | ||
|  | 1a44ef0469 | ||
|  | ebce9a2e51 | ||
|  | 633af4d404 | ||
|  | 76a73c835c | ||
|  | c1d1c451ef | ||
|  | 3be30d8c71 | ||
|  | d4c1244485 | ||
|  | c61b9dca17 | ||
|  | 39bf682016 | ||
|  | 60ac9b49ea | ||
|  | a8bb18e2cf | ||
|  | 1852786609 | ||
|  | 31df8c7e91 | ||
|  | 832939f5b7 | ||
|  | c2d9e1ec81 | ||
|  | 673b915ee8 | ||
|  | 032a62dfff | ||
|  | f2d78182a3 | ||
|  | de68e70246 | ||
|  | e07447eb9a | ||
|  | 5cdeb58571 | ||
|  | ce14cc8677 | ||
|  | bcd0479074 | ||
|  | d72dd8c4ff | ||
|  | f7ce86fef8 | ||
|  | 55f2fccf5e | ||
|  | c939a274be | ||
|  | 101fb5d7bf | ||
|  | 3c51e335c3 | ||
|  | 33ea90678c | ||
|  | 11ae2c64ba | ||
|  | 26624d7652 | ||
|  | 85fb4773b0 | ||
|  | 099d66804e | ||
|  | 086596c28e | ||
|  | 3aeb4213fe | ||
|  | 558b96bc05 | ||
|  | e97cc40a2c | ||
|  | 94503ed771 | ||
|  | c4f86cc324 | ||
|  | 70c4d6b9b3 | ||
|  | 78c7137427 | ||
|  | 74a2f717b3 | ||
|  | 98bb5bd9f1 | ||
|  | c91eaaf8da | ||
|  | a36f37d240 | ||
|  | c773d3501a | ||
|  | 5810f9b3f9 | ||
|  | 3f56683342 | ||
|  | 16ccbdefd6 | ||
|  | a533d09fe7 | ||
|  | e9aaa5bbdf | ||
|  | ecb26e3281 | ||
|  | 5aa0b17720 | ||
|  | 632b37ecec | ||
|  | c905de2e40 | ||
|  | bc2afe69e1 | ||
|  | 894998b163 | ||
|  | 51192d8397 | ||
|  | 3c33ccd730 | ||
|  | 3e35109d63 | ||
|  | 99c770eab4 | ||
|  | 34aa78b7ce | ||
|  | 8cca9c2055 | ||
|  | 85ce21c79f | ||
|  | d19d949b9c | ||
|  | 1cb3713b84 | ||
|  | 689850d698 | ||
|  | c572a52049 | ||
|  | 41765e00c4 | ||
|  | 080aa0acc5 | ||
|  | 5e7c46a72a | ||
|  | 5f2b9b2d5a | ||
|  | 5c4506a9db | ||
|  | 55a6431fb3 | ||
|  | ede2696a77 | ||
|  | 59b9e39022 | ||
|  | 6b2970f2f2 | ||
|  | 6a73fe7d65 | ||
|  | 1362906f94 | ||
|  | 8f4042c4bb | ||
|  | c05b6397b0 | ||
|  | 8d18808efe | ||
|  | 09950d9414 | ||
|  | badbbdf155 | ||
|  | 2832792fed | ||
|  | efa45b9504 | ||
|  | 523749edf8 | ||
|  | 5a0499e8a7 | ||
|  | 258c8b5900 | ||
|  | 24b861f056 | ||
|  | 29f7f4d432 | ||
|  | 21080a1149 | ||
|  | 1d068fd09b | ||
|  | 92065813ef | ||
|  | 3e9ef6b8cb | ||
|  | c9451a5382 | ||
|  | 2be3b027db | ||
|  | e339d169c5 | ||
|  | 87001f86ee | ||
|  | 58484e8f37 | ||
|  | 94f68f9d55 | ||
|  | 3f6944de54 | ||
|  | 00cb4d26b3 | ||
|  | 774d8668bf | ||
|  | 8503589828 | ||
|  | 0f95ef2059 | ||
|  | efd812cf22 | ||
|  | 736e14c83e | ||
|  | 57f161e64c | ||
|  | 0897210969 | ||
|  | 7e58a44771 | ||
|  | e8f847d288 | ||
|  | a0f817108e | ||
|  | 3862fdb44c | ||
|  | 3e2d271566 | ||
|  | c97c5fa03a | ||
|  | fa63f7ffc3 | ||
|  | bfccadd356 | ||
|  | 5b3512f1df | ||
|  | 6e34e60f8a | ||
|  | a391d0f4ae | ||
|  | abc5c50b2e | ||
|  | 1fcb461c42 | ||
|  | abca38a548 | ||
|  | b4be2cd063 | ||
|  | 2d83eeb9c4 | ||
|  | 4d9e897cc3 | ||
|  | be664b5695 | ||
|  | c3751066b7 | ||
|  | 77feee8197 | ||
|  | f75af3b45e | ||
|  | 1471a35bb8 | ||
|  | 555c2a4377 | ||
|  | 16bef0dcd5 | ||
|  | cd464fc7de | ||
|  | 5b88207477 | ||
|  | df8c896193 | ||
|  | 5d3e1f7084 | ||
|  | 59f8eeb05a | ||
|  | 0b14850467 | ||
|  | f72e260915 | ||
|  | 640a84d456 | ||
|  | 04f6cb1750 | ||
|  | 87d688b7e3 | ||
|  | 26141a59b0 | ||
|  | a93f8103ad | ||
|  | 4a3d7c338a | ||
|  | 55ab305dbf | ||
|  | e48ba89721 | ||
|  | 9bb55b6b61 | ||
|  | c33308bdc5 | ||
|  | 44a33941bf | ||
|  | cc34cd2133 | ||
|  | 52c9f9e89e | ||
|  | 2363deb19c | ||
|  | 1c6af279b2 | ||
|  | 6e96275e1c | ||
|  | 9968342a11 | ||
|  | c248ecde48 | ||
|  | 370952ab33 | ||
|  | 154c89e041 | ||
|  | d45f1a793d | ||
|  | 9800951f18 | ||
|  | 17251997c2 | ||
|  | 5ab4cfee84 | ||
|  | a9eb0d02c6 | ||
|  | 1f8b69a5b0 | ||
|  | 8b83f58d7a | ||
|  | 9a91ae38c1 | ||
|  | ad57caed5e | ||
|  | 283ed8dbae | ||
|  | acb74185d5 | ||
|  | 7a5d16ccf8 | ||
|  | adca862166 | ||
|  | 1bdc718527 | ||
|  | 685a80f95b | ||
|  | 62eef8cb40 | ||
|  | 6ed3a49fe1 | ||
|  | 17702bfb89 | ||
|  | 292e02702a | ||
|  | 5a56d8a5d0 | ||
|  | 3da1d5700c | ||
|  | d437e06e15 | ||
|  | 6a3702a5c7 | ||
|  | 83a654540a | ||
|  | 678bd93c52 | ||
|  | 1bf0c1891a | ||
|  | b899a22c7d | ||
|  | 1bd6bbca8d | ||
|  | 14a2e470e4 | ||
|  | 41dcf1de42 | ||
|  | 0c65385c82 | ||
|  | 4aaf43150a | ||
|  | f05ee525cb | ||
|  | 1172c4fd97 | ||
|  | 15deef50c8 | ||
|  | 7728adfc5a | ||
|  | eff67f2250 | ||
|  | 64e3cf5de2 | ||
|  | 31a6d620e8 | ||
|  | dfd37e7dec | ||
|  | 8d8f244bf5 | ||
|  | 037b4802db | ||
|  | 51da21b844 | ||
|  | 0be19d8de7 | ||
|  | f26e4734b3 | ||
|  | f1b430338e | ||
|  | 2954373115 | ||
|  | 42d21ea3a9 | ||
|  | 7d761f145f | ||
|  | 27657fcde0 | ||
|  | 3ea2a4ccb8 | ||
|  | a1c60152d4 | ||
|  | 69da00fcfb | ||
|  | c4108efc5c | ||
|  | d576ff1172 | ||
|  | af54666c23 | ||
|  | e0b75b6e3d | ||
|  | 12c59ede09 | ||
|  | b4d0d4fff6 | ||
|  | a694844190 | ||
|  | 28f2d331a8 | ||
|  | dde9b73a22 | ||
|  | fb4bb21bf6 | ||
|  | 744c35b617 | ||
|  | 9ac21a4e71 | ||
|  | 94359e9c75 | ||
|  | 076fa55651 | ||
|  | d380595ad4 | ||
|  | d84b8700a3 | ||
|  | 80b281d9f1 | ||
|  | 69dc3cc4d8 | ||
|  | 1a9cea050e | ||
|  | 0833412df9 | ||
|  | 35e84ff1a8 | ||
|  | 8dd7c6ef23 | ||
|  | a26ab7086d | ||
|  | b2464598d0 | ||
|  | 6812a001d8 | ||
|  | 6c16754a6b | ||
|  | 75f9e3caeb | ||
|  | ad5afe21ee | ||
|  | 8a566cc1dd | ||
|  | 928aab13dc | ||
|  | f3fe711542 | ||
|  | db8d8d8404 | ||
|  | 6220ccb5d3 | ||
|  | 20843305dd | ||
|  | 8f6c0f6a8d | ||
|  | ede2df7e70 | ||
|  | d45231c1a8 | ||
|  | 772812b35f | ||
|  | f443fd44b5 | ||
|  | 79c60b8984 | ||
|  | 2dc2c2ce79 | ||
|  | 523ca3264b | ||
|  | 4036c60b45 | ||
|  | 7d652e53e2 | ||
|  | 7c3dd55e5c | ||
|  | 1b43381be0 | ||
|  | 8f78e5039e | ||
|  | 57ee6d4e41 | ||
|  | 2868b1eca7 | ||
|  | a4d7703efd | ||
|  | ca4bc92c33 | ||
|  | 853261364e | ||
|  | d3c5e4267f | ||
|  | 086b801c29 | ||
|  | f9c25372c2 | ||
|  | ea92363e6c | ||
|  | 015f692bd3 | ||
|  | 80d34f5511 | ||
|  | e482929da8 | ||
|  | 4952657b31 | ||
|  | 46fae1a761 | ||
|  | a09fb01d71 | ||
|  | 7cee3b7449 | ||
|  | 8263c48a1d | ||
|  | ed06533e60 | ||
|  | 7b7beb13a3 | ||
|  | c46007332a | ||
|  | 908d3b0ee5 | ||
|  | 8a031b1137 | ||
|  | 1aba9f807e | ||
|  | 4c49963988 | ||
|  | 821d40fe74 | ||
|  | 6ab1cf9325 | ||
|  | 076c0a48e9 | ||
|  | fde613a5c4 | ||
|  | 44ad0970be | ||
|  | b3f4d0ed8c | ||
|  | bfdd3468ea | ||
|  | f7decd80b6 | ||
|  | 7c2721d54d | ||
|  | 8907d0a9a7 | ||
|  | 6c8e6e9303 | ||
|  | 85c4e009f3 | ||
|  | 76802b5e38 | ||
|  | 9f2f388e5a | ||
|  | 729f53d84f | ||
|  | d2d7ab5d04 | ||
|  | 5107c7c23d | ||
|  | 4dbd1f1358 | ||
|  | 7996040f35 | ||
|  | 0055efb720 | ||
|  | dfa5eef20d | ||
|  | 3053acb4f3 | ||
|  | dea9892a85 | ||
|  | b9b6327707 | ||
|  | 84ae2964fd | ||
|  | 149b940f84 | ||
|  | 7226d8d4f7 | ||
|  | ad9b0cd4e3 | ||
|  | 484e640d43 | ||
|  | 5d6b5d9f10 | ||
|  | 0b771ce61a | ||
|  | 72e07d4e83 | ||
|  | 2252c29495 | ||
|  | 39c0bc6c47 | ||
|  | 8f1a516a2c | ||
|  | a6b8e88406 | ||
|  | c19b50619f | ||
|  | 3747d96b22 | ||
|  | 8b23a08fc4 | ||
|  | 3fdefb94e4 | ||
|  | 49592ebaf3 | ||
|  | f410dcb3f3 | ||
|  | bd27f61a03 | ||
|  | d703328114 | ||
|  | afe222cb16 | ||
|  | d0fd4dd4db | ||
|  | 3ba6b6f1ee | ||
|  | bc464e247f | ||
|  | c23f6d8d19 | ||
|  | 39d779edf0 | ||
|  | 0cb5362c6f | ||
|  | a43ca0db35 | ||
|  | 9089bf6535 | ||
|  | ef19a03efc | ||
|  | 85e1610627 | ||
|  | d16ae84d0b | ||
|  | 95f859cf5c | ||
|  | 578a5b3e69 | ||
|  | 25f7e3af31 | ||
|  | 86192b18d1 | ||
|  | c3144382c5 | ||
|  | 6bb9b7be04 | ||
|  | 8ee34fafa6 | ||
|  | 312171fa59 | ||
|  | a8dbfb0569 | ||
|  | b09b4b4433 | ||
|  | 45bd24ada0 | ||
|  | c3a2f7717b | ||
|  | 70e6c3b2f6 | ||
|  | d1b889aa61 | ||
|  | f65c65569a | ||
|  | 1139caa83f | ||
|  | d613c3c187 | ||
|  | 36f8b165cf | ||
|  | d6e8b34942 | ||
|  | 4c4ab25d0e | ||
|  | 9ff34d90f4 | ||
|  | 9593e0f7fe | ||
|  | 1293d8b69e | ||
|  | 3e0055737e | ||
|  | ba7fbc4032 | ||
|  | c36d7b4972 | ||
|  | 1c0b5bb02b | ||
|  | 0dece80b5d | ||
|  | e3b4aebf1a | ||
|  | 2e20191c01 | ||
|  | 59718e132b | ||
|  | 4d070fbfe3 | ||
|  | 723ee88043 | ||
|  | 65ba9ee6e7 | ||
|  | fcc750784a | ||
|  | 3787d094ec | ||
|  | 4b4ea4a103 | ||
|  | af0cf0d40a | ||
|  | eecea93b3b | ||
|  | ac4948c4b1 | ||
|  | 5e34c1b6b8 | ||
|  | 05e31d7594 | ||
|  | f4097290c2 | ||
|  | 9da481b060 | ||
|  | 79002d6962 | ||
|  | dbd9282efc | ||
|  | b32538f3c8 | ||
|  | e7618bb32e | ||
|  | aacf26f05d | ||
|  | 265bc80d44 | ||
|  | 10c0e687f5 | ||
|  | a9d4fe0b41 | ||
|  | 5cd15147eb | ||
|  | c62db6665a | ||
|  | fabcb261dc | ||
|  | 45cf28e0eb | ||
|  | 5b35c88be2 | ||
|  | 7f03f5d02f | ||
|  | b98d5b790a | ||
|  | 5c74044e62 | ||
|  | 992a99d792 | ||
|  | 41075356e2 | ||
|  | 850a394eb5 | ||
|  | 244721a6f8 | ||
|  | d59db504a3 | ||
|  | c90e122eb2 | ||
|  | 4c6dc597f4 | ||
|  | b4f6dee954 | ||
|  | 2685e9087e | ||
|  | 376b26c1e4 | ||
|  | 7061537ff5 | ||
|  | 2f2390b5aa | ||
|  | 99de8f1c5c | ||
|  | af61bbc3e2 | ||
|  | 56d88f23ef | ||
|  | 4bff44377a | ||
|  | 7463edaa1b | ||
|  | e92e06a5f4 | ||
|  | 4cbe5068a9 | ||
|  | 38b2302b59 | ||
|  | bce0702745 | ||
|  | d447e81abd | ||
|  | 6592745e53 | ||
|  | e87a3cffd4 | ||
|  | fa0b6e8a08 | ||
|  | 074b4c3500 | ||
|  | 5968c9a391 | ||
|  | 72bc5f8d7b | ||
|  | 0a0d81cd5a | ||
|  | 75e9c3678b | ||
|  | aebe8a64a2 | ||
|  | 1aacf437b5 | ||
|  | 7e8e3fdd39 | ||
|  | b8ae283049 | ||
|  | 6621e54952 | ||
|  | e03a403a51 | ||
|  | ba43b3e6b8 | ||
|  | b4a2d1395c | ||
|  | f5ae8d0f79 | ||
|  | 5f1c210746 | ||
|  | f6c2f6e896 | ||
|  | 6547560e52 | ||
|  | a167e3849b | ||
|  | f22c23cb4c | ||
|  | a07c99d778 | ||
|  | 1c605d58e3 | ||
|  | 6a79ce9eb1 | ||
|  | 465c38f03c | ||
|  | be05d51e07 | ||
|  | 9bc470027e | ||
|  | 335c633884 | ||
|  | cd26f11818 | ||
|  | abe47b6ed8 | ||
|  | 61659faeaa | ||
|  | 71adb964e5 | ||
|  | e599e65087 | ||
|  | 7efee9b52b | ||
|  | 079dc671e1 | ||
|  | a32a7d1374 | ||
|  | 467cd5450f | ||
|  | 1580874a55 | ||
|  | 15f7cbe8c1 | ||
|  | 428b6145fa | ||
|  | 3ad0b31db8 | ||
|  | 8d4d5d1f46 | ||
|  | 4c8a68c6a4 | ||
|  | 0b4b6f4aec | ||
|  | bb4db6b382 | ||
|  | 94b1c37fb2 | ||
|  | cf6f6c5c15 | ||
|  | f541986333 | ||
|  | 44513d6912 | ||
|  | b20cbcd5fe | ||
|  | 1c5972f7b0 | ||
|  | 28947bb3c4 | ||
|  | 865c47a1ac | ||
|  | 3821679efd | ||
|  | 506b4da6c3 | ||
|  | 10f637d2cf | ||
|  | 0bab7c88f0 | ||
|  | 78c612ca17 | ||
|  | e1c4035812 | ||
|  | eb6d6c8033 | ||
|  | 7bf88565ce | ||
|  | ee10155296 | ||
|  | cc49140f6f | ||
|  | 3e846f89a1 | ||
|  | 5782cab2a0 | ||
|  | 8c511e2b76 | ||
|  | ec72fb3baf | ||
|  | bab1440f5c | ||
|  | 60c1da6a66 | ||
|  | a849b3f2e4 | ||
|  | dbe3c5c3f8 | ||
|  | 60cf6b3cfd | ||
|  | 5044aac337 | ||
|  | 36e0cb29c0 | ||
|  | c0b4dd65da | ||
|  | d061ea232b | ||
|  | 49feca4ddf | ||
|  | 46b1c57bf4 | ||
|  | eaf1482182 | ||
|  | d3418550eb | ||
|  | 3ffa9e2751 | ||
|  | c697dd78f0 | ||
|  | 7dac791290 | ||
|  | cde2faeda6 | ||
|  | 69f520428d | ||
|  | 80c84ddd75 | ||
|  | fca8a58b36 | ||
|  | 33084899d0 | ||
|  | 7b381a8b6b | ||
|  | 9c75689a8d | ||
|  | 0ee40e8556 | ||
|  | 8b45377b89 | ||
|  | f6fb368d88 | ||
|  | 183a5379de | ||
|  | 912791d3d4 | ||
|  | 163a61dd63 | ||
|  | 207d462dbf | ||
|  | 33281b9d89 | ||
|  | 389979923e | ||
|  | 067174965e | ||
|  | 286259c83b | ||
|  | e1aa3e5a7f | ||
|  | 78e1c2851a | ||
|  | 0869213c55 | ||
|  | f3fe16215a | ||
|  | ec353ce663 | ||
|  | b7ff5ef9dd | ||
|  | 3b26e0a7c5 | ||
|  | 6d464557a0 | ||
|  | a776bec46a | ||
|  | a2da51c30b | ||
|  | 8067bf548a | ||
|  | 62b0645ed0 | ||
|  | 39a94874ae | ||
|  | e15d6717a1 | ||
|  | 37ef46e7bb | ||
|  | 70c09b3031 | ||
|  | 9378fbb0df | ||
|  | 2118b9c0cd | ||
|  | d0c53de250 | ||
|  | d98507eab0 | ||
|  | 760c75103e | ||
|  | 4407fd2f1f | ||
|  | 7fcd243be0 | ||
|  | 3165e9d82e | ||
|  | 6656a08c60 | ||
|  | 76661c0b51 | ||
|  | 3bb496f9ae | ||
|  | 45be1c19df | ||
|  | a301964bd0 | ||
|  | eea6858121 | ||
|  | 2a320fdf56 | ||
|  | 4695296ef2 | ||
|  | 0fdbbeca1d | ||
|  | 34cc39ad65 | ||
|  | 3d0c832a21 | ||
|  | 1acdab9448 | ||
|  | 93e85c5c4a | ||
|  | ab98189d25 | ||
|  | cd0fb7624b | ||
|  | bae38497bb | ||
|  | 29921bfa8d | ||
|  | 2712702461 | ||
|  | a3fa9440d1 | ||
|  | 6419b0e619 | ||
|  | 58e5b6e3f1 | ||
|  | 682c3d8079 | ||
|  | da3d65c18f | ||
|  | ece3a05504 | ||
|  | 927697b0f0 | ||
|  | 74dfc80b0f | ||
|  | a7f229bc4b | ||
|  | 89bec2919f | ||
|  | 78eaecb29e | ||
|  | d410aea856 | ||
|  | 6b1eef572b | ||
|  | 719f5d79c2 | ||
|  | 48737a32a7 | ||
|  | 53f05efb2d | ||
|  | 0e73ba4b3e | ||
|  | f0f9d5a6af | ||
|  | 03501df9e5 | ||
|  | dd6f85d4db | ||
|  | 1804ea6849 | ||
|  | c8657e08f4 | ||
|  | a942e1319b | ||
|  | 9e0a56b4f0 | ||
|  | 9abc020818 | ||
|  | 2dade8d353 | ||
|  | 1100dc6993 | ||
|  | f212b18511 | ||
|  | a6ca69550f | ||
|  | 2452641844 | ||
|  | c82af4b814 | ||
|  | fdef914137 | 
							
								
								
									
										6
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | [*] | ||||||
|  | charset = utf-8 | ||||||
|  | indent_style = tab | ||||||
|  | indent_size = 4 | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,5 +1,13 @@ | |||||||
| language: objective-c | # language: objective-c | ||||||
| osx_image: xcode8.2 | # osx_image: xcode8.2 | ||||||
| xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | # xcode_project: OSBindings/Mac/Clock Signal.xcodeproj | ||||||
| xcode_scheme: Clock Signal | # xcode_scheme: Clock Signal | ||||||
| xcode_sdk: macosx10.12 | # xcode_sdk: macosx10.12 | ||||||
|  |  | ||||||
|  | language: cpp | ||||||
|  | before_install: | ||||||
|  | 	- sudo apt-get install libsdl2-dev | ||||||
|  | script: cd OSBindings/SDL && scons | ||||||
|  | compiler: | ||||||
|  | 	- clang | ||||||
|  | 	- gcc | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								Activity/Observer.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Activity/Observer.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | // | ||||||
|  | //  ActivityObserver.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/05/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ActivityObserver_h | ||||||
|  | #define ActivityObserver_h | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Activity { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a purely virtual base class for anybody that wants to receive notifications of | ||||||
|  | 	'activity': any feedback from an emulated system which a user could perceive other than | ||||||
|  | 	through the machine's native audio and video outputs. | ||||||
|  |  | ||||||
|  | 	So: status LEDs, drive activity, etc. A receiver may choose to make appropriate noises | ||||||
|  | 	and/or to show or unshow status indicators. | ||||||
|  | */ | ||||||
|  | class Observer { | ||||||
|  | 	public: | ||||||
|  | 		/// Announces to the receiver that there is an LED of name @c name. | ||||||
|  | 		virtual void register_led(const std::string &name) {} | ||||||
|  |  | ||||||
|  | 		/// Announces to the receiver that there is a drive of name @c name. | ||||||
|  | 		virtual void register_drive(const std::string &name) {} | ||||||
|  |  | ||||||
|  | 		/// Informs the receiver of the new state of the LED with name @c name. | ||||||
|  | 		virtual void set_led_status(const std::string &name, bool lit) {} | ||||||
|  |  | ||||||
|  | 		enum class DriveEvent { | ||||||
|  | 			StepNormal, | ||||||
|  | 			StepBelowZero, | ||||||
|  | 			StepBeyondMaximum | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Informs the receiver that the named event just occurred for the drive with name @c name. | ||||||
|  | 		virtual void announce_drive_event(const std::string &name, DriveEvent event) {} | ||||||
|  |  | ||||||
|  | 		/// Informs the receiver of the motor-on status of the drive with name @c name. | ||||||
|  | 		virtual void set_drive_motor_status(const std::string &name, bool is_on) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ActivityObserver_h */ | ||||||
							
								
								
									
										24
									
								
								Activity/Source.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Activity/Source.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // | ||||||
|  | //  ActivitySource.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 07/05/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ActivitySource_h | ||||||
|  | #define ActivitySource_h | ||||||
|  |  | ||||||
|  | #include "Observer.hpp" | ||||||
|  |  | ||||||
|  | namespace Activity { | ||||||
|  |  | ||||||
|  | class Source { | ||||||
|  | 	public: | ||||||
|  | 		virtual void set_activity_observer(Observer *observer) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* ActivitySource_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 21/01/2018. | //  Created by Thomas Harte on 21/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "ConfidenceCounter.hpp" | #include "ConfidenceCounter.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 21/01/2018. | //  Created by Thomas Harte on 21/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef ConfidenceCounter_hpp | #ifndef ConfidenceCounter_hpp | ||||||
| @@ -24,10 +24,10 @@ class ConfidenceCounter: public ConfidenceSource { | |||||||
| 		/*! @returns The computed probability, based on the history of events. */ | 		/*! @returns The computed probability, based on the history of events. */ | ||||||
| 		float get_confidence() override; | 		float get_confidence() override; | ||||||
|  |  | ||||||
| 		/*! Records an event that implies this is the appropriate class — pushes probability up towards 1.0. */ | 		/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */ | ||||||
| 		void add_hit(); | 		void add_hit(); | ||||||
|  |  | ||||||
| 		/*! Records an event that implies this is not the appropriate class — pushes probability down towards 0.0. */ | 		/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */ | ||||||
| 		void add_miss(); | 		void add_miss(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 21/01/2018. | //  Created by Thomas Harte on 21/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef ConfidenceSource_hpp | #ifndef ConfidenceSource_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 21/01/2018. | //  Created by Thomas Harte on 21/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "ConfidenceSummary.hpp" | #include "ConfidenceSummary.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 21/01/2018. | //  Created by Thomas Harte on 21/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef ConfidenceSummary_hpp | #ifndef ConfidenceSummary_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 29/01/2018. | //  Created by Thomas Harte on 29/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "MultiCRTMachine.hpp" | #include "MultiCRTMachine.hpp" | ||||||
| @@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { | |||||||
| 	return speaker_; | 	return speaker_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiCRTMachine::run_for(const Cycles cycles) { | void MultiCRTMachine::run_for(Time::Seconds duration) { | ||||||
| 	perform_parallel([=](::CRTMachine::Machine *machine) { | 	perform_parallel([=](::CRTMachine::Machine *machine) { | ||||||
| 		if(machine->get_confidence() >= 0.01f) machine->run_for(cycles); | 		if(machine->get_confidence() >= 0.01f) machine->run_for(duration); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	if(delegate_) delegate_->multi_crt_did_run_machines(); | 	if(delegate_) delegate_->multi_crt_did_run_machines(); | ||||||
| } | } | ||||||
|  |  | ||||||
| double MultiCRTMachine::get_clock_rate() { |  | ||||||
| 	// TODO: something smarter than this? Not all clock rates will necessarily be the same. |  | ||||||
| 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); |  | ||||||
| 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); |  | ||||||
| 	return crt_machine ? crt_machine->get_clock_rate() : 0.0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MultiCRTMachine::get_clock_is_unlimited() { |  | ||||||
| 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); |  | ||||||
| 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); |  | ||||||
| 	return crt_machine ? crt_machine->get_clock_is_unlimited() : false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiCRTMachine::did_change_machine_order() { | void MultiCRTMachine::did_change_machine_order() { | ||||||
| 	if(speaker_) { | 	if(speaker_) { | ||||||
| 		speaker_->set_new_front_machine(machines_.front().get()); | 		speaker_->set_new_front_machine(machines_.front().get()); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) { |  | ||||||
| 	// TODO:  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) { |  | ||||||
| 	// TODO: consider passing along. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) { |  | ||||||
| 	// TODO: consider passing along. |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 29/01/2018. | //  Created by Thomas Harte on 29/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef MultiCRTMachine_hpp | #ifndef MultiCRTMachine_hpp | ||||||
| @@ -29,7 +29,7 @@ namespace Dynamic { | |||||||
| 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | ||||||
| 	if the order of machines changes. | 	if the order of machines changes. | ||||||
| */ | */ | ||||||
| class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate { | class MultiCRTMachine: public CRTMachine::Machine { | ||||||
| 	public: | 	public: | ||||||
| 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); | 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); | ||||||
|  |  | ||||||
| @@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D | |||||||
| 		void close_output() override; | 		void close_output() override; | ||||||
| 		Outputs::CRT::CRT *get_crt() override; | 		Outputs::CRT::CRT *get_crt() override; | ||||||
| 		Outputs::Speaker::Speaker *get_speaker() override; | 		Outputs::Speaker::Speaker *get_speaker() override; | ||||||
| 		void run_for(const Cycles cycles) override; | 		void run_for(Time::Seconds duration) override; | ||||||
| 		double get_clock_rate() override; |  | ||||||
| 		bool get_clock_is_unlimited() override; |  | ||||||
| 		void set_delegate(::CRTMachine::Machine::Delegate *delegate) override; |  | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		// CRTMachine::Machine::Delegate | 		void run_for(const Cycles cycles) override {} | ||||||
| 		void machine_did_change_clock_rate(Machine *machine) override; |  | ||||||
| 		void machine_did_change_clock_is_unlimited(Machine *machine) override; |  | ||||||
|  |  | ||||||
| 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | ||||||
| 		std::mutex &machines_mutex_; | 		std::mutex &machines_mutex_; | ||||||
| 		std::vector<Concurrency::AsyncTaskQueue> queues_; | 		std::vector<Concurrency::AsyncTaskQueue> queues_; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 09/02/2018. | //  Created by Thomas Harte on 09/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "MultiConfigurable.hpp" | #include "MultiConfigurable.hpp" | ||||||
| @@ -16,7 +16,7 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine | |||||||
| 	for(const auto &machine: machines) { | 	for(const auto &machine: machines) { | ||||||
| 		Configurable::Device *device = machine->configurable_device(); | 		Configurable::Device *device = machine->configurable_device(); | ||||||
| 		if(device) devices_.push_back(device); | 		if(device) devices_.push_back(device); | ||||||
|     } | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() { | std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 09/02/2018. | //  Created by Thomas Harte on 09/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef MultiConfigurable_hpp | #ifndef MultiConfigurable_hpp | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| // |  | ||||||
| //  MultiConfigurationTarget.cpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 29/01/2018. |  | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "MultiConfigurationTarget.hpp" |  | ||||||
|  |  | ||||||
| using namespace Analyser::Dynamic; |  | ||||||
|  |  | ||||||
| MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { |  | ||||||
| 	for(const auto &machine: machines) { |  | ||||||
| 		ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); |  | ||||||
| 		if(configuration_target) targets_.push_back(configuration_target); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target &target) { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { |  | ||||||
| 	bool inserted = false; |  | ||||||
| 	for(const auto &target : targets_) { |  | ||||||
| 		inserted |= target->insert_media(media); |  | ||||||
| 	} |  | ||||||
| 	return inserted; |  | ||||||
| } |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| // |  | ||||||
| //  MultiConfigurationTarget.hpp |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 29/01/2018. |  | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef MultiConfigurationTarget_hpp |  | ||||||
| #define MultiConfigurationTarget_hpp |  | ||||||
|  |  | ||||||
| #include "../../../../Machines/ConfigurationTarget.hpp" |  | ||||||
| #include "../../../../Machines/DynamicMachine.hpp" |  | ||||||
|  |  | ||||||
| #include <memory> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace Analyser { |  | ||||||
| namespace Dynamic { |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	Provides a class that multiplexes the configuration target interface to multiple machines. |  | ||||||
|  |  | ||||||
| 	Makes a static internal copy of the list of machines; makes no guarantees about the |  | ||||||
| 	order of delivered messages. |  | ||||||
| */ |  | ||||||
| struct MultiConfigurationTarget: public ConfigurationTarget::Machine { |  | ||||||
| 	public: |  | ||||||
| 		MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); |  | ||||||
|  |  | ||||||
| 		// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target &target) override; |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override; |  | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		std::vector<ConfigurationTarget::Machine *> targets_; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* MultiConfigurationTarget_hpp */ |  | ||||||
| @@ -3,18 +3,82 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 09/02/2018. | //  Created by Thomas Harte on 09/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "MultiJoystickMachine.hpp" | #include "MultiJoystickMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
| using namespace Analyser::Dynamic; | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | class MultiJoystick: public Inputs::Joystick { | ||||||
|  | 	public: | ||||||
|  | 		MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) { | ||||||
|  | 			for(const auto &machine: machines) { | ||||||
|  | 				const auto &joysticks = machine->get_joysticks(); | ||||||
|  | 				if(joysticks.size() >= index) { | ||||||
|  | 					joysticks_.push_back(joysticks[index].get()); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<Input> &get_inputs() override { | ||||||
|  | 			if(inputs.empty()) { | ||||||
|  | 				for(const auto &joystick: joysticks_) { | ||||||
|  | 					std::vector<Input> joystick_inputs = joystick->get_inputs(); | ||||||
|  | 					for(const auto &input: joystick_inputs) { | ||||||
|  | 						if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { | ||||||
|  | 							inputs.push_back(input); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return inputs; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_input(const Input &digital_input, bool is_active) override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->set_input(digital_input, is_active); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_input(const Input &digital_input, float value) override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->set_input(digital_input, value); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void reset_all_inputs() override { | ||||||
|  | 			for(const auto &joystick: joysticks_) { | ||||||
|  | 				joystick->reset_all_inputs(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<Input> inputs; | ||||||
|  | 		std::vector<Inputs::Joystick *> joysticks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	std::size_t total_joysticks = 0; | ||||||
|  | 	std::vector<JoystickMachine::Machine *> joystick_machines; | ||||||
| 	for(const auto &machine: machines) { | 	for(const auto &machine: machines) { | ||||||
| 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | ||||||
| 		if(joystick_machine) machines_.push_back(joystick_machine); | 		if(joystick_machine) { | ||||||
|     } | 			joystick_machines.push_back(joystick_machine); | ||||||
|  | 			total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for(std::size_t index = 0; index < total_joysticks; ++index) { | ||||||
|  | 		joysticks_.emplace_back(new MultiJoystick(joystick_machines, index)); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { | std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 09/02/2018. | //  Created by Thomas Harte on 09/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef MultiJoystickMachine_hpp | #ifndef MultiJoystickMachine_hpp | ||||||
| @@ -31,7 +31,6 @@ class MultiJoystickMachine: public JoystickMachine::Machine { | |||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::vector<JoystickMachine::Machine *> machines_; |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 09/02/2018. | //  Created by Thomas Harte on 09/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "MultiKeyboardMachine.hpp" | #include "MultiKeyboardMachine.hpp" | ||||||
| @@ -14,30 +14,30 @@ MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::M | |||||||
| 	for(const auto &machine: machines) { | 	for(const auto &machine: machines) { | ||||||
| 		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); | 		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); | ||||||
| 		if(keyboard_machine) machines_.push_back(keyboard_machine); | 		if(keyboard_machine) machines_.push_back(keyboard_machine); | ||||||
|     } | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiKeyboardMachine::clear_all_keys() { | void MultiKeyboardMachine::clear_all_keys() { | ||||||
| 	for(const auto &machine: machines_) { | 	for(const auto &machine: machines_) { | ||||||
| 		machine->clear_all_keys(); | 		machine->clear_all_keys(); | ||||||
|     } | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) { | void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) { | ||||||
|     for(const auto &machine: machines_) { | 	for(const auto &machine: machines_) { | ||||||
|         machine->set_key_state(key, is_pressed); | 		machine->set_key_state(key, is_pressed); | ||||||
|     } | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiKeyboardMachine::type_string(const std::string &string) { | void MultiKeyboardMachine::type_string(const std::string &string) { | ||||||
|     for(const auto &machine: machines_) { | 	for(const auto &machine: machines_) { | ||||||
|         machine->type_string(string); | 		machine->type_string(string); | ||||||
|     } | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | ||||||
|     for(const auto &machine: machines_) { | 	for(const auto &machine: machines_) { | ||||||
| 		uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key); | 		uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key); | ||||||
| 		if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed); | 		if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed); | ||||||
|     } | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 09/02/2018. | //  Created by Thomas Harte on 09/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef MultiKeyboardMachine_hpp | #ifndef MultiKeyboardMachine_hpp | ||||||
|   | |||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  MultiMediaTarget.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiMediaTarget.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		MediaTarget::Machine *media_target = machine->media_target(); | ||||||
|  | 		if(media_target) targets_.push_back(media_target); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) { | ||||||
|  | 	bool inserted = false; | ||||||
|  | 	for(const auto &target : targets_) { | ||||||
|  | 		inserted |= target->insert_media(media); | ||||||
|  | 	} | ||||||
|  | 	return inserted; | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  MultiMediaTarget.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiMediaTarget_hpp | ||||||
|  | #define MultiMediaTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/MediaTarget.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the media target interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | struct MultiMediaTarget: public MediaTarget::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard MediaTarget::Machine interface; see there for documentation. | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<MediaTarget::Machine *> targets_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiMediaTarget_hpp */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 18/02/2018. | //  Created by Thomas Harte on 18/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "MultiSpeaker.hpp" | #include "MultiSpeaker.hpp" | ||||||
| @@ -48,13 +48,29 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | ||||||
| 	std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | 	if(!delegate_) return; | ||||||
| 	if(delegate_ && speaker == front_speaker_) { | 	{ | ||||||
| 		delegate_->speaker_did_complete_samples(this, buffer); | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		if(speaker != front_speaker_) return; | ||||||
| 	} | 	} | ||||||
|  | 	delegate_->speaker_did_complete_samples(this, buffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) { | ||||||
|  | 	if(!delegate_) return; | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		if(speaker != front_speaker_) return; | ||||||
|  | 	} | ||||||
|  | 	delegate_->speaker_did_change_input_clock(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { | void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { | ||||||
| 	std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | 	{ | ||||||
| 	front_speaker_ = machine->crt_machine()->get_speaker(); | 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 		front_speaker_ = machine->crt_machine()->get_speaker(); | ||||||
|  | 	} | ||||||
|  | 	if(delegate_) { | ||||||
|  | 		delegate_->speaker_did_change_input_clock(this); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 18/02/2018. | //  Created by Thomas Harte on 18/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef MultiSpeaker_hpp | #ifndef MultiSpeaker_hpp | ||||||
| @@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker: | |||||||
| 		void set_new_front_machine(::Machine::DynamicMachine *machine); | 		void set_new_front_machine(::Machine::DynamicMachine *machine); | ||||||
|  |  | ||||||
| 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | ||||||
| 		float get_ideal_clock_rate_in_range(float minimum, float maximum); | 		float get_ideal_clock_rate_in_range(float minimum, float maximum) override; | ||||||
| 		void set_output_rate(float cycles_per_second, int buffer_size); | 		void set_output_rate(float cycles_per_second, int buffer_size) override; | ||||||
| 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate); | 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer); | 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override; | ||||||
|  | 		void speaker_did_change_input_clock(Speaker *speaker) override; | ||||||
| 		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); | 		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); | ||||||
|  |  | ||||||
| 		std::vector<Outputs::Speaker::Speaker *> speakers_; | 		std::vector<Outputs::Speaker::Speaker *> speakers_; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 28/01/2018. | //  Created by Thomas Harte on 28/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "MultiMachine.hpp" | #include "MultiMachine.hpp" | ||||||
| @@ -15,18 +15,22 @@ using namespace Analyser::Dynamic; | |||||||
| MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | ||||||
| 	machines_(std::move(machines)), | 	machines_(std::move(machines)), | ||||||
| 	configurable_(machines_), | 	configurable_(machines_), | ||||||
| 	configuration_target_(machines_), |  | ||||||
| 	crt_machine_(machines_, machines_mutex_), | 	crt_machine_(machines_, machines_mutex_), | ||||||
| 	joystick_machine_(machines), | 	joystick_machine_(machines), | ||||||
| 	keyboard_machine_(machines_) { | 	keyboard_machine_(machines_), | ||||||
|  | 	media_target_(machines_) { | ||||||
| 	crt_machine_.set_delegate(this); | 	crt_machine_.set_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| ConfigurationTarget::Machine *MultiMachine::configuration_target() { | Activity::Source *MultiMachine::activity_source() { | ||||||
|  | 	return nullptr; // TODO | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MediaTarget::Machine *MultiMachine::media_target() { | ||||||
| 	if(has_picked_) { | 	if(has_picked_) { | ||||||
| 		return machines_.front()->configuration_target(); | 		return machines_.front()->media_target(); | ||||||
| 	} else { | 	} else { | ||||||
| 		return &configuration_target_; | 		return &media_target_; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -62,8 +66,15 @@ Configurable::Device *MultiMachine::configurable_device() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) { | ||||||
|  | 	return | ||||||
|  | 		(machines.front()->crt_machine()->get_confidence() > 0.9f) || | ||||||
|  | 		(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence()); | ||||||
|  | } | ||||||
|  |  | ||||||
| void MultiMachine::multi_crt_did_run_machines() { | void MultiMachine::multi_crt_did_run_machines() { | ||||||
| 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | #ifdef DEBUG | ||||||
| 	for(const auto &machine: machines_) { | 	for(const auto &machine: machines_) { | ||||||
| 		CRTMachine::Machine *crt = machine->crt_machine(); | 		CRTMachine::Machine *crt = machine->crt_machine(); | ||||||
| 		printf("%0.2f ", crt->get_confidence()); | 		printf("%0.2f ", crt->get_confidence()); | ||||||
| @@ -71,23 +82,21 @@ void MultiMachine::multi_crt_did_run_machines() { | |||||||
| 		printf("; "); | 		printf("; "); | ||||||
| 	} | 	} | ||||||
| 	printf("\n"); | 	printf("\n"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
| 	DynamicMachine *front = machines_.front().get(); | 	DynamicMachine *front = machines_.front().get(); | ||||||
| 	std::stable_sort(machines_.begin(), machines_.end(), | 	std::stable_sort(machines_.begin(), machines_.end(), | ||||||
|         [] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){ | 		[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){ | ||||||
| 		    CRTMachine::Machine *lhs_crt = lhs->crt_machine(); | 			CRTMachine::Machine *lhs_crt = lhs->crt_machine(); | ||||||
| 		    CRTMachine::Machine *rhs_crt = rhs->crt_machine(); | 			CRTMachine::Machine *rhs_crt = rhs->crt_machine(); | ||||||
| 		    return lhs_crt->get_confidence() > rhs_crt->get_confidence(); | 			return lhs_crt->get_confidence() > rhs_crt->get_confidence(); | ||||||
| 	    }); | 		}); | ||||||
|  |  | ||||||
| 	if(machines_.front().get() != front) { | 	if(machines_.front().get() != front) { | ||||||
| 		crt_machine_.did_change_machine_order(); | 		crt_machine_.did_change_machine_order(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if( | 	if(would_collapse(machines_)) { | ||||||
| 		(machines_.front()->crt_machine()->get_confidence() > 0.9f) || |  | ||||||
| 		(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence()) |  | ||||||
| 	) { |  | ||||||
| 		pick_first(); | 		pick_first(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 28/01/2018. | //  Created by Thomas Harte on 28/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef MultiMachine_hpp | #ifndef MultiMachine_hpp | ||||||
| @@ -12,10 +12,10 @@ | |||||||
| #include "../../../Machines/DynamicMachine.hpp" | #include "../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
| #include "Implementation/MultiConfigurable.hpp" | #include "Implementation/MultiConfigurable.hpp" | ||||||
| #include "Implementation/MultiConfigurationTarget.hpp" |  | ||||||
| #include "Implementation/MultiCRTMachine.hpp" | #include "Implementation/MultiCRTMachine.hpp" | ||||||
| #include "Implementation/MultiJoystickMachine.hpp" | #include "Implementation/MultiJoystickMachine.hpp" | ||||||
| #include "Implementation/MultiKeyboardMachine.hpp" | #include "Implementation/MultiKeyboardMachine.hpp" | ||||||
|  | #include "Implementation/MultiMediaTarget.hpp" | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| @@ -40,13 +40,22 @@ namespace Dynamic { | |||||||
| */ | */ | ||||||
| class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | ||||||
| 	public: | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in | ||||||
|  | 			requesting this class as a proxy. | ||||||
|  |  | ||||||
|  | 			@returns @c true if the multimachine would discard all but the first machine in this list; | ||||||
|  | 				@c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines); | ||||||
| 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | ||||||
|  |  | ||||||
| 		ConfigurationTarget::Machine *configuration_target() override; | 		Activity::Source *activity_source() override; | ||||||
|  | 		Configurable::Device *configurable_device() override; | ||||||
| 		CRTMachine::Machine *crt_machine() override; | 		CRTMachine::Machine *crt_machine() override; | ||||||
| 		JoystickMachine::Machine *joystick_machine() override; | 		JoystickMachine::Machine *joystick_machine() override; | ||||||
| 		KeyboardMachine::Machine *keyboard_machine() override; | 		KeyboardMachine::Machine *keyboard_machine() override; | ||||||
| 		Configurable::Device *configurable_device() override; | 		MediaTarget::Machine *media_target() override; | ||||||
| 		void *raw_pointer() override; | 		void *raw_pointer() override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| @@ -56,10 +65,10 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | |||||||
| 		std::mutex machines_mutex_; | 		std::mutex machines_mutex_; | ||||||
|  |  | ||||||
| 		MultiConfigurable configurable_; | 		MultiConfigurable configurable_; | ||||||
| 		MultiConfigurationTarget configuration_target_; |  | ||||||
| 		MultiCRTMachine crt_machine_; | 		MultiCRTMachine crt_machine_; | ||||||
| 		MultiJoystickMachine joystick_machine_; | 		MultiJoystickMachine joystick_machine_; | ||||||
| 		MultiKeyboardMachine keyboard_machine_; | 		MultiKeyboardMachine keyboard_machine_; | ||||||
|  | 		MultiMediaTarget media_target_; | ||||||
|  |  | ||||||
| 		void pick_first(); | 		void pick_first(); | ||||||
| 		bool has_picked_ = false; | 		bool has_picked_ = false; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 24/01/2018. | //  Created by Thomas Harte on 24/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef Machines_h | #ifndef Machines_h | ||||||
| @@ -13,6 +13,7 @@ namespace Analyser { | |||||||
|  |  | ||||||
| enum class Machine { | enum class Machine { | ||||||
| 	AmstradCPC, | 	AmstradCPC, | ||||||
|  | 	AppleII, | ||||||
| 	Atari2600, | 	Atari2600, | ||||||
| 	ColecoVision, | 	ColecoVision, | ||||||
| 	Electron, | 	Electron, | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 18/09/2016. | //  Created by Thomas Harte on 18/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 18/09/2016. | //  Created by Thomas Harte on 18/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Acorn_Disk_hpp | #ifndef StaticAnalyser_Acorn_Disk_hpp | ||||||
| @@ -16,7 +16,7 @@ namespace Analyser { | |||||||
| namespace Static { | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
|  |  | ||||||
| /// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option. | /// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option. | ||||||
| struct Catalogue { | struct Catalogue { | ||||||
| 	std::string name; | 	std::string name; | ||||||
| 	std::vector<File> files; | 	std::vector<File> files; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 18/09/2016. | //  Created by Thomas Harte on 18/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Acorn_File_hpp | #ifndef StaticAnalyser_Acorn_File_hpp | ||||||
|   | |||||||
| @@ -3,13 +3,14 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 29/08/2016. | //  Created by Thomas Harte on 29/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Acorn; | using namespace Analyser::Static::Acorn; | ||||||
|  |  | ||||||
| @@ -49,32 +50,32 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 		//		1/(2^32) * | 		//		1/(2^32) * | ||||||
| 		//		( ((2^24)-1)/(2^24)*(1/4)		+		1/(2^24)	) * | 		//		( ((2^24)-1)/(2^24)*(1/4)		+		1/(2^24)	) * | ||||||
| 		//		1/4 | 		//		1/4 | ||||||
| 		//	= something very improbable — around 1/16th of 1 in 2^32, but not exactly. | 		//	= something very improbable, around 1/16th of 1 in 2^32, but not exactly. | ||||||
| 		acorn_cartridges.push_back(cartridge); | 		acorn_cartridges.push_back(cartridge); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return acorn_cartridges; | 	return acorn_cartridges; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::Electron; | 	target->machine = Machine::Electron; | ||||||
| 	target->confidence = 1.0; // TODO: a proper estimation | 	target->confidence = 0.5; // TODO: a proper estimation | ||||||
| 	target->acorn.has_dfs = false; | 	target->has_dfs = false; | ||||||
| 	target->acorn.has_adfs = false; | 	target->has_adfs = false; | ||||||
| 	target->acorn.should_shift_restart = false; | 	target->should_shift_restart = false; | ||||||
|  |  | ||||||
| 	// strip out inappropriate cartridges | 	// strip out inappropriate cartridges | ||||||
| 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | ||||||
|  |  | ||||||
| 	// if there are any tapes, attempt to get data from the first | 	// if there are any tapes, attempt to get data from the first | ||||||
| 	if(media.tapes.size() > 0) { | 	if(!media.tapes.empty()) { | ||||||
| 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | ||||||
| 		std::vector<File> files = GetFiles(tape); | 		std::vector<File> files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
|  |  | ||||||
| 		// continue if there are any files | 		// continue if there are any files | ||||||
| 		if(files.size()) { | 		if(!files.empty()) { | ||||||
| 			bool is_basic = true; | 			bool is_basic = true; | ||||||
|  |  | ||||||
| 			// protected files are always for *RUNning only | 			// protected files are always for *RUNning only | ||||||
| @@ -102,24 +103,27 @@ void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::un | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(media.disks.size() > 0) { | 	if(!media.disks.empty()) { | ||||||
| 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | ||||||
| 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | ||||||
| 		dfs_catalogue = GetDFSCatalogue(disk); | 		dfs_catalogue = GetDFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue || adfs_catalogue) { | 		if(dfs_catalogue || adfs_catalogue) { | ||||||
| 			target->media.disks = media.disks; | 			target->media.disks = media.disks; | ||||||
| 			target->acorn.has_dfs = !!dfs_catalogue; | 			target->has_dfs = !!dfs_catalogue; | ||||||
| 			target->acorn.has_adfs = !!adfs_catalogue; | 			target->has_adfs = !!adfs_catalogue; | ||||||
|  |  | ||||||
| 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | ||||||
| 			if(bootOption != Catalogue::BootOption::None) | 			if(bootOption != Catalogue::BootOption::None) | ||||||
| 				target->acorn.should_shift_restart = true; | 				target->should_shift_restart = true; | ||||||
| 			else | 			else | ||||||
| 				target->loading_command = "*CAT\n"; | 				target->loading_command = "*CAT\n"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | 	TargetList targets; | ||||||
| 		destination.push_back(std::move(target)); | 	if(!target->media.empty()) { | ||||||
|  | 		targets.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | 	return targets; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 29/08/2016. | //  Created by Thomas Harte on 29/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp | #ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_Acorn_StaticAnalyser_hpp | #define StaticAnalyser_Acorn_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 29/08/2016. | //  Created by Thomas Harte on 29/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 29/08/2016. | //  Created by Thomas Harte on 29/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Acorn_Tape_hpp | #ifndef StaticAnalyser_Acorn_Tape_hpp | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Analyser/Static/Acorn/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Acorn_Target_h | ||||||
|  | #define Analyser_Static_Acorn_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Acorn { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool has_adfs = false; | ||||||
|  | 	bool has_dfs = false; | ||||||
|  | 	bool should_shift_restart = false; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Acorn_Target_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 30/07/2017. | //  Created by Thomas Harte on 30/07/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| @@ -11,6 +11,8 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstring> | #include <cstring> | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../../../Storage/Disk/Parsers/CPM.hpp" | #include "../../../Storage/Disk/Parsers/CPM.hpp" | ||||||
| #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  |  | ||||||
| @@ -33,8 +35,8 @@ static bool is_implied_extension(const std::string &extension) { | |||||||
|  |  | ||||||
| static void right_trim(std::string &string) { | static void right_trim(std::string &string) { | ||||||
| 	string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) { | 	string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) { | ||||||
|         return !std::isspace(ch); | 		return !std::isspace(ch); | ||||||
|     }).base(), string.end()); | 	}).base(), string.end()); | ||||||
| } | } | ||||||
|  |  | ||||||
| static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { | static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { | ||||||
| @@ -58,18 +60,18 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { | |||||||
|  |  | ||||||
| static void InspectCatalogue( | static void InspectCatalogue( | ||||||
| 	const Storage::Disk::CPM::Catalogue &catalogue, | 	const Storage::Disk::CPM::Catalogue &catalogue, | ||||||
| 	const std::unique_ptr<Analyser::Static::Target> &target) { | 	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) { | ||||||
|  |  | ||||||
| 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | ||||||
| 	candidate_files.reserve(catalogue.files.size()); | 	candidate_files.reserve(catalogue.files.size()); | ||||||
| 	for(auto &file : catalogue.files) { | 	for(const auto &file : catalogue.files) { | ||||||
| 		candidate_files.push_back(&file); | 		candidate_files.push_back(&file); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Remove all files with untypable characters. | 	// Remove all files with untypable characters. | ||||||
| 	candidate_files.erase( | 	candidate_files.erase( | ||||||
| 		std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) { | 		std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) { | ||||||
| 			for(auto c : file->name + file->type) { | 			for(const auto c : file->name + file->type) { | ||||||
| 				if(c < 32) return true; | 				if(c < 32) return true; | ||||||
| 			} | 			} | ||||||
| 			return false; | 			return false; | ||||||
| @@ -78,7 +80,7 @@ static void InspectCatalogue( | |||||||
|  |  | ||||||
| 	// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files. | 	// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files. | ||||||
| 	bool are_all_system = true; | 	bool are_all_system = true; | ||||||
| 	for(auto file : candidate_files) { | 	for(const auto &file : candidate_files) { | ||||||
| 		if(!file->system) { | 		if(!file->system) { | ||||||
| 			are_all_system = false; | 			are_all_system = false; | ||||||
| 			break; | 			break; | ||||||
| @@ -135,13 +137,13 @@ static void InspectCatalogue( | |||||||
| 	std::map<std::string, int> name_counts; | 	std::map<std::string, int> name_counts; | ||||||
| 	std::map<std::string, std::size_t> indices_by_name; | 	std::map<std::string, std::size_t> indices_by_name; | ||||||
| 	std::size_t index = 0; | 	std::size_t index = 0; | ||||||
| 	for(auto file : candidate_files) { | 	for(const auto &file : candidate_files) { | ||||||
| 		name_counts[file->name]++; | 		name_counts[file->name]++; | ||||||
| 		indices_by_name[file->name] = index; | 		indices_by_name[file->name] = index; | ||||||
| 		index++; | 		index++; | ||||||
| 	} | 	} | ||||||
| 	if(name_counts.size() == 2) { | 	if(name_counts.size() == 2) { | ||||||
| 		for(auto &pair : name_counts) { | 		for(const auto &pair : name_counts) { | ||||||
| 			if(pair.second == 1) { | 			if(pair.second == 1) { | ||||||
| 				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]); | 				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]); | ||||||
| 				return; | 				return; | ||||||
| @@ -153,10 +155,10 @@ static void InspectCatalogue( | |||||||
| 	target->loading_command = "cat\n"; | 	target->loading_command = "cat\n"; | ||||||
| } | } | ||||||
|  |  | ||||||
| static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) { | static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) { | ||||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | ||||||
| 	if(boot_sector != nullptr && !boot_sector->samples.empty()) { | 	if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) { | ||||||
| 		// Check that the first 64 bytes of the sector aren't identical; if they are then probably | 		// Check that the first 64 bytes of the sector aren't identical; if they are then probably | ||||||
| 		// this disk was formatted and the filler byte never replaced. | 		// this disk was formatted and the filler byte never replaced. | ||||||
| 		bool matched = true; | 		bool matched = true; | ||||||
| @@ -177,24 +179,25 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	TargetList destination; | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::AmstradCPC; | 	target->machine = Machine::AmstradCPC; | ||||||
| 	target->confidence = 1.0; | 	target->confidence = 0.5; | ||||||
| 	target->media.disks = media.disks; |  | ||||||
| 	target->media.tapes = media.tapes; |  | ||||||
| 	target->media.cartridges = media.cartridges; |  | ||||||
|  |  | ||||||
| 	target->amstradcpc.model = AmstradCPCModel::CPC6128; | 	target->model = Target::Model::CPC6128; | ||||||
|  |  | ||||||
|  | 	if(!media.tapes.empty()) { | ||||||
|  | 		// TODO: which of these are actually potentially CPC tapes? | ||||||
|  | 		target->media.tapes = media.tapes; | ||||||
|  |  | ||||||
| 	if(!target->media.tapes.empty()) { |  | ||||||
| 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing | 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing | ||||||
| 		// enter and responding to the follow-on prompt to press a key, so just type for | 		// enter and responding to the follow-on prompt to press a key, so just type for | ||||||
| 		// a while. Yuck! | 		// a while. Yuck! | ||||||
| 		target->loading_command = "|tape\nrun\"\n1234567890"; | 		target->loading_command = "|tape\nrun\"\n1234567890"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!target->media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		Storage::Disk::CPM::ParameterBlock data_format; | 		Storage::Disk::CPM::ParameterBlock data_format; | ||||||
| 		data_format.sectors_per_track = 9; | 		data_format.sectors_per_track = 9; | ||||||
| 		data_format.tracks = 40; | 		data_format.tracks = 40; | ||||||
| @@ -203,26 +206,42 @@ void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<st | |||||||
| 		data_format.catalogue_allocation_bitmap = 0xc000; | 		data_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 		data_format.reserved_tracks = 0; | 		data_format.reserved_tracks = 0; | ||||||
|  |  | ||||||
| 		std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format); | 		Storage::Disk::CPM::ParameterBlock system_format; | ||||||
| 		if(data_catalogue) { | 		system_format.sectors_per_track = 9; | ||||||
| 			InspectCatalogue(*data_catalogue, target); | 		system_format.tracks = 40; | ||||||
| 		} else { | 		system_format.block_size = 1024; | ||||||
| 			if(!CheckBootSector(target->media.disks.front(), target)) { | 		system_format.first_sector = 0x41; | ||||||
| 				Storage::Disk::CPM::ParameterBlock system_format; | 		system_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 				system_format.sectors_per_track = 9; | 		system_format.reserved_tracks = 2; | ||||||
| 				system_format.tracks = 40; |  | ||||||
| 				system_format.block_size = 1024; |  | ||||||
| 				system_format.first_sector = 0x41; |  | ||||||
| 				system_format.catalogue_allocation_bitmap = 0xc000; |  | ||||||
| 				system_format.reserved_tracks = 2; |  | ||||||
|  |  | ||||||
| 				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format); | 		for(auto &disk: media.disks) { | ||||||
| 				if(system_catalogue) { | 			// Check for an ordinary catalogue. | ||||||
| 					InspectCatalogue(*system_catalogue, target); | 			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format); | ||||||
| 				} | 			if(data_catalogue) { | ||||||
|  | 				InspectCatalogue(*data_catalogue, target); | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Failing that check for a boot sector. | ||||||
|  | 			if(CheckBootSector(disk, target)) { | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Failing that check for a system catalogue. | ||||||
|  | 			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format); | ||||||
|  | 			if(system_catalogue) { | ||||||
|  | 				InspectCatalogue(*system_catalogue, target); | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 				continue; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	destination.push_back(std::move(target)); | 	// If any media survived, add the target. | ||||||
|  | 	if(!target->media.empty()) | ||||||
|  | 		destination.push_back(std::move(target)); | ||||||
|  |  | ||||||
|  | 	return destination; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,22 +3,24 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 30/07/2017. | //  Created by Thomas Harte on 30/07/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_AmstradCPC_StaticAnalyser_hpp | #ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_AmstradCPC_StaticAnalyser_hpp | #define Analyser_Static_AmstradCPC_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* StaticAnalyser_AmstradCPC_StaticAnalyser_hpp */ | #endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */ | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Analyser/Static/AmstradCPC/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_AmstradCPC_Target_h | ||||||
|  | #define Analyser_Static_AmstradCPC_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace AmstradCPC { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class Model { | ||||||
|  | 		CPC464, | ||||||
|  | 		CPC664, | ||||||
|  | 		CPC6128 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Model model = Model::CPC464; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_AmstradCPC_Target_h */ | ||||||
							
								
								
									
										23
									
								
								Analyser/Static/AppleII/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Analyser/Static/AppleII/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 14/04/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
|  | Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	auto target = std::unique_ptr<Target>(new Target); | ||||||
|  | 	target->machine = Machine::AppleII; | ||||||
|  | 	target->media = media; | ||||||
|  |  | ||||||
|  | 	if(!target->media.disks.empty()) | ||||||
|  | 		target->disk_controller = Target::DiskController::SixteenSector; | ||||||
|  |  | ||||||
|  | 	TargetList targets; | ||||||
|  | 	targets.push_back(std::move(target)); | ||||||
|  | 	return targets; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								Analyser/Static/AppleII/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Analyser/Static/AppleII/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 14/04/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_AppleII_StaticAnalyser_hpp | ||||||
|  | #define Analyser_Static_AppleII_StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace AppleII { | ||||||
|  |  | ||||||
|  | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */ | ||||||
							
								
								
									
										39
									
								
								Analyser/Static/AppleII/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Analyser/Static/AppleII/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/04/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Target_h | ||||||
|  | #define Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace AppleII { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class Model { | ||||||
|  | 		II, | ||||||
|  | 		IIplus, | ||||||
|  | 		IIe, | ||||||
|  | 		EnhancedIIe | ||||||
|  | 	}; | ||||||
|  | 	enum class DiskController { | ||||||
|  | 		None, | ||||||
|  | 		SixteenSector, | ||||||
|  | 		ThirteenSector | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Model model = Model::IIe; | ||||||
|  | 	DiskController disk_controller = DiskController::None; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Target_h */ | ||||||
| @@ -3,16 +3,18 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 15/09/2016. | //  Created by Thomas Harte on 15/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../Disassembler/6502.hpp" | #include "../Disassembler/6502.hpp" | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Atari; | using namespace Analyser::Static::Atari; | ||||||
|  |  | ||||||
| static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid | 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid | ||||||
| 	uint16_t entry_address, break_address; | 	uint16_t entry_address, break_address; | ||||||
|  |  | ||||||
| @@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, cons | |||||||
| 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses | 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses | ||||||
| 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it | 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it | ||||||
| 	// attempts to modify itself but it probably doesn't | 	// attempts to modify itself but it probably doesn't | ||||||
| 	if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid; | 	if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and | 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and | ||||||
| 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all | 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all | ||||||
| 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?) | 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?) | ||||||
| @@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons | |||||||
| 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | ||||||
| 		segment.data[0] == 0x78 | 		segment.data[0] == 0x78 | ||||||
| 	) { | 	) { | ||||||
| 		target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack; | 		target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack; | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// make an assumption that this is the Atari paging model | 	// make an assumption that this is the Atari paging model | ||||||
| 	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k; | 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k; | ||||||
|  |  | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons | |||||||
| 		tigervision_access_count += masked_address == 0x3f; | 		tigervision_access_count += masked_address == 0x3f; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros; | 	if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros; | ||||||
| 	else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; | 	else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is the Atari paging model | 	// make an assumption that this is the Atari paging model | ||||||
| 	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k; | 	target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k; | ||||||
|  |  | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, con | |||||||
| 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork; | 	if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is a Tigervision if there is a write to 3F | 	// make an assumption that this is a Tigervision if there is a write to 3F | ||||||
| 	target.atari.paging_model = | 	target.paging_model = | ||||||
| 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | ||||||
| 			Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy; | 			Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	if(segment.data.size() == 2048) { | 	if(segment.data.size() == 2048) { | ||||||
| 		DeterminePagingFor2kCartridge(target, segment); | 		DeterminePagingFor2kCartridge(target, segment); | ||||||
| 		return; | 		return; | ||||||
| @@ -138,16 +140,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const | |||||||
| 			DeterminePagingFor8kCartridge(target, segment, disassembly); | 			DeterminePagingFor8kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 10495: | 		case 10495: | ||||||
| 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2; | ||||||
| 		break; | 		break; | ||||||
| 		case 12288: | 		case 12288: | ||||||
| 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus; | ||||||
| 		break; | 		break; | ||||||
| 		case 16384: | 		case 16384: | ||||||
| 			DeterminePagingFor16kCartridge(target, segment, disassembly); | 			DeterminePagingFor16kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 32768: | 		case 32768: | ||||||
| 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k; | 			target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k; | ||||||
| 		break; | 		break; | ||||||
| 		case 65536: | 		case 65536: | ||||||
| 			DeterminePagingFor64kCartridge(target, segment, disassembly); | 			DeterminePagingFor64kCartridge(target, segment, disassembly); | ||||||
| @@ -159,8 +161,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const | |||||||
| 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM | 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM | ||||||
| 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the | 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the | ||||||
| 	// next 128 bytes. So check for that. | 	// next 128 bytes. So check for that. | ||||||
| 	if(	target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus && | 	if(	target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus && | ||||||
| 		target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) { | 		target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) { | ||||||
| 		bool has_superchip = true; | 		bool has_superchip = true; | ||||||
| 		for(std::size_t address = 0; address < 128; address++) { | 		for(std::size_t address = 0; address < 128; address++) { | ||||||
| 			if(segment.data[address] != segment.data[address+128]) { | 			if(segment.data[address] != segment.data[address+128]) { | ||||||
| @@ -168,24 +170,24 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		target.atari.uses_superchip = has_superchip; | 		target.uses_superchip = has_superchip; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// check for a Tigervision or Tigervision-esque scheme | 	// check for a Tigervision or Tigervision-esque scheme | ||||||
| 	if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) { | 	if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) { | ||||||
| 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | ||||||
| 		if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; | 		if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
| 	// TODO: sanity checking; is this image really for an Atari 2600. | 	// TODO: sanity checking; is this image really for an Atari 2600? | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target); | ||||||
| 	target->machine = Machine::Atari2600; | 	target->machine = Machine::Atari2600; | ||||||
| 	target->confidence = 1.0; | 	target->confidence = 0.5; | ||||||
| 	target->media.cartridges = media.cartridges; | 	target->media.cartridges = media.cartridges; | ||||||
| 	target->atari.paging_model = Atari2600PagingModel::None; | 	target->paging_model = Analyser::Static::Atari::Target::PagingModel::None; | ||||||
| 	target->atari.uses_superchip = false; | 	target->uses_superchip = false; | ||||||
|  |  | ||||||
| 	// try to figure out the paging scheme | 	// try to figure out the paging scheme | ||||||
| 	if(!media.cartridges.empty()) { | 	if(!media.cartridges.empty()) { | ||||||
| @@ -196,6 +198,7 @@ void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::un | |||||||
| 			DeterminePagingForCartridge(*target, segment); | 			DeterminePagingForCartridge(*target, segment); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	TargetList destinations; | ||||||
| 	destination.push_back(std::move(target)); | 	destinations.push_back(std::move(target)); | ||||||
|  | 	return destinations; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 15/09/2016. | //  Created by Thomas Harte on 15/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Atari_StaticAnalyser_hpp | #ifndef StaticAnalyser_Atari_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_Atari_StaticAnalyser_hpp | #define StaticAnalyser_Atari_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace Atari { | namespace Atari { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								Analyser/Static/Atari/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Analyser/Static/Atari/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Atari_Target_h | ||||||
|  | #define Analyser_Static_Atari_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Atari { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class PagingModel { | ||||||
|  | 		None, | ||||||
|  | 		CommaVid, | ||||||
|  | 		Atari8k, | ||||||
|  | 		Atari16k, | ||||||
|  | 		Atari32k, | ||||||
|  | 		ActivisionStack, | ||||||
|  | 		ParkerBros, | ||||||
|  | 		Tigervision, | ||||||
|  | 		CBSRamPlus, | ||||||
|  | 		MNetwork, | ||||||
|  | 		MegaBoy, | ||||||
|  | 		Pitfall2 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// TODO: shouldn't these be properties of the cartridge? | ||||||
|  | 	PagingModel paging_model = PagingModel::None; | ||||||
|  | 	bool uses_superchip = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Atari_Target_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 23/02/2018. | //  Created by Thomas Harte on 23/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| @@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
|  |  | ||||||
| 		// only one mapped item is allowed | 		// only one mapped item is allowed | ||||||
| 		if(segments.size() != 1) continue; | 		if(segments.size() != 1) continue; | ||||||
|  |  | ||||||
| 		// which must be 8, 12, 16, 24 or 32 kb in size |  | ||||||
| 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 		const std::size_t data_size = segment.data.size(); | 		const std::size_t data_size = segment.data.size(); | ||||||
| 		const std::size_t overflow = data_size&8191; |  | ||||||
| 		if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue; |  | ||||||
| 		if(data_size < 8192) continue; |  | ||||||
|  |  | ||||||
| 		// the two bytes that will be first must be 0xaa and 0x55, either way around | 		// the two bytes that will be first must be 0xaa and 0x55, either way around | ||||||
| 		auto *start = &segment.data[0]; | 		auto *start = &segment.data[0]; | ||||||
| @@ -34,29 +29,36 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 		if(start[0] == start[1]) continue; | 		if(start[0] == start[1]) continue; | ||||||
|  |  | ||||||
| 		// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. | 		// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. | ||||||
| 		if(!overflow) { |  | ||||||
| 			coleco_cartridges.push_back(cartridge); | 		// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if | ||||||
|  | 		// this image is within a short distance of 32kb. | ||||||
|  | 		std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  |  | ||||||
|  | 		size_t target_size; | ||||||
|  | 		if(data_size >= 32*1024 && data_size < 32*1024 + 512) { | ||||||
|  | 			target_size = 32 * 1024; | ||||||
| 		} else { | 		} else { | ||||||
| 			// Size down to a multiple of 8kb and apply the start address. | 			target_size = data_size + ((8192 - (data_size & 8191)) & 8191); | ||||||
| 			std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; |  | ||||||
|  |  | ||||||
| 			std::vector<uint8_t> truncated_data; |  | ||||||
| 			std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191; |  | ||||||
| 			truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); |  | ||||||
| 			output_segments.emplace_back(0x8000, truncated_data); |  | ||||||
|  |  | ||||||
| 			coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> truncated_data; | ||||||
|  | 		truncated_data = segment.data; | ||||||
|  | 		truncated_data.resize(target_size); | ||||||
|  | 		output_segments.emplace_back(0x8000, truncated_data); | ||||||
|  |  | ||||||
|  | 		coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return coleco_cartridges; | 	return coleco_cartridges; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	TargetList targets; | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::ColecoVision; | 	target->machine = Machine::ColecoVision; | ||||||
| 	target->confidence = 0.5; | 	target->confidence = 1.0f - 1.0f / 32768.0f; | ||||||
| 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | ||||||
| 	if(!target->media.empty()) | 	if(!target->media.empty()) | ||||||
| 		destination.push_back(std::move(target)); | 		targets.push_back(std::move(target)); | ||||||
|  | 	return targets; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 23/02/2018. | //  Created by Thomas Harte on 23/02/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp | #ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_Coleco_StaticAnalyser_hpp | #define StaticAnalyser_Coleco_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace Coleco { | namespace Coleco { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 13/09/2016. | //  Created by Thomas Harte on 13/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| @@ -45,9 +45,11 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
|  |  | ||||||
| 			if(difference) { | 			if(difference) { | ||||||
| 				int direction = difference < 0 ? -1 : 1; | 				int direction = difference < 0 ? -1 : 1; | ||||||
| 				difference *= 2 * direction; | 				difference *= direction; | ||||||
|  |  | ||||||
| 				for(int c = 0; c < difference; c++) get_drive().step(direction); | 				for(int c = 0; c < difference; c++) { | ||||||
|  | 					get_drive().step(Storage::Disk::HeadPosition(direction)); | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				unsigned int zone = 3; | 				unsigned int zone = 3; | ||||||
| 				if(track >= 18) zone = 2; | 				if(track >= 18) zone = 2; | ||||||
| @@ -71,19 +73,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 			bit_count_++; | 			bit_count_++; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		unsigned int proceed_to_next_block() { | 		unsigned int proceed_to_next_block(int max_index_count) { | ||||||
| 			// find GCR lead-in | 			// find GCR lead-in | ||||||
| 			proceed_to_shift_value(0x3ff); | 			proceed_to_shift_value(0x3ff); | ||||||
| 			if(shift_register_ != 0x3ff) return 0xff; | 			if(shift_register_ != 0x3ff) return 0xff; | ||||||
|  |  | ||||||
| 			// find end of lead-in | 			// find end of lead-in | ||||||
| 			while(shift_register_ == 0x3ff && index_count_ < 2) { | 			while(shift_register_ == 0x3ff && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// continue for a further nine bits | 			// continue for a further nine bits | ||||||
| 			bit_count_ = 0; | 			bit_count_ = 0; | ||||||
| 			while(bit_count_ < 9 && index_count_ < 2) { | 			while(bit_count_ < 9 && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -97,8 +99,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void proceed_to_shift_value(unsigned int shift_value) { | 		void proceed_to_shift_value(unsigned int shift_value) { | ||||||
| 			index_count_ = 0; | 			const int max_index_count = index_count_ + 2; | ||||||
| 			while(shift_register_ != shift_value && index_count_ < 2) { | 			while(shift_register_ != shift_value && index_count_ < max_index_count) { | ||||||
| 				run_for(Cycles(1)); | 				run_for(Cycles(1)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -124,13 +126,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
|  |  | ||||||
| 		std::shared_ptr<Sector> get_next_sector() { | 		std::shared_ptr<Sector> get_next_sector() { | ||||||
| 			std::shared_ptr<Sector> sector(new Sector); | 			std::shared_ptr<Sector> sector(new Sector); | ||||||
| 			index_count_ = 0; | 			const int max_index_count = index_count_ + 2; | ||||||
|  |  | ||||||
| 			while(index_count_ < 2) { | 			while(index_count_ < max_index_count) { | ||||||
| 				// look for a sector header | 				// look for a sector header | ||||||
| 				while(1) { | 				while(1) { | ||||||
| 					if(proceed_to_next_block() == 0x08) break; | 					if(proceed_to_next_block(max_index_count) == 0x08) break; | ||||||
| 					if(index_count_ >= 2) return nullptr; | 					if(index_count_ >= max_index_count) return nullptr; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// get sector details, skip if this looks malformed | 				// get sector details, skip if this looks malformed | ||||||
| @@ -144,8 +146,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
|  |  | ||||||
| 				// look for the following data | 				// look for the following data | ||||||
| 				while(1) { | 				while(1) { | ||||||
| 					if(proceed_to_next_block() == 0x07) break; | 					if(proceed_to_next_block(max_index_count) == 0x07) break; | ||||||
| 					if(index_count_ >= 2) return nullptr; | 					if(index_count_ >= max_index_count) return nullptr; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				checksum = 0; | 				checksum = 0; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 13/09/2016. | //  Created by Thomas Harte on 13/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Commodore_Disk_hpp | #ifndef StaticAnalyser_Commodore_Disk_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 10/09/2016. | //  Created by Thomas Harte on 10/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| @@ -21,7 +21,7 @@ bool Analyser::Static::Commodore::File::is_basic() { | |||||||
| 	//		[4 bytes: address of start of next line] | 	//		[4 bytes: address of start of next line] | ||||||
| 	//		[4 bytes: this line number] | 	//		[4 bytes: this line number] | ||||||
| 	//		... null-terminated code ... | 	//		... null-terminated code ... | ||||||
| 	//	(with a next line address of 0000 indicating end of program)ß | 	//	(with a next line address of 0000 indicating end of program) | ||||||
| 	while(1) { | 	while(1) { | ||||||
| 		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break; | 		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 10/09/2016. | //  Created by Thomas Harte on 10/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef File_hpp | #ifndef File_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 06/09/2016. | //  Created by Thomas Harte on 06/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| @@ -11,8 +11,10 @@ | |||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
| #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
| #include <sstream> | #include <sstream> | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| @@ -38,10 +40,12 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return vic20_cartridges; | 	return vic20_cartridges; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	TargetList destination; | ||||||
|  |  | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::Vic20;	// TODO: machine estimation | 	target->machine = Machine::Vic20;	// TODO: machine estimation | ||||||
| 	target->confidence = 1.0; // TODO: a proper estimation | 	target->confidence = 0.5; // TODO: a proper estimation | ||||||
|  |  | ||||||
| 	int device = 0; | 	int device = 0; | ||||||
| 	std::vector<File> files; | 	std::vector<File> files; | ||||||
| @@ -73,7 +77,7 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!files.empty()) { | 	if(!files.empty()) { | ||||||
| 		target->vic20.memory_model = Vic20MemoryModel::Unexpanded; | 		target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
| 		std::ostringstream string_stream; | 		std::ostringstream string_stream; | ||||||
| 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | ||||||
|   		if(files.front().is_basic()) { |   		if(files.front().is_basic()) { | ||||||
| @@ -86,19 +90,22 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std | |||||||
|  |  | ||||||
| 		// make a first guess based on loading address | 		// make a first guess based on loading address | ||||||
| 		switch(files.front().starting_address) { | 		switch(files.front().starting_address) { | ||||||
|  | 			default: | ||||||
|  | 				printf("Starting address %04x?\n", files.front().starting_address); | ||||||
| 			case 0x1001: | 			case 0x1001: | ||||||
| 			default: break; | 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
|  | 			break; | ||||||
| 			case 0x1201: | 			case 0x1201: | ||||||
| 				target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 				target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
| 			break; | 			break; | ||||||
| 			case 0x0401: | 			case 0x0401: | ||||||
| 				target->vic20.memory_model = Vic20MemoryModel::EightKB; | 				target->memory_model = Target::MemoryModel::EightKB; | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// General approach: increase memory size conservatively such that the largest file found will fit. | 		// General approach: increase memory size conservatively such that the largest file found will fit. | ||||||
| 		for(File &file : files) { | //		for(File &file : files) { | ||||||
| 			std::size_t file_size = file.data.size(); | //			std::size_t file_size = file.data.size(); | ||||||
| //			bool is_basic = file.is_basic(); | //			bool is_basic = file.is_basic(); | ||||||
|  |  | ||||||
| 			/*if(is_basic) | 			/*if(is_basic) | ||||||
| @@ -124,18 +131,31 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std | |||||||
| 				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000. | 				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000. | ||||||
| 				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000. | 				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000. | ||||||
| 				// A 32kb expanded Vic has memory in the entire low 32kb. | 				// A 32kb expanded Vic has memory in the entire low 32kb. | ||||||
| 				uint16_t starting_address = file.starting_address; | //				uint16_t starting_address = file.starting_address; | ||||||
|  |  | ||||||
| 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the | 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the | ||||||
| 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb. | 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb. | ||||||
| 				if(starting_address + file_size > 0x2000) | //				if(starting_address + file_size > 0x2000) | ||||||
| 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | //					target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
| 				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | //				else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | ||||||
| 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | //					target->memory_model = Target::MemoryModel::ThirtyTwoKB; | ||||||
| //			} | //			} | ||||||
| 		} | //		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!target->media.empty()) | 	if(!target->media.empty()) { | ||||||
|  | 		// Inspect filename for a region hint. | ||||||
|  | 		std::string lowercase_name = file_name; | ||||||
|  | 		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower); | ||||||
|  | 		if(lowercase_name.find("ntsc") != std::string::npos) { | ||||||
|  | 			target->region = Analyser::Static::Commodore::Target::Region::American; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Attach a 1540 if there are any disks here. | ||||||
|  | 		target->has_c1540 = !target->media.disks.empty(); | ||||||
|  |  | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return destination; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 06/09/2016. | //  Created by Thomas Harte on 06/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp | #ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_Commodore_StaticAnalyser_hpp | #define StaticAnalyser_Commodore_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 24/08/2016. | //  Created by Thomas Harte on 24/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 24/08/2016. | //  Created by Thomas Harte on 24/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Commodore_Tape_hpp | #ifndef StaticAnalyser_Commodore_Tape_hpp | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Analyser/Static/Commodore/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Commodore_Target_h | ||||||
|  | #define Analyser_Static_Commodore_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Commodore { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class MemoryModel { | ||||||
|  | 		Unexpanded, | ||||||
|  | 		EightKB, | ||||||
|  | 		ThirtyTwoKB | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	enum class Region { | ||||||
|  | 		American, | ||||||
|  | 		Danish, | ||||||
|  | 		Japanese, | ||||||
|  | 		European, | ||||||
|  | 		Swedish | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||||
|  | 	Region region = Region::European; | ||||||
|  | 	bool has_c1540 = false; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Commodore_Target_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 10/11/2016. | //  Created by Thomas Harte on 10/11/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "6502.hpp" | #include "6502.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 10/11/2016. | //  Created by Thomas Harte on 10/11/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Disassembler_6502_hpp | #ifndef StaticAnalyser_Disassembler_6502_hpp | ||||||
| @@ -21,7 +21,7 @@ namespace Static { | |||||||
| namespace MOS6502 { | namespace MOS6502 { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Describes a 6502 instruciton — its address, the operation it performs, its addressing mode | 	Describes a 6502 instruciton: its address, the operation it performs, its addressing mode | ||||||
| 	and its operand, if any. | 	and its operand, if any. | ||||||
| */ | */ | ||||||
| struct Instruction { | struct Instruction { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 30/12/2017. | //  Created by Thomas Harte on 30/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "AddressMapper.hpp" | #include "AddressMapper.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 30/12/2017. | //  Created by Thomas Harte on 30/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef AddressMapper_hpp | #ifndef AddressMapper_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 31/12/2017. | //  Created by Thomas Harte on 31/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef Kernel_hpp | #ifndef Kernel_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 30/12/2017. | //  Created by Thomas Harte on 30/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Z80.hpp" | #include "Z80.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 30/12/2017. | //  Created by Thomas Harte on 30/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Disassembler_Z80_hpp | #ifndef StaticAnalyser_Disassembler_Z80_hpp | ||||||
|   | |||||||
							
								
								
									
										125
									
								
								Analyser/Static/DiskII/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								Analyser/Static/DiskII/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include "../AppleII/Target.hpp" | ||||||
|  | #include "../Oric/Target.hpp" | ||||||
|  | #include "../Disassembler/6502.hpp" | ||||||
|  | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Disk/Track/TrackSerialiser.hpp" | ||||||
|  | #include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp" | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) { | ||||||
|  | 	using Target = Analyser::Static::AppleII::Target; | ||||||
|  | 	auto *target = new Target; | ||||||
|  | 	target->machine = Analyser::Machine::AppleII; | ||||||
|  |  | ||||||
|  | 	if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) { | ||||||
|  | 		target->disk_controller = Target::DiskController::ThirteenSector; | ||||||
|  | 	} else { | ||||||
|  | 		target->disk_controller = Target::DiskController::SixteenSector; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return target; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) { | ||||||
|  | 	using Target = Analyser::Static::Oric::Target; | ||||||
|  | 	auto *target = new Target; | ||||||
|  | 	target->machine = Analyser::Machine::Oric; | ||||||
|  | 	target->rom = Target::ROM::Pravetz; | ||||||
|  | 	target->disk_interface = Target::DiskInterface::Pravetz; | ||||||
|  | 	target->loading_command = "CALL 800\n"; | ||||||
|  | 	return target; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	// This analyser can comprehend disks only. | ||||||
|  | 	if(media.disks.empty()) return {}; | ||||||
|  |  | ||||||
|  | 	// Grab track 0, sector 0: the boot sector. | ||||||
|  | 	auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0))); | ||||||
|  | 	auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment( | ||||||
|  | 		Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000))); | ||||||
|  |  | ||||||
|  | 	const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr; | ||||||
|  | 	for(const auto &pair: sector_map) { | ||||||
|  | 		if(!pair.second.address.sector) { | ||||||
|  | 			sector_zero = &pair.second; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If there's no boot sector then if there are also no sectors at all, | ||||||
|  | 	// decline to nominate a machine. Otherwise go with an Apple as the default. | ||||||
|  | 	TargetList targets; | ||||||
|  | 	if(!sector_zero) { | ||||||
|  | 		if(sector_map.empty()) { | ||||||
|  | 			return targets; | ||||||
|  | 		} else { | ||||||
|  | 			targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr))); | ||||||
|  | 			targets.back()->media = media; | ||||||
|  | 			return targets; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If the boot sector looks like it's intended for the Oric, create an Oric. | ||||||
|  | 	// Otherwise go with the Apple II. | ||||||
|  |  | ||||||
|  | 	auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800}); | ||||||
|  |  | ||||||
|  | 	bool did_read_shift_register = false; | ||||||
|  | 	bool is_oric = false; | ||||||
|  |  | ||||||
|  | 	// Look for a tight BPL loop reading the Oric's shift register address of 0x31c. The Apple II just has RAM there, | ||||||
|  | 	// so the probability of such a loop is infinitesimal. | ||||||
|  | 	for(const auto &instruction: disassembly.instructions_by_address) { | ||||||
|  | 		// Is this a read of the shift register? | ||||||
|  | 		if( | ||||||
|  | 			( | ||||||
|  | 				(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDA) || | ||||||
|  | 				(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDX) || | ||||||
|  | 				(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDY) | ||||||
|  | 			) && | ||||||
|  | 			instruction.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Absolute && | ||||||
|  | 			instruction.second.address == 0x031c) { | ||||||
|  | 			did_read_shift_register = true; | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(did_read_shift_register) { | ||||||
|  | 			if( | ||||||
|  | 				instruction.second.operation == Analyser::Static::MOS6502::Instruction::BPL && | ||||||
|  | 				instruction.second.address == 0xfb) { | ||||||
|  | 				is_oric = true; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			did_read_shift_register = false; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check also for calls into the 0x3xx page above 0x320, as that's where the Oric's boot ROM is. | ||||||
|  | 	for(const auto address: disassembly.outward_calls) { | ||||||
|  | 		is_oric |= address >= 0x320 && address < 0x400; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(is_oric) { | ||||||
|  | 		targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero))); | ||||||
|  | 	} else { | ||||||
|  | 		targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero))); | ||||||
|  | 	} | ||||||
|  | 	targets.back()->media = media; | ||||||
|  | 	return targets; | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								Analyser/Static/DiskII/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Static/DiskII/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 03/05/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_DiskII_StaticAnalyser_hpp | ||||||
|  | #define Analyser_Static_DiskII_StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace DiskII { | ||||||
|  |  | ||||||
|  | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 25/01/2018. | //  Created by Thomas Harte on 25/01/2018. | ||||||
| //  Copyright © 2018 Thomas Harte. All rights reserved. | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef Cartridge_hpp | #ifndef Cartridge_hpp | ||||||
|   | |||||||
| @@ -3,13 +3,15 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 25/11/2017. | //  Created by Thomas Harte on 25/11/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
| #include "Cartridge.hpp" | #include "Cartridge.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../Disassembler/Z80.hpp" | #include "../Disassembler/Z80.hpp" | ||||||
| #include "../Disassembler/AddressMapper.hpp" | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
| @@ -32,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | |||||||
| 		output_segments.emplace_back(start_address, segment.data); | 		output_segments.emplace_back(start_address, segment.data); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	std::unique_ptr<Analyser::Static::Target> target(new Analyser::Static::Target); | 	std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target); | ||||||
| 	target->machine = Analyser::Machine::MSX; | 	target->machine = Analyser::Machine::MSX; | ||||||
| 	target->confidence = confidence; | 	target->confidence = confidence; | ||||||
|  |  | ||||||
| @@ -61,14 +63,14 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | |||||||
|  |  | ||||||
| 	(additional audio hardware is also sometimes included, but it's implied by the banking hardware) | 	(additional audio hardware is also sometimes included, but it's implied by the banking hardware) | ||||||
| */ | */ | ||||||
| static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom( | static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||||
| 	const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | 	const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
| 	// No cartridges implies no targets. | 	// No cartridges implies no targets. | ||||||
| 	if(cartridges.empty()) { | 	if(cartridges.empty()) { | ||||||
| 		return {}; | 		return {}; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	std::vector<std::unique_ptr<Analyser::Static::Target>> targets; | 	Analyser::Static::TargetList targets; | ||||||
| 	for(const auto &cartridge : cartridges) { | 	for(const auto &cartridge : cartridges) { | ||||||
| 		const auto &segments = cartridge->get_segments(); | 		const auto &segments = cartridge->get_segments(); | ||||||
|  |  | ||||||
| @@ -259,16 +261,18 @@ static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFr | |||||||
| 	return targets; | 	return targets; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	TargetList destination; | ||||||
|  |  | ||||||
| 	// Append targets for any cartridges that look correct. | 	// Append targets for any cartridges that look correct. | ||||||
| 	std::vector<std::unique_ptr<Target>> cartridge_targets = CartridgeTargetsFrom(media.cartridges); | 	auto cartridge_targets = CartridgeTargetsFrom(media.cartridges); | ||||||
| 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | ||||||
|  |  | ||||||
| 	// Consider building a target for disks and/or tapes. | 	// Consider building a target for disks and/or tapes. | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
|  |  | ||||||
| 	// Check tapes for loadable files. | 	// Check tapes for loadable files. | ||||||
| 	for(const auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::vector<File> files_on_tape = GetFiles(tape); | 		std::vector<File> files_on_tape = GetFiles(tape); | ||||||
| 		if(!files_on_tape.empty()) { | 		if(!files_on_tape.empty()) { | ||||||
| 			switch(files_on_tape.front().type) { | 			switch(files_on_tape.front().type) { | ||||||
| @@ -283,10 +287,13 @@ void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::uniq | |||||||
|  |  | ||||||
| 	// Blindly accept disks for now. | 	// Blindly accept disks for now. | ||||||
| 	target->media.disks = media.disks; | 	target->media.disks = media.disks; | ||||||
|  | 	target->has_disk_drive = !media.disks.empty(); | ||||||
|  |  | ||||||
| 	if(!target->media.empty()) { | 	if(!target->media.empty()) { | ||||||
| 		target->machine = Machine::MSX; | 		target->machine = Machine::MSX; | ||||||
| 		target->confidence = 1.0; | 		target->confidence = 0.5; | ||||||
| 		destination.push_back(std::move(target)); | 		destination.push_back(std::move(target)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return destination; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 25/11/2017. | //  Created by Thomas Harte on 25/11/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_MSX_StaticAnalyser_hpp | #ifndef StaticAnalyser_MSX_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_MSX_StaticAnalyser_hpp | #define StaticAnalyser_MSX_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace MSX { | namespace MSX { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 25/12/2017. | //  Created by Thomas Harte on 25/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 25/12/2017. | //  Created by Thomas Harte on 25/12/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_MSX_Tape_hpp | #ifndef StaticAnalyser_MSX_Tape_hpp | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Static/MSX/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 02/04/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_MSX_Target_h | ||||||
|  | #define Analyser_Static_MSX_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	bool has_disk_drive = false; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_MSX_Target_h */ | ||||||
| @@ -3,29 +3,35 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 11/10/2016. | //  Created by Thomas Harte on 11/10/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
| #include "../Disassembler/6502.hpp" | #include "../Disassembler/6502.hpp" | ||||||
| #include "../Disassembler/AddressMapper.hpp" | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace Analyser::Static::Oric; | using namespace Analyser::Static::Oric; | ||||||
|  |  | ||||||
| static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | ||||||
| 	int score = 0; | 	int score = 0; | ||||||
|  |  | ||||||
| 	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | 	for(const auto address : disassembly.outward_calls)		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | ||||||
| 	for(auto address : disassembly.external_stores)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1; | 	for(const auto address : disassembly.external_stores)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1; | ||||||
| 	for(auto address : disassembly.external_loads)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1; | 	for(const auto address : disassembly.external_loads)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1; | ||||||
|  |  | ||||||
| 	return score; | 	return score; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	std::set<uint16_t> rom_functions = { | 	const std::set<uint16_t> rom_functions = { | ||||||
| 		0x0228,	0x022b, | 		0x0228,	0x022b, | ||||||
| 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | ||||||
| 		0xc773,	0xc824,	0xc832,	0xc841,	0xc8c1,	0xc8fe,	0xc91f,	0xc93f,	0xc941,	0xc91e,	0xc98b,	0xc996,	0xc9b3,	0xc9e0,	0xca0a,	0xca1c, | 		0xc773,	0xc824,	0xc832,	0xc841,	0xc8c1,	0xc8fe,	0xc91f,	0xc93f,	0xc941,	0xc91e,	0xc98b,	0xc996,	0xc9b3,	0xc9e0,	0xca0a,	0xca1c, | ||||||
| @@ -41,7 +47,7 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl | |||||||
| 		0xf43c,	0xf4ef,	0xf523,	0xf561,	0xf535,	0xf57b,	0xf5d3,	0xf71a,	0xf73f,	0xf7e4,	0xf7e0,	0xf82f,	0xf88f,	0xf8af,	0xf8b5,	0xf920, | 		0xf43c,	0xf4ef,	0xf523,	0xf561,	0xf535,	0xf57b,	0xf5d3,	0xf71a,	0xf73f,	0xf7e4,	0xf7e0,	0xf82f,	0xf88f,	0xf8af,	0xf8b5,	0xf920, | ||||||
| 		0xf967,	0xf960,	0xf9c9,	0xfa14,	0xfa85,	0xfa9b,	0xfab1,	0xfac7,	0xfafa,	0xfb10,	0xfb26,	0xfbb6,	0xfbfe | 		0xf967,	0xf960,	0xf9c9,	0xfa14,	0xfa85,	0xfa9b,	0xfab1,	0xfac7,	0xfafa,	0xfb10,	0xfb26,	0xfbb6,	0xfbfe | ||||||
| 	}; | 	}; | ||||||
| 	std::set<uint16_t> variable_locations = { | 	const std::set<uint16_t> variable_locations = { | ||||||
| 		0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230 | 		0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230 | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| @@ -49,7 +55,7 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl | |||||||
| } | } | ||||||
|  |  | ||||||
| static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	std::set<uint16_t> rom_functions = { | 	const std::set<uint16_t> rom_functions = { | ||||||
| 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | ||||||
| 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | ||||||
| 		0xc748,	0xc7fd,	0xc809,	0xc816,	0xc82f,	0xc855,	0xc8c1,	0xc915,	0xc952,	0xc971,	0xc973,	0xc9a0,	0xc9bd,	0xc9c8,	0xc9e5,	0xca12, | 		0xc748,	0xc7fd,	0xc809,	0xc816,	0xc82f,	0xc855,	0xc8c1,	0xc915,	0xc952,	0xc971,	0xc973,	0xc9a0,	0xc9bd,	0xc9c8,	0xc9e5,	0xca12, | ||||||
| @@ -66,17 +72,38 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl | |||||||
| 		0xf88f,	0xf8af,	0xf8b5,	0xf920,	0xf967,	0xf9aa,	0xf9c9,	0xfa14,	0xfa9f,	0xfab5,	0xfacb,	0xfae1,	0xfb14,	0xfb2a,	0xfb40,	0xfbd0, | 		0xf88f,	0xf8af,	0xf8b5,	0xf920,	0xf967,	0xf9aa,	0xf9c9,	0xfa14,	0xfa9f,	0xfab5,	0xfacb,	0xfae1,	0xfb14,	0xfb2a,	0xfb40,	0xfbd0, | ||||||
| 		0xfc18 | 		0xfc18 | ||||||
| 	}; | 	}; | ||||||
| 	std::set<uint16_t> variable_locations = { | 	const std::set<uint16_t> variable_locations = { | ||||||
| 		0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c | 		0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	return Score(disassembly, rom_functions, variable_locations); | 	return Score(disassembly, rom_functions, variable_locations); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) { | ||||||
|  | 	/* | ||||||
|  | 		The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature. | ||||||
|  | 	*/ | ||||||
|  | 	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2); | ||||||
|  | 	if(!sector) return false; | ||||||
|  | 	if(sector->samples.empty()) return false; | ||||||
|  |  | ||||||
|  | 	const std::vector<uint8_t> &first_sample = sector->samples[0]; | ||||||
|  | 	if(first_sample.size() != 256) return false; | ||||||
|  |  | ||||||
|  | 	const uint8_t signature[] = { | ||||||
|  | 		0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0, | ||||||
|  | 		0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00, | ||||||
|  | 		0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6, | ||||||
|  | 		0x12, 0x00 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	return !std::memcmp(signature, first_sample.data(), sizeof(signature)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
| 	std::unique_ptr<Target> target(new Target); | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target->machine = Machine::Oric; | 	target->machine = Machine::Oric; | ||||||
| 	target->confidence = 1.0; | 	target->confidence = 0.5; | ||||||
|  |  | ||||||
| 	int basic10_votes = 0; | 	int basic10_votes = 0; | ||||||
| 	int basic11_votes = 0; | 	int basic11_votes = 0; | ||||||
| @@ -84,8 +111,8 @@ void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::uni | |||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::vector<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(tape_files.size()) { | 		if(!tape_files.empty()) { | ||||||
| 			for(auto file : tape_files) { | 			for(const auto &file : tape_files) { | ||||||
| 				if(file.data_type == File::MachineCode) { | 				if(file.data_type == File::MachineCode) { | ||||||
| 					std::vector<uint16_t> entry_points = {file.starting_address}; | 					std::vector<uint16_t> entry_points = {file.starting_address}; | ||||||
| 					Analyser::Static::MOS6502::Disassembly disassembly = | 					Analyser::Static::MOS6502::Disassembly disassembly = | ||||||
| @@ -102,18 +129,27 @@ void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::uni | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// trust that any disk supplied can be handled by the Microdisc. TODO: check. |  | ||||||
| 	if(!media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		target->oric.has_microdisc = true; | 		// Only the Microdisc is emulated right now, so accept only disks that it can boot from. | ||||||
| 		target->media.disks = media.disks; | 		for(auto &disk: media.disks) { | ||||||
| 	} else { | 			Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 		target->oric.has_microdisc = false; | 			if(IsMicrodisc(parser)) { | ||||||
|  | 				target->disk_interface = Target::DiskInterface::Microdisc; | ||||||
|  | 				target->media.disks.push_back(disk); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  | 	else | ||||||
|  | 		target->disk_interface = Target::DiskInterface::None; | ||||||
|  |  | ||||||
| 	// TODO: really this should add two targets if not all votes agree | 	// TODO: really this should add two targets if not all votes agree | ||||||
| 	target->oric.use_atmos_rom = basic11_votes >= basic10_votes; | 	if(basic11_votes >= basic10_votes || target->disk_interface == Target::DiskInterface::Microdisc) | ||||||
| 	if(target->oric.has_microdisc) target->oric.use_atmos_rom = true; | 		target->rom = Target::ROM::BASIC11; | ||||||
|  | 	else | ||||||
|  | 		target->rom = Target::ROM::BASIC10; | ||||||
|  |  | ||||||
|  | 	TargetList targets; | ||||||
| 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | ||||||
| 		destination.push_back(std::move(target)); | 		targets.push_back(std::move(target)); | ||||||
|  | 	return targets; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 11/10/2016. | //  Created by Thomas Harte on 11/10/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Oric_StaticAnalyser_hpp | #ifndef StaticAnalyser_Oric_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_Oric_StaticAnalyser_hpp | #define StaticAnalyser_Oric_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
|  | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace Oric { | namespace Oric { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 06/11/2016. | //  Created by Thomas Harte on 06/11/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 06/11/2016. | //  Created by Thomas Harte on 06/11/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_Oric_Tape_hpp | #ifndef StaticAnalyser_Oric_Tape_hpp | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Analyser/Static/Oric/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_Oric_Target_h | ||||||
|  | #define Analyser_Static_Oric_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Oric { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class ROM { | ||||||
|  | 		BASIC10, | ||||||
|  | 		BASIC11, | ||||||
|  | 		Pravetz | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	enum class DiskInterface { | ||||||
|  | 		Microdisc, | ||||||
|  | 		Pravetz, | ||||||
|  | 		None | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	ROM rom = ROM::BASIC11; | ||||||
|  | 	DiskInterface disk_interface = DiskInterface::None; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_Oric_Target_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 23/08/2016. | //  Created by Thomas Harte on 23/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| @@ -11,13 +11,16 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstdlib> | #include <cstdlib> | ||||||
| #include <cstring> | #include <cstring> | ||||||
|  | #include <iterator> | ||||||
|  |  | ||||||
| // Analysers | // Analysers | ||||||
| #include "Acorn/StaticAnalyser.hpp" | #include "Acorn/StaticAnalyser.hpp" | ||||||
| #include "AmstradCPC/StaticAnalyser.hpp" | #include "AmstradCPC/StaticAnalyser.hpp" | ||||||
|  | #include "AppleII/StaticAnalyser.hpp" | ||||||
| #include "Atari/StaticAnalyser.hpp" | #include "Atari/StaticAnalyser.hpp" | ||||||
| #include "Coleco/StaticAnalyser.hpp" | #include "Coleco/StaticAnalyser.hpp" | ||||||
| #include "Commodore/StaticAnalyser.hpp" | #include "Commodore/StaticAnalyser.hpp" | ||||||
|  | #include "DiskII/StaticAnalyser.hpp" | ||||||
| #include "MSX/StaticAnalyser.hpp" | #include "MSX/StaticAnalyser.hpp" | ||||||
| #include "Oric/StaticAnalyser.hpp" | #include "Oric/StaticAnalyser.hpp" | ||||||
| #include "ZX8081/StaticAnalyser.hpp" | #include "ZX8081/StaticAnalyser.hpp" | ||||||
| @@ -28,14 +31,17 @@ | |||||||
|  |  | ||||||
| // Disks | // Disks | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp" | #include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" | #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/NIB.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | ||||||
|  | #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" | ||||||
|  |  | ||||||
| // Tapes | // Tapes | ||||||
| #include "../../Storage/Tape/Formats/CAS.hpp" | #include "../../Storage/Tape/Formats/CAS.hpp" | ||||||
| @@ -52,21 +58,16 @@ | |||||||
|  |  | ||||||
| using namespace Analyser::Static; | using namespace Analyser::Static; | ||||||
|  |  | ||||||
| static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) { | static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) { | ||||||
|  | 	Media result; | ||||||
|  |  | ||||||
| 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase | 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase | ||||||
| 	// test as to file format. | 	// test as to file format. | ||||||
| 	const char *mixed_case_extension = strrchr(file_name, '.'); | 	std::string::size_type final_dot = file_name.find_last_of("."); | ||||||
| 	char *lowercase_extension = nullptr; | 	if(final_dot == std::string::npos) return result; | ||||||
| 	if(mixed_case_extension) { | 	std::string extension = file_name.substr(final_dot + 1); | ||||||
| 		lowercase_extension = strdup(mixed_case_extension+1); | 	std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); | ||||||
| 		char *parser = lowercase_extension; |  | ||||||
| 		while(*parser) { |  | ||||||
| 			*parser = (char)tolower(*parser); |  | ||||||
| 			parser++; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	Media result; |  | ||||||
| #define Insert(list, class, platforms) \ | #define Insert(list, class, platforms) \ | ||||||
| 	list.emplace_back(new Storage::class(file_name));\ | 	list.emplace_back(new Storage::class(file_name));\ | ||||||
| 	potential_platforms |= platforms;\ | 	potential_platforms |= platforms;\ | ||||||
| @@ -78,75 +79,78 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 		Insert(list, class, platforms) \ | 		Insert(list, class, platforms) \ | ||||||
| 	} catch(...) {} | 	} catch(...) {} | ||||||
|  |  | ||||||
| #define Format(extension, list, class, platforms) \ | #define Format(ext, list, class, platforms) \ | ||||||
| 	if(!std::strcmp(lowercase_extension, extension))	{	\ | 	if(extension == ext)	{		\ | ||||||
| 		TryInsert(list, class, platforms)	\ | 		TryInsert(list, class, platforms)	\ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(lowercase_extension) { | 	Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 80 | ||||||
| 		Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 80 | 	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 81 | ||||||
| 		Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// 81 | 	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// A26 | ||||||
| 		Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// A26 | 	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF | ||||||
| 		Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF | 	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)					// BIN | ||||||
| 		Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// BIN | 	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS | ||||||
| 		Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS | 	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT | ||||||
| 		Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT | 	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL | ||||||
| 		Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL | 	Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)											// CSW | ||||||
| 		Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)											// CSW | 	Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)		// D64 | ||||||
| 		Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)		// D64 | 	Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)				// DMK | ||||||
| 		Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)				// DMK | 	Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// DO | ||||||
| 		Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// DSD | 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// DSD | ||||||
| 		Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)	// DSK (Amstrad CPC) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)	// DSK (Amstrad CPC) | ||||||
| 		Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)			// DSK (MSX) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// DSK (Apple) | ||||||
| 		Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)		// DSK (Oric) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)			// DSK (MSX) | ||||||
| 		Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)		// G64 | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)		// DSK (Oric) | ||||||
| 		Format("hfe", result.disks, Disk::DiskImageHolder<Storage::Disk::HFE>, TargetPlatform::AmstradCPC)		// HFE (TODO: plus other target platforms) | 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)		// G64 | ||||||
| 		Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O | 	Format(	"hfe", | ||||||
| 		Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P | 			result.disks, | ||||||
| 		Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P81 | 			Disk::DiskImageHolder<Storage::Disk::HFE>, | ||||||
|  | 			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) | ||||||
|  | 			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy) | ||||||
|  | 	Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)			// NIB | ||||||
|  | 	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// O | ||||||
|  | 	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P | ||||||
|  | 	Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)		// PO | ||||||
|  | 	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)										// P81 | ||||||
|  |  | ||||||
| 		// PRG | 	// PRG | ||||||
| 		if(!std::strcmp(lowercase_extension, "prg")) { | 	if(extension == "prg") { | ||||||
| 			// try instantiating as a ROM; failing that accept as a tape | 		// try instantiating as a ROM; failing that accept as a tape | ||||||
|  | 		try { | ||||||
|  | 			Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore) | ||||||
|  | 		} catch(...) { | ||||||
| 			try { | 			try { | ||||||
| 				Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore) | 				Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore) | ||||||
| 			} catch(...) { | 			} catch(...) {} | ||||||
| 				try { |  | ||||||
| 					Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore) |  | ||||||
| 				} catch(...) {} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 		Format(	"rom", | 	Format(	"rom", | ||||||
| 				result.cartridges, | 			result.cartridges, | ||||||
| 				Cartridge::BinaryDump, | 			Cartridge::BinaryDump, | ||||||
| 				TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision)						// ROM | 			TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX)				// ROM | ||||||
| 		Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD | 	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD | ||||||
| 		Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore) | 	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore) | ||||||
| 		Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric) | 	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)										// TAP (Oric) | ||||||
| 		Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)												// TSX | 	Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)												// TSX | ||||||
| 		Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)											// TZX | 	Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081)											// TZX | ||||||
| 		Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)											// UEF (tape) | 	Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)											// UEF (tape) | ||||||
|  | 	Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII)			// WOZ | ||||||
|  |  | ||||||
| #undef Format | #undef Format | ||||||
| #undef Insert | #undef Insert | ||||||
| #undef TryInsert | #undef TryInsert | ||||||
|  |  | ||||||
| 		// Filter potential platforms as per file preferences, if any. |  | ||||||
|  |  | ||||||
| 		free(lowercase_extension); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| Media Analyser::Static::GetMedia(const char *file_name) { | Media Analyser::Static::GetMedia(const std::string &file_name) { | ||||||
| 	TargetPlatform::IntType throwaway; | 	TargetPlatform::IntType throwaway; | ||||||
| 	return GetMediaAndPlatforms(file_name, throwaway); | 	return GetMediaAndPlatforms(file_name, throwaway); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *file_name) { | TargetList Analyser::Static::GetTargets(const std::string &file_name) { | ||||||
| 	std::vector<std::unique_ptr<Target>> targets; | 	TargetList targets; | ||||||
|  |  | ||||||
| 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | ||||||
| 	// union of all platforms this file might be a target for. | 	// union of all platforms this file might be a target for. | ||||||
| @@ -155,17 +159,24 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *fi | |||||||
|  |  | ||||||
| 	// Hand off to platform-specific determination of whether these things are actually compatible and, | 	// Hand off to platform-specific determination of whether these things are actually compatible and, | ||||||
| 	// if so, how to load them. | 	// if so, how to load them. | ||||||
| 	if(potential_platforms & TargetPlatform::Acorn)			Acorn::AddTargets(media, targets); | 	#define Append(x) {\ | ||||||
| 	if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets); | 		auto new_targets = x::GetTargets(media, file_name, potential_platforms);\ | ||||||
| 	if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets); | 		std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\ | ||||||
| 	if(potential_platforms & TargetPlatform::ColecoVision)	Coleco::AddTargets(media, targets); | 	} | ||||||
| 	if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Acorn)			Append(Acorn); | ||||||
| 	if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::AmstradCPC)	Append(AmstradCPC); | ||||||
| 	if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::AppleII)		Append(AppleII); | ||||||
| 	if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms); | 	if(potential_platforms & TargetPlatform::Atari2600)		Append(Atari); | ||||||
|  | 	if(potential_platforms & TargetPlatform::ColecoVision)	Append(Coleco); | ||||||
|  | 	if(potential_platforms & TargetPlatform::Commodore)		Append(Commodore); | ||||||
|  | 	if(potential_platforms & TargetPlatform::DiskII)		Append(DiskII); | ||||||
|  | 	if(potential_platforms & TargetPlatform::MSX)			Append(MSX); | ||||||
|  | 	if(potential_platforms & TargetPlatform::Oric)			Append(Oric); | ||||||
|  | 	if(potential_platforms & TargetPlatform::ZX8081)		Append(ZX8081); | ||||||
|  | 	#undef Append | ||||||
|  |  | ||||||
| 	// Reset any tapes to their initial position | 	// Reset any tapes to their initial position | ||||||
| 	for(auto &target : targets) { | 	for(const auto &target : targets) { | ||||||
| 		for(auto &tape : target->media.tapes) { | 		for(auto &tape : target->media.tapes) { | ||||||
| 			tape->reset(); | 			tape->reset(); | ||||||
| 		} | 		} | ||||||
| @@ -174,9 +185,9 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *fi | |||||||
| 	// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers | 	// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers | ||||||
| 	// picked their insertion order carefully. | 	// picked their insertion order carefully. | ||||||
| 	std::stable_sort(targets.begin(), targets.end(), | 	std::stable_sort(targets.begin(), targets.end(), | ||||||
|         [] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) { | 		[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) { | ||||||
| 		    return a->confidence > b->confidence; | 			return a->confidence > b->confidence; | ||||||
| 	    }); | 		}); | ||||||
|  |  | ||||||
| 	return targets; | 	return targets; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 23/08/2016. | //  Created by Thomas Harte on 23/08/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_hpp | #ifndef StaticAnalyser_hpp | ||||||
| @@ -15,45 +15,13 @@ | |||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../Storage/Disk/Disk.hpp" | ||||||
| #include "../../Storage/Cartridge/Cartridge.hpp" | #include "../../Storage/Cartridge/Cartridge.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
|  |  | ||||||
| enum class Vic20MemoryModel { |  | ||||||
| 	Unexpanded, |  | ||||||
| 	EightKB, |  | ||||||
| 	ThirtyTwoKB |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class Atari2600PagingModel { |  | ||||||
| 	None, |  | ||||||
| 	CommaVid, |  | ||||||
| 	Atari8k, |  | ||||||
| 	Atari16k, |  | ||||||
| 	Atari32k, |  | ||||||
| 	ActivisionStack, |  | ||||||
| 	ParkerBros, |  | ||||||
| 	Tigervision, |  | ||||||
| 	CBSRamPlus, |  | ||||||
| 	MNetwork, |  | ||||||
| 	MegaBoy, |  | ||||||
| 	Pitfall2 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class ZX8081MemoryModel { |  | ||||||
| 	Unexpanded, |  | ||||||
| 	SixteenKB, |  | ||||||
| 	SixtyFourKB |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class AmstradCPCModel { |  | ||||||
| 	CPC464, |  | ||||||
| 	CPC664, |  | ||||||
| 	CPC6128 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	A list of disks, tapes and cartridges. | 	A list of disks, tapes and cartridges. | ||||||
| */ | */ | ||||||
| @@ -72,58 +40,25 @@ struct Media { | |||||||
| 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | ||||||
| */ | */ | ||||||
| struct Target { | struct Target { | ||||||
|  | 	virtual ~Target() {} | ||||||
|  |  | ||||||
| 	Machine machine; | 	Machine machine; | ||||||
| 	Media media; | 	Media media; | ||||||
|  |  | ||||||
| 	float confidence; | 	float confidence; | ||||||
| 	std::string loading_command; |  | ||||||
|  |  | ||||||
| 	// TODO: this is too C-like a solution; make Target a base class and |  | ||||||
| 	// turn the following into information held by more specific subclasses. |  | ||||||
| 	union { |  | ||||||
| 		struct { |  | ||||||
| 			bool has_adfs; |  | ||||||
| 			bool has_dfs; |  | ||||||
| 			bool should_shift_restart; |  | ||||||
| 		} acorn; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			Atari2600PagingModel paging_model; |  | ||||||
| 			bool uses_superchip; |  | ||||||
| 		} atari; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			bool use_atmos_rom; |  | ||||||
| 			bool has_microdisc; |  | ||||||
| 		} oric; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			Vic20MemoryModel memory_model; |  | ||||||
| 			bool has_c1540; |  | ||||||
| 		} vic20; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			ZX8081MemoryModel memory_model; |  | ||||||
| 			bool isZX81; |  | ||||||
| 		} zx8081; |  | ||||||
|  |  | ||||||
| 		struct { |  | ||||||
| 			AmstradCPCModel model; |  | ||||||
| 		} amstradcpc; |  | ||||||
| 	}; |  | ||||||
| }; | }; | ||||||
|  | typedef std::vector<std::unique_ptr<Target>> TargetList; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Attempts, through any available means, to return a list of potential targets for the file with the given name. | 	Attempts, through any available means, to return a list of potential targets for the file with the given name. | ||||||
|  |  | ||||||
| 	@returns The list of potential targets, sorted from most to least probable. | 	@returns The list of potential targets, sorted from most to least probable. | ||||||
| */ | */ | ||||||
| std::vector<std::unique_ptr<Target>> GetTargets(const char *file_name); | TargetList GetTargets(const std::string &file_name); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Inspects the supplied file and determines the media included. | 	Inspects the supplied file and determines the media included. | ||||||
| */ | */ | ||||||
| Media GetMedia(const char *file_name); | Media GetMedia(const std::string &file_name); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 04/06/2017. | //  Created by Thomas Harte on 04/06/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| @@ -11,6 +11,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
| #include "../../../Storage/Tape/Parsers/ZX8081.hpp" | #include "../../../Storage/Tape/Parsers/ZX8081.hpp" | ||||||
|  |  | ||||||
| static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| @@ -27,41 +28,43 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S | |||||||
| 	return files; | 	return files; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) { | Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
|  | 	TargetList destination; | ||||||
| 	if(!media.tapes.empty()) { | 	if(!media.tapes.empty()) { | ||||||
| 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | ||||||
| 		media.tapes.front()->reset(); | 		media.tapes.front()->reset(); | ||||||
| 		if(!files.empty()) { | 		if(!files.empty()) { | ||||||
| 			std::unique_ptr<Target> target(new Target); | 			Target *target = new Target; | ||||||
|  | 			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target)); | ||||||
| 			target->machine = Machine::ZX8081; | 			target->machine = Machine::ZX8081; | ||||||
|  |  | ||||||
| 			// Guess the machine type from the file only if it isn't already known. | 			// Guess the machine type from the file only if it isn't already known. | ||||||
| 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | ||||||
| 				default: | 				default: | ||||||
| 					target->zx8081.isZX81 = files.front().isZX81; | 					target->is_ZX81 = files.front().isZX81; | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				case TargetPlatform::ZX80:	target->zx8081.isZX81 = false;	break; | 				case TargetPlatform::ZX80:	target->is_ZX81 = false;	break; | ||||||
| 				case TargetPlatform::ZX81:	target->zx8081.isZX81 = true;	break; | 				case TargetPlatform::ZX81:	target->is_ZX81 = true;		break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			/*if(files.front().data.size() > 16384) { | 			/*if(files.front().data.size() > 16384) { | ||||||
| 				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | 				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | ||||||
| 			} else*/ if(files.front().data.size() > 1024) { | 			} else*/ if(files.front().data.size() > 1024) { | ||||||
| 				target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB; | 				target->memory_model = Target::MemoryModel::SixteenKB; | ||||||
| 			} else { | 			} else { | ||||||
| 				target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded; | 				target->memory_model = Target::MemoryModel::Unexpanded; | ||||||
| 			} | 			} | ||||||
| 			target->media.tapes = media.tapes; | 			target->media.tapes = media.tapes; | ||||||
|  |  | ||||||
| 			// TODO: how to run software once loaded? Might require a BASIC detokeniser. | 			// TODO: how to run software once loaded? Might require a BASIC detokeniser. | ||||||
| 			if(target->zx8081.isZX81) { | 			if(target->is_ZX81) { | ||||||
| 				target->loading_command = "J\"\"\n"; | 				target->loading_command = "J\"\"\n"; | ||||||
| 			} else { | 			} else { | ||||||
| 				target->loading_command = "W\n"; | 				target->loading_command = "W\n"; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			destination.push_back(std::move(target)); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	return destination; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,20 +3,21 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 04/06/2017. | //  Created by Thomas Harte on 04/06/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef StaticAnalyser_ZX8081_StaticAnalyser_hpp | #ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_ZX8081_StaticAnalyser_hpp | #define Analyser_Static_ZX8081_StaticAnalyser_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| #include "../../../Storage/TargetPlatforms.hpp" | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
| namespace Analyser { | namespace Analyser { | ||||||
| namespace Static { | namespace Static { | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
|  |  | ||||||
| void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms); | TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); | ||||||
|  |  | ||||||
| } | } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Analyser/Static/ZX8081/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Analyser_Static_ZX8081_Target_h | ||||||
|  | #define Analyser_Static_ZX8081_Target_h | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace ZX8081 { | ||||||
|  |  | ||||||
|  | struct Target: public ::Analyser::Static::Target { | ||||||
|  | 	enum class MemoryModel { | ||||||
|  | 		Unexpanded, | ||||||
|  | 		SixteenKB, | ||||||
|  | 		SixtyFourKB | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||||
|  | 	bool is_ZX81 = false; | ||||||
|  | 	bool ZX80_uses_ZX81_ROM = false; | ||||||
|  | 	std::string loading_command; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Analyser_Static_ZX8081_Target_h */ | ||||||
							
								
								
									
										81
									
								
								ClockReceiver/ClockDeferrer.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								ClockReceiver/ClockDeferrer.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | // | ||||||
|  | //  ClockDeferrer.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/08/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ClockDeferrer_h | ||||||
|  | #define ClockDeferrer_h | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A ClockDeferrer maintains a list of ordered actions and the times at which | ||||||
|  | 	they should happen, and divides a total execution period up into the portions | ||||||
|  | 	that occur between those actions, triggering each action when it is reached. | ||||||
|  | */ | ||||||
|  | template <typename TimeUnit> class ClockDeferrer { | ||||||
|  | 	public: | ||||||
|  | 		/// Constructs a ClockDeferrer that will call target(period) in between deferred actions. | ||||||
|  | 		ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Schedules @c action to occur in @c delay units of time. | ||||||
|  |  | ||||||
|  | 			Actions must be scheduled in the order they will occur. It is undefined behaviour | ||||||
|  | 			to schedule them out of order. | ||||||
|  | 		*/ | ||||||
|  | 		void defer(TimeUnit delay, const std::function<void(void)> &action) { | ||||||
|  | 			pending_actions_.emplace_back(delay, action); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Runs for @c length units of time. | ||||||
|  |  | ||||||
|  | 			The constructor-supplied target will be called with one or more periods that add up to @c length; | ||||||
|  | 			any scheduled actions will be called between periods. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(TimeUnit length) { | ||||||
|  | 			// If there are no pending actions, just run for the entire length. | ||||||
|  | 			// This should be the normal branch. | ||||||
|  | 			if(pending_actions_.empty()) { | ||||||
|  | 				target_(length); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Divide the time to run according to the pending actions. | ||||||
|  | 			while(length > TimeUnit(0)) { | ||||||
|  | 				TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay); | ||||||
|  | 				target_(next_period); | ||||||
|  | 				length -= next_period; | ||||||
|  |  | ||||||
|  | 				off_t performances = 0; | ||||||
|  | 				for(auto &action: pending_actions_) { | ||||||
|  | 					action.delay -= next_period; | ||||||
|  | 					if(!action.delay) { | ||||||
|  | 						action.action(); | ||||||
|  | 						++performances; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				if(performances) { | ||||||
|  | 					pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::function<void(TimeUnit)> target_; | ||||||
|  |  | ||||||
|  | 		// The list of deferred actions. | ||||||
|  | 		struct DeferredAction { | ||||||
|  | 			TimeUnit delay; | ||||||
|  | 			std::function<void(void)> action; | ||||||
|  |  | ||||||
|  | 			DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {} | ||||||
|  | 		}; | ||||||
|  | 		std::vector<DeferredAction> pending_actions_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* ClockDeferrer_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 22/07/2017. | //  Created by Thomas Harte on 22/07/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef ClockReceiver_hpp | #ifndef ClockReceiver_hpp | ||||||
| @@ -52,79 +52,79 @@ | |||||||
| */ | */ | ||||||
| template <class T> class WrappedInt { | template <class T> class WrappedInt { | ||||||
| 	public: | 	public: | ||||||
| 		inline WrappedInt(int l) : length_(l) {} | 		constexpr WrappedInt(int l) : length_(l) {} | ||||||
| 		inline WrappedInt() : length_(0) {} | 		constexpr WrappedInt() : length_(0) {} | ||||||
|  |  | ||||||
| 		inline T &operator =(const T &rhs) { | 		T &operator =(const T &rhs) { | ||||||
| 			length_ = rhs.length_; | 			length_ = rhs.length_; | ||||||
| 			return *this; | 			return *this; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator +=(const T &rhs) { | 		T &operator +=(const T &rhs) { | ||||||
| 			length_ += rhs.length_; | 			length_ += rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator -=(const T &rhs) { | 		T &operator -=(const T &rhs) { | ||||||
| 			length_ -= rhs.length_; | 			length_ -= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator ++() { | 		T &operator ++() { | ||||||
| 			++ length_; | 			++ length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator ++(int) { | 		T &operator ++(int) { | ||||||
| 			length_ ++; | 			length_ ++; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator --() { | 		T &operator --() { | ||||||
| 			-- length_; | 			-- length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator --(int) { | 		T &operator --(int) { | ||||||
| 			length_ --; | 			length_ --; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator %=(const T &rhs) { | 		T &operator %=(const T &rhs) { | ||||||
| 			length_ %= rhs.length_; | 			length_ %= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T &operator &=(const T &rhs) { | 		T &operator &=(const T &rhs) { | ||||||
| 			length_ &= rhs.length_; | 			length_ &= rhs.length_; | ||||||
| 			return *static_cast<T *>(this); | 			return *static_cast<T *>(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		inline T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | 		constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	} | ||||||
| 		inline T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | 		constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	} | ||||||
|  |  | ||||||
| 		inline T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | 		constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	} | ||||||
| 		inline T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | 		constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	} | ||||||
|  |  | ||||||
| 		inline T operator -() const						{	return T(- length_);				} | 		constexpr T operator -() const						{	return T(- length_);				} | ||||||
|  |  | ||||||
| 		inline bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | 		constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		} | ||||||
| 		inline bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} | 		constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		} | ||||||
| 		inline bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} | 		constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		} | ||||||
| 		inline bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} | 		constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		} | ||||||
| 		inline bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} | 		constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		} | ||||||
| 		inline bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} | 		constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		} | ||||||
|  |  | ||||||
| 		inline bool operator !() const					{	return !length_;					} | 		constexpr bool operator !() const					{	return !length_;					} | ||||||
| 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | 		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse | ||||||
|  |  | ||||||
| 		inline int as_int() const { return length_; } | 		constexpr int as_int() const { return length_; } | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Severs from @c this the effect of dividing by @c divisor — @c this will end up with | 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | ||||||
| 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline T divide(const T &divisor) { | 		T divide(const T &divisor) { | ||||||
| 			T result(length_ / divisor.length_); | 			T result(length_ / divisor.length_); | ||||||
| 			length_ %= divisor.length_; | 			length_ %= divisor.length_; | ||||||
| 			return result; | 			return result; | ||||||
| @@ -134,7 +134,7 @@ template <class T> class WrappedInt { | |||||||
| 			Flushes the value in @c this. The current value is returned, and the internal value | 			Flushes the value in @c this. The current value is returned, and the internal value | ||||||
| 			is reset to zero. | 			is reset to zero. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline T flush() { | 		T flush() { | ||||||
| 			T result(length_); | 			T result(length_); | ||||||
| 			length_ = 0; | 			length_ = 0; | ||||||
| 			return result; | 			return result; | ||||||
| @@ -147,47 +147,47 @@ template <class T> class WrappedInt { | |||||||
| 		int length_; | 		int length_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Describes an integer number of whole cycles — pairs of clock signal transitions. | /// Describes an integer number of whole cycles: pairs of clock signal transitions. | ||||||
| class Cycles: public WrappedInt<Cycles> { | class Cycles: public WrappedInt<Cycles> { | ||||||
| 	public: | 	public: | ||||||
| 		inline Cycles(int l) : WrappedInt<Cycles>(l) {} | 		constexpr Cycles(int l) : WrappedInt<Cycles>(l) {} | ||||||
| 		inline Cycles() : WrappedInt<Cycles>() {} | 		constexpr Cycles() : WrappedInt<Cycles>() {} | ||||||
| 		inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} | 		constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Describes an integer number of half cycles — single clock signal transitions. | /// Describes an integer number of half cycles: single clock signal transitions. | ||||||
| class HalfCycles: public WrappedInt<HalfCycles> { | class HalfCycles: public WrappedInt<HalfCycles> { | ||||||
| 	public: | 	public: | ||||||
| 		inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | 		constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {} | ||||||
| 		inline HalfCycles() : WrappedInt<HalfCycles>() {} | 		constexpr HalfCycles() : WrappedInt<HalfCycles>() {} | ||||||
|  |  | ||||||
| 		inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | 		constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {} | ||||||
| 		inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | 		constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {} | ||||||
|  |  | ||||||
| 		/// @returns The number of whole cycles completely covered by this span of half cycles. | 		/// @returns The number of whole cycles completely covered by this span of half cycles. | ||||||
| 		inline Cycles cycles() { | 		constexpr Cycles cycles() const { | ||||||
| 			return Cycles(length_ >> 1); | 			return Cycles(length_ >> 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. | 		/// Flushes the whole cycles in @c this, subtracting that many from the total stored here. | ||||||
| 		inline Cycles flush_cycles() { | 		Cycles flush_cycles() { | ||||||
| 			Cycles result(length_ >> 1); | 			Cycles result(length_ >> 1); | ||||||
| 			length_ &= 1; | 			length_ &= 1; | ||||||
| 			return result; | 			return result; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. | 		/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. | ||||||
| 		inline HalfCycles flush() { | 		HalfCycles flush() { | ||||||
| 			HalfCycles result(length_); | 			HalfCycles result(length_); | ||||||
| 			length_ = 0; | 			length_ = 0; | ||||||
| 			return result; | 			return result; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Severs from @c this the effect of dividing by @c divisor — @c this will end up with | 			Severs from @c this the effect of dividing by @c divisor; @c this will end up with | ||||||
| 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | 			the value of @c this modulo @c divisor and @c divided by @c divisor is returned. | ||||||
| 		*/ | 		*/ | ||||||
| 		inline Cycles divide_cycles(const Cycles &divisor) { | 		Cycles divide_cycles(const Cycles &divisor) { | ||||||
| 			HalfCycles half_divisor = HalfCycles(divisor); | 			HalfCycles half_divisor = HalfCycles(divisor); | ||||||
| 			Cycles result(length_ / half_divisor.length_); | 			Cycles result(length_ / half_divisor.length_); | ||||||
| 			length_ %= half_divisor.length_; | 			length_ %= half_divisor.length_; | ||||||
| @@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T { | |||||||
| 	public: | 	public: | ||||||
| 		using T::T; | 		using T::T; | ||||||
|  |  | ||||||
| 		using T::run_for; |  | ||||||
| 		inline void run_for(const HalfCycles half_cycles) { | 		inline void run_for(const HalfCycles half_cycles) { | ||||||
| 			half_cycles_ += half_cycles; | 			half_cycles_ += half_cycles; | ||||||
| 			T::run_for(half_cycles_.flush_cycles()); | 			T::run_for(half_cycles_.flush_cycles()); | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								ClockReceiver/ClockingHintSource.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								ClockReceiver/ClockingHintSource.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | // | ||||||
|  | //  ClockingHintSource.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 20/08/2017. | ||||||
|  | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ClockingHintSource_hpp | ||||||
|  | #define ClockingHintSource_hpp | ||||||
|  |  | ||||||
|  | namespace ClockingHint { | ||||||
|  |  | ||||||
|  | enum class Preference { | ||||||
|  | 	/// The component doesn't currently require a clock signal. | ||||||
|  | 	None, | ||||||
|  | 	/// The component can be clocked only immediate prior to (explicit) accesses. | ||||||
|  | 	JustInTime, | ||||||
|  | 	/// The component require real-time clocking. | ||||||
|  | 	RealTime | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Source; | ||||||
|  |  | ||||||
|  | struct Observer { | ||||||
|  | 	/// Called to inform an observer that the component @c component has changed its clocking requirements. | ||||||
|  | 	virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	An clocking hint source is any component that can provide hints as to the type of | ||||||
|  | 	clocking required for accurate emulation. A disk controller is an archetypal example. | ||||||
|  |  | ||||||
|  | 	Types of clocking are: | ||||||
|  |  | ||||||
|  | 		- none: | ||||||
|  | 			a component that acts and reacts to direct contact but does not have a state that autonomously evolves. | ||||||
|  | 			E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command. | ||||||
|  |  | ||||||
|  | 		- just-in-time: | ||||||
|  | 			a component that has an evolving state but can receive clock updates only immediately before a | ||||||
|  | 			direct contact. This is possibly the most common kind of component. | ||||||
|  |  | ||||||
|  | 		- real-time: | ||||||
|  | 			a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example | ||||||
|  | 			so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of | ||||||
|  | 			the machine rather than waiting to be called upon, or because there's some other non-obvious relationship | ||||||
|  | 			at play. | ||||||
|  |  | ||||||
|  | 	A clocking hint source can signal changes in preferred clocking to an observer. | ||||||
|  |  | ||||||
|  | 	This is intended to allow for performance improvements to machines with components that can be messaged selectively. | ||||||
|  | 	The observer callout is virtual so the intended use case is that a machine holds a component that might go through | ||||||
|  | 	periods of different clocking requirements. | ||||||
|  |  | ||||||
|  | 	Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that | ||||||
|  | 	the saved or deferred ::run_fors add up to a substantial amount. | ||||||
|  |  | ||||||
|  | 	The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency. | ||||||
|  | */ | ||||||
|  | class Source { | ||||||
|  | 	public: | ||||||
|  | 		/// Registers @c observer as the new clocking observer. | ||||||
|  | 		void set_clocking_hint_observer(Observer *observer) { | ||||||
|  | 			observer_ = observer; | ||||||
|  | 			update_clocking_observer(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// @returns the current preferred clocking strategy. | ||||||
|  | 		virtual Preference preferred_clocking() = 0; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Observer *observer_ = nullptr; | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		/*! | ||||||
|  | 			Provided for subclasses; call this whenever the clocking preference might have changed. | ||||||
|  | 			This will notify the observer if there is one. | ||||||
|  | 		*/ | ||||||
|  | 		void update_clocking_observer() { | ||||||
|  | 			if(!observer_) return; | ||||||
|  | 			observer_->set_component_prefers_clocking(this, preferred_clocking()); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ClockingHintSource_h */ | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 01/08/2017. | //  Created by Thomas Harte on 01/08/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef ForceInline_hpp | #ifndef ForceInline_hpp | ||||||
|   | |||||||
| @@ -1,60 +0,0 @@ | |||||||
| // |  | ||||||
| //  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 */ |  | ||||||
							
								
								
									
										18
									
								
								ClockReceiver/TimeTypes.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ClockReceiver/TimeTypes.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | // | ||||||
|  | //  TimeTypes.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/03/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef TimeTypes_h | ||||||
|  | #define TimeTypes_h | ||||||
|  |  | ||||||
|  | namespace Time { | ||||||
|  |  | ||||||
|  | typedef double Seconds; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* TimeTypes_h */ | ||||||
| @@ -3,11 +3,13 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 17/09/2016. | //  Created by Thomas Harte on 17/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "1770.hpp" | #include "1770.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" | #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| using namespace WD; | using namespace WD; | ||||||
|  |  | ||||||
| @@ -25,10 +27,10 @@ void WD1770::set_register(int address, uint8_t value) { | |||||||
| 			if((value&0xf0) == 0xd0) { | 			if((value&0xf0) == 0xd0) { | ||||||
| 				if(value == 0xd0) { | 				if(value == 0xd0) { | ||||||
| 					// Force interrupt **immediately**. | 					// Force interrupt **immediately**. | ||||||
| 					printf("Force interrupt immediately\n"); | 					LOG("Force interrupt immediately"); | ||||||
| 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | 					posit_event(static_cast<int>(Event1770::ForceInterrupt)); | ||||||
| 				} else { | 				} else { | ||||||
| 					printf("!!!TODO: force interrupt!!!\n"); | 					ERROR("!!!TODO: force interrupt!!!"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.type = Status::One; | 						status.type = Status::One; | ||||||
| 					}); | 					}); | ||||||
| @@ -193,7 +195,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 	// Wait for a new command, branch to the appropriate handler. | 	// Wait for a new command, branch to the appropriate handler. | ||||||
| 	case 0: | 	case 0: | ||||||
| 	wait_for_command: | 	wait_for_command: | ||||||
| 		printf("Idle...\n"); | 		LOG("Idle..."); | ||||||
| 		set_data_mode(DataMode::Scanning); | 		set_data_mode(DataMode::Scanning); | ||||||
| 		index_hole_count_ = 0; | 		index_hole_count_ = 0; | ||||||
|  |  | ||||||
| @@ -209,7 +211,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			status.interrupt_request = false; | 			status.interrupt_request = false; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		printf("Starting %02x\n", command_); | 		LOG("Starting " << std::hex << command_ << std::endl); | ||||||
|  |  | ||||||
| 		if(!(command_ & 0x80)) goto begin_type_1; | 		if(!(command_ & 0x80)) goto begin_type_1; | ||||||
| 		if(!(command_ & 0x40)) goto begin_type_2; | 		if(!(command_ & 0x40)) goto begin_type_2; | ||||||
| @@ -282,7 +284,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			track_ = 0; | 			track_ = 0; | ||||||
| 			goto verify; | 			goto verify; | ||||||
| 		} | 		} | ||||||
| 		get_drive().step(step_direction_ ? 1 : -1); | 		get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1)); | ||||||
| 		unsigned int time_to_wait; | 		unsigned int time_to_wait; | ||||||
| 		switch(command_ & 3) { | 		switch(command_ & 3) { | ||||||
| 			default: | 			default: | ||||||
| @@ -327,7 +329,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(header_[0] == track_) { | 			if(header_[0] == track_) { | ||||||
| 				printf("Reached track %d\n", track_); | 				LOG("Reached track " << std::dec << track_); | ||||||
| 				update_status([] (Status &status) { | 				update_status([] (Status &status) { | ||||||
| 					status.crc_error = false; | 					status.crc_error = false; | ||||||
| 				}); | 				}); | ||||||
| @@ -396,20 +398,20 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		READ_ID(); | 		READ_ID(); | ||||||
|  |  | ||||||
| 		if(index_hole_count_ == 5) { | 		if(index_hole_count_ == 5) { | ||||||
| 			printf("Failed to find sector %d\n", sector_); | 			LOG("Failed to find sector " << std::dec << sector_); | ||||||
| 			update_status([] (Status &status) { | 			update_status([] (Status &status) { | ||||||
| 				status.record_not_found = true; | 				status.record_not_found = true; | ||||||
| 			}); | 			}); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		if(distance_into_section_ == 7) { | 		if(distance_into_section_ == 7) { | ||||||
| 			printf("Considering %d/%d\n", header_[0], header_[2]); | 			LOG("Considering " << std::dec << header_[0] << "/" << header_[2]); | ||||||
| 			set_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]); | 				LOG("Found " << std::dec << header_[0] << "/" << header_[2]); | ||||||
| 				if(get_crc_generator().get_value()) { | 				if(get_crc_generator().get_value()) { | ||||||
| 					printf("CRC error; back to searching\n"); | 					LOG("CRC error; back to searching"); | ||||||
| 					update_status([] (Status &status) { | 					update_status([] (Status &status) { | ||||||
| 						status.crc_error = true; | 						status.crc_error = true; | ||||||
| 					}); | 					}); | ||||||
| @@ -465,7 +467,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 		distance_into_section_++; | 		distance_into_section_++; | ||||||
| 		if(distance_into_section_ == 2) { | 		if(distance_into_section_ == 2) { | ||||||
| 			if(get_crc_generator().get_value()) { | 			if(get_crc_generator().get_value()) { | ||||||
| 				printf("CRC error; terminating\n"); | 				LOG("CRC error; terminating"); | ||||||
| 				update_status([this] (Status &status) { | 				update_status([this] (Status &status) { | ||||||
| 					status.crc_error = true; | 					status.crc_error = true; | ||||||
| 				}); | 				}); | ||||||
| @@ -476,7 +478,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 				sector_++; | 				sector_++; | ||||||
| 				goto test_type2_write_protection; | 				goto test_type2_write_protection; | ||||||
| 			} | 			} | ||||||
| 			printf("Finished reading sector %d\n", sector_); | 			LOG("Finished reading sector " << std::dec << sector_); | ||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
| 		} | 		} | ||||||
| 		goto type2_check_crc; | 		goto type2_check_crc; | ||||||
| @@ -522,7 +524,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 	type2_write_loop: | 	type2_write_loop: | ||||||
| 		/* | 		/* | ||||||
| 			This deviates from the data sheet slightly since that would prima facie request one more byte | 			This deviates from the data sheet slightly since that would prima facie request one more byte | ||||||
| 			of data than is actually written — the last time around the loop it has transferred from the | 			of data than is actually written; the last time around the loop it has transferred from the | ||||||
| 			data register to the data shift register, set data request, written the byte, checked that data | 			data register to the data shift register, set data request, written the byte, checked that data | ||||||
| 			request has been satified, then finally considers whether all bytes are done. Based on both | 			request has been satified, then finally considers whether all bytes are done. Based on both | ||||||
| 			natural expectations and the way that emulated machines responded, I believe that to be a | 			natural expectations and the way that emulated machines responded, I believe that to be a | ||||||
| @@ -558,7 +560,7 @@ void WD1770::posit_event(int new_event_type) { | |||||||
| 			sector_++; | 			sector_++; | ||||||
| 			goto test_type2_write_protection; | 			goto test_type2_write_protection; | ||||||
| 		} | 		} | ||||||
| 		printf("Wrote sector %d\n", sector_); | 		LOG("Wrote sector " << std::dec << sector_); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 17/09/2016. | //  Created by Thomas Harte on 17/09/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef _770_hpp | #ifndef _770_hpp | ||||||
| @@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController { | |||||||
|  |  | ||||||
| 		/// Runs the controller for @c number_of_cycles cycles. | 		/// Runs the controller for @c number_of_cycles cycles. | ||||||
| 		void run_for(const 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, | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 06/06/2016. | //  Created by Thomas Harte on 06/06/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef _522_hpp | #ifndef _522_hpp | ||||||
| @@ -113,6 +113,9 @@ template <class T> class MOS6522: public MOS6522Base { | |||||||
| 		/*! Gets a register value. */ | 		/*! Gets a register value. */ | ||||||
| 		uint8_t get_register(int address); | 		uint8_t get_register(int address); | ||||||
|  |  | ||||||
|  | 		/*! @returns the bus handler. */ | ||||||
|  | 		T &bus_handler(); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		T &bus_handler_; | 		T &bus_handler_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 04/09/2017. | //  Created by Thomas Harte on 04/09/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "../6522.hpp" | #include "../6522.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 04/09/2017. | //  Created by Thomas Harte on 04/09/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) { | template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) { | ||||||
| @@ -145,6 +145,10 @@ template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t outp | |||||||
| 	return (input & ~output_mask) | (output & output_mask); | 	return (input & ~output_mask) | (output & output_mask); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template <typename T> T &MOS6522<T>::bus_handler() { | ||||||
|  | 	return bus_handler_; | ||||||
|  | } | ||||||
|  |  | ||||||
| // Delegate and communications | // Delegate and communications | ||||||
| template <typename T> void MOS6522<T>::reevaluate_interrupts() { | template <typename T> void MOS6522<T>::reevaluate_interrupts() { | ||||||
| 	bool new_interrupt_status = get_interrupt_line(); | 	bool new_interrupt_status = get_interrupt_line(); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 04/09/2017. | //  Created by Thomas Harte on 04/09/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef _522Storage_hpp | #ifndef _522Storage_hpp | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 04/09/2017. | //  Created by Thomas Harte on 04/09/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "../6522.hpp" | #include "../6522.hpp" | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 19/06/2016. | //  Created by Thomas Harte on 19/06/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef _532_hpp | #ifndef _532_hpp | ||||||
|   | |||||||
| @@ -3,14 +3,14 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 05/06/2016. | //  Created by Thomas Harte on 05/06/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "6560.hpp" | #include "6560.hpp" | ||||||
|  |  | ||||||
| #include <cstring> | #include <cstring> | ||||||
|  |  | ||||||
| using namespace MOS; | using namespace MOS::MOS6560; | ||||||
|  |  | ||||||
| AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : | ||||||
| 	audio_queue_(audio_queue) {} | 	audio_queue_(audio_queue) {} | ||||||
| @@ -18,7 +18,7 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue | |||||||
|  |  | ||||||
| void AudioGenerator::set_volume(uint8_t volume) { | void AudioGenerator::set_volume(uint8_t volume) { | ||||||
| 	audio_queue_.defer([=]() { | 	audio_queue_.defer([=]() { | ||||||
| 		volume_ = volume; | 		volume_ = static_cast<int16_t>(volume) * range_multiplier_; | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -106,7 +106,7 @@ static uint8_t noise_pattern[] = { | |||||||
| // means every second cycle, etc. | // means every second cycle, etc. | ||||||
|  |  | ||||||
| void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; ++c) { | ||||||
| 		update(0, 2, shift); | 		update(0, 2, shift); | ||||||
| 		update(1, 1, shift); | 		update(1, 1, shift); | ||||||
| 		update(2, 0, shift); | 		update(2, 0, shift); | ||||||
| @@ -114,17 +114,17 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) | |||||||
|  |  | ||||||
| 		// this sums the output of all three sounds channels plus a DC offset for volume; | 		// this sums the output of all three sounds channels plus a DC offset for volume; | ||||||
| 		// TODO: what's the real ratio of this stuff? | 		// TODO: what's the real ratio of this stuff? | ||||||
| 		target[c] = ( | 		target[c] = static_cast<int16_t>( | ||||||
| 			(shift_registers_[0]&1) + | 			(shift_registers_[0]&1) + | ||||||
| 			(shift_registers_[1]&1) + | 			(shift_registers_[1]&1) + | ||||||
| 			(shift_registers_[2]&1) + | 			(shift_registers_[2]&1) + | ||||||
| 			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) | 			((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) | ||||||
| 		) * volume_ * 700 + volume_ * 44; | 		) * volume_ + (volume_ >> 4); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void AudioGenerator::skip_samples(std::size_t number_of_samples) { | void AudioGenerator::skip_samples(std::size_t number_of_samples) { | ||||||
| 	for(unsigned int c = 0; c < number_of_samples; c++) { | 	for(unsigned int c = 0; c < number_of_samples; ++c) { | ||||||
| 		update(0, 2, shift); | 		update(0, 2, shift); | ||||||
| 		update(1, 1, shift); | 		update(1, 1, shift); | ||||||
| 		update(2, 0, shift); | 		update(2, 0, shift); | ||||||
| @@ -132,6 +132,10 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void AudioGenerator::set_sample_volume_range(std::int16_t range) { | ||||||
|  | 	range_multiplier_ = static_cast<int16_t>(range / 64); | ||||||
|  | } | ||||||
|  |  | ||||||
| #undef shift | #undef shift | ||||||
| #undef increment | #undef increment | ||||||
| #undef update | #undef update | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 05/06/2016. | //  Created by Thomas Harte on 05/06/2016. | ||||||
| //  Copyright © 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef _560_hpp | #ifndef _560_hpp | ||||||
| @@ -16,6 +16,7 @@ | |||||||
| #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  |  | ||||||
| namespace MOS { | namespace MOS { | ||||||
|  | namespace MOS6560 { | ||||||
|  |  | ||||||
| // audio state | // audio state | ||||||
| class AudioGenerator: public ::Outputs::Speaker::SampleSource { | class AudioGenerator: public ::Outputs::Speaker::SampleSource { | ||||||
| @@ -25,8 +26,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | |||||||
| 		void set_volume(uint8_t volume); | 		void set_volume(uint8_t volume); | ||||||
| 		void set_control(int channel, uint8_t value); | 		void set_control(int channel, uint8_t value); | ||||||
|  |  | ||||||
|  | 		// For ::SampleSource. | ||||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
| 		void skip_samples(std::size_t number_of_samples); | 		void skip_samples(std::size_t number_of_samples); | ||||||
|  | 		void set_sample_volume_range(std::int16_t range); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||||
| @@ -34,7 +37,19 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | |||||||
| 		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels | 		unsigned int counters_[4] = {2, 1, 0, 0}; 	// create a slight phase offset for the three channels | ||||||
| 		unsigned int shift_registers_[4] = {0, 0, 0, 0}; | 		unsigned int shift_registers_[4] = {0, 0, 0, 0}; | ||||||
| 		uint8_t control_registers_[4] = {0, 0, 0, 0}; | 		uint8_t control_registers_[4] = {0, 0, 0, 0}; | ||||||
| 		uint8_t volume_ = 0; | 		int16_t volume_ = 0; | ||||||
|  | 		int16_t range_multiplier_ = 1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct BusHandler { | ||||||
|  | 	void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { | ||||||
|  | 		*pixel_data = 0xff; | ||||||
|  | 		*colour_data = 0xff; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class OutputMode { | ||||||
|  | 	PAL, NTSC | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -45,27 +60,36 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | |||||||
|  |  | ||||||
| 	@c set_register and @c get_register provide register access. | 	@c set_register and @c get_register provide register access. | ||||||
| */ | */ | ||||||
| template <class T> class MOS6560 { | template <class BusHandler> class MOS6560 { | ||||||
| 	public: | 	public: | ||||||
| 		MOS6560() : | 		MOS6560(BusHandler &bus_handler) : | ||||||
|  | 				bus_handler_(bus_handler), | ||||||
| 				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)), | 				crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)), | ||||||
| 				audio_generator_(audio_queue_), | 				audio_generator_(audio_queue_), | ||||||
| 				speaker_(audio_generator_) | 				speaker_(audio_generator_) | ||||||
| 		{ | 		{ | ||||||
| 			crt_->set_composite_sampling_function( | 			crt_->set_svideo_sampling_function( | ||||||
| 				"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | 				"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" | ||||||
| 				"{" | 				"{" | ||||||
| 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | 					"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" | ||||||
| 					"float phaseOffset = 6.283185308 * 2.0 * yc.y;" |  | ||||||
|  |  | ||||||
| 					"float chroma = cos(phase + phaseOffset);" | 					"float phaseOffset = 6.283185308 * 2.0 * yc.y;" | ||||||
| 					"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);" | 					"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);" | ||||||
|  |  | ||||||
|  | 					"return vec2(yc.x, chroma);" | ||||||
| 				"}"); | 				"}"); | ||||||
|  |  | ||||||
|  | 			// default to s-video output | ||||||
|  | 			crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo); | ||||||
|  |  | ||||||
| 			// default to NTSC | 			// default to NTSC | ||||||
| 			set_output_mode(OutputMode::NTSC); | 			set_output_mode(OutputMode::NTSC); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		~MOS6560() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_clock_rate(double clock_rate) { | 		void set_clock_rate(double clock_rate) { | ||||||
| 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | ||||||
| 		} | 		} | ||||||
| @@ -77,38 +101,34 @@ template <class T> class MOS6560 { | |||||||
| 			speaker_.set_high_frequency_cutoff(cutoff); | 			speaker_.set_high_frequency_cutoff(cutoff); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		enum OutputMode { |  | ||||||
| 			PAL, NTSC |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the output mode to either PAL or NTSC. | 			Sets the output mode to either PAL or NTSC. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_output_mode(OutputMode output_mode) { | 		void set_output_mode(OutputMode output_mode) { | ||||||
| 			output_mode_ = output_mode; | 			output_mode_ = output_mode; | ||||||
|  |  | ||||||
| 			// Lumunances are encoded trivially: on a 0–255 scale. | 			// Luminances are encoded trivially: on a 0-255 scale. | ||||||
| 			const uint8_t luminances[16] = { | 			const uint8_t luminances[16] = { | ||||||
| 				0,		255,	109,	189, | 				0,		255,	64,		192, | ||||||
| 				199,	144,	159,	161, | 				128,	128,	64,		192, | ||||||
| 				126,	227,	227,	207, | 				128,	192,	128,	255, | ||||||
| 				235,	173,	188,	196 | 				192,	192,	128,	255 | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			// Chrominances are encoded such that 0–128 is a complete revolution of phase; | 			// Chrominances are encoded such that 0-128 is a complete revolution of phase; | ||||||
| 			// anything above 191 disables the colour subcarrier. Phase is relative to the | 			// anything above 191 disables the colour subcarrier. Phase is relative to the | ||||||
| 			// colour burst, so 0 is green. | 			// colour burst, so 0 is green. | ||||||
| 			const uint8_t pal_chrominances[16] = { | 			const uint8_t pal_chrominances[16] = { | ||||||
| 				255,	255,	40,		112, | 				255,	255,	37,		101, | ||||||
| 				8,		88,		120,	56, | 				19,		86,		123,	59, | ||||||
| 				40,		48,		40,		112, | 				46,		53,		37,		101, | ||||||
| 				8,		88,		120,	56, | 				19,		86,		123,	59, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t ntsc_chrominances[16] = { | 			const uint8_t ntsc_chrominances[16] = { | ||||||
| 				255,	255,	8,		72, | 				255,	255,	121,	57, | ||||||
| 				32,		88,		48,		112, | 				103,	42,		80,		16, | ||||||
| 				0,		0,		8,		72, | 				0,		9,		121,	57, | ||||||
| 				32,		88,		48,		112, | 				103,	42,		80,		16, | ||||||
| 			}; | 			}; | ||||||
| 			const uint8_t *chrominances; | 			const uint8_t *chrominances; | ||||||
| 			Outputs::CRT::DisplayType display_type; | 			Outputs::CRT::DisplayType display_type; | ||||||
| @@ -118,7 +138,8 @@ template <class T> class MOS6560 { | |||||||
| 					chrominances = pal_chrominances; | 					chrominances = pal_chrominances; | ||||||
| 					display_type = Outputs::CRT::DisplayType::PAL50; | 					display_type = Outputs::CRT::DisplayType::PAL50; | ||||||
| 					timing_.cycles_per_line = 71; | 					timing_.cycles_per_line = 71; | ||||||
| 					timing_.line_counter_increment_offset = 0; | 					timing_.line_counter_increment_offset = 4; | ||||||
|  | 					timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset; | ||||||
| 					timing_.lines_per_progressive_field = 312; | 					timing_.lines_per_progressive_field = 312; | ||||||
| 					timing_.supports_interlacing = false; | 					timing_.supports_interlacing = false; | ||||||
| 				break; | 				break; | ||||||
| @@ -127,23 +148,23 @@ template <class T> class MOS6560 { | |||||||
| 					chrominances = ntsc_chrominances; | 					chrominances = ntsc_chrominances; | ||||||
| 					display_type = Outputs::CRT::DisplayType::NTSC60; | 					display_type = Outputs::CRT::DisplayType::NTSC60; | ||||||
| 					timing_.cycles_per_line = 65; | 					timing_.cycles_per_line = 65; | ||||||
| 					timing_.line_counter_increment_offset = 65 - 33;	// TODO: this is a bit of a hack; separate vertical and horizontal counting | 					timing_.line_counter_increment_offset = 40; | ||||||
|  | 					timing_.final_line_increment_position = 58; | ||||||
| 					timing_.lines_per_progressive_field = 261; | 					timing_.lines_per_progressive_field = 261; | ||||||
| 					timing_.supports_interlacing = true; | 					timing_.supports_interlacing = true; | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | 			crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); | ||||||
| 			crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); |  | ||||||
|  |  | ||||||
| //			switch(output_mode) { | 			switch(output_mode) { | ||||||
| //				case OutputMode::PAL: | 				case OutputMode::PAL: | ||||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f)); | 					crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f)); | ||||||
| //				break; | 				break; | ||||||
| //				case OutputMode::NTSC: | 				case OutputMode::NTSC: | ||||||
| //					crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); | 					crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f)); | ||||||
| //				break; | 				break; | ||||||
| //			} | 			} | ||||||
|  |  | ||||||
| 			for(int c = 0; c < 16; c++) { | 			for(int c = 0; c < 16; c++) { | ||||||
| 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | 				uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); | ||||||
| @@ -166,7 +187,6 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 				// keep track of internal time relative to this scanline | 				// keep track of internal time relative to this scanline | ||||||
| 				horizontal_counter_++; | 				horizontal_counter_++; | ||||||
| 				full_frame_counter_++; |  | ||||||
| 				if(horizontal_counter_ == timing_.cycles_per_line) { | 				if(horizontal_counter_ == timing_.cycles_per_line) { | ||||||
| 					if(horizontal_drawing_latch_) { | 					if(horizontal_drawing_latch_) { | ||||||
| 						current_character_row_++; | 						current_character_row_++; | ||||||
| @@ -188,9 +208,8 @@ template <class T> class MOS6560 { | |||||||
| 					horizontal_drawing_latch_ = false; | 					horizontal_drawing_latch_ = false; | ||||||
|  |  | ||||||
| 					vertical_counter_ ++; | 					vertical_counter_ ++; | ||||||
| 					if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) { | 					if(vertical_counter_ == lines_this_field()) { | ||||||
| 						vertical_counter_ = 0; | 						vertical_counter_ = 0; | ||||||
| 						full_frame_counter_ = 0; |  | ||||||
|  |  | ||||||
| 						if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true; | 						if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true; | ||||||
| 						current_row_ = 0; | 						current_row_ = 0; | ||||||
| @@ -238,7 +257,7 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 				uint8_t pixel_data; | 				uint8_t pixel_data; | ||||||
| 				uint8_t colour_data; | 				uint8_t colour_data; | ||||||
| 				static_cast<T *>(this)->perform_read(fetch_address, &pixel_data, &colour_data); | 				bus_handler_.perform_read(fetch_address, &pixel_data, &colour_data); | ||||||
|  |  | ||||||
| 				// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should | 				// TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should | ||||||
| 				// divide the byte it is set for 3:1 and then continue as usual. | 				// divide the byte it is set for 3:1 and then continue as usual. | ||||||
| @@ -252,7 +271,7 @@ template <class T> class MOS6560 { | |||||||
|  |  | ||||||
| 				// apply vertical sync | 				// apply vertical sync | ||||||
| 				if( | 				if( | ||||||
| 					(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) || | 					(vertical_counter_ < 3 && is_odd_frame()) || | ||||||
| 					(registers_.interlaced && | 					(registers_.interlaced && | ||||||
| 						( | 						( | ||||||
| 							(vertical_counter_ == 0 && horizontal_counter_ > 32) || | 							(vertical_counter_ == 0 && horizontal_counter_ > 32) || | ||||||
| @@ -268,7 +287,7 @@ template <class T> class MOS6560 { | |||||||
| 						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break; | 						case State::Sync:			crt_->output_sync(cycles_in_state_ * 4);														break; | ||||||
| 						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0);		break; | 						case State::ColourBurst:	crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0);		break; | ||||||
| 						case State::Border:			output_border(cycles_in_state_ * 4);															break; | 						case State::Border:			output_border(cycles_in_state_ * 4);															break; | ||||||
| 						case State::Pixels:			crt_->output_data(cycles_in_state_ * 4, 1);														break; | 						case State::Pixels:			crt_->output_data(cycles_in_state_ * 4);														break; | ||||||
| 					} | 					} | ||||||
| 					output_state_ = this_state_; | 					output_state_ = this_state_; | ||||||
| 					cycles_in_state_ = 0; | 					cycles_in_state_ = 0; | ||||||
| @@ -281,6 +300,9 @@ template <class T> class MOS6560 { | |||||||
| 				cycles_in_state_++; | 				cycles_in_state_++; | ||||||
|  |  | ||||||
| 				if(this_state_ == State::Pixels) { | 				if(this_state_ == State::Pixels) { | ||||||
|  | 					// TODO: palette changes can happen within half-characters; the below needs to be divided. | ||||||
|  | 					// Also: a perfect opportunity to rearrange this inner loop for no longer needing to be | ||||||
|  | 					// two parts with a cooperative owner? | ||||||
| 					if(column_counter_&1) { | 					if(column_counter_&1) { | ||||||
| 						character_value_ = pixel_data; | 						character_value_ = pixel_data; | ||||||
|  |  | ||||||
| @@ -321,7 +343,10 @@ template <class T> class MOS6560 { | |||||||
| 						character_code_ = pixel_data; | 						character_code_ = pixel_data; | ||||||
| 						character_colour_ = colour_data; | 						character_colour_ = colour_data; | ||||||
| 					} | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Keep counting columns even if sync or the colour burst have interceded. | ||||||
|  | 				if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) { | ||||||
| 					column_counter_++; | 					column_counter_++; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @@ -404,15 +429,15 @@ template <class T> class MOS6560 { | |||||||
| 		*/ | 		*/ | ||||||
| 		uint8_t get_register(int address) { | 		uint8_t get_register(int address) { | ||||||
| 			address &= 0xf; | 			address &= 0xf; | ||||||
| 			int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line; |  | ||||||
| 			switch(address) { | 			switch(address) { | ||||||
| 				default: return registers_.direct_values[address]; | 				default: return registers_.direct_values[address]; | ||||||
| 				case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f); | 				case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f); | ||||||
| 				case 0x04: return (current_line >> 1) & 0xff; | 				case 0x04: return (raster_value() >> 1) & 0xff; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		BusHandler &bus_handler_; | ||||||
| 		std::unique_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
| @@ -443,7 +468,29 @@ template <class T> class MOS6560 { | |||||||
| 		unsigned int cycles_in_state_; | 		unsigned int cycles_in_state_; | ||||||
|  |  | ||||||
| 		// counters that cover an entire field | 		// counters that cover an entire field | ||||||
| 		int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_; | 		int horizontal_counter_ = 0, vertical_counter_ = 0; | ||||||
|  | 		const int lines_this_field() { | ||||||
|  | 			// Necessary knowledge here: only the NTSC 6560 supports interlaced video. | ||||||
|  | 			return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field; | ||||||
|  | 		} | ||||||
|  | 		const int raster_value() { | ||||||
|  | 			const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line; | ||||||
|  | 			const int line = vertical_counter_ + bonus_line; | ||||||
|  | 			const int final_line = lines_this_field(); | ||||||
|  |  | ||||||
|  | 			if(line < final_line) | ||||||
|  | 				return line; | ||||||
|  |  | ||||||
|  | 			if(is_odd_frame()) { | ||||||
|  | 				return (horizontal_counter_ >= timing_.final_line_increment_position) ? 0 : final_line - 1; | ||||||
|  | 			} else { | ||||||
|  | 				return line % final_line; | ||||||
|  | 			} | ||||||
|  | 			// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737 | ||||||
|  | 		} | ||||||
|  | 		bool is_odd_frame() { | ||||||
|  | 			return is_odd_frame_ || !registers_.interlaced; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// latches dictating start and length of drawing | 		// latches dictating start and length of drawing | ||||||
| 		bool vertical_drawing_latch_, horizontal_drawing_latch_; | 		bool vertical_drawing_latch_, horizontal_drawing_latch_; | ||||||
| @@ -473,12 +520,14 @@ template <class T> class MOS6560 { | |||||||
| 		struct { | 		struct { | ||||||
| 			int cycles_per_line; | 			int cycles_per_line; | ||||||
| 			int line_counter_increment_offset; | 			int line_counter_increment_offset; | ||||||
|  | 			int final_line_increment_position; | ||||||
| 			int lines_per_progressive_field; | 			int lines_per_progressive_field; | ||||||
| 			bool supports_interlacing; | 			bool supports_interlacing; | ||||||
| 		} timing_; | 		} timing_; | ||||||
| 		OutputMode output_mode_; | 		OutputMode output_mode_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | } | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* _560_hpp */ | #endif /* _560_hpp */ | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 31/07/2017. | //  Created by Thomas Harte on 31/07/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef CRTC6845_hpp | #ifndef CRTC6845_hpp | ||||||
| @@ -35,7 +35,7 @@ class BusHandler { | |||||||
| 		void perform_bus_cycle_phase1(const BusState &) {} | 		void perform_bus_cycle_phase1(const BusState &) {} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated | 			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 | 			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 | 			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. | 			having to wait until the next cycle has begun. | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 01/08/2017. | //  Created by Thomas Harte on 01/08/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef i8255_hpp | #ifndef i8255_hpp | ||||||
|   | |||||||
| @@ -3,13 +3,12 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 05/08/2017. | //  Created by Thomas Harte on 05/08/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "i8272.hpp" | #include "i8272.hpp" | ||||||
| //#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp" |  | ||||||
|  |  | ||||||
| #include <cstdio> | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| using namespace Intel::i8272; | using namespace Intel::i8272; | ||||||
|  |  | ||||||
| @@ -83,8 +82,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : | |||||||
| 	posit_event(static_cast<int>(Event8272::CommandByte)); | 	posit_event(static_cast<int>(Event8272::CommandByte)); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool i8272::is_sleeping() { | ClockingHint::Preference i8272::preferred_clocking() { | ||||||
| 	return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); | 	const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking(); | ||||||
|  | 	if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking; | ||||||
|  | 	return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::run_for(Cycles cycles) { | void i8272::run_for(Cycles cycles) { | ||||||
| @@ -113,9 +114,9 @@ void i8272::run_for(Cycles cycles) { | |||||||
| 				while(steps--) { | 				while(steps--) { | ||||||
| 					// Perform a step. | 					// Perform a step. | ||||||
| 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | ||||||
| 					printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); | 					LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position)); | ||||||
| 					select_drive(c); | 					select_drive(c); | ||||||
| 					get_drive().step(direction); | 					get_drive().step(Storage::Disk::HeadPosition(direction)); | ||||||
| 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | ||||||
|  |  | ||||||
| 					// Check for completion. | 					// Check for completion. | ||||||
| @@ -159,7 +160,7 @@ void i8272::run_for(Cycles cycles) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | ||||||
| 	if(is_sleeping_) update_sleep_observer(); | 	if(is_sleeping_) update_clocking_observer(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void i8272::set_register(int address, uint8_t value) { | void i8272::set_register(int address, uint8_t value) { | ||||||
| @@ -198,7 +199,7 @@ uint8_t i8272::get_register(int address) { | |||||||
|  |  | ||||||
| #define MS_TO_CYCLES(x)			x * 8000 | #define MS_TO_CYCLES(x)			x * 8000 | ||||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__: | ||||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; | #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return; | ||||||
|  |  | ||||||
| #define PASTE(x, y) x##y | #define PASTE(x, y) x##y | ||||||
| #define CONCAT(x, y) PASTE(x, y) | #define CONCAT(x, y) PASTE(x, y) | ||||||
| @@ -257,7 +258,7 @@ uint8_t i8272::get_register(int address) { | |||||||
| 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | 		if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {	\ | ||||||
| 			head_timers_running_++;	\ | 			head_timers_running_++;	\ | ||||||
| 			is_sleeping_ = false;	\ | 			is_sleeping_ = false;	\ | ||||||
| 			update_sleep_observer();	\ | 			update_clocking_observer();	\ | ||||||
| 		}	\ | 		}	\ | ||||||
| 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | 		drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ | ||||||
| 	} | 	} | ||||||
| @@ -384,17 +385,17 @@ void i8272::posit_event(int event_type) { | |||||||
| 		// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the | 		// 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. | 		// values in the internal registers. | ||||||
| 			index_hole_limit_ = 2; | 			index_hole_limit_ = 2; | ||||||
| //			printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_); | //			LOG("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_); | ||||||
| 		find_next_sector: | 		find_next_sector: | ||||||
| 			FIND_HEADER(); | 			FIND_HEADER(); | ||||||
| 			if(!index_hole_limit_) { | 			if(!index_hole_limit_) { | ||||||
| 				// Two index holes have passed wihout finding the header sought. | 				// Two index holes have passed wihout finding the header sought. | ||||||
| //				printf("Not found\n"); | //				LOG("Not found"); | ||||||
| 				SetNoData(); | 				SetNoData(); | ||||||
| 				goto abort; | 				goto abort; | ||||||
| 			} | 			} | ||||||
| 			index_hole_count_ = 0; | 			index_hole_count_ = 0; | ||||||
| //			printf("Header\n"); | //			LOG("Header"); | ||||||
| 			READ_HEADER(); | 			READ_HEADER(); | ||||||
| 			if(index_hole_count_) { | 			if(index_hole_count_) { | ||||||
| 				// This implies an index hole was sighted within the header. Error out. | 				// This implies an index hole was sighted within the header. Error out. | ||||||
| @@ -405,11 +406,11 @@ void i8272::posit_event(int event_type) { | |||||||
| 				// This implies a CRC error in the header; mark as such but continue. | 				// This implies a CRC error in the header; mark as such but continue. | ||||||
| 				SetDataError(); | 				SetDataError(); | ||||||
| 			} | 			} | ||||||
| //			printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); | //			LOG("Considering << PADHEX(2) << 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; | 			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 | 			// Branch to whatever is supposed to happen next | ||||||
| //			printf("Proceeding\n"); | //			LOG("Proceeding"); | ||||||
| 			switch(command_[0] & 0x1f) { | 			switch(command_[0] & 0x1f) { | ||||||
| 				case CommandReadData: | 				case CommandReadData: | ||||||
| 				case CommandReadDeletedData: | 				case CommandReadDeletedData: | ||||||
| @@ -423,7 +424,13 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs the read data or read deleted data command. | 	// Performs the read data or read deleted data command. | ||||||
| 	read_data: | 	read_data: | ||||||
| 			printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); | 			LOG(PADHEX(2) << "Read [deleted] data [" | ||||||
|  | 				<< static_cast<int>(command_[2]) << " " | ||||||
|  | 				<< static_cast<int>(command_[3]) << " " | ||||||
|  | 				<< static_cast<int>(command_[4]) << " " | ||||||
|  | 				<< static_cast<int>(command_[5]) << " ... " | ||||||
|  | 				<< static_cast<int>(command_[6]) << " " | ||||||
|  | 				<< static_cast<int>(command_[8]) << "]"); | ||||||
| 		read_next_data: | 		read_next_data: | ||||||
| 			goto read_write_find_header; | 			goto read_write_find_header; | ||||||
|  |  | ||||||
| @@ -434,7 +441,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 			ClearControlMark(); | 			ClearControlMark(); | ||||||
| 			if(event_type == static_cast<int>(Event::Token)) { | 			if(event_type == static_cast<int>(Event::Token)) { | ||||||
| 				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) { | 				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) { | ||||||
| 					// Something other than a data mark came next — impliedly an ID or index mark. | 					// Something other than a data mark came next, impliedly an ID or index mark. | ||||||
| 					SetMissingAddressMark(); | 					SetMissingAddressMark(); | ||||||
| 					SetMissingDataAddressMark(); | 					SetMissingDataAddressMark(); | ||||||
| 					goto abort;	// TODO: or read_next_data? | 					goto abort;	// TODO: or read_next_data? | ||||||
| @@ -507,7 +514,13 @@ void i8272::posit_event(int event_type) { | |||||||
| 			goto post_st012chrn; | 			goto post_st012chrn; | ||||||
|  |  | ||||||
| 	write_data: | 	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]); | 			LOG(PADHEX(2) << "Write [deleted] data [" | ||||||
|  | 				<< static_cast<int>(command_[2]) << " " | ||||||
|  | 				<< static_cast<int>(command_[3]) << " " | ||||||
|  | 				<< static_cast<int>(command_[4]) << " " | ||||||
|  | 				<< static_cast<int>(command_[5]) << " ... " | ||||||
|  | 				<< static_cast<int>(command_[6]) << " " | ||||||
|  | 				<< static_cast<int>(command_[8]) << "]"); | ||||||
|  |  | ||||||
| 			if(get_drive().get_is_read_only()) { | 			if(get_drive().get_is_read_only()) { | ||||||
| 				SetNotWriteable(); | 				SetNotWriteable(); | ||||||
| @@ -542,7 +555,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 				goto write_loop; | 				goto write_loop; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			printf("Wrote %d bytes\n", distance_into_section_); | 			LOG("Wrote " << PADDEC(0) << distance_into_section_ << " bytes"); | ||||||
| 			write_crc(); | 			write_crc(); | ||||||
| 			expects_input_ = false; | 			expects_input_ = false; | ||||||
| 			WAIT_FOR_EVENT(Event::DataWritten); | 			WAIT_FOR_EVENT(Event::DataWritten); | ||||||
| @@ -558,7 +571,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// Performs the read ID command. | 	// Performs the read ID command. | ||||||
| 	read_id: | 	read_id: | ||||||
| 		// Establishes the drive and head being addressed, and whether in double density mode. | 		// Establishes the drive and head being addressed, and whether in double density mode. | ||||||
| 			printf("Read ID [%02x %02x]\n", command_[0], command_[1]); | 			LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]"); | ||||||
|  |  | ||||||
| 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | ||||||
| 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | ||||||
| @@ -580,7 +593,11 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs read track. | 	// Performs read track. | ||||||
| 	read_track: | 	read_track: | ||||||
| 			printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]); | 			LOG(PADHEX(2) << "Read track [" | ||||||
|  | 				<< static_cast<int>(command_[2]) << " " | ||||||
|  | 				<< static_cast<int>(command_[3]) << " " | ||||||
|  | 				<< static_cast<int>(command_[4]) << " " | ||||||
|  | 				<< static_cast<int>(command_[5]) << "]"); | ||||||
|  |  | ||||||
| 			// Wait for the index hole. | 			// Wait for the index hole. | ||||||
| 			WAIT_FOR_EVENT(Event::IndexHole); | 			WAIT_FOR_EVENT(Event::IndexHole); | ||||||
| @@ -621,7 +638,7 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs format [/write] track. | 	// Performs format [/write] track. | ||||||
| 	format_track: | 	format_track: | ||||||
| 			printf("Format track\n"); | 			LOG("Format track"); | ||||||
| 			if(get_drive().get_is_read_only()) { | 			if(get_drive().get_is_read_only()) { | ||||||
| 				SetNotWriteable(); | 				SetNotWriteable(); | ||||||
| 				goto abort; | 				goto abort; | ||||||
| @@ -665,7 +682,12 @@ void i8272::posit_event(int event_type) { | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); | 			LOG(PADHEX(2) << "W:" | ||||||
|  | 				<< static_cast<int>(header_[0]) << " " | ||||||
|  | 				<< static_cast<int>(header_[1]) << " " | ||||||
|  | 				<< static_cast<int>(header_[2]) << " " | ||||||
|  | 				<< static_cast<int>(header_[3]) << ", " | ||||||
|  | 				<< get_crc_generator().get_value()); | ||||||
| 			write_crc(); | 			write_crc(); | ||||||
|  |  | ||||||
| 			// Write the sector body. | 			// Write the sector body. | ||||||
| @@ -697,15 +719,15 @@ void i8272::posit_event(int event_type) { | |||||||
| 		goto post_st012chrn; | 		goto post_st012chrn; | ||||||
|  |  | ||||||
| 	scan_low: | 	scan_low: | ||||||
| 		printf("Scan low unimplemented!!\n"); | 		ERROR("Scan low unimplemented!!"); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
| 	scan_low_or_equal: | 	scan_low_or_equal: | ||||||
| 		printf("Scan low or equal unimplemented!!\n"); | 		ERROR("Scan low or equal unimplemented!!"); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
| 	scan_high_or_equal: | 	scan_high_or_equal: | ||||||
| 		printf("Scan high or equal unimplemented!!\n"); | 		ERROR("Scan high or equal unimplemented!!"); | ||||||
| 		goto wait_for_command; | 		goto wait_for_command; | ||||||
|  |  | ||||||
| 	// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work | 	// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work | ||||||
| @@ -720,7 +742,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 				if(drives_[drive].phase != Drive::Seeking) { | 				if(drives_[drive].phase != Drive::Seeking) { | ||||||
| 					drives_seeking_++; | 					drives_seeking_++; | ||||||
| 					is_sleeping_ = false; | 					is_sleeping_ = false; | ||||||
| 					update_sleep_observer(); | 					update_clocking_observer(); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | 				// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these | ||||||
| @@ -736,11 +758,11 @@ void i8272::posit_event(int event_type) { | |||||||
| 				// up in run_for understands to mean 'keep going until track 0 is active'). | 				// up in run_for understands to mean 'keep going until track 0 is active'). | ||||||
| 				if(command_.size() > 2) { | 				if(command_.size() > 2) { | ||||||
| 					drives_[drive].target_head_position = command_[2]; | 					drives_[drive].target_head_position = command_[2]; | ||||||
| 					printf("Seek to %02x\n", command_[2]); | 					LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2])); | ||||||
| 				} else { | 				} else { | ||||||
| 					drives_[drive].target_head_position = -1; | 					drives_[drive].target_head_position = -1; | ||||||
| 					drives_[drive].head_position = 0; | 					drives_[drive].head_position = 0; | ||||||
| 					printf("Recalibrate\n"); | 					LOG("Recalibrate"); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Check whether any steps are even needed; if not then mark as completed already. | 				// Check whether any steps are even needed; if not then mark as completed already. | ||||||
| @@ -753,7 +775,7 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 	// Performs sense interrupt status. | 	// Performs sense interrupt status. | ||||||
| 	sense_interrupt_status: | 	sense_interrupt_status: | ||||||
| 			printf("Sense interrupt status\n"); | 			LOG("Sense interrupt status"); | ||||||
| 			{ | 			{ | ||||||
| 				// Find the first drive that is in the CompletedSeeking state. | 				// Find the first drive that is in the CompletedSeeking state. | ||||||
| 				int found_drive = -1; | 				int found_drive = -1; | ||||||
| @@ -781,7 +803,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 	// Performs specify. | 	// Performs specify. | ||||||
| 	specify: | 	specify: | ||||||
| 		// Just store the values, and terminate the command. | 		// Just store the values, and terminate the command. | ||||||
| 			printf("Specify\n"); | 			LOG("Specify"); | ||||||
| 			step_rate_time_ = 16 - (command_[1] >> 4);			// i.e. 1 to 16ms | 			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_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 | 			head_load_time_ = command_[2] & ~1;					// i.e. 2 to 254 ms in increments of 2ms | ||||||
| @@ -792,7 +814,7 @@ void i8272::posit_event(int event_type) { | |||||||
| 			goto wait_for_command; | 			goto wait_for_command; | ||||||
|  |  | ||||||
| 	sense_drive_status: | 	sense_drive_status: | ||||||
| 			printf("Sense drive status\n"); | 			LOG("Sense drive status"); | ||||||
| 			{ | 			{ | ||||||
| 				int drive = command_[1] & 3; | 				int drive = command_[1] & 3; | ||||||
| 				select_drive(drive); | 				select_drive(drive); | ||||||
| @@ -828,14 +850,14 @@ void i8272::posit_event(int event_type) { | |||||||
|  |  | ||||||
| 			goto post_result; | 			goto post_result; | ||||||
|  |  | ||||||
| 	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the | 	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the | ||||||
| 	// last thing in it will be returned first. | 	// last thing in it will be returned first. | ||||||
| 	post_result: | 	post_result: | ||||||
| 			printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); | 			LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; "); | ||||||
| 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | ||||||
| 				printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); | 				LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c])); | ||||||
| 			} | 			} | ||||||
| 			printf("\n"); | 			LOGNBR(std::endl); | ||||||
|  |  | ||||||
| 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | 			// Set ready to send data to the processor, no longer in non-DMA execution phase. | ||||||
| 			is_executing_ = false; | 			is_executing_ = false; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 05/08/2017. | //  Created by Thomas Harte on 05/08/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #ifndef i8272_hpp | #ifndef i8272_hpp | ||||||
| @@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 		void set_dma_acknowledge(bool dack); | 		void set_dma_acknowledge(bool dack); | ||||||
| 		void set_terminal_count(bool tc); | 		void set_terminal_count(bool tc); | ||||||
|  |  | ||||||
| 		bool is_sleeping(); | 		ClockingHint::Preference preferred_clocking() override; | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		virtual void select_drive(int number) = 0; | 		virtual void select_drive(int number) = 0; | ||||||
| @@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 			ResultEmpty = (1 << 5), | 			ResultEmpty = (1 << 5), | ||||||
| 			NoLongerReady = (1 << 6) | 			NoLongerReady = (1 << 6) | ||||||
| 		}; | 		}; | ||||||
| 		void posit_event(int type); | 		void posit_event(int type) override; | ||||||
| 		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); | 		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); | ||||||
| 		int resume_point_ = 0; | 		int resume_point_ = 0; | ||||||
| 		bool is_access_command_ = false; | 		bool is_access_command_ = false; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| //  Clock Signal | //  Clock Signal | ||||||
| // | // | ||||||
| //  Created by Thomas Harte on 25/11/2017. | //  Created by Thomas Harte on 25/11/2017. | ||||||
| //  Copyright © 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| #include "9918.hpp" | #include "9918.hpp" | ||||||
| @@ -96,7 +96,7 @@ TMS9918::TMS9918(Personality p) { | |||||||
| 		"{" | 		"{" | ||||||
| 			"return texture(sampler, coordinate).rgb / vec3(255.0);" | 			"return texture(sampler, coordinate).rgb / vec3(255.0);" | ||||||
| 		"}"); | 		"}"); | ||||||
| 	crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB); | ||||||
| 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | ||||||
| 	crt_->set_input_gamma(2.8f); | 	crt_->set_input_gamma(2.8f); | ||||||
|  |  | ||||||
| @@ -496,17 +496,25 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 								} | 								} | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// Paint sprites and check for collisions. | 							// Paint sprites and check for collisions, but only if at least one sprite is active | ||||||
|  | 							// on this line. | ||||||
| 							if(sprite_set.active_sprite_slot) { | 							if(sprite_set.active_sprite_slot) { | ||||||
| 								int sprite_pixels_left = pixels_left; | 								int sprite_pixels_left = pixels_left; | ||||||
| 								const int shift_advance = sprites_magnified_ ? 1 : 2; | 								const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  |  | ||||||
| 								const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | 								static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||||
| 								const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | 								static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||||
|  |  | ||||||
| 								while(sprite_pixels_left--) { | 								while(sprite_pixels_left--) { | ||||||
|  | 									// sprite_colour is the colour that's going to reach the display after sprite logic has been | ||||||
|  | 									// applied; by default assume that nothing is going to be drawn. | ||||||
| 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | ||||||
|  |  | ||||||
|  | 									// The sprite_mask is used to keep track of whether two sprites have both sought to output | ||||||
|  | 									// a pixel at the same location, and to feed that into the status register's sprite | ||||||
|  | 									// collision bit. | ||||||
| 									int sprite_mask = 0; | 									int sprite_mask = 0; | ||||||
|  |  | ||||||
| 									int c = sprite_set.active_sprite_slot; | 									int c = sprite_set.active_sprite_slot; | ||||||
| 									while(c--) { | 									while(c--) { | ||||||
| 										SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | 										SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
| @@ -517,15 +525,24 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 										} else if(sprite.shift_position < 32) { | 										} else if(sprite.shift_position < 32) { | ||||||
| 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | ||||||
| 											mask = (mask >> 7) & 1; | 											mask = (mask >> 7) & 1; | ||||||
| 											status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; |  | ||||||
| 											sprite_mask |= mask; |  | ||||||
| 											sprite.shift_position += shift_advance; |  | ||||||
|  |  | ||||||
| 											mask &= colour_masks[sprite.info[3]&15]; | 											// Ignore the right half of whatever was collected if sprites are not in 16x16 mode. | ||||||
| 											sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | 											if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { | ||||||
|  | 												// If any previous sprite has been painted in this column and this sprite | ||||||
|  | 												// has this pixel set, set the sprite collision status bit. | ||||||
|  | 												status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; | ||||||
|  | 												sprite_mask |= mask; | ||||||
|  |  | ||||||
|  | 												// Check that the sprite colour is not transparent | ||||||
|  | 												mask &= colour_masks[sprite.info[3]&15]; | ||||||
|  | 												sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | ||||||
|  | 											} | ||||||
|  |  | ||||||
|  | 											sprite.shift_position += shift_advance; | ||||||
| 										} | 										} | ||||||
| 									} | 									} | ||||||
|  |  | ||||||
|  | 									// Output whichever sprite colour was on top. | ||||||
| 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | ||||||
| 									output_column_++; | 									output_column_++; | ||||||
| 								} | 								} | ||||||
| @@ -536,7 +553,8 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					if(output_column_ == first_right_border_column_) { | 					if(output_column_ == first_right_border_column_) { | ||||||
| 						crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4); | 						const unsigned int data_length = static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_); | ||||||
|  | 						crt_->output_data(data_length * 4, data_length); | ||||||
| 						pixel_target_ = nullptr; | 						pixel_target_ = nullptr; | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user