mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			543 Commits
		
	
	
		
			2020-02-12
			...
			2020-07-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e8cd5a0511 | ||
|  | 5ebbab6f35 | ||
|  | 84dd194afd | ||
|  | e1ad1a4cb6 | ||
|  | 9de43dac95 | ||
|  | 47f121ee4c | ||
|  | d8b699c869 | ||
|  | a7855e8c98 | ||
|  | 8dcb48254a | ||
|  | f6b7467d75 | ||
|  | 9d1d162cc8 | ||
|  | 4ee29b3266 | ||
|  | cbb0594e6b | ||
|  | 8aeebdbc99 | ||
|  | c7ef258494 | ||
|  | 4fec7c82ab | ||
|  | 9a952c889f | ||
|  | 8da7806ee9 | ||
|  | aed61f6251 | ||
|  | d065d6d98f | ||
|  | ab20a23f2b | ||
|  | e1c57b6fbe | ||
|  | 371c26251c | ||
|  | 645d198bee | ||
|  | ab1a999df4 | ||
|  | 42f2bf05bf | ||
|  | 5e55d3d7c7 | ||
|  | 1288369865 | ||
|  | d86b0d4213 | ||
|  | d91cf598be | ||
|  | 9be56aa4a2 | ||
|  | 86737454a0 | ||
|  | f0c0caf800 | ||
|  | 223a960a06 | ||
|  | f72570386c | ||
|  | 56e5491e5c | ||
|  | be1c3e9136 | ||
|  | 2d223305eb | ||
|  | 48c2dcf50e | ||
|  | fa26c82273 | ||
|  | 0763ae38dd | ||
|  | 2230ac6c38 | ||
|  | abe1e7f244 | ||
|  | 24b03f733f | ||
|  | 5a729f92c1 | ||
|  | 366793498a | ||
|  | cdda3f74ab | ||
|  | 51f4242e9b | ||
|  | d97ebae200 | ||
|  | daa195a7fb | ||
|  | 2d5e9bf1bb | ||
|  | 402f2ddbd9 | ||
|  | 8bf5ed52ea | ||
|  | b850183a1e | ||
|  | f7e13356c4 | ||
|  | 55cc3089f9 | ||
|  | a096a09c72 | ||
|  | 365e1f2e85 | ||
|  | b9e117cdcf | ||
|  | fc3a03a856 | ||
|  | f6e5a2fb04 | ||
|  | 404c35feb5 | ||
|  | b5962c58bb | ||
|  | 74da762ae1 | ||
|  | d87c840b76 | ||
|  | afb835398f | ||
|  | c982c78285 | ||
|  | b4cdf8d987 | ||
|  | 6925a04088 | ||
|  | a0e534b309 | ||
|  | 74d1ca4fa8 | ||
|  | 3c896050fb | ||
|  | 387500f01a | ||
|  | 21c41ed4cb | ||
|  | 293ab25634 | ||
|  | 3ddc1a1722 | ||
|  | 478d081095 | ||
|  | 9d4b49bbb5 | ||
|  | 4417f81014 | ||
|  | b96f7711e3 | ||
|  | 1875a03757 | ||
|  | 13336b8ad5 | ||
|  | b17cceaeaf | ||
|  | 782a62585e | ||
|  | c5d8d9127b | ||
|  | 336dffefe0 | ||
|  | e297d4cced | ||
|  | b052ca5ca2 | ||
|  | 68d4d7d10a | ||
|  | a03211c410 | ||
|  | c953ab09db | ||
|  | 68645742f7 | ||
|  | 1fbb733f7f | ||
|  | 6e4b8d58a5 | ||
|  | 7af8646470 | ||
|  | 945a9da94f | ||
|  | 2477752fa4 | ||
|  | 240d3c482b | ||
|  | 91229a1dbd | ||
|  | 4f9b3259d5 | ||
|  | 3cb1072c29 | ||
|  | 12ee8e4db4 | ||
|  | 95e98323c5 | ||
|  | 7431d56166 | ||
|  | 222c16c5b8 | ||
|  | 4e83e80962 | ||
|  | 4fdbe578cc | ||
|  | c5cad865d7 | ||
|  | ae5fe9225f | ||
|  | 327b9051c8 | ||
|  | 8151c24cf5 | ||
|  | ee659095c2 | ||
|  | 8c35fe1062 | ||
|  | 9ca6a1031c | ||
|  | 59458f6444 | ||
|  | e8939aada4 | ||
|  | 17bb3dce26 | ||
|  | 495024d6fe | ||
|  | 902b33d25d | ||
|  | ac732e2e7b | ||
|  | d08ffd6c8b | ||
|  | 530ff7471d | ||
|  | 79833deeaf | ||
|  | 405e9e7c68 | ||
|  | 5f13ee7c19 | ||
|  | d9f02aecdf | ||
|  | bcb23d9a15 | ||
|  | d027450502 | ||
|  | 63ad1290f4 | ||
|  | 7c7cb61d2f | ||
|  | 68b165e244 | ||
|  | fe1b6812f1 | ||
|  | 378ff39e5e | ||
|  | e47cb91223 | ||
|  | d62fb16a58 | ||
|  | 235efcb2d4 | ||
|  | a6ada129e8 | ||
|  | a681576d6c | ||
|  | fdc234ed3b | ||
|  | e2ceb77501 | ||
|  | 11c28357a1 | ||
|  | f215405beb | ||
|  | ba2a0600dc | ||
|  | ab53165b34 | ||
|  | a30723c3d4 | ||
|  | d64b4fbc26 | ||
|  | 73131735fa | ||
|  | 5e0bea9d1c | ||
|  | 48afc54af6 | ||
|  | d066dd2b44 | ||
|  | 0bf7de9d43 | ||
|  | 267006782f | ||
|  | a9de745e51 | ||
|  | ca1f3c600d | ||
|  | 743353a0ed | ||
|  | f7c10ef9e9 | ||
|  | ecb44711d1 | ||
|  | 603b747ac5 | ||
|  | 0f2f776e6a | ||
|  | 1308f119a6 | ||
|  | 51d684820f | ||
|  | 023d76a3e7 | ||
|  | 4d34d9ae2b | ||
|  | c83c827484 | ||
|  | b8b880a91d | ||
|  | bb2f21a22e | ||
|  | b3587d4cde | ||
|  | 39ffe45f3c | ||
|  | d36e592afb | ||
|  | 74fb697fa6 | ||
|  | 512a52e88d | ||
|  | 41fc6c20a0 | ||
|  | 28881cb391 | ||
|  | a16b710d22 | ||
|  | a3d4c7599b | ||
|  | 6f16928215 | ||
|  | ff3c2fdc59 | ||
|  | 57edfe8751 | ||
|  | dcc0ee3679 | ||
|  | f7a16762b4 | ||
|  | 375835a950 | ||
|  | 4481386a3d | ||
|  | 8b76d4007e | ||
|  | 4f30118b37 | ||
|  | c5b746543b | ||
|  | 11d936331d | ||
|  | 4f619de675 | ||
|  | 80f2836cb8 | ||
|  | 3709aa7555 | ||
|  | 7c9d9ee048 | ||
|  | e4335577ca | ||
|  | 66c2eb0414 | ||
|  | f82e4ee923 | ||
|  | b62ee33318 | ||
|  | 8596a9826f | ||
|  | 3f2fb1fa58 | ||
|  | 5f39938a19 | ||
|  | d964ebd4c1 | ||
|  | 9458963311 | ||
|  | 44690b1066 | ||
|  | c41028cdc7 | ||
|  | 64c62c16fb | ||
|  | afef4f05fe | ||
|  | fc0f290c85 | ||
|  | 81d70ee325 | ||
|  | 6dc7a4471d | ||
|  | fcb8bd00b6 | ||
|  | 05c3f2a30d | ||
|  | 25996ce180 | ||
|  | 3729bddb2a | ||
|  | 4136428db3 | ||
|  | 31c6faf3c8 | ||
|  | 5c1ae40a9c | ||
|  | 4c6d0f7fa0 | ||
|  | 40b60fe5d4 | ||
|  | eed357abb4 | ||
|  | 8f541602c1 | ||
|  | 668f4b77f3 | ||
|  | 303965fbb8 | ||
|  | 792aed242d | ||
|  | dc5654b941 | ||
|  | e51e2425cc | ||
|  | 95c6b9b55d | ||
|  | ea25ead19d | ||
|  | 24100ec3b0 | ||
|  | 32437fbf8b | ||
|  | 5219a86a41 | ||
|  | e12dc5d894 | ||
|  | 75315406bb | ||
|  | ea42fe638a | ||
|  | 744211cec0 | ||
|  | 1a4321d7d0 | ||
|  | b943441901 | ||
|  | 0505b82384 | ||
|  | c9fb5721cd | ||
|  | 386a7ca442 | ||
|  | e929d5d819 | ||
|  | 94614ae4c3 | ||
|  | 1223c99e0f | ||
|  | 1ff5ea0a6e | ||
|  | 9d2691d1d2 | ||
|  | e4ef2c68bb | ||
|  | 7fffafdfd4 | ||
|  | 5896288edd | ||
|  | c4135fad2b | ||
|  | 1f34214fb3 | ||
|  | f899af0eef | ||
|  | 9f0c8bcae7 | ||
|  | 2bc36a6cde | ||
|  | ee10fe3d2c | ||
|  | a424e867f9 | ||
|  | f52b40396a | ||
|  | cd2ab70a58 | ||
|  | a5d1941d28 | ||
|  | 65a3783dd2 | ||
|  | b9b5c2a3bc | ||
|  | 12c618642e | ||
|  | 6ebc93c995 | ||
|  | 6d4e29c851 | ||
|  | b3979e2fda | ||
|  | 983c32bf75 | ||
|  | 9e3614066a | ||
|  | c7ad6b1b50 | ||
|  | 676dcf7fbb | ||
|  | 50d725330c | ||
|  | 2886dd1dae | ||
|  | 40424ac38b | ||
|  | a4d3865394 | ||
|  | 0ac99e8d42 | ||
|  | bdce1c464a | ||
|  | 475d75c16a | ||
|  | 32fd1897d0 | ||
|  | 39e6a28730 | ||
|  | 3852e119aa | ||
|  | f19fd7c166 | ||
|  | 100fddcee1 | ||
|  | 99fa86a67e | ||
|  | 6568c29c54 | ||
|  | c54bbc5a04 | ||
|  | 92d0c466c2 | ||
|  | 020c760976 | ||
|  | cdfd7de221 | ||
|  | 3da2e91acf | ||
|  | 3948304172 | ||
|  | 4a295cd95e | ||
|  | 6f7c8b35c5 | ||
|  | e58ba27c00 | ||
|  | 0aceddd088 | ||
|  | 30ff399218 | ||
|  | a7e63b61eb | ||
|  | b13b0d9311 | ||
|  | d8380dc3e2 | ||
|  | d805e9a8f0 | ||
|  | aa45142728 | ||
|  | 09d1aed3a5 | ||
|  | a1f80b5142 | ||
|  | cb1970ebab | ||
|  | d3fbdba77c | ||
|  | 632d797c9d | ||
|  | 559a2d81c1 | ||
|  | 7a5f23c0a5 | ||
|  | 84b115f15f | ||
|  | a0d14f4030 | ||
|  | dd6769bfbc | ||
|  | 027af5acca | ||
|  | db4b71fc9a | ||
|  | d9e41d42b5 | ||
|  | 0ed7d257e1 | ||
|  | 335a68396f | ||
|  | 84cdf6130f | ||
|  | b0abc4f7bb | ||
|  | ab81d1093d | ||
|  | e4d4e4e002 | ||
|  | cc357a6afa | ||
|  | dfc1c7d358 | ||
|  | 7ed8e33622 | ||
|  | 474822e83d | ||
|  | fe3942c5b3 | ||
|  | f417fa82a4 | ||
|  | c4b114133a | ||
|  | 2f4b0c2b9a | ||
|  | a491650c8b | ||
|  | 6805acd74f | ||
|  | 95c68c76e1 | ||
|  | 60aa383c95 | ||
|  | edc553fa1d | ||
|  | 4f2ebad8e0 | ||
|  | 1810ef60be | ||
|  | f720a6201b | ||
|  | cfb75b58ca | ||
|  | 4fbe983527 | ||
|  | 272383cac7 | ||
|  | 39380c63cb | ||
|  | ea26f4f7bf | ||
|  | 5fd2be3c8e | ||
|  | 2320b5c1fe | ||
|  | e5cbdfc67c | ||
|  | 894d196b64 | ||
|  | af037649c3 | ||
|  | cfca3e2507 | ||
|  | 7a12a0149a | ||
|  | fcdc1bfbd0 | ||
|  | d1d14ba9a0 | ||
|  | 0e502f6d5c | ||
|  | d3bac57d6a | ||
|  | bd1b4b8a9f | ||
|  | 38d81c394f | ||
|  | 72103a4adb | ||
|  | e6bae261c4 | ||
|  | 5edb0c0ee7 | ||
|  | 442ce403f9 | ||
|  | 7398cb44e2 | ||
|  | 15d54dfb4c | ||
|  | 9087bb9b08 | ||
|  | 0c689e85a5 | ||
|  | 75f2b0487e | ||
|  | 5a1bae8a9c | ||
|  | 129bc485bf | ||
|  | 69277bbb27 | ||
|  | b8b335f67d | ||
|  | eef7868199 | ||
|  | 23aa7ea85f | ||
|  | c1b69fd091 | ||
|  | 7ab7efdbc1 | ||
|  | b8ebdc012f | ||
|  | 9995d776de | ||
|  | c6f35c9aac | ||
|  | 615ea2f573 | ||
|  | 311458f41f | ||
|  | b2a381d401 | ||
|  | ffc1b0ff29 | ||
|  | ead2823322 | ||
|  | a7e1920597 | ||
|  | ec6664f590 | ||
|  | 8c6ca89da2 | ||
|  | b6e81242e7 | ||
|  | f9ca443667 | ||
|  | 394ee61c78 | ||
|  | 1d40aa687e | ||
|  | 8e3bf0dbca | ||
|  | 2031a33edf | ||
|  | fc3d3c76f8 | ||
|  | 880bed04f5 | ||
|  | f9c8470b20 | ||
|  | 36acc2dddd | ||
|  | a59963b6a0 | ||
|  | cab4bead72 | ||
|  | 1a2872c815 | ||
|  | f27e0a141d | ||
|  | 52f644c4f1 | ||
|  | 06c08a0574 | ||
|  | 724e2e6d27 | ||
|  | fd052189ca | ||
|  | 044a2b67e1 | ||
|  | 7e8b86e9bb | ||
|  | ce80825abb | ||
|  | a99bb3ba6d | ||
|  | 3428e9887d | ||
|  | 5a8fcac4dc | ||
|  | 6a9b14f7d1 | ||
|  | a74d8bd6e8 | ||
|  | 3c70f056ed | ||
|  | a546880a65 | ||
|  | 238145f27f | ||
|  | 0502e6be67 | ||
|  | 6a8c6f5a06 | ||
|  | 5248475e73 | ||
|  | d6c6b9bdb8 | ||
|  | 7bf04d5338 | ||
|  | 9668ec789a | ||
|  | ead32fb6b2 | ||
|  | 2ee24d29e5 | ||
|  | a560601338 | ||
|  | a51fe70498 | ||
|  | e47aa7653b | ||
|  | 58b8dfb929 | ||
|  | 462a76dd96 | ||
|  | 3758ec79ac | ||
|  | a0311858f9 | ||
|  | f08d500fd6 | ||
|  | df76d57c47 | ||
|  | 0ef953a1ea | ||
|  | 05cbed6b6c | ||
|  | 9225c4ef70 | ||
|  | 32136b75cd | ||
|  | 1f41d9c5f5 | ||
|  | dc47a2b7d7 | ||
|  | 1a539521f2 | ||
|  | 2db30a91c6 | ||
|  | b2c07b3110 | ||
|  | 90e6bef6d7 | ||
|  | 535634daca | ||
|  | 575d0da4d1 | ||
|  | ed18092088 | ||
|  | 611182910a | ||
|  | 9273e9b6ed | ||
|  | 77c0cc8b5f | ||
|  | 0705a99ea0 | ||
|  | 560394fead | ||
|  | 86a09b5e7d | ||
|  | 32b2026734 | ||
|  | b33f568fdd | ||
|  | 6e4bd4f505 | ||
|  | b971e2a42c | ||
|  | 3c103506c9 | ||
|  | 41d2062342 | ||
|  | 672c59f970 | ||
|  | 99229df017 | ||
|  | 346d80e30b | ||
|  | 54b3e511e9 | ||
|  | f25683ebec | ||
|  | d5e781e8e1 | ||
|  | 4572c86f0f | ||
|  | 8a5c4e384a | ||
|  | 4594a3c02b | ||
|  | bd45c1c963 | ||
|  | 5f8bb92f36 | ||
|  | 3f64cdaff8 | ||
|  | 7ac0ea8529 | ||
|  | a3569d7201 | ||
|  | 01faffd5bf | ||
|  | 26de5be07c | ||
|  | 87474d5916 | ||
|  | a366077509 | ||
|  | 06163165d9 | ||
|  | ec82c075be | ||
|  | 3b0df172a7 | ||
|  | 7058dbc3cc | ||
|  | b64de89d2d | ||
|  | 8878396339 | ||
|  | da6d5e2e24 | ||
|  | 18bb90329a | ||
|  | 604bb50adf | ||
|  | e4887c0c56 | ||
|  | 3097c4ccae | ||
|  | 7959d243f6 | ||
|  | 79dd402bc8 | ||
|  | 3f3229851b | ||
|  | 989628a024 | ||
|  | e0475343f5 | ||
|  | da0a9113d4 | ||
|  | cf7ab97451 | ||
|  | 2370575eb5 | ||
|  | 825b68e5c4 | ||
|  | 851cba0b25 | ||
|  | f0ec168ac7 | ||
|  | fa933952f7 | ||
|  | ba6e23784c | ||
|  | 614032198e | ||
|  | 3715e6b48a | ||
|  | 91e7400bbb | ||
|  | a8d082c7d2 | ||
|  | 95756f9716 | ||
|  | a5e1765ce4 | ||
|  | f43c31da1f | ||
|  | 95d0adf10e | ||
|  | 2e1b245cd8 | ||
|  | 5400c47f07 | ||
|  | 4153442703 | ||
|  | 5e4b721e97 | ||
|  | aca41ac089 | ||
|  | 01a883e669 | ||
|  | 1e4356f83a | ||
|  | 545a6177bb | ||
|  | 50d356be2f | ||
|  | 9835e800ec | ||
|  | 5242362f31 | ||
|  | 808e4e8537 | ||
|  | 43740a4b2f | ||
|  | f99d672237 | ||
|  | 337cb4fb86 | ||
|  | 90856a0e7a | ||
|  | ea1c8a3b81 | ||
|  | d55d077a95 | ||
|  | f760a68173 | ||
|  | e66a3523b6 | ||
|  | 89d6b85b83 | ||
|  | e02d109864 | ||
|  | 743981e9ad | ||
|  | 49b8e771b5 | ||
|  | dde672701f | ||
|  | 9ca2d8f9f2 | ||
|  | fd786412aa | ||
|  | eb88c7cfba | ||
|  | e1892ff370 | ||
|  | 763159a6f6 | ||
|  | 6810a6ee58 | ||
|  | 65e6c3a9fe | ||
|  | dcbbf988c1 | ||
|  | 199cafebcf | ||
|  | 555d807d76 | ||
|  | 003c6ad11b | ||
|  | dc77d87427 | ||
|  | cfc44cf778 | ||
|  | 3df99788ff | ||
|  | 3600d2d193 | ||
|  | 45a391d69e | ||
|  | 15bc18b64f | ||
|  | fb0343cafb | ||
|  | 7d9bedf7de | ||
|  | 6c99048211 | ||
|  | 2638a901d9 | ||
|  | 71083fd0f7 | 
							
								
								
									
										16
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,13 +1,6 @@ | ||||
| name: SDL/Ubuntu | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches:  | ||||
|       - master | ||||
|  | ||||
|   pull_request: | ||||
|     branches:  | ||||
|       - master | ||||
| on: [push, pull_request] | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
| @@ -15,8 +8,9 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v1 | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: Install dependencies | ||||
|       run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons | ||||
|       run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons | ||||
|     - name: Make | ||||
|       run: cd OSBindings/SDL; scons | ||||
|       working-directory: OSBindings/SDL | ||||
|       run: scons -j$(nproc --all) | ||||
|   | ||||
| @@ -24,13 +24,13 @@ namespace Activity { | ||||
| class Observer { | ||||
| 	public: | ||||
| 		/// Announces to the receiver that there is an LED of name @c name. | ||||
| 		virtual void register_led(const std::string &name) {} | ||||
| 		virtual void register_led([[maybe_unused]] const std::string &name) {} | ||||
|  | ||||
| 		/// Announces to the receiver that there is a drive of name @c name. | ||||
| 		virtual void register_drive(const std::string &name) {} | ||||
| 		virtual void register_drive([[maybe_unused]] const std::string &name) {} | ||||
|  | ||||
| 		/// Informs the receiver of the new state of the LED with name @c name. | ||||
| 		virtual void set_led_status(const std::string &name, bool lit) {} | ||||
| 		virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {} | ||||
|  | ||||
| 		enum class DriveEvent { | ||||
| 			StepNormal, | ||||
| @@ -39,10 +39,10 @@ class Observer { | ||||
| 		}; | ||||
|  | ||||
| 		/// Informs the receiver that the named event just occurred for the drive with name @c name. | ||||
| 		virtual void announce_drive_event(const std::string &name, DriveEvent event) {} | ||||
| 		virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {} | ||||
|  | ||||
| 		/// Informs the receiver of the motor-on status of the drive with name @c name. | ||||
| 		virtual void set_drive_motor_status(const std::string &name, bool is_on) {} | ||||
| 		virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -11,20 +11,20 @@ | ||||
| using namespace Analyser::Dynamic; | ||||
|  | ||||
| float ConfidenceCounter::get_confidence() { | ||||
| 	return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_); | ||||
| 	return float(hits_) / float(hits_ + misses_); | ||||
| } | ||||
|  | ||||
| void ConfidenceCounter::add_hit() { | ||||
| 	hits_++; | ||||
| 	++hits_; | ||||
| } | ||||
|  | ||||
| void ConfidenceCounter::add_miss() { | ||||
| 	misses_++; | ||||
| 	++misses_; | ||||
| } | ||||
|  | ||||
| void ConfidenceCounter::add_equivocal() { | ||||
| 	if(hits_ > misses_) { | ||||
| 		hits_++; | ||||
| 		misses_++; | ||||
| 		++hits_; | ||||
| 		++misses_; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -35,8 +35,8 @@ class ConfidenceSummary: public ConfidenceSource { | ||||
| 		float get_confidence() final; | ||||
|  | ||||
| 	private: | ||||
| 		std::vector<ConfidenceSource *> sources_; | ||||
| 		std::vector<float> weights_; | ||||
| 		const std::vector<ConfidenceSource *> sources_; | ||||
| 		const std::vector<float> weights_; | ||||
| 		float weight_sum_; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,94 +0,0 @@ | ||||
| // | ||||
| //  MultiCRTMachine.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 29/01/2018. | ||||
| //  Copyright 2018 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "MultiCRTMachine.hpp" | ||||
|  | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
|  | ||||
| using namespace Analyser::Dynamic; | ||||
|  | ||||
| MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : | ||||
| 	machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) { | ||||
| 	speaker_ = MultiSpeaker::create(machines); | ||||
| } | ||||
|  | ||||
| void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) { | ||||
| 	// Apply a blunt force parallelisation of the machines; each run_for is dispatched | ||||
| 	// to a separate queue and this queue will block until all are done. | ||||
| 	volatile std::size_t outstanding_machines; | ||||
| 	std::condition_variable condition; | ||||
| 	std::mutex mutex; | ||||
| 	{ | ||||
| 		std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_); | ||||
| 		std::lock_guard<std::mutex> lock(mutex); | ||||
| 		outstanding_machines = machines_.size(); | ||||
|  | ||||
| 		for(std::size_t index = 0; index < machines_.size(); ++index) { | ||||
| 			CRTMachine::Machine *crt_machine = machines_[index]->crt_machine(); | ||||
| 			queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() { | ||||
| 				if(crt_machine) function(crt_machine); | ||||
|  | ||||
| 				std::lock_guard<std::mutex> lock(mutex); | ||||
| 				outstanding_machines--; | ||||
| 				condition.notify_all(); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::unique_lock<std::mutex> lock(mutex); | ||||
| 	condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; }); | ||||
| } | ||||
|  | ||||
| void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) { | ||||
| 	std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_); | ||||
| 	for(const auto &machine: machines_) { | ||||
| 		CRTMachine::Machine *const crt_machine = machine->crt_machine(); | ||||
| 		if(crt_machine) function(crt_machine); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) { | ||||
| 	scan_target_ = scan_target; | ||||
|  | ||||
| 	CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine(); | ||||
| 	if(crt_machine) crt_machine->set_scan_target(scan_target); | ||||
| } | ||||
|  | ||||
| Outputs::Display::ScanStatus MultiCRTMachine::get_scan_status() const { | ||||
| 	CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine(); | ||||
| 	if(crt_machine) crt_machine->get_scan_status(); | ||||
|  | ||||
| 	return Outputs::Display::ScanStatus(); | ||||
| } | ||||
|  | ||||
| Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { | ||||
| 	return speaker_; | ||||
| } | ||||
|  | ||||
| void MultiCRTMachine::run_for(Time::Seconds duration) { | ||||
| 	perform_parallel([=](::CRTMachine::Machine *machine) { | ||||
| 		if(machine->get_confidence() >= 0.01f) machine->run_for(duration); | ||||
| 	}); | ||||
|  | ||||
| 	if(delegate_) delegate_->multi_crt_did_run_machines(); | ||||
| } | ||||
|  | ||||
| void MultiCRTMachine::did_change_machine_order() { | ||||
| 	if(scan_target_) scan_target_->will_change_owner(); | ||||
|  | ||||
| 	perform_serial([=](::CRTMachine::Machine *machine) { | ||||
| 		machine->set_scan_target(nullptr); | ||||
| 	}); | ||||
| 	CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine(); | ||||
| 	if(crt_machine) crt_machine->set_scan_target(scan_target_); | ||||
|  | ||||
| 	if(speaker_) { | ||||
| 		speaker_->set_new_front_machine(machines_.front().get()); | ||||
| 	} | ||||
| } | ||||
| @@ -12,6 +12,97 @@ | ||||
|  | ||||
| using namespace Analyser::Dynamic; | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| class MultiStruct: public Reflection::Struct { | ||||
| 	public: | ||||
| 		MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) { | ||||
| 			for(auto device: devices) { | ||||
| 				options_.emplace_back(device->get_options()); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		void apply() { | ||||
| 			auto options = options_.begin(); | ||||
| 			for(auto device: devices_) { | ||||
| 				device->set_options(*options); | ||||
| 				++options; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::string> all_keys() const final { | ||||
| 			std::set<std::string> keys; | ||||
| 			for(auto &options: options_) { | ||||
| 				const auto new_keys = options->all_keys(); | ||||
| 				keys.insert(new_keys.begin(), new_keys.end()); | ||||
| 			} | ||||
| 			return std::vector<std::string>(keys.begin(), keys.end()); | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::string> values_for(const std::string &name) const final { | ||||
| 			std::set<std::string> values; | ||||
| 			for(auto &options: options_) { | ||||
| 				const auto new_values = options->values_for(name); | ||||
| 				values.insert(new_values.begin(), new_values.end()); | ||||
| 			} | ||||
| 			return std::vector<std::string>(values.begin(), values.end()); | ||||
| 		} | ||||
|  | ||||
| 		const std::type_info *type_of(const std::string &name) const final { | ||||
| 			for(auto &options: options_) { | ||||
| 				auto info = options->type_of(name); | ||||
| 				if(info) return info; | ||||
| 			} | ||||
| 			return nullptr; | ||||
| 		} | ||||
|  | ||||
| 		size_t count_of(const std::string &name) const final { | ||||
| 			for(auto &options: options_) { | ||||
| 				auto info = options->type_of(name); | ||||
| 				if(info) return options->count_of(name); | ||||
| 			} | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		const void *get(const std::string &name) const final { | ||||
| 			for(auto &options: options_) { | ||||
| 				auto value = options->get(name); | ||||
| 				if(value) return value; | ||||
| 			} | ||||
| 			return nullptr; | ||||
| 		} | ||||
|  | ||||
| 		void *get(const std::string &name) final { | ||||
| 			for(auto &options: options_) { | ||||
| 				auto value = options->get(name); | ||||
| 				if(value) return value; | ||||
| 			} | ||||
| 			return nullptr; | ||||
| 		} | ||||
|  | ||||
| 		void set(const std::string &name, const void *value, size_t offset) final { | ||||
| 			const auto safe_type = type_of(name); | ||||
| 			if(!safe_type) return; | ||||
|  | ||||
| 			// Set this property only where the child's type is the same as that | ||||
| 			// which was returned from here for type_of. | ||||
| 			for(auto &options: options_) { | ||||
| 				const auto type = options->type_of(name); | ||||
| 				if(!type) continue; | ||||
|  | ||||
| 				if(*type == *safe_type) { | ||||
| 					options->set(name, value, offset); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		const std::vector<Configurable::Device *> &devices_; | ||||
| 		std::vector<std::unique_ptr<Reflection::Struct>> options_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||
| 	for(const auto &machine: machines) { | ||||
| 		Configurable::Device *device = machine->configurable_device(); | ||||
| @@ -19,46 +110,11 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() { | ||||
| 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||
|  | ||||
| 	// Produce the list of unique options. | ||||
| 	for(const auto &device : devices_) { | ||||
| 		std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options(); | ||||
| 		for(auto &option : device_options) { | ||||
| 			if(std::find(options.begin(), options.end(), option) == options.end()) { | ||||
| 				options.push_back(std::move(option)); | ||||
| 			} | ||||
| 		} | ||||
| void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &str) { | ||||
| 	const auto options = dynamic_cast<MultiStruct *>(str.get()); | ||||
| 	options->apply(); | ||||
| } | ||||
|  | ||||
| 	return options; | ||||
| } | ||||
|  | ||||
| void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) { | ||||
| 	for(const auto &device : devices_) { | ||||
| 		device->set_selections(selection_by_option); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Configurable::SelectionSet MultiConfigurable::get_accurate_selections() { | ||||
| 	Configurable::SelectionSet set; | ||||
| 	for(const auto &device : devices_) { | ||||
| 		Configurable::SelectionSet device_set = device->get_accurate_selections(); | ||||
| 		for(auto &selection : device_set) { | ||||
| 			set.insert(std::move(selection)); | ||||
| 		} | ||||
| 	} | ||||
| 	return set; | ||||
| } | ||||
|  | ||||
| Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() { | ||||
| 	Configurable::SelectionSet set; | ||||
| 	for(const auto &device : devices_) { | ||||
| 		Configurable::SelectionSet device_set = device->get_user_friendly_selections(); | ||||
| 		for(auto &selection : device_set) { | ||||
| 			set.insert(std::move(selection)); | ||||
| 		} | ||||
| 	} | ||||
| 	return set; | ||||
| std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() { | ||||
| 	return std::make_unique<MultiStruct>(devices_); | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| #define MultiConfigurable_hpp | ||||
|  | ||||
| #include "../../../../Machines/DynamicMachine.hpp" | ||||
| #include "../../../../Configurable/Configurable.hpp" | ||||
|  | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| @@ -28,10 +29,8 @@ class MultiConfigurable: public Configurable::Device { | ||||
| 		MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||
|  | ||||
| 		// Below is the standard Configurable::Device interface; see there for documentation. | ||||
| 		std::vector<std::unique_ptr<Configurable::Option>> get_options() final; | ||||
| 		void set_selections(const Configurable::SelectionSet &selection_by_option) final; | ||||
| 		Configurable::SelectionSet get_accurate_selections() final; | ||||
| 		Configurable::SelectionSet get_user_friendly_selections() final; | ||||
| 		void set_options(const std::unique_ptr<Reflection::Struct> &options) final; | ||||
| 		std::unique_ptr<Reflection::Struct> get_options() final; | ||||
|  | ||||
| 	private: | ||||
| 		std::vector<Configurable::Device *> devices_; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ namespace { | ||||
|  | ||||
| class MultiJoystick: public Inputs::Joystick { | ||||
| 	public: | ||||
| 		MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) { | ||||
| 		MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) { | ||||
| 			for(const auto &machine: machines) { | ||||
| 				const auto &joysticks = machine->get_joysticks(); | ||||
| 				if(joysticks.size() >= index) { | ||||
| @@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		std::vector<Input> &get_inputs() final { | ||||
| 		const std::vector<Input> &get_inputs() final { | ||||
| 			if(inputs.empty()) { | ||||
| 				for(const auto &joystick: joysticks_) { | ||||
| 					std::vector<Input> joystick_inputs = joystick->get_inputs(); | ||||
| @@ -67,9 +67,9 @@ class MultiJoystick: public Inputs::Joystick { | ||||
|  | ||||
| MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||
| 	std::size_t total_joysticks = 0; | ||||
| 	std::vector<JoystickMachine::Machine *> joystick_machines; | ||||
| 	std::vector<MachineTypes::JoystickMachine *> joystick_machines; | ||||
| 	for(const auto &machine: machines) { | ||||
| 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | ||||
| 		auto joystick_machine = machine->joystick_machine(); | ||||
| 		if(joystick_machine) { | ||||
| 			joystick_machines.push_back(joystick_machine); | ||||
| 			total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size()); | ||||
|   | ||||
| @@ -23,7 +23,7 @@ namespace Dynamic { | ||||
| 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||
| 	order of delivered messages. | ||||
| */ | ||||
| class MultiJoystickMachine: public JoystickMachine::Machine { | ||||
| class MultiJoystickMachine: public MachineTypes::JoystickMachine { | ||||
| 	public: | ||||
| 		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ using namespace Analyser::Dynamic; | ||||
| MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) : | ||||
| 	keyboard_(machines_) { | ||||
| 	for(const auto &machine: machines) { | ||||
| 		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); | ||||
| 		auto keyboard_machine = machine->keyboard_machine(); | ||||
| 		if(keyboard_machine) machines_.push_back(keyboard_machine); | ||||
| 	} | ||||
| } | ||||
| @@ -36,11 +36,19 @@ void MultiKeyboardMachine::type_string(const std::string &string) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool MultiKeyboardMachine::can_type(char c) const { | ||||
| 	bool can_type = true; | ||||
| 	for(const auto &machine: machines_) { | ||||
| 		can_type &= machine->can_type(c); | ||||
| 	} | ||||
| 	return can_type; | ||||
| } | ||||
|  | ||||
| Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() { | ||||
| 	return keyboard_; | ||||
| } | ||||
|  | ||||
| MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines) | ||||
| MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines) | ||||
| 	: machines_(machines) { | ||||
| 	for(const auto &machine: machines_) { | ||||
| 		observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end()); | ||||
| @@ -48,10 +56,12 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardM | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) { | ||||
| bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) { | ||||
| 	bool was_consumed = false; | ||||
| 	for(const auto &machine: machines_) { | ||||
| 		machine->get_keyboard().set_key_pressed(key, value, is_pressed); | ||||
| 		was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed); | ||||
| 	} | ||||
| 	return was_consumed; | ||||
| } | ||||
|  | ||||
| void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() { | ||||
| @@ -60,10 +70,10 @@ void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() { | ||||
| const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() const { | ||||
| 	return observed_keys_; | ||||
| } | ||||
|  | ||||
| bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() { | ||||
| bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() const { | ||||
| 	return is_exclusive_; | ||||
| } | ||||
|   | ||||
| @@ -24,21 +24,21 @@ namespace Dynamic { | ||||
| 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||
| 	order of delivered messages. | ||||
| */ | ||||
| class MultiKeyboardMachine: public KeyboardMachine::Machine { | ||||
| class MultiKeyboardMachine: public MachineTypes::KeyboardMachine { | ||||
| 	private: | ||||
| 		std::vector<::KeyboardMachine::Machine *> machines_; | ||||
| 		std::vector<MachineTypes::KeyboardMachine *> machines_; | ||||
|  | ||||
| 		class MultiKeyboard: public Inputs::Keyboard { | ||||
| 			public: | ||||
| 				MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines); | ||||
| 				MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines); | ||||
|  | ||||
| 				void set_key_pressed(Key key, char value, bool is_pressed) final; | ||||
| 				bool set_key_pressed(Key key, char value, bool is_pressed) final; | ||||
| 				void reset_all_keys() final; | ||||
| 				const std::set<Key> &observed_keys() final; | ||||
| 				bool is_exclusive() final; | ||||
| 				const std::set<Key> &observed_keys() const final; | ||||
| 				bool is_exclusive() const final; | ||||
|  | ||||
| 			private: | ||||
| 				const std::vector<::KeyboardMachine::Machine *> &machines_; | ||||
| 				const std::vector<MachineTypes::KeyboardMachine *> &machines_; | ||||
| 				std::set<Key> observed_keys_; | ||||
| 				bool is_exclusive_ = false; | ||||
| 		}; | ||||
| @@ -51,6 +51,7 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine { | ||||
| 		void clear_all_keys() final; | ||||
| 		void set_key_state(uint16_t key, bool is_pressed) final; | ||||
| 		void type_string(const std::string &) final; | ||||
| 		bool can_type(char c) const final; | ||||
| 		Inputs::Keyboard &get_keyboard() final; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ using namespace Analyser::Dynamic; | ||||
|  | ||||
| MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||
| 	for(const auto &machine: machines) { | ||||
| 		MediaTarget::Machine *media_target = machine->media_target(); | ||||
| 		auto media_target = machine->media_target(); | ||||
| 		if(media_target) targets_.push_back(media_target); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace Dynamic { | ||||
| 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||
| 	order of delivered messages. | ||||
| */ | ||||
| struct MultiMediaTarget: public MediaTarget::Machine { | ||||
| struct MultiMediaTarget: public MachineTypes::MediaTarget { | ||||
| 	public: | ||||
| 		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||
|  | ||||
| @@ -32,7 +32,7 @@ struct MultiMediaTarget: public MediaTarget::Machine { | ||||
| 		bool insert_media(const Analyser::Static::Media &media) final; | ||||
|  | ||||
| 	private: | ||||
| 		std::vector<MediaTarget::Machine *> targets_; | ||||
| 		std::vector<MachineTypes::MediaTarget *> targets_; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										105
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| // | ||||
| //  MultiProducer.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 29/01/2018. | ||||
| //  Copyright 2018 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "MultiProducer.hpp" | ||||
|  | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
|  | ||||
| using namespace Analyser::Dynamic; | ||||
|  | ||||
| // MARK: - MultiInterface | ||||
|  | ||||
| template <typename MachineType> | ||||
| void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) { | ||||
| 	// Apply a blunt force parallelisation of the machines; each run_for is dispatched | ||||
| 	// to a separate queue and this queue will block until all are done. | ||||
| 	volatile std::size_t outstanding_machines; | ||||
| 	std::condition_variable condition; | ||||
| 	std::mutex mutex; | ||||
| 	{ | ||||
| 		std::lock_guard machines_lock(machines_mutex_); | ||||
| 		std::lock_guard lock(mutex); | ||||
| 		outstanding_machines = machines_.size(); | ||||
|  | ||||
| 		for(std::size_t index = 0; index < machines_.size(); ++index) { | ||||
| 			const auto machine = ::Machine::get<MachineType>(*machines_[index].get()); | ||||
| 			queues_[index].enqueue([&mutex, &condition, machine, function, &outstanding_machines]() { | ||||
| 				if(machine) function(machine); | ||||
|  | ||||
| 				std::lock_guard lock(mutex); | ||||
| 				outstanding_machines--; | ||||
| 				condition.notify_all(); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::unique_lock lock(mutex); | ||||
| 	condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; }); | ||||
| } | ||||
|  | ||||
| template <typename MachineType> | ||||
| void MultiInterface<MachineType>::perform_serial(const std::function<void(MachineType *)> &function) { | ||||
| 	std::lock_guard machines_lock(machines_mutex_); | ||||
| 	for(const auto &machine: machines_) { | ||||
| 		const auto typed_machine = ::Machine::get<MachineType>(*machine.get()); | ||||
| 		if(typed_machine) function(typed_machine); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MARK: - MultiScanProducer | ||||
| void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) { | ||||
| 	scan_target_ = scan_target; | ||||
|  | ||||
| 	std::lock_guard machines_lock(machines_mutex_); | ||||
| 	const auto machine = machines_.front()->scan_producer(); | ||||
| 	if(machine) machine->set_scan_target(scan_target); | ||||
| } | ||||
|  | ||||
| Outputs::Display::ScanStatus MultiScanProducer::get_scan_status() const { | ||||
| 	std::lock_guard machines_lock(machines_mutex_); | ||||
| 	const auto machine = machines_.front()->scan_producer(); | ||||
| 	if(machine) return machine->get_scan_status(); | ||||
| 	return Outputs::Display::ScanStatus(); | ||||
| } | ||||
|  | ||||
| void MultiScanProducer::did_change_machine_order() { | ||||
| 	if(scan_target_) scan_target_->will_change_owner(); | ||||
|  | ||||
| 	perform_serial([](MachineTypes::ScanProducer *machine) { | ||||
| 		machine->set_scan_target(nullptr); | ||||
| 	}); | ||||
| 	std::lock_guard machines_lock(machines_mutex_); | ||||
| 	const auto machine = machines_.front()->scan_producer(); | ||||
| 	if(machine) machine->set_scan_target(scan_target_); | ||||
| } | ||||
|  | ||||
| // MARK: - MultiAudioProducer | ||||
| MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) { | ||||
| 	speaker_ = MultiSpeaker::create(machines); | ||||
| } | ||||
|  | ||||
| Outputs::Speaker::Speaker *MultiAudioProducer::get_speaker() { | ||||
| 	return speaker_; | ||||
| } | ||||
|  | ||||
| void MultiAudioProducer::did_change_machine_order() { | ||||
| 	if(speaker_) { | ||||
| 		speaker_->set_new_front_machine(machines_.front().get()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MARK: - MultiTimedMachine | ||||
|  | ||||
| void MultiTimedMachine::run_for(Time::Seconds duration) { | ||||
| 	perform_parallel([duration](::MachineTypes::TimedMachine *machine) { | ||||
| 		if(machine->get_confidence() >= 0.01f) machine->run_for(duration); | ||||
| 	}); | ||||
|  | ||||
| 	if(delegate_) delegate_->did_run_machines(this); | ||||
| } | ||||
| @@ -1,16 +1,16 @@ | ||||
| //
 | ||||
| //  MultiCRTMachine.hpp
 | ||||
| //  MultiProducer.hpp
 | ||||
| //  Clock Signal
 | ||||
| //
 | ||||
| //  Created by Thomas Harte on 29/01/2018.
 | ||||
| //  Copyright 2018 Thomas Harte. All rights reserved.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef MultiCRTMachine_hpp | ||||
| #define MultiCRTMachine_hpp | ||||
| #ifndef MultiProducer_hpp | ||||
| #define MultiProducer_hpp | ||||
| 
 | ||||
| #include "../../../../Concurrency/AsyncTaskQueue.hpp" | ||||
| #include "../../../../Machines/CRTMachine.hpp" | ||||
| #include "../../../../Machines/MachineTypes.hpp" | ||||
| #include "../../../../Machines/DynamicMachine.hpp" | ||||
| 
 | ||||
| #include "MultiSpeaker.hpp" | ||||
| @@ -22,6 +22,91 @@ | ||||
| namespace Analyser { | ||||
| namespace Dynamic { | ||||
| 
 | ||||
| template <typename MachineType> class MultiInterface { | ||||
| 	public: | ||||
| 		MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : | ||||
| 			machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {} | ||||
| 
 | ||||
| 	protected: | ||||
| 		/*!
 | ||||
| 			Performs a parallel for operation across all machines, performing the supplied | ||||
| 			function on each and returning only once all applications have completed. | ||||
| 
 | ||||
| 			No guarantees are extended as to which thread operations will occur on. | ||||
| 		*/ | ||||
| 		void perform_parallel(const std::function<void(MachineType *)> &); | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Performs a serial for operation across all machines, performing the supplied | ||||
| 			function on each on the calling thread. | ||||
| 		*/ | ||||
| 		void perform_serial(const std::function<void(MachineType *)> &); | ||||
| 
 | ||||
| 	protected: | ||||
| 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | ||||
| 		std::recursive_mutex &machines_mutex_; | ||||
| 
 | ||||
| 	private: | ||||
| 		std::vector<Concurrency::AsyncTaskQueue> queues_; | ||||
| }; | ||||
| 
 | ||||
| class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine { | ||||
| 	public: | ||||
| 		using MultiInterface::MultiInterface; | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Provides a mechanism by which a delegate can be informed each time a call to run_for has | ||||
| 			been received. | ||||
| 		*/ | ||||
| 		struct Delegate { | ||||
| 			virtual void did_run_machines(MultiTimedMachine *) = 0; | ||||
| 		}; | ||||
| 		/// Sets @c delegate as the receiver of delegate messages.
 | ||||
| 		void set_delegate(Delegate *delegate) { | ||||
| 			delegate_ = delegate; | ||||
| 		} | ||||
| 
 | ||||
| 		void run_for(Time::Seconds duration) final; | ||||
| 
 | ||||
| 	private: | ||||
| 		void run_for(const Cycles) final {} | ||||
| 		Delegate *delegate_ = nullptr; | ||||
| }; | ||||
| 
 | ||||
| class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer { | ||||
| 	public: | ||||
| 		using MultiInterface::MultiInterface; | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Informs the MultiScanProducer that the order of machines has changed; it | ||||
| 			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that | ||||
| 			are necessary to bridge the gap between one machine and the next. | ||||
| 		*/ | ||||
| 		void did_change_machine_order(); | ||||
| 
 | ||||
| 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) final; | ||||
| 		Outputs::Display::ScanStatus get_scan_status() const final; | ||||
| 
 | ||||
| 	private: | ||||
| 		Outputs::Display::ScanTarget *scan_target_ = nullptr; | ||||
| }; | ||||
| 
 | ||||
| class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer { | ||||
| 	public: | ||||
| 		MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex); | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Informs the MultiAudio that the order of machines has changed; it | ||||
| 			uses this as an opportunity to switch speaker delegates as appropriate. | ||||
| 		*/ | ||||
| 		void did_change_machine_order(); | ||||
| 
 | ||||
| 		Outputs::Speaker::Speaker *get_speaker() final; | ||||
| 
 | ||||
| 	private: | ||||
| 		MultiSpeaker *speaker_ = nullptr; | ||||
| }; | ||||
| 
 | ||||
| /*!
 | ||||
| 	Provides a class that multiplexes the CRT machine interface to multiple machines. | ||||
| 
 | ||||
| @@ -29,61 +114,9 @@ namespace Dynamic { | ||||
| 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | ||||
| 	if the order of machines changes. | ||||
| */ | ||||
| class MultiCRTMachine: public CRTMachine::Machine { | ||||
| 	public: | ||||
| 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex); | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine | ||||
| 			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that | ||||
| 			are necessary to bridge the gap between one machine and the next. | ||||
| 		*/ | ||||
| 		void did_change_machine_order(); | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Provides a mechanism by which a delegate can be informed each time a call to run_for has | ||||
| 			been received. | ||||
| 		*/ | ||||
| 		struct Delegate { | ||||
| 			virtual void multi_crt_did_run_machines() = 0; | ||||
| 		}; | ||||
| 		/// Sets @c delegate as the receiver of delegate messages.
 | ||||
| 		void set_delegate(Delegate *delegate) { | ||||
| 			delegate_ = delegate; | ||||
| 		} | ||||
| 
 | ||||
| 		// Below is the standard CRTMachine::Machine interface; see there for documentation.
 | ||||
| 		void set_scan_target(Outputs::Display::ScanTarget *scan_target) final; | ||||
| 		Outputs::Display::ScanStatus get_scan_status() const final; | ||||
| 		Outputs::Speaker::Speaker *get_speaker() final; | ||||
| 		void run_for(Time::Seconds duration) final; | ||||
| 
 | ||||
| 	private: | ||||
| 		void run_for(const Cycles cycles) final {} | ||||
| 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | ||||
| 		std::recursive_mutex &machines_mutex_; | ||||
| 		std::vector<Concurrency::AsyncTaskQueue> queues_; | ||||
| 		MultiSpeaker *speaker_ = nullptr; | ||||
| 		Delegate *delegate_ = nullptr; | ||||
| 		Outputs::Display::ScanTarget *scan_target_ = nullptr; | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Performs a parallel for operation across all machines, performing the supplied | ||||
| 			function on each and returning only once all applications have completed. | ||||
| 
 | ||||
| 			No guarantees are extended as to which thread operations will occur on. | ||||
| 		*/ | ||||
| 		void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &); | ||||
| 
 | ||||
| 		/*!
 | ||||
| 			Performs a serial for operation across all machines, performing the supplied | ||||
| 			function on each on the calling thread. | ||||
| 		*/ | ||||
| 		void perform_serial(const std::function<void(::CRTMachine::Machine *)> &); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #endif /* MultiCRTMachine_hpp */ | ||||
| #endif /* MultiProducer_hpp */ | ||||
| @@ -13,7 +13,7 @@ using namespace Analyser::Dynamic; | ||||
| MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||
| 	std::vector<Outputs::Speaker::Speaker *> speakers; | ||||
| 	for(const auto &machine: machines) { | ||||
| 		Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker(); | ||||
| 		Outputs::Speaker::Speaker *speaker = machine->audio_producer()->get_speaker(); | ||||
| 		if(speaker) speakers.push_back(speaker); | ||||
| 	} | ||||
| 	if(speakers.empty()) return nullptr; | ||||
| @@ -34,12 +34,29 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) | ||||
| 		ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum); | ||||
| 	} | ||||
|  | ||||
| 	return ideal / static_cast<float>(speakers_.size()); | ||||
| 	return ideal / float(speakers_.size()); | ||||
| } | ||||
|  | ||||
| void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size) { | ||||
| void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) { | ||||
| 	stereo_output_ = stereo; | ||||
| 	for(const auto &speaker: speakers_) { | ||||
| 		speaker->set_computed_output_rate(cycles_per_second, buffer_size); | ||||
| 		speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool MultiSpeaker::get_is_stereo() { | ||||
| 	// Return as stereo if any subspeaker is stereo. | ||||
| 	for(const auto &speaker: speakers_) { | ||||
| 		if(speaker->get_is_stereo()) { | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void MultiSpeaker::set_output_volume(float volume) { | ||||
| 	for(const auto &speaker: speakers_) { | ||||
| 		speaker->set_output_volume(volume); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -50,16 +67,16 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) { | ||||
| void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | ||||
| 	if(!delegate_) return; | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||
| 		std::lock_guard lock_guard(front_speaker_mutex_); | ||||
| 		if(speaker != front_speaker_) return; | ||||
| 	} | ||||
| 	did_complete_samples(this, buffer); | ||||
| 	did_complete_samples(this, buffer, stereo_output_); | ||||
| } | ||||
|  | ||||
| void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) { | ||||
| 	if(!delegate_) return; | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||
| 		std::lock_guard lock_guard(front_speaker_mutex_); | ||||
| 		if(speaker != front_speaker_) return; | ||||
| 	} | ||||
| 	delegate_->speaker_did_change_input_clock(this); | ||||
| @@ -67,8 +84,8 @@ void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) { | ||||
|  | ||||
| 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 lock_guard(front_speaker_mutex_); | ||||
| 		front_speaker_ = machine->audio_producer()->get_speaker(); | ||||
| 	} | ||||
| 	if(delegate_) { | ||||
| 		delegate_->speaker_did_change_input_clock(this); | ||||
|   | ||||
| @@ -39,8 +39,10 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker: | ||||
|  | ||||
| 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | ||||
| 		float get_ideal_clock_rate_in_range(float minimum, float maximum) override; | ||||
| 		void set_computed_output_rate(float cycles_per_second, int buffer_size) override; | ||||
| 		void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override; | ||||
| 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override; | ||||
| 		bool get_is_stereo() override; | ||||
| 		void set_output_volume(float) override; | ||||
|  | ||||
| 	private: | ||||
| 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final; | ||||
| @@ -51,6 +53,8 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker: | ||||
| 		Outputs::Speaker::Speaker *front_speaker_ = nullptr; | ||||
| 		Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr; | ||||
| 		std::mutex front_speaker_mutex_; | ||||
|  | ||||
| 		bool stereo_output_ = false; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -16,74 +16,55 @@ using namespace Analyser::Dynamic; | ||||
| MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | ||||
| 	machines_(std::move(machines)), | ||||
| 	configurable_(machines_), | ||||
| 	crt_machine_(machines_, machines_mutex_), | ||||
| 	joystick_machine_(machines), | ||||
| 	timed_machine_(machines_, machines_mutex_), | ||||
| 	scan_producer_(machines_, machines_mutex_), | ||||
| 	audio_producer_(machines_, machines_mutex_), | ||||
| 	joystick_machine_(machines_), | ||||
| 	keyboard_machine_(machines_), | ||||
| 	media_target_(machines_) { | ||||
| 	crt_machine_.set_delegate(this); | ||||
| 	timed_machine_.set_delegate(this); | ||||
| } | ||||
|  | ||||
| Activity::Source *MultiMachine::activity_source() { | ||||
| 	return nullptr; // TODO | ||||
| } | ||||
|  | ||||
| MediaTarget::Machine *MultiMachine::media_target() { | ||||
| 	if(has_picked_) { | ||||
| 		return machines_.front()->media_target(); | ||||
| 	} else { | ||||
| 		return &media_target_; | ||||
| 	} | ||||
| #define Provider(type, name, member)	\ | ||||
| 	type *MultiMachine::name() {	\ | ||||
| 		if(has_picked_) {	\ | ||||
| 			return machines_.front()->name();	\ | ||||
| 		} else {	\ | ||||
| 			return &member;	\ | ||||
| 		}	\ | ||||
| 	} | ||||
|  | ||||
| CRTMachine::Machine *MultiMachine::crt_machine() { | ||||
| 	if(has_picked_) { | ||||
| 		return machines_.front()->crt_machine(); | ||||
| 	} else { | ||||
| 		return &crt_machine_; | ||||
| 	} | ||||
| } | ||||
| Provider(Configurable::Device, configurable_device, configurable_) | ||||
| Provider(MachineTypes::TimedMachine, timed_machine, timed_machine_) | ||||
| Provider(MachineTypes::ScanProducer, scan_producer, scan_producer_) | ||||
| Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_) | ||||
| Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_) | ||||
| Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_) | ||||
| Provider(MachineTypes::MediaTarget, media_target, media_target_) | ||||
|  | ||||
| JoystickMachine::Machine *MultiMachine::joystick_machine() { | ||||
| 	if(has_picked_) { | ||||
| 		return machines_.front()->joystick_machine(); | ||||
| 	} else { | ||||
| 		return &joystick_machine_; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| KeyboardMachine::Machine *MultiMachine::keyboard_machine() { | ||||
| 	if(has_picked_) { | ||||
| 		return machines_.front()->keyboard_machine(); | ||||
| 	} else { | ||||
| 		return &keyboard_machine_; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| MouseMachine::Machine *MultiMachine::mouse_machine() { | ||||
| MachineTypes::MouseMachine *MultiMachine::mouse_machine() { | ||||
| 	// TODO. | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| Configurable::Device *MultiMachine::configurable_device() { | ||||
| 	if(has_picked_) { | ||||
| 		return machines_.front()->configurable_device(); | ||||
| 	} else { | ||||
| 		return &configurable_; | ||||
| 	} | ||||
| } | ||||
| #undef Provider | ||||
|  | ||||
| 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()); | ||||
| 		(machines.front()->timed_machine()->get_confidence() > 0.9f) || | ||||
| 		(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence()); | ||||
| } | ||||
|  | ||||
| void MultiMachine::multi_crt_did_run_machines() { | ||||
| 	std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_); | ||||
| void MultiMachine::did_run_machines(MultiTimedMachine *) { | ||||
| 	std::lock_guard machines_lock(machines_mutex_); | ||||
| #ifndef NDEBUG | ||||
| 	for(const auto &machine: machines_) { | ||||
| 		CRTMachine::Machine *crt = machine->crt_machine(); | ||||
| 		LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; "); | ||||
| 		auto timed_machine = machine->timed_machine(); | ||||
| 		LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; "); | ||||
| 	} | ||||
| 	LOGNBR(std::endl); | ||||
| #endif | ||||
| @@ -91,13 +72,14 @@ void MultiMachine::multi_crt_did_run_machines() { | ||||
| 	DynamicMachine *front = machines_.front().get(); | ||||
| 	std::stable_sort(machines_.begin(), machines_.end(), | ||||
| 		[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){ | ||||
| 			CRTMachine::Machine *lhs_crt = lhs->crt_machine(); | ||||
| 			CRTMachine::Machine *rhs_crt = rhs->crt_machine(); | ||||
| 			return lhs_crt->get_confidence() > rhs_crt->get_confidence(); | ||||
| 			auto lhs_timed = lhs->timed_machine(); | ||||
| 			auto rhs_timed = rhs->timed_machine(); | ||||
| 			return lhs_timed->get_confidence() > rhs_timed->get_confidence(); | ||||
| 		}); | ||||
|  | ||||
| 	if(machines_.front().get() != front) { | ||||
| 		crt_machine_.did_change_machine_order(); | ||||
| 		scan_producer_.did_change_machine_order(); | ||||
| 		audio_producer_.did_change_machine_order(); | ||||
| 	} | ||||
|  | ||||
| 	if(would_collapse(machines_)) { | ||||
|   | ||||
| @@ -11,8 +11,9 @@ | ||||
|  | ||||
| #include "../../../Machines/DynamicMachine.hpp" | ||||
|  | ||||
| #include "Implementation/MultiProducer.hpp" | ||||
| #include "Implementation/MultiConfigurable.hpp" | ||||
| #include "Implementation/MultiCRTMachine.hpp" | ||||
| #include "Implementation/MultiProducer.hpp" | ||||
| #include "Implementation/MultiJoystickMachine.hpp" | ||||
| #include "Implementation/MultiKeyboardMachine.hpp" | ||||
| #include "Implementation/MultiMediaTarget.hpp" | ||||
| @@ -38,7 +39,7 @@ namespace Dynamic { | ||||
| 	If confidence for any machine becomes disproportionately low compared to | ||||
| 	the others in the set, that machine stops running. | ||||
| */ | ||||
| class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | ||||
| class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate { | ||||
| 	public: | ||||
| 		/*! | ||||
| 			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in | ||||
| @@ -52,21 +53,25 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | ||||
|  | ||||
| 		Activity::Source *activity_source() final; | ||||
| 		Configurable::Device *configurable_device() final; | ||||
| 		CRTMachine::Machine *crt_machine() final; | ||||
| 		JoystickMachine::Machine *joystick_machine() final; | ||||
| 		MouseMachine::Machine *mouse_machine() final; | ||||
| 		KeyboardMachine::Machine *keyboard_machine() final; | ||||
| 		MediaTarget::Machine *media_target() final; | ||||
| 		MachineTypes::TimedMachine *timed_machine() final; | ||||
| 		MachineTypes::ScanProducer *scan_producer() final; | ||||
| 		MachineTypes::AudioProducer *audio_producer() final; | ||||
| 		MachineTypes::JoystickMachine *joystick_machine() final; | ||||
| 		MachineTypes::KeyboardMachine *keyboard_machine() final; | ||||
| 		MachineTypes::MouseMachine *mouse_machine() final; | ||||
| 		MachineTypes::MediaTarget *media_target() final; | ||||
| 		void *raw_pointer() final; | ||||
|  | ||||
| 	private: | ||||
| 		void multi_crt_did_run_machines() final; | ||||
| 		void did_run_machines(MultiTimedMachine *) final; | ||||
|  | ||||
| 		std::vector<std::unique_ptr<DynamicMachine>> machines_; | ||||
| 		std::recursive_mutex machines_mutex_; | ||||
|  | ||||
| 		MultiConfigurable configurable_; | ||||
| 		MultiCRTMachine crt_machine_; | ||||
| 		MultiTimedMachine timed_machine_; | ||||
| 		MultiScanProducer scan_producer_; | ||||
| 		MultiAudioProducer audio_producer_; | ||||
| 		MultiJoystickMachine joystick_machine_; | ||||
| 		MultiKeyboardMachine keyboard_machine_; | ||||
| 		MultiMediaTarget media_target_; | ||||
|   | ||||
| @@ -21,8 +21,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s | ||||
| 	auto catalogue = std::make_unique<Catalogue>(); | ||||
| 	Storage::Encodings::MFM::Parser parser(false, disk); | ||||
|  | ||||
| 	Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0); | ||||
| 	Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1); | ||||
| 	const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0); | ||||
| 	const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1); | ||||
|  | ||||
| 	if(!names || !details) return nullptr; | ||||
| 	if(names->samples.empty() || details->samples.empty()) return nullptr; | ||||
| @@ -48,18 +48,18 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s | ||||
| 		char name[10]; | ||||
| 		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]); | ||||
| 		new_file.name = name; | ||||
| 		new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14)); | ||||
| 		new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10)); | ||||
| 		new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80); | ||||
| 		new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14)); | ||||
| 		new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10)); | ||||
| 		new_file.is_protected = names->samples[0][file_offset + 7] & 0x80; | ||||
|  | ||||
| 		long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12)); | ||||
| 		long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12)); | ||||
| 		int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8); | ||||
| 		new_file.data.reserve(static_cast<std::size_t>(data_length)); | ||||
| 		new_file.data.reserve(size_t(data_length)); | ||||
|  | ||||
| 		if(start_sector < 2) continue; | ||||
| 		while(data_length > 0) { | ||||
| 			uint8_t sector = static_cast<uint8_t>(start_sector % 10); | ||||
| 			uint8_t track = static_cast<uint8_t>(start_sector / 10); | ||||
| 			uint8_t sector = uint8_t(start_sector % 10); | ||||
| 			uint8_t track = uint8_t(start_sector / 10); | ||||
| 			start_sector++; | ||||
|  | ||||
| 			Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector); | ||||
| @@ -84,7 +84,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std:: | ||||
| 	std::vector<uint8_t> root_directory; | ||||
| 	root_directory.reserve(5 * 256); | ||||
| 	for(uint8_t c = 2; c < 7; c++) { | ||||
| 		Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c); | ||||
| 		const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c); | ||||
| 		if(!sector) return nullptr; | ||||
| 		root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end()); | ||||
| 	} | ||||
|   | ||||
| @@ -29,7 +29,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
| 		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue; | ||||
|  | ||||
| 		// is a copyright string present? | ||||
| 		uint8_t copyright_offset = segment.data[7]; | ||||
| 		const uint8_t copyright_offset = segment.data[7]; | ||||
| 		if( | ||||
| 			segment.data[copyright_offset] != 0x00 || | ||||
| 			segment.data[copyright_offset+1] != 0x28 || | ||||
| @@ -57,9 +57,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
| 	return acorn_cartridges; | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Electron; | ||||
| 	target->confidence = 0.5; // TODO: a proper estimation | ||||
| 	target->has_dfs = false; | ||||
| 	target->has_adfs = false; | ||||
| @@ -84,8 +83,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me | ||||
| 			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code, | ||||
| 			// so that's also justification to *RUN | ||||
| 			std::size_t pointer = 0; | ||||
| 			uint8_t *data = &files.front().data[0]; | ||||
| 			std::size_t data_size = files.front().data.size(); | ||||
| 			uint8_t *const data = &files.front().data[0]; | ||||
| 			const std::size_t data_size = files.front().data.size(); | ||||
| 			while(1) { | ||||
| 				if(pointer >= data_size-1 || data[pointer] != 13) { | ||||
| 					is_basic = false; | ||||
|   | ||||
| @@ -41,24 +41,24 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage:: | ||||
| 	char name[11]; | ||||
| 	std::size_t name_ptr = 0; | ||||
| 	while(!tape->is_at_end() && name_ptr < sizeof(name)) { | ||||
| 		name[name_ptr] = (char)parser.get_next_byte(tape); | ||||
| 		name[name_ptr] = char(parser.get_next_byte(tape)); | ||||
| 		if(!name[name_ptr]) break; | ||||
| 		name_ptr++; | ||||
| 		++name_ptr; | ||||
| 	} | ||||
| 	name[sizeof(name)-1] = '\0'; | ||||
| 	new_chunk->name = name; | ||||
|  | ||||
| 	// addresses | ||||
| 	new_chunk->load_address = (uint32_t)parser.get_next_word(tape); | ||||
| 	new_chunk->execution_address = (uint32_t)parser.get_next_word(tape); | ||||
| 	new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape)); | ||||
| 	new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape)); | ||||
| 	new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape)); | ||||
| 	new_chunk->next_address = (uint32_t)parser.get_next_word(tape); | ||||
| 	new_chunk->load_address = uint32_t(parser.get_next_word(tape)); | ||||
| 	new_chunk->execution_address = uint32_t(parser.get_next_word(tape)); | ||||
| 	new_chunk->block_number = uint16_t(parser.get_next_short(tape)); | ||||
| 	new_chunk->block_length = uint16_t(parser.get_next_short(tape)); | ||||
| 	new_chunk->block_flag = uint8_t(parser.get_next_byte(tape)); | ||||
| 	new_chunk->next_address = uint32_t(parser.get_next_word(tape)); | ||||
|  | ||||
| 	uint16_t calculated_header_crc = parser.get_crc(); | ||||
| 	uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape)); | ||||
| 	stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8)); | ||||
| 	uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape)); | ||||
| 	stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8)); | ||||
| 	new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc; | ||||
|  | ||||
| 	if(!new_chunk->header_crc_matched) return nullptr; | ||||
| @@ -66,13 +66,13 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage:: | ||||
| 	parser.reset_crc(); | ||||
| 	new_chunk->data.reserve(new_chunk->block_length); | ||||
| 	for(int c = 0; c < new_chunk->block_length; c++) { | ||||
| 		new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape))); | ||||
| 		new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape))); | ||||
| 	} | ||||
|  | ||||
| 	if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) { | ||||
| 		uint16_t calculated_data_crc = parser.get_crc(); | ||||
| 		uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape)); | ||||
| 		stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8)); | ||||
| 		uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape)); | ||||
| 		stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8)); | ||||
| 		new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc; | ||||
| 	} else { | ||||
| 		new_chunk->data_crc_matched = true; | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| #ifndef Analyser_Static_Acorn_Target_h | ||||
| #define Analyser_Static_Acorn_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| @@ -16,11 +17,18 @@ namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Acorn { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	bool has_adfs = false; | ||||
| 	bool has_dfs = false; | ||||
| 	bool should_shift_restart = false; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::Electron) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(has_adfs); | ||||
| 			DeclareField(has_dfs); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -179,10 +179,9 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	TargetList destination; | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::AmstradCPC; | ||||
| 	target->confidence = 0.5; | ||||
|  | ||||
| 	target->model = Target::Model::CPC6128; | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
| #ifndef Analyser_Static_AmstradCPC_Target_h | ||||
| #define Analyser_Static_AmstradCPC_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| @@ -16,15 +18,17 @@ namespace Analyser { | ||||
| namespace Static { | ||||
| namespace AmstradCPC { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class Model { | ||||
| 		CPC464, | ||||
| 		CPC664, | ||||
| 		CPC6128 | ||||
| 	}; | ||||
|  | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	ReflectableEnum(Model, CPC464, CPC664, CPC6128); | ||||
| 	Model model = Model::CPC464; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::AmstradCPC) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(model); | ||||
| 			AnnounceEnum(Model); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -9,9 +9,8 @@ | ||||
| #include "StaticAnalyser.hpp" | ||||
| #include "Target.hpp" | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::AppleII; | ||||
| 	target->media = media; | ||||
|  | ||||
| 	if(!target->media.disks.empty()) | ||||
|   | ||||
| @@ -9,27 +9,38 @@ | ||||
| #ifndef Target_h | ||||
| #define Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
|  | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace AppleII { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class Model { | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	ReflectableEnum(Model, | ||||
| 		II, | ||||
| 		IIplus, | ||||
| 		IIe, | ||||
| 		EnhancedIIe | ||||
| 	}; | ||||
| 	enum class DiskController { | ||||
| 	); | ||||
| 	ReflectableEnum(DiskController, | ||||
| 		None, | ||||
| 		SixteenSector, | ||||
| 		ThirteenSector | ||||
| 	}; | ||||
| 	); | ||||
|  | ||||
| 	Model model = Model::IIe; | ||||
| 	DiskController disk_controller = DiskController::None; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::AppleII) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(model); | ||||
| 			DeclareField(disk_controller); | ||||
| 			AnnounceEnum(Model); | ||||
| 			AnnounceEnum(DiskController); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -16,24 +16,22 @@ using namespace Analyser::Static::Atari2600; | ||||
| using Target = Analyser::Static::Atari2600::Target; | ||||
|  | ||||
| static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||
| 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid | ||||
| 	uint16_t entry_address, break_address; | ||||
| 	// If this is a 2kb cartridge then it's definitely either unpaged or a CommaVid. | ||||
| 	const uint16_t entry_address = uint16_t(segment.data[0x7fc] | (segment.data[0x7fd] << 8)) & 0x1fff; | ||||
| 	const uint16_t break_address = uint16_t(segment.data[0x7fe] | (segment.data[0x7ff] << 8)) & 0x1fff; | ||||
|  | ||||
| 	entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff; | ||||
| 	break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff; | ||||
|  | ||||
| 	// a CommaVid start address needs to be outside of its RAM | ||||
| 	// A CommaVid start address needs to be outside of its RAM. | ||||
| 	if(entry_address < 0x1800 || break_address < 0x1800) return; | ||||
|  | ||||
| 	std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) { | ||||
| 		address &= 0x1fff; | ||||
| 		return static_cast<std::size_t>(address - 0x1800); | ||||
| 		return size_t(address - 0x1800); | ||||
| 	}; | ||||
| 	Analyser::Static::MOS6502::Disassembly high_location_disassembly = | ||||
| 		Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address}); | ||||
|  | ||||
| 	// assume that any kind of store that looks likely to be intended for large amounts of memory implies | ||||
| 	// large amounts of memory | ||||
| 	// Assume that any kind of store that looks likely to be intended for large amounts of memory implies | ||||
| 	// large amounts of memory. | ||||
| 	bool has_wide_area_store = false; | ||||
| 	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) { | ||||
| 		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) { | ||||
| @@ -45,17 +43,17 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations; | ||||
| 	// Conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations; | ||||
| 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses | ||||
| 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it | ||||
| 	// attempts to modify itself but it probably doesn't | ||||
| 	// attempts to modify itself but it probably doesn't. | ||||
| 	if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid; | ||||
| } | ||||
|  | ||||
| static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and | ||||
| 	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all | ||||
| 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?) | ||||
| 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?). | ||||
| 	if( | ||||
| 		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 && | ||||
| 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | ||||
| @@ -65,7 +63,7 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// make an assumption that this is the Atari paging model | ||||
| 	// Make an assumption that this is the Atari paging model. | ||||
| 	target.paging_model = Target::PagingModel::Atari8k; | ||||
|  | ||||
| 	std::set<uint16_t> internal_accesses; | ||||
| @@ -90,8 +88,8 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid | ||||
| 	else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision; | ||||
| } | ||||
|  | ||||
| static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// make an assumption that this is the Atari paging model | ||||
| static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// Make an assumption that this is the Atari paging model. | ||||
| 	target.paging_model = Target::PagingModel::Atari16k; | ||||
|  | ||||
| 	std::set<uint16_t> internal_accesses; | ||||
| @@ -110,8 +108,8 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri | ||||
| 	if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork; | ||||
| } | ||||
|  | ||||
| static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// make an assumption that this is a Tigervision if there is a write to 3F | ||||
| static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||
| 	// Make an assumption that this is a Tigervision if there is a write to 3F. | ||||
| 	target.paging_model = | ||||
| 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | ||||
| 			Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy; | ||||
| @@ -123,17 +121,15 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	uint16_t entry_address, break_address; | ||||
|  | ||||
| 	entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8)); | ||||
| 	break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8)); | ||||
| 	const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8)); | ||||
| 	const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8)); | ||||
|  | ||||
| 	std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) { | ||||
| 		if(!(address & 0x1000)) return static_cast<std::size_t>(-1); | ||||
| 		return static_cast<std::size_t>(address & 0xfff); | ||||
| 		if(!(address & 0x1000)) return size_t(-1); | ||||
| 		return size_t(address & 0xfff); | ||||
| 	}; | ||||
|  | ||||
| 	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end()); | ||||
| 	const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end()); | ||||
| 	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address}); | ||||
|  | ||||
| 	switch(segment.data.size()) { | ||||
| @@ -159,7 +155,7 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM | ||||
| 	// Check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM | ||||
| 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the | ||||
| 	// next 128 bytes. So check for that. | ||||
| 	if(	target.paging_model != Target::PagingModel::CBSRamPlus && | ||||
| @@ -174,17 +170,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge | ||||
| 		target.uses_superchip = has_superchip; | ||||
| 	} | ||||
|  | ||||
| 	// check for a Tigervision or Tigervision-esque scheme | ||||
| 	// Check for a Tigervision or Tigervision-esque scheme | ||||
| 	if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) { | ||||
| 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | ||||
| 		if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	// TODO: sanity checking; is this image really for an Atari 2600? | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Atari2600; | ||||
| 	target->confidence = 0.5; | ||||
| 	target->media.cartridges = media.cartridges; | ||||
| 	target->paging_model = Target::PagingModel::None; | ||||
|   | ||||
| @@ -34,6 +34,8 @@ struct Target: public ::Analyser::Static::Target { | ||||
| 	// TODO: shouldn't these be properties of the cartridge? | ||||
| 	PagingModel paging_model = PagingModel::None; | ||||
| 	bool uses_superchip = false; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::Atari2600) {} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -9,16 +9,15 @@ | ||||
| #include "StaticAnalyser.hpp" | ||||
| #include "Target.hpp" | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	// This analyser can comprehend disks and mass-storage devices only. | ||||
| 	if(media.disks.empty()) return {}; | ||||
|  | ||||
| 	// As there is at least one usable media image, wave it through. | ||||
| 	Analyser::Static::TargetList targets; | ||||
|  | ||||
| 	using Target = Analyser::Static::Target; | ||||
| 	auto *target = new Target; | ||||
| 	target->machine = Analyser::Machine::AtariST; | ||||
| 	using Target = Analyser::Static::AtariST::Target; | ||||
| 	auto *const target = new Target(); | ||||
| 	target->media = media; | ||||
| 	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target)); | ||||
|  | ||||
|   | ||||
| @@ -9,11 +9,15 @@ | ||||
| #ifndef Analyser_Static_AtariST_Target_h | ||||
| #define Analyser_Static_AtariST_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
|  | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace AtariST { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	Target() : Analyser::Static::Target(Machine::AtariST) {} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
|  | ||||
| 		// the two bytes that will be first must be 0xaa and 0x55, either way around | ||||
| 		auto *start = &segment.data[0]; | ||||
| 		if((data_size & static_cast<std::size_t>(~8191)) > 32768) { | ||||
| 		if((data_size & size_t(~8191)) > 32768) { | ||||
| 			start = &segment.data[segment.data.size() - 16384]; | ||||
| 		} | ||||
| 		if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue; | ||||
| @@ -52,10 +52,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
| 	return coleco_cartridges; | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	TargetList targets; | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::ColecoVision; | ||||
| 	auto target = std::make_unique<Target>(Machine::ColecoVision); | ||||
| 	target->confidence = 1.0f - 1.0f / 32768.0f; | ||||
| 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | ||||
| 	if(!target->media.empty()) | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
| 			@returns a sector if one was found; @c nullptr otherwise. | ||||
| 		*/ | ||||
| 		std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) { | ||||
| 			int difference = static_cast<int>(track) - static_cast<int>(track_); | ||||
| 			int difference = int(track) - int(track_); | ||||
| 			track_ = track; | ||||
|  | ||||
| 			if(difference) { | ||||
| @@ -71,7 +71,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
| 		std::shared_ptr<Sector> sector_cache_[65536]; | ||||
|  | ||||
| 		void process_input_bit(int value) { | ||||
| 			shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff; | ||||
| 			shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff; | ||||
| 			bit_count_++; | ||||
| 		} | ||||
|  | ||||
| @@ -112,15 +112,15 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
| 		} | ||||
|  | ||||
| 		std::shared_ptr<Sector> get_sector(uint8_t sector) { | ||||
| 			uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector); | ||||
| 			const uint16_t sector_address = uint16_t((track_ << 8) | sector); | ||||
| 			if(sector_cache_[sector_address]) return sector_cache_[sector_address]; | ||||
|  | ||||
| 			std::shared_ptr<Sector> first_sector = get_next_sector(); | ||||
| 			const std::shared_ptr<Sector> first_sector = get_next_sector(); | ||||
| 			if(!first_sector) return first_sector; | ||||
| 			if(first_sector->sector == sector) return first_sector; | ||||
|  | ||||
| 			while(1) { | ||||
| 				std::shared_ptr<Sector> next_sector = get_next_sector(); | ||||
| 				const std::shared_ptr<Sector> next_sector = get_next_sector(); | ||||
| 				if(next_sector->sector == first_sector->sector) return nullptr; | ||||
| 				if(next_sector->sector == sector) return next_sector; | ||||
| 			} | ||||
| @@ -138,12 +138,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
| 				} | ||||
|  | ||||
| 				// get sector details, skip if this looks malformed | ||||
| 				uint8_t checksum = static_cast<uint8_t>(get_next_byte()); | ||||
| 				sector->sector = static_cast<uint8_t>(get_next_byte()); | ||||
| 				sector->track = static_cast<uint8_t>(get_next_byte()); | ||||
| 				uint8_t checksum = uint8_t(get_next_byte()); | ||||
| 				sector->sector = uint8_t(get_next_byte()); | ||||
| 				sector->track = uint8_t(get_next_byte()); | ||||
| 				uint8_t disk_id[2]; | ||||
| 				disk_id[0] = static_cast<uint8_t>(get_next_byte()); | ||||
| 				disk_id[1] = static_cast<uint8_t>(get_next_byte()); | ||||
| 				disk_id[0] = uint8_t(get_next_byte()); | ||||
| 				disk_id[1] = uint8_t(get_next_byte()); | ||||
| 				if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue; | ||||
|  | ||||
| 				// look for the following data | ||||
| @@ -154,12 +154,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | ||||
|  | ||||
| 				checksum = 0; | ||||
| 				for(std::size_t c = 0; c < 256; c++) { | ||||
| 					sector->data[c] = static_cast<uint8_t>(get_next_byte()); | ||||
| 					sector->data[c] = uint8_t(get_next_byte()); | ||||
| 					checksum ^= sector->data[c]; | ||||
| 				} | ||||
|  | ||||
| 				if(checksum == get_next_byte()) { | ||||
| 					uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector); | ||||
| 					uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector); | ||||
| 					sector_cache_[sector_address] = sector; | ||||
| 					return sector; | ||||
| 				} | ||||
| @@ -192,7 +192,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St | ||||
| 	} | ||||
|  | ||||
| 	// parse directory | ||||
| 	std::size_t header_pointer = static_cast<std::size_t>(-32); | ||||
| 	std::size_t header_pointer = size_t(-32); | ||||
| 	while(header_pointer+32+31 < directory.size()) { | ||||
| 		header_pointer += 32; | ||||
|  | ||||
| @@ -216,7 +216,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St | ||||
| 		} | ||||
| 		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false); | ||||
|  | ||||
| 		std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8); | ||||
| 		std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8); | ||||
| 		new_file.data.reserve((number_of_sectors - 1) * 254 + 252); | ||||
|  | ||||
| 		bool is_first_sector = true; | ||||
| @@ -227,7 +227,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St | ||||
| 			next_track = sector->data[0]; | ||||
| 			next_sector = sector->data[1]; | ||||
|  | ||||
| 			if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8); | ||||
| 			if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8); | ||||
| 			if(next_track) | ||||
| 				new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end()); | ||||
| 			else | ||||
|   | ||||
| @@ -23,7 +23,7 @@ bool Analyser::Static::Commodore::File::is_basic() { | ||||
| 	//		... null-terminated code ... | ||||
| 	//	(with a next line address of 0000 indicating end of program) | ||||
| 	while(1) { | ||||
| 		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break; | ||||
| 		if(size_t(line_address - starting_address) >= data.size() + 2) break; | ||||
|  | ||||
| 		uint16_t next_line_address = data[line_address - starting_address]; | ||||
| 		next_line_address |= data[line_address - starting_address + 1] << 8; | ||||
| @@ -33,13 +33,13 @@ bool Analyser::Static::Commodore::File::is_basic() { | ||||
| 		} | ||||
| 		if(next_line_address < line_address + 5) break; | ||||
|  | ||||
| 		if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break; | ||||
| 		if(size_t(line_address - starting_address) >= data.size() + 5) break; | ||||
| 		uint16_t next_line_number = data[line_address - starting_address + 2]; | ||||
| 		next_line_number |= data[line_address - starting_address + 3] << 8; | ||||
|  | ||||
| 		if(next_line_number <= line_number) break; | ||||
|  | ||||
| 		line_number = static_cast<uint16_t>(next_line_number); | ||||
| 		line_number = uint16_t(next_line_number); | ||||
| 		line_address = next_line_address; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -42,7 +42,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||
| 	return vic20_cartridges; | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) { | ||||
| 	TargetList destination; | ||||
|  | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| @@ -94,6 +94,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media | ||||
| 		switch(files.front().starting_address) { | ||||
| 			default: | ||||
| 				LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) <<  files.front().starting_address); | ||||
| 				[[fallthrough]]; | ||||
| 			case 0x1001: | ||||
| 				memory_model = Target::MemoryModel::Unexpanded; | ||||
| 			break; | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
| #ifndef Analyser_Static_Commodore_Target_h | ||||
| #define Analyser_Static_Commodore_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| @@ -16,20 +18,20 @@ namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Commodore { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	enum class MemoryModel { | ||||
| 		Unexpanded, | ||||
| 		EightKB, | ||||
| 		ThirtyTwoKB | ||||
| 	}; | ||||
|  | ||||
| 	enum class Region { | ||||
| 	ReflectableEnum(Region, | ||||
| 		American, | ||||
| 		Danish, | ||||
| 		Japanese, | ||||
| 		European, | ||||
| 		Swedish | ||||
| 	}; | ||||
| 	); | ||||
|  | ||||
| 	/// Maps from a named memory model to a bank enabled/disabled set. | ||||
| 	void set_memory_model(MemoryModel memory_model) { | ||||
| @@ -54,6 +56,19 @@ struct Target: public ::Analyser::Static::Target { | ||||
| 	Region region = Region::European; | ||||
| 	bool has_c1540 = false; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::Vic20) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(enabled_ram.bank0); | ||||
| 			DeclareField(enabled_ram.bank1); | ||||
| 			DeclareField(enabled_ram.bank2); | ||||
| 			DeclareField(enabled_ram.bank3); | ||||
| 			DeclareField(enabled_ram.bank5); | ||||
| 			DeclareField(region); | ||||
| 			DeclareField(has_c1540); | ||||
| 			AnnounceEnum(Region); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -26,12 +26,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
|  | ||||
| 		Instruction instruction; | ||||
| 		instruction.address = address; | ||||
| 		address++; | ||||
| 		++address; | ||||
|  | ||||
| 		// get operation | ||||
| 		uint8_t operation = memory[local_address]; | ||||
| 		// Get operation. | ||||
| 		const uint8_t operation = memory[local_address]; | ||||
|  | ||||
| 		// decode addressing mode | ||||
| 		// Decode addressing mode. | ||||
| 		switch(operation&0x1f) { | ||||
| 			case 0x00: | ||||
| 				if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate; | ||||
| @@ -74,7 +74,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		// decode operation | ||||
| 		// Decode operation. | ||||
| #define RM_INSTRUCTION(base, op)	\ | ||||
| 	case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\ | ||||
| 		instruction.operation = op;	\ | ||||
| @@ -222,14 +222,14 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
| #undef M_INSTRUCTION | ||||
| #undef IM_INSTRUCTION | ||||
|  | ||||
| 		// get operand | ||||
| 		// Get operand. | ||||
| 		switch(instruction.addressing_mode) { | ||||
| 			// zero-byte operands | ||||
| 			// Zero-byte operands. | ||||
| 			case Instruction::Implied: | ||||
| 				instruction.operand = 0; | ||||
| 			break; | ||||
|  | ||||
| 			// one-byte operands | ||||
| 			// One-byte operands. | ||||
| 			case Instruction::Immediate: | ||||
| 			case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY: | ||||
| 			case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY: | ||||
| @@ -242,7 +242,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
| 			} | ||||
| 			break; | ||||
|  | ||||
| 			// two-byte operands | ||||
| 			// Two-byte operands. | ||||
| 			case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY: | ||||
| 			case Instruction::Indirect: { | ||||
| 				std::size_t low_operand_address = address_mapper(address); | ||||
| @@ -250,18 +250,18 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
| 				if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return; | ||||
| 				address += 2; | ||||
|  | ||||
| 				instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8); | ||||
| 				instruction.operand = memory[low_operand_address] | uint16_t(memory[high_operand_address] << 8); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		// store the instruction away | ||||
| 		// Store the instruction. | ||||
| 		disassembly.disassembly.instructions_by_address[instruction.address] = instruction; | ||||
|  | ||||
| 		// TODO: something wider-ranging than this | ||||
| 		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) { | ||||
| 			std::size_t mapped_address = address_mapper(instruction.operand); | ||||
| 			bool is_external = mapped_address >= memory.size(); | ||||
| 			const size_t mapped_address = address_mapper(instruction.operand); | ||||
| 			const bool is_external = mapped_address >= memory.size(); | ||||
|  | ||||
| 			switch(instruction.operation) { | ||||
| 				default: break; | ||||
| @@ -290,7 +290,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// decide on overall flow control | ||||
| 		// Decide on overall flow control. | ||||
| 		if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return; | ||||
| 		if(instruction.operation == Instruction::BRK) return;	// TODO: check whether IRQ vector is within memory range | ||||
| 		if(instruction.operation == Instruction::JSR) { | ||||
| @@ -302,7 +302,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | ||||
| 			return; | ||||
| 		} | ||||
| 		if(instruction.addressing_mode == Instruction::Relative) { | ||||
| 			uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand); | ||||
| 			uint16_t destination = uint16_t(address + int8_t(instruction.operand)); | ||||
| 			disassembly.remaining_entry_points.push_back(destination); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -1,9 +0,0 @@ | ||||
| // | ||||
| //  AddressMapper.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 30/12/2017. | ||||
| //  Copyright 2017 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "AddressMapper.hpp" | ||||
| @@ -21,7 +21,7 @@ namespace Disassembler { | ||||
| */ | ||||
| template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) { | ||||
| 	return [start_address](T argument) { | ||||
| 		return static_cast<std::size_t>(argument - start_address); | ||||
| 		return size_t(argument - start_address); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class Accessor { | ||||
| 		uint16_t word() { | ||||
| 			uint8_t low = byte(); | ||||
| 			uint8_t high = byte(); | ||||
| 			return static_cast<uint16_t>(low | (high << 8)); | ||||
| 			return uint16_t(low | (high << 8)); | ||||
| 		} | ||||
|  | ||||
| 		bool overrun() { | ||||
| @@ -562,7 +562,7 @@ struct Z80Disassembler { | ||||
| 			int access_type = | ||||
| 				((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) | | ||||
| 				((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0); | ||||
| 			uint16_t address = static_cast<uint16_t>(instruction.operand); | ||||
| 			uint16_t address = uint16_t(instruction.operand); | ||||
| 			bool is_internal = address_mapper(address) < memory.size(); | ||||
| 			switch(access_type) { | ||||
| 				default: break; | ||||
| @@ -594,7 +594,7 @@ struct Z80Disassembler { | ||||
| 				instruction.operation == Instruction::Operation::JR || | ||||
| 				instruction.operation == Instruction::Operation::CALL || | ||||
| 				instruction.operation == Instruction::Operation::RST) { | ||||
| 				disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand)); | ||||
| 				disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand)); | ||||
| 			} | ||||
|  | ||||
| 			// This is it if: an unconditional RET, RETI, RETN, JP or JR is found. | ||||
|   | ||||
| @@ -20,8 +20,7 @@ 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; | ||||
| 	auto *const target = new Target; | ||||
|  | ||||
| 	if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) { | ||||
| 		target->disk_controller = Target::DiskController::ThirteenSector; | ||||
| @@ -32,10 +31,9 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector | ||||
| 	return target; | ||||
| } | ||||
|  | ||||
| Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) { | ||||
| Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) { | ||||
| 	using Target = Analyser::Static::Oric::Target; | ||||
| 	auto *target = new Target; | ||||
| 	target->machine = Analyser::Machine::Oric; | ||||
| 	auto *const target = new Target; | ||||
| 	target->rom = Target::ROM::Pravetz; | ||||
| 	target->disk_interface = Target::DiskInterface::Pravetz; | ||||
| 	target->loading_command = "CALL 800\n"; | ||||
| @@ -44,13 +42,13 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector | ||||
|  | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	// This analyser can comprehend disks only. | ||||
| 	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( | ||||
| 	const auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0))); | ||||
| 	const 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; | ||||
| @@ -77,7 +75,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m | ||||
| 	// If the boot sector looks like it's intended for the Oric, create an Oric. | ||||
| 	// Otherwise go with the Apple II. | ||||
|  | ||||
| 	auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800}); | ||||
| 	const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800}); | ||||
|  | ||||
| 	bool did_read_shift_register = false; | ||||
| 	bool is_oric = false; | ||||
|   | ||||
| @@ -27,7 +27,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | ||||
| 	std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||
| 	if(segment.data.size() & 0x1fff) { | ||||
| 		std::vector<uint8_t> truncated_data; | ||||
| 		std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff; | ||||
| 		std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff; | ||||
| 		truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); | ||||
| 		output_segments.emplace_back(start_address, truncated_data); | ||||
| 	} else { | ||||
| @@ -35,7 +35,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | ||||
| 	} | ||||
|  | ||||
| 	auto target = std::make_unique<Analyser::Static::MSX::Target>(); | ||||
| 	target->machine = Analyser::Machine::MSX; | ||||
| 	target->confidence = confidence; | ||||
|  | ||||
| 	if(type == Analyser::Static::MSX::Cartridge::Type::None) { | ||||
| @@ -97,7 +96,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 		// Reject cartridge if the ROM header wasn't found. | ||||
| 		if(!found_start) continue; | ||||
|  | ||||
| 		uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8)); | ||||
| 		uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8)); | ||||
| 		// TODO: check for a rational init address? | ||||
|  | ||||
| 		// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on. | ||||
| @@ -147,7 +146,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| //				) && | ||||
| //				((next_iterator->second.operand >> 13) != (0x4000 >> 13)) | ||||
| //			) { | ||||
| //				const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand); | ||||
| //				const uint16_t address = uint16_t(next_iterator->second.operand); | ||||
| //				switch(iterator->second.operand) { | ||||
| //					case 0x6000: | ||||
| //						if(address >= 0x6000 && address < 0x8000) { | ||||
| @@ -208,13 +207,13 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 			if(	instruction_pair.second.operation == Instruction::Operation::LD && | ||||
| 				instruction_pair.second.destination == Instruction::Location::Operand_Indirect && | ||||
| 				instruction_pair.second.source == Instruction::Location::A) { | ||||
| 				address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++; | ||||
| 				address_counts[uint16_t(instruction_pair.second.operand)]++; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Weight confidences by number of observed hits. | ||||
| 		float total_hits = | ||||
| 			static_cast<float>( | ||||
| 			float( | ||||
| 				address_counts[0x6000] + address_counts[0x6800] + | ||||
| 				address_counts[0x7000] + address_counts[0x7800] + | ||||
| 				address_counts[0x77ff] + address_counts[0x8000] + | ||||
| @@ -226,7 +225,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 			segment, | ||||
| 			start_address, | ||||
| 			Analyser::Static::MSX::Cartridge::ASCII8kb, | ||||
| 			static_cast<float>(	address_counts[0x6000] + | ||||
| 			float(	address_counts[0x6000] + | ||||
| 					address_counts[0x6800] + | ||||
| 					address_counts[0x7000] + | ||||
| 					address_counts[0x7800]) / total_hits)); | ||||
| @@ -234,7 +233,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 			segment, | ||||
| 			start_address, | ||||
| 			Analyser::Static::MSX::Cartridge::ASCII16kb, | ||||
| 			static_cast<float>(	address_counts[0x6000] + | ||||
| 			float(	address_counts[0x6000] + | ||||
| 					address_counts[0x7000] + | ||||
| 					address_counts[0x77ff]) / total_hits)); | ||||
| 		if(!is_ascii) { | ||||
| @@ -242,7 +241,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 				segment, | ||||
| 				start_address, | ||||
| 				Analyser::Static::MSX::Cartridge::Konami, | ||||
| 				static_cast<float>(	address_counts[0x6000] + | ||||
| 				float(	address_counts[0x6000] + | ||||
| 						address_counts[0x8000] + | ||||
| 						address_counts[0xa000]) / total_hits)); | ||||
| 		} | ||||
| @@ -251,7 +250,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 				segment, | ||||
| 				start_address, | ||||
| 				Analyser::Static::MSX::Cartridge::KonamiWithSCC, | ||||
| 				static_cast<float>(	address_counts[0x5000] + | ||||
| 				float(	address_counts[0x5000] + | ||||
| 						address_counts[0x7000] + | ||||
| 						address_counts[0x9000] + | ||||
| 						address_counts[0xb000]) / total_hits)); | ||||
| @@ -261,7 +260,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom( | ||||
| 	return targets; | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	TargetList destination; | ||||
|  | ||||
| 	// Append targets for any cartridges that look correct. | ||||
| @@ -295,7 +294,6 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi | ||||
| 	target->has_disk_drive = !media.disks.empty(); | ||||
|  | ||||
| 	if(!target->media.empty()) { | ||||
| 		target->machine = Machine::MSX; | ||||
| 		target->confidence = 0.5; | ||||
| 		destination.push_back(std::move(target)); | ||||
| 	} | ||||
|   | ||||
| @@ -44,7 +44,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 		for(std::size_t c = 0; c < sizeof(header); ++c) { | ||||
| 			int next_byte = Parser::get_byte(*file_speed, tape_player); | ||||
| 			if(next_byte == -1) break; | ||||
| 			header[c] = static_cast<uint8_t>(next_byte); | ||||
| 			header[c] = uint8_t(next_byte); | ||||
| 		} | ||||
|  | ||||
| 		bool bytes_are_same = true; | ||||
| @@ -67,7 +67,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 		// Read file name. | ||||
| 		char name[7]; | ||||
| 		for(std::size_t c = 1; c < 6; ++c) | ||||
| 			name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player)); | ||||
| 			name[c] = char(Parser::get_byte(*file_speed, tape_player)); | ||||
| 		name[6] = '\0'; | ||||
| 		file.name = name; | ||||
|  | ||||
| @@ -82,7 +82,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 					int byte = Parser::get_byte(*file_speed, tape_player); | ||||
| 					if(byte == -1) break; | ||||
| 					contains_end_of_file |= (byte == 0x1a); | ||||
| 					file.data.push_back(static_cast<uint8_t>(byte)); | ||||
| 					file.data.push_back(uint8_t(byte)); | ||||
| 				} | ||||
| 				if(c != -1) break; | ||||
| 				if(contains_end_of_file) { | ||||
| @@ -105,13 +105,13 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 			for(c = 0; c < sizeof(locations); ++c) { | ||||
| 				int byte = Parser::get_byte(*file_speed, tape_player); | ||||
| 				if(byte == -1) break; | ||||
| 				locations[c] = static_cast<uint8_t>(byte); | ||||
| 				locations[c] = uint8_t(byte); | ||||
| 			} | ||||
| 			if(c != sizeof(locations)) continue; | ||||
|  | ||||
| 			file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8)); | ||||
| 			end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8)); | ||||
| 			file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8)); | ||||
| 			file.starting_address = uint16_t(locations[0] | (locations[1] << 8)); | ||||
| 			end_address = uint16_t(locations[2] | (locations[3] << 8)); | ||||
| 			file.entry_address = uint16_t(locations[4] | (locations[5] << 8)); | ||||
|  | ||||
| 			if(end_address < file.starting_address) continue; | ||||
|  | ||||
| @@ -119,7 +119,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 			while(length--) { | ||||
| 				int byte = Parser::get_byte(*file_speed, tape_player); | ||||
| 				if(byte == -1) continue; | ||||
| 				file.data.push_back(static_cast<uint8_t>(byte)); | ||||
| 				file.data.push_back(uint8_t(byte)); | ||||
| 			} | ||||
|  | ||||
| 			files.push_back(std::move(file)); | ||||
| @@ -135,10 +135,10 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 			next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player); | ||||
|  | ||||
| 			if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break; | ||||
| 			file.data.push_back(static_cast<uint8_t>(next_address_buffer[0])); | ||||
| 			file.data.push_back(static_cast<uint8_t>(next_address_buffer[1])); | ||||
| 			file.data.push_back(uint8_t(next_address_buffer[0])); | ||||
| 			file.data.push_back(uint8_t(next_address_buffer[1])); | ||||
|  | ||||
| 			uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8)); | ||||
| 			uint16_t next_address = uint16_t(next_address_buffer[0] | (next_address_buffer[1] << 8)); | ||||
| 			if(!next_address) { | ||||
| 				files.push_back(std::move(file)); | ||||
| 				break; | ||||
| @@ -155,7 +155,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage: | ||||
| 					found_error = true; | ||||
| 					break; | ||||
| 				} | ||||
| 				file.data.push_back(static_cast<uint8_t>(byte)); | ||||
| 				file.data.push_back(uint8_t(byte)); | ||||
| 			} | ||||
| 			if(found_error) break; | ||||
| 		} | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
| #ifndef Analyser_Static_MSX_Target_h | ||||
| #define Analyser_Static_MSX_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| @@ -16,15 +18,24 @@ namespace Analyser { | ||||
| namespace Static { | ||||
| namespace MSX { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	bool has_disk_drive = false; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	enum class Region { | ||||
| 	ReflectableEnum(Region, | ||||
| 		Japan, | ||||
| 		USA, | ||||
| 		Europe | ||||
| 	} region = Region::USA; | ||||
| 	); | ||||
| 	Region region = Region::USA; | ||||
|  | ||||
| 	Target(): Analyser::Static::Target(Machine::MSX) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(has_disk_drive); | ||||
| 			DeclareField(region); | ||||
| 			AnnounceEnum(Region); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| #include "StaticAnalyser.hpp" | ||||
| #include "Target.hpp" | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	// This analyser can comprehend disks and mass-storage devices only. | ||||
| 	if(media.disks.empty() && media.mass_storage_devices.empty()) return {}; | ||||
|  | ||||
| @@ -17,8 +17,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media | ||||
| 	Analyser::Static::TargetList targets; | ||||
|  | ||||
| 	using Target = Analyser::Static::Macintosh::Target; | ||||
| 	auto *target = new Target; | ||||
| 	target->machine = Analyser::Machine::Macintosh; | ||||
| 	auto *const target = new Target; | ||||
| 	target->media = media; | ||||
| 	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target)); | ||||
|  | ||||
|   | ||||
| @@ -9,19 +9,25 @@ | ||||
| #ifndef Analyser_Static_Macintosh_Target_h | ||||
| #define Analyser_Static_Macintosh_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
|  | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Macintosh { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class Model { | ||||
| 		Mac128k, | ||||
| 		Mac512k, | ||||
| 		Mac512ke, | ||||
| 		MacPlus | ||||
| 	}; | ||||
|  | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus); | ||||
| 	Model model = Model::MacPlus; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::Macintosh) { | ||||
| 		// Boilerplate for declaring fields and potential values. | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(model); | ||||
| 			AnnounceEnum(Model); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -145,9 +145,8 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) { | ||||
|  | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { | ||||
| 	auto target = std::make_unique<Target>(); | ||||
| 	target->machine = Machine::Oric; | ||||
| 	target->confidence = 0.5; | ||||
|  | ||||
| 	int basic10_votes = 0; | ||||
|   | ||||
| @@ -49,10 +49,10 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage | ||||
| 		} | ||||
|  | ||||
| 		// read end and start addresses | ||||
| 		new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8); | ||||
| 		new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast)); | ||||
| 		new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8); | ||||
| 		new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast)); | ||||
| 		new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8); | ||||
| 		new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast)); | ||||
| 		new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8); | ||||
| 		new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast)); | ||||
|  | ||||
| 		// skip an empty byte | ||||
| 		parser.get_next_byte(tape, is_fast); | ||||
| @@ -61,7 +61,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage | ||||
| 		char file_name[17]; | ||||
| 		int name_pos = 0; | ||||
| 		while(name_pos < 16) { | ||||
| 			file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast); | ||||
| 			file_name[name_pos] = char(parser.get_next_byte(tape, is_fast)); | ||||
| 			if(!file_name[name_pos]) break; | ||||
| 			name_pos++; | ||||
| 		} | ||||
| @@ -72,7 +72,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage | ||||
| 		std::size_t body_length = new_file.ending_address - new_file.starting_address + 1; | ||||
| 		new_file.data.reserve(body_length); | ||||
| 		for(std::size_t c = 0; c < body_length; c++) { | ||||
| 			new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast))); | ||||
| 			new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast))); | ||||
| 		} | ||||
|  | ||||
| 		// only one validation check: was there enough tape? | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
| #ifndef Analyser_Static_Oric_Target_h | ||||
| #define Analyser_Static_Oric_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| @@ -16,25 +18,34 @@ namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Oric { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class ROM { | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	ReflectableEnum(ROM, | ||||
| 		BASIC10, | ||||
| 		BASIC11, | ||||
| 		Pravetz | ||||
| 	}; | ||||
| 	); | ||||
|  | ||||
| 	enum class DiskInterface { | ||||
| 	ReflectableEnum(DiskInterface, | ||||
| 		None, | ||||
| 		Microdisc, | ||||
| 		Pravetz, | ||||
| 		Jasmin, | ||||
| 		BD500, | ||||
| 		None | ||||
| 	}; | ||||
| 		BD500 | ||||
| 	); | ||||
|  | ||||
| 	ROM rom = ROM::BASIC11; | ||||
| 	DiskInterface disk_interface = DiskInterface::None; | ||||
| 	std::string loading_command; | ||||
| 	bool should_start_jasmin = false; | ||||
|  | ||||
| 	Target(): Analyser::Static::Target(Machine::Oric) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(rom); | ||||
| 			DeclareField(disk_interface); | ||||
| 			AnnounceEnum(ROM); | ||||
| 			AnnounceEnum(DiskInterface); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -13,15 +13,13 @@ | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) { | ||||
| 	if(media.cartridges.empty()) | ||||
| 		return {}; | ||||
|  | ||||
| 	TargetList targets; | ||||
| 	auto target = std::make_unique<Target>(); | ||||
|  | ||||
| 	target->machine = Machine::MasterSystem; | ||||
|  | ||||
| 	// Files named .sg are treated as for the SG1000; otherwise assume a Master System. | ||||
| 	if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') { | ||||
| 		target->model = Target::Model::SG1000; | ||||
|   | ||||
| @@ -9,23 +9,27 @@ | ||||
| #ifndef Analyser_Static_Sega_Target_h | ||||
| #define Analyser_Static_Sega_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
|  | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Sega { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	enum class Model { | ||||
| 		SG1000, | ||||
| 		MasterSystem, | ||||
| 		MasterSystem2, | ||||
| 	}; | ||||
|  | ||||
| 	enum class Region { | ||||
| 	ReflectableEnum(Region, | ||||
| 		Japan, | ||||
| 		USA, | ||||
| 		Europe, | ||||
| 		Brazil | ||||
| 	}; | ||||
| 	); | ||||
|  | ||||
| 	enum class PagingScheme { | ||||
| 		Sega, | ||||
| @@ -35,6 +39,13 @@ struct Target: public ::Analyser::Static::Target { | ||||
| 	Model model = Model::MasterSystem; | ||||
| 	Region region = Region::Japan; | ||||
| 	PagingScheme paging_scheme = PagingScheme::Sega; | ||||
|  | ||||
| 	Target() : Analyser::Static::Target(Machine::MasterSystem) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(region); | ||||
| 			AnnounceEnum(Region); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| #define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem | ||||
|   | ||||
| @@ -35,6 +35,16 @@ struct Media { | ||||
| 	bool empty() const { | ||||
| 		return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty(); | ||||
| 	} | ||||
|  | ||||
| 	Media &operator +=(const Media &rhs) { | ||||
| #define append(name)	name.insert(name.end(), rhs.name.begin(), rhs.name.end()); | ||||
| 		append(disks); | ||||
| 		append(tapes); | ||||
| 		append(cartridges); | ||||
| 		append(mass_storage_devices); | ||||
| #undef append | ||||
| 		return *this; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| /*! | ||||
| @@ -42,11 +52,12 @@ struct Media { | ||||
| 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | ||||
| */ | ||||
| struct Target { | ||||
| 	Target(Machine machine) : machine(machine) {} | ||||
| 	virtual ~Target() {} | ||||
|  | ||||
| 	Machine machine; | ||||
| 	Media media; | ||||
| 	float confidence; | ||||
| 	float confidence = 0.0f; | ||||
| }; | ||||
| typedef std::vector<std::unique_ptr<Target>> TargetList; | ||||
|  | ||||
|   | ||||
| @@ -28,13 +28,13 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S | ||||
| 	return files; | ||||
| } | ||||
|  | ||||
| Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||
| Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) { | ||||
| 	TargetList destination; | ||||
| 	if(!media.tapes.empty()) { | ||||
| 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | ||||
| 		media.tapes.front()->reset(); | ||||
| 		if(!files.empty()) { | ||||
| 			Target *target = new Target; | ||||
| 			Target *const target = new Target; | ||||
| 			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target)); | ||||
| 			target->machine = Machine::ZX8081; | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
| #ifndef Analyser_Static_ZX8081_Target_h | ||||
| #define Analyser_Static_ZX8081_Target_h | ||||
|  | ||||
| #include "../../../Reflection/Enum.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| @@ -16,17 +18,26 @@ namespace Analyser { | ||||
| namespace Static { | ||||
| namespace ZX8081 { | ||||
|  | ||||
| struct Target: public ::Analyser::Static::Target { | ||||
| 	enum class MemoryModel { | ||||
| struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	ReflectableEnum(MemoryModel, | ||||
| 		Unexpanded, | ||||
| 		SixteenKB, | ||||
| 		SixtyFourKB | ||||
| 	}; | ||||
| 	); | ||||
|  | ||||
| 	MemoryModel memory_model = MemoryModel::Unexpanded; | ||||
| 	bool is_ZX81 = false; | ||||
| 	bool ZX80_uses_ZX81_ROM = false; | ||||
| 	std::string loading_command; | ||||
|  | ||||
| 	Target(): Analyser::Static::Target(Machine::ZX8081) { | ||||
| 		if(needs_declare()) { | ||||
| 			DeclareField(memory_model); | ||||
| 			DeclareField(is_ZX81); | ||||
| 			DeclareField(ZX80_uses_ZX81_ROM); | ||||
| 			AnnounceEnum(MemoryModel); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -176,7 +176,6 @@ class Cycles: public WrappedInt<Cycles> { | ||||
| 	public: | ||||
| 		forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {} | ||||
| 		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {} | ||||
| 		forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {} | ||||
|  | ||||
| 	private: | ||||
| 		friend WrappedInt; | ||||
| @@ -198,7 +197,6 @@ class HalfCycles: public WrappedInt<HalfCycles> { | ||||
| 		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {} | ||||
|  | ||||
| 		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {} | ||||
| 		forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {} | ||||
|  | ||||
| 		/// @returns The number of whole cycles completely covered by this span of half cycles. | ||||
| 		forceinline constexpr Cycles cycles() const { | ||||
|   | ||||
| @@ -67,7 +67,7 @@ class Source { | ||||
| 		} | ||||
|  | ||||
| 		/// @returns the current preferred clocking strategy. | ||||
| 		virtual Preference preferred_clocking() = 0; | ||||
| 		virtual Preference preferred_clocking() const = 0; | ||||
|  | ||||
| 	private: | ||||
| 		Observer *observer_ = nullptr; | ||||
|   | ||||
| @@ -49,7 +49,7 @@ template <typename TimeUnit> class DeferredQueue { | ||||
| 			@returns The amount of time until the next enqueued action will occur, | ||||
| 				or TimeUnit(-1) if the queue is empty. | ||||
| 		*/ | ||||
| 		TimeUnit time_until_next_action() { | ||||
| 		TimeUnit time_until_next_action() const { | ||||
| 			if(pending_actions_.empty()) return TimeUnit(-1); | ||||
| 			return pending_actions_.front().delay; | ||||
| 		} | ||||
| @@ -95,7 +95,7 @@ template <typename TimeUnit> class DeferredQueue { | ||||
| template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> { | ||||
| 	public: | ||||
| 		/// Constructs a DeferredQueue that will call target(period) in between deferred actions. | ||||
| 		DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | ||||
| 		constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | ||||
|  | ||||
| 		/*! | ||||
| 			Runs for @c length units of time. | ||||
|   | ||||
							
								
								
									
										151
									
								
								ClockReceiver/VSyncPredictor.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								ClockReceiver/VSyncPredictor.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| // | ||||
| //  VSyncPredictor.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 14/06/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef VSyncPredictor_hpp | ||||
| #define VSyncPredictor_hpp | ||||
|  | ||||
| #include "TimeTypes.hpp" | ||||
| #include <cassert> | ||||
| #include <cmath> | ||||
| #include <cstdio> | ||||
|  | ||||
| namespace Time { | ||||
|  | ||||
| /*! | ||||
| 	For platforms that provide no avenue into vsync tracking other than block-until-sync, | ||||
| 	this class tracks: (i) how long frame draw takes; (ii) the apparent frame period; and | ||||
| 	(iii) optionally, timer jitter; in order to suggest when you should next start drawing. | ||||
| */ | ||||
| class VSyncPredictor { | ||||
| 	public: | ||||
| 		/*! | ||||
| 			Announces to the predictor that the work of producing an output frame has begun. | ||||
| 		*/ | ||||
| 		void begin_redraw() { | ||||
| 			redraw_begin_time_ = nanos_now(); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Announces to the predictor that the work of producing an output frame has ended; | ||||
| 			the predictor will use the amount of time between each begin/end pair to modify | ||||
| 			its expectations as to how long it takes to draw a frame. | ||||
| 		*/ | ||||
| 		void end_redraw() { | ||||
| 			redraw_period_.post(nanos_now() - redraw_begin_time_); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this | ||||
| 			machine calls retrace is now. The predictor uses these notifications to estimate output | ||||
| 			frame rate. | ||||
| 		*/ | ||||
| 		void announce_vsync() { | ||||
| 			const auto now = nanos_now(); | ||||
|  | ||||
| 			if(last_vsync_) { | ||||
| 				last_vsync_ += frame_duration_; | ||||
| 				vsync_jitter_.post(last_vsync_ - now); | ||||
| 				last_vsync_ = (last_vsync_ + now) >> 1; | ||||
| 			} else { | ||||
| 				last_vsync_ = now; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the frame rate for the target display. | ||||
| 		*/ | ||||
| 		void set_frame_rate(float rate) { | ||||
| 			frame_duration_ = Nanos(1'000'000'000.0f / rate); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Adds a record of how much jitter was experienced in scheduling; these values will be | ||||
| 			factored into the @c suggested_draw_time if supplied. | ||||
|  | ||||
| 			A positive number means the timer occurred late. A negative number means it occurred early. | ||||
| 		*/ | ||||
| 		void add_timer_jitter(Time::Nanos jitter) { | ||||
| 			timer_jitter_.post(jitter); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Announces to the vsync predictor that output is now paused. This ends frame period | ||||
| 			calculations until the next announce_vsync() restarts frame-length counting. | ||||
| 		*/ | ||||
| 		void pause() { | ||||
| 			last_vsync_ = 0; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@return The time at which redrawing should begin, given the predicted frame period, how | ||||
| 			long it appears to take to draw a frame and how much jitter there is in scheduling | ||||
| 			(if those figures are being supplied). | ||||
| 		*/ | ||||
| 		Nanos suggested_draw_time() { | ||||
| 			const auto mean = redraw_period_.mean() - timer_jitter_.mean() - vsync_jitter_.mean(); | ||||
| 			const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance(); | ||||
|  | ||||
| 			// Permit three standard deviations from the mean, to cover 99.9% of cases. | ||||
| 			const auto period = mean - Nanos(3.0f * sqrt(float(variance))); | ||||
|  | ||||
| 			assert(abs(period) < 10'000'000'000); | ||||
|  | ||||
| 			return last_vsync_ + period; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		class VarianceCollector { | ||||
| 			public: | ||||
| 				VarianceCollector(Time::Nanos default_value) { | ||||
| 					sum_ = default_value * 128; | ||||
| 					for(int c = 0; c < 128; ++c) { | ||||
| 						history_[c] = default_value; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				void post(Time::Nanos value) { | ||||
| 					assert(abs(value) < 10'000'000'000);	// 10 seconds is a very liberal maximum. | ||||
| 					sum_ -= history_[write_pointer_]; | ||||
| 					sum_ += value; | ||||
| 					history_[write_pointer_] = value; | ||||
| 					write_pointer_ = (write_pointer_ + 1) & 127; | ||||
| 				} | ||||
|  | ||||
| 				Time::Nanos mean() { | ||||
| 					return sum_ / 128; | ||||
| 				} | ||||
|  | ||||
| 				Time::Nanos variance() { | ||||
| 					// I haven't yet come up with a better solution that calculating this | ||||
| 					// in whole every time, given the way that the mean mutates. | ||||
| 					Time::Nanos variance = 0; | ||||
| 					for(int c = 0; c < 128; ++c) { | ||||
| 						const auto difference = ((history_[c] * 128) - sum_) / 128; | ||||
| 						variance += (difference * difference); | ||||
| 					} | ||||
| 					return variance / 128; | ||||
| 				} | ||||
|  | ||||
| 			private: | ||||
| 				Time::Nanos sum_; | ||||
| 				Time::Nanos history_[128]; | ||||
| 				size_t write_pointer_ = 0; | ||||
| 		}; | ||||
|  | ||||
| 		Nanos redraw_begin_time_ = 0; | ||||
| 		Nanos last_vsync_ = 0; | ||||
| 		Nanos frame_duration_ = 1'000'000'000 / 60; | ||||
|  | ||||
| 		VarianceCollector vsync_jitter_{0}; | ||||
| 		VarianceCollector redraw_period_{1'000'000'000 / 60};	// A less convincing first guess. | ||||
| 		VarianceCollector timer_jitter_{0};						// Seed at 0 in case this feature isn't used by the owner. | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif /* VSyncPredictor_hpp */ | ||||
| @@ -498,7 +498,7 @@ void WD1770::posit_event(int new_event_type) { | ||||
|  | ||||
| 			if(get_crc_generator().get_value()) { | ||||
| 				LOG("CRC error; terminating"); | ||||
| 				update_status([this] (Status &status) { | ||||
| 				update_status([] (Status &status) { | ||||
| 					status.crc_error = true; | ||||
| 				}); | ||||
| 				goto wait_for_command; | ||||
| @@ -816,19 +816,19 @@ void WD1770::update_status(std::function<void(Status &)> updater) { | ||||
| 	if(status_.busy != old_status.busy) update_clocking_observer(); | ||||
| } | ||||
|  | ||||
| void WD1770::set_head_load_request(bool head_load) {} | ||||
| void WD1770::set_motor_on(bool motor_on) {} | ||||
| void WD1770::set_head_load_request(bool) {} | ||||
| void WD1770::set_motor_on(bool) {} | ||||
|  | ||||
| void WD1770::set_head_loaded(bool head_loaded) { | ||||
| 	head_is_loaded_ = head_loaded; | ||||
| 	if(head_loaded) posit_event(int(Event1770::HeadLoad)); | ||||
| } | ||||
|  | ||||
| bool WD1770::get_head_loaded() { | ||||
| bool WD1770::get_head_loaded() const { | ||||
| 	return head_is_loaded_; | ||||
| } | ||||
|  | ||||
| ClockingHint::Preference WD1770::preferred_clocking() { | ||||
| ClockingHint::Preference WD1770::preferred_clocking() const { | ||||
| 	if(status_.busy) return ClockingHint::Preference::RealTime; | ||||
| 	return Storage::Disk::MFMController::preferred_clocking(); | ||||
| } | ||||
|   | ||||
| @@ -31,6 +31,7 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 			@param p The type of controller to emulate. | ||||
| 		*/ | ||||
| 		WD1770(Personality p); | ||||
| 		virtual ~WD1770() {} | ||||
|  | ||||
| 		/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data. | ||||
| 		using Storage::Disk::MFMController::set_is_double_density; | ||||
| @@ -62,10 +63,10 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 		}; | ||||
|  | ||||
| 		/// @returns The current value of the IRQ line output. | ||||
| 		inline bool get_interrupt_request_line()		{	return status_.interrupt_request;	} | ||||
| 		inline bool get_interrupt_request_line() const	{	return status_.interrupt_request;	} | ||||
|  | ||||
| 		/// @returns The current value of the DRQ line output. | ||||
| 		inline bool get_data_request_line()				{	return status_.data_request;		} | ||||
| 		inline bool get_data_request_line() const		{	return status_.data_request;		} | ||||
|  | ||||
| 		class Delegate { | ||||
| 			public: | ||||
| @@ -73,7 +74,7 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 		}; | ||||
| 		inline void set_delegate(Delegate *delegate)	{	delegate_ = delegate;				} | ||||
|  | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
| 		ClockingHint::Preference preferred_clocking() const final; | ||||
|  | ||||
| 	protected: | ||||
| 		virtual void set_head_load_request(bool head_load); | ||||
| @@ -81,12 +82,12 @@ class WD1770: public Storage::Disk::MFMController { | ||||
| 		void set_head_loaded(bool head_loaded); | ||||
|  | ||||
| 		/// @returns The last value posted to @c set_head_loaded. | ||||
| 		bool get_head_loaded(); | ||||
| 		bool get_head_loaded() const; | ||||
|  | ||||
| 	private: | ||||
| 		Personality personality_; | ||||
| 		inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); } | ||||
| 		inline bool has_head_load_line() { return (personality_ == P1793 ); } | ||||
| 		const Personality personality_; | ||||
| 		bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); } | ||||
| 		bool has_head_load_line() const { return (personality_ == P1793 ); } | ||||
|  | ||||
| 		struct Status { | ||||
| 			bool write_protect = false; | ||||
|   | ||||
| @@ -18,9 +18,14 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) : | ||||
| 	clock_rate_(clock_rate) { | ||||
| 	device_id_ = bus_.add_device(); | ||||
| 	bus_.add_observer(this); | ||||
|  | ||||
| 	// TODO: use clock rate and expected phase. This implementation currently | ||||
| 	// provides only CPU-driven polling behaviour. | ||||
| 	(void)clock_rate_; | ||||
| 	(void)expected_phase_; | ||||
| } | ||||
|  | ||||
| void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) { | ||||
| void NCR5380::write(int address, uint8_t value, bool) { | ||||
| 	switch(address & 7) { | ||||
| 		case 0: | ||||
| //			LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value)); | ||||
| @@ -128,7 +133,7 @@ void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t NCR5380::read(int address, bool dma_acknowledge) { | ||||
| uint8_t NCR5380::read(int address, bool) { | ||||
| 	switch(address & 7) { | ||||
| 		case 0: | ||||
| //			LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff)); | ||||
| @@ -258,6 +263,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double | ||||
| 		case ExecutionState::WaitingForBusy: | ||||
| 			if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return; | ||||
| 			state_ = ExecutionState::WatchingBusy; | ||||
| 			[[fallthrough]]; | ||||
|  | ||||
| 		case ExecutionState::WatchingBusy: | ||||
| 			if(!(new_state & SCSI::Line::Busy)) { | ||||
|   | ||||
| @@ -37,19 +37,19 @@ enum Line { | ||||
| class PortHandler { | ||||
| 	public: | ||||
| 		/// Requests the current input value of @c port from the port handler. | ||||
| 		uint8_t get_port_input(Port port)										{	return 0xff;	} | ||||
| 	uint8_t get_port_input([[maybe_unused]] Port port)										{	return 0xff;	} | ||||
|  | ||||
| 		/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output. | ||||
| 		void set_port_output(Port port, uint8_t value, uint8_t direction_mask)	{} | ||||
| 		void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask)	{} | ||||
|  | ||||
| 		/// Sets the current logical output level for line @c line on port @c port. | ||||
| 		void set_control_line_output(Port port, Line line, bool value)			{} | ||||
| 		void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value)			{} | ||||
|  | ||||
| 		/// Sets the current logical value of the interrupt line. | ||||
| 		void set_interrupt_status(bool status)									{} | ||||
| 		void set_interrupt_status([[maybe_unused]] bool status)									{} | ||||
|  | ||||
| 		/// Provides a measure of time elapsed between other calls. | ||||
| 		void run_for(HalfCycles duration)										{} | ||||
| 		void run_for([[maybe_unused]] HalfCycles duration)										{} | ||||
|  | ||||
| 		/// Receives passed-on flush() calls from the 6522. | ||||
| 		void flush()																			{} | ||||
| @@ -112,7 +112,7 @@ template <class T> class MOS6522: public MOS6522Storage { | ||||
| 		void run_for(const Cycles cycles); | ||||
|  | ||||
| 		/// @returns @c true if the IRQ line is currently active; @c false otherwise. | ||||
| 		bool get_interrupt_line(); | ||||
| 		bool get_interrupt_line() const; | ||||
|  | ||||
| 		/// Updates the port handler to the current time and then requests that it flush. | ||||
| 		void flush(); | ||||
|   | ||||
| @@ -69,7 +69,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | ||||
| 		// Timer 1 | ||||
| 		case 0x6:	case 0x4:	registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;	break; | ||||
| 		case 0x5:	case 0x7: | ||||
| 			registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8); | ||||
| 			registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8); | ||||
| 			registers_.interrupt_flags &= ~InterruptFlag::Timer1; | ||||
| 			if(address == 0x05) { | ||||
| 				registers_.next_timer[0] = registers_.timer_latch[0]; | ||||
| @@ -82,7 +82,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) { | ||||
| 		case 0x8:	registers_.timer_latch[1] = value;	break; | ||||
| 		case 0x9: | ||||
| 			registers_.interrupt_flags &= ~InterruptFlag::Timer2; | ||||
| 			registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8); | ||||
| 			registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8); | ||||
| 			timer_is_running_[1] = true; | ||||
| 			reevaluate_interrupts(); | ||||
| 		break; | ||||
| @@ -281,11 +281,11 @@ template <typename T> void MOS6522<T>::do_phase2() { | ||||
|  | ||||
| 	registers_.timer[1] --; | ||||
| 	if(registers_.next_timer[0] >= 0) { | ||||
| 		registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]); | ||||
| 		registers_.timer[0] = uint16_t(registers_.next_timer[0]); | ||||
| 		registers_.next_timer[0] = -1; | ||||
| 	} | ||||
| 	if(registers_.next_timer[1] >= 0) { | ||||
| 		registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]); | ||||
| 		registers_.timer[1] = uint16_t(registers_.next_timer[1]); | ||||
| 		registers_.next_timer[1] = -1; | ||||
| 	} | ||||
|  | ||||
| @@ -383,9 +383,9 @@ template <typename T> void MOS6522<T>::run_for(const Cycles cycles) { | ||||
| } | ||||
|  | ||||
| /*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ | ||||
| template <typename T> bool MOS6522<T>::get_interrupt_line() { | ||||
| template <typename T> bool MOS6522<T>::get_interrupt_line() const { | ||||
| 	uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; | ||||
| 	return !!interrupt_status; | ||||
| 	return interrupt_status; | ||||
| } | ||||
|  | ||||
| template <typename T> void MOS6522<T>::evaluate_cb2_output() { | ||||
|   | ||||
| @@ -14,6 +14,6 @@ void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) { | ||||
| 	delegate_ = delegate; | ||||
| } | ||||
|  | ||||
| void IRQDelegatePortHandler::set_interrupt_status(bool new_status) { | ||||
| void IRQDelegatePortHandler::set_interrupt_status(bool) { | ||||
| 	if(delegate_) delegate_->mos6522_did_change_interrupt_status(this); | ||||
| } | ||||
|   | ||||
| @@ -51,7 +51,7 @@ template <class T> class MOS6532 { | ||||
| 				case 0x04: case 0x05: case 0x06: case 0x07: | ||||
| 					if(address & 0x10) { | ||||
| 						timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07);	// i.e. 0, 3, 6, 10 | ||||
| 						timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ; | ||||
| 						timer_.value = (unsigned(value) << timer_.activeShift) ; | ||||
| 						timer_.interrupt_enabled = !!(address&0x08); | ||||
| 						interrupt_status_ &= ~InterruptFlag::Timer; | ||||
| 						evaluate_interrupts(); | ||||
| @@ -79,7 +79,7 @@ template <class T> class MOS6532 { | ||||
|  | ||||
| 				// Timer and interrupt control | ||||
| 				case 0x04: case 0x06: { | ||||
| 					uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift); | ||||
| 					uint8_t value = uint8_t(timer_.value >> timer_.activeShift); | ||||
| 					timer_.interrupt_enabled = !!(address&0x08); | ||||
| 					interrupt_status_ &= ~InterruptFlag::Timer; | ||||
| 					evaluate_interrupts(); | ||||
| @@ -107,7 +107,7 @@ template <class T> class MOS6532 { | ||||
| 		} | ||||
|  | ||||
| 		inline void run_for(const Cycles cycles) { | ||||
| 			unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral()); | ||||
| 			unsigned int number_of_cycles = unsigned(cycles.as_integral()); | ||||
|  | ||||
| 			// permit counting _to_ zero; counting _through_ zero initiates the other behaviour | ||||
| 			if(timer_.value >= number_of_cycles) { | ||||
| @@ -122,7 +122,7 @@ template <class T> class MOS6532 { | ||||
| 		} | ||||
|  | ||||
| 		MOS6532() { | ||||
| 			timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10); | ||||
| 			timer_.value = unsigned((rand() & 0xff) << 10); | ||||
| 		} | ||||
|  | ||||
| 		inline void set_port_did_change(int port) { | ||||
| @@ -142,7 +142,7 @@ template <class T> class MOS6532 { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		inline bool get_inerrupt_line() { | ||||
| 		inline bool get_inerrupt_line() const { | ||||
| 			return interrupt_line_; | ||||
| 		} | ||||
|  | ||||
| @@ -173,9 +173,11 @@ template <class T> class MOS6532 { | ||||
| 		bool interrupt_line_ = false; | ||||
|  | ||||
| 		// expected to be overridden | ||||
| 		uint8_t get_port_input(int port)										{	return 0xff;	} | ||||
| 		void set_port_output(int port, uint8_t value, uint8_t output_mask)		{} | ||||
| 		void set_irq_line(bool new_value)										{} | ||||
| 		void set_port_output([[maybe_unused]] int port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t output_mask) {} | ||||
| 		uint8_t get_port_input([[maybe_unused]] int port) { | ||||
| 			return 0xff; | ||||
| 		} | ||||
| 		void set_irq_line(bool) {} | ||||
|  | ||||
| 		inline void evaluate_interrupts() { | ||||
| 			interrupt_line_ = | ||||
|   | ||||
| @@ -17,13 +17,13 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue | ||||
|  | ||||
|  | ||||
| void AudioGenerator::set_volume(uint8_t volume) { | ||||
| 	audio_queue_.defer([=]() { | ||||
| 		volume_ = static_cast<int16_t>(volume) * range_multiplier_; | ||||
| 	audio_queue_.defer([this, volume]() { | ||||
| 		volume_ = int16_t(volume) * range_multiplier_; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void AudioGenerator::set_control(int channel, uint8_t value) { | ||||
| 	audio_queue_.defer([=]() { | ||||
| 	audio_queue_.defer([this, channel, value]() { | ||||
| 		control_registers_[channel] = value; | ||||
| 	}); | ||||
| } | ||||
| @@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = { | ||||
|  | ||||
| #define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7) | ||||
| #define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191 | ||||
| #define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; } | ||||
| #define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; } | ||||
| // Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen | ||||
| // is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment | ||||
| // ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and | ||||
| @@ -114,7 +114,7 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) | ||||
|  | ||||
| 		// this sums the output of all three sounds channels plus a DC offset for volume; | ||||
| 		// TODO: what's the real ratio of this stuff? | ||||
| 		target[c] = static_cast<int16_t>( | ||||
| 		target[c] = int16_t( | ||||
| 			(shift_registers_[0]&1) + | ||||
| 			(shift_registers_[1]&1) + | ||||
| 			(shift_registers_[2]&1) + | ||||
| @@ -133,7 +133,7 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) { | ||||
| } | ||||
|  | ||||
| void AudioGenerator::set_sample_volume_range(std::int16_t range) { | ||||
| 	range_multiplier_ = static_cast<int16_t>(range / 64); | ||||
| 	range_multiplier_ = int16_t(range / 64); | ||||
| } | ||||
|  | ||||
| #undef shift | ||||
|   | ||||
| @@ -30,6 +30,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | ||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||
| 		void skip_samples(std::size_t number_of_samples); | ||||
| 		void set_sample_volume_range(std::int16_t range); | ||||
| 		static constexpr bool get_is_stereo() { return false; } | ||||
|  | ||||
| 	private: | ||||
| 		Concurrency::DeferringAsyncTaskQueue &audio_queue_; | ||||
| @@ -42,7 +43,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { | ||||
| }; | ||||
|  | ||||
| struct BusHandler { | ||||
| 	void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { | ||||
| 	void perform_read([[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *pixel_data, [[maybe_unused]] uint8_t *colour_data) { | ||||
| 		*pixel_data = 0xff; | ||||
| 		*colour_data = 0xff; | ||||
| 	} | ||||
| @@ -80,12 +81,13 @@ template <class BusHandler> class MOS6560 { | ||||
| 		} | ||||
|  | ||||
| 		void set_clock_rate(double clock_rate) { | ||||
| 			speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); | ||||
| 			speaker_.set_input_rate(float(clock_rate / 4.0)); | ||||
| 		} | ||||
|  | ||||
| 		void set_scan_target(Outputs::Display::ScanTarget *scan_target)		{ crt_.set_scan_target(scan_target); 			} | ||||
| 		Outputs::Display::ScanStatus get_scaled_scan_status() const			{ return crt_.get_scaled_scan_status() / 4.0f;	} | ||||
| 		void set_display_type(Outputs::Display::DisplayType display_type)	{ crt_.set_display_type(display_type); 			} | ||||
| 		Outputs::Display::DisplayType get_display_type() const				{ return crt_.get_display_type(); 				} | ||||
| 		Outputs::Speaker::Speaker *get_speaker()	 						{ return &speaker_; 							} | ||||
|  | ||||
| 		void set_high_frequency_cutoff(float cutoff) { | ||||
| @@ -233,7 +235,7 @@ template <class BusHandler> class MOS6560 { | ||||
| 					if(column_counter_&1) { | ||||
| 						fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_; | ||||
| 					} else { | ||||
| 						fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_); | ||||
| 						fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_); | ||||
| 						video_matrix_address_counter_++; | ||||
| 						if( | ||||
| 							(current_character_row_ == 15) || | ||||
| @@ -369,7 +371,7 @@ template <class BusHandler> class MOS6560 { | ||||
|  | ||||
| 				case 0x2: | ||||
| 					registers_.number_of_columns = value & 0x7f; | ||||
| 					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); | ||||
| 					registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); | ||||
| 				break; | ||||
|  | ||||
| 				case 0x3: | ||||
| @@ -378,8 +380,8 @@ template <class BusHandler> class MOS6560 { | ||||
| 				break; | ||||
|  | ||||
| 				case 0x5: | ||||
| 					registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10); | ||||
| 					registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); | ||||
| 					registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10); | ||||
| 					registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); | ||||
| 				break; | ||||
|  | ||||
| 				case 0xa: | ||||
| @@ -418,11 +420,11 @@ template <class BusHandler> class MOS6560 { | ||||
| 		/* | ||||
| 			Reads from a 6560 register. | ||||
| 		*/ | ||||
| 		uint8_t read(int address) { | ||||
| 		uint8_t read(int address) const { | ||||
| 			address &= 0xf; | ||||
| 			switch(address) { | ||||
| 				default: return registers_.direct_values[address]; | ||||
| 				case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f); | ||||
| 				case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f); | ||||
| 				case 0x04: return (raster_value() >> 1) & 0xff; | ||||
| 			} | ||||
| 		} | ||||
| @@ -460,11 +462,11 @@ template <class BusHandler> class MOS6560 { | ||||
|  | ||||
| 		// counters that cover an entire field | ||||
| 		int horizontal_counter_ = 0, vertical_counter_ = 0; | ||||
| 		const int lines_this_field() { | ||||
| 		int lines_this_field() const { | ||||
| 			// Necessary knowledge here: only the NTSC 6560 supports interlaced video. | ||||
| 			return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field; | ||||
| 		} | ||||
| 		const int raster_value() { | ||||
| 		int raster_value() const { | ||||
| 			const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line; | ||||
| 			const int line = vertical_counter_ + bonus_line; | ||||
| 			const int final_line = lines_this_field(); | ||||
| @@ -479,7 +481,7 @@ template <class BusHandler> class MOS6560 { | ||||
| 			} | ||||
| 			// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737 | ||||
| 		} | ||||
| 		bool is_odd_frame() { | ||||
| 		bool is_odd_frame() const { | ||||
| 			return is_odd_frame_ || !registers_.interlaced; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -167,8 +167,8 @@ template <class T> class CRTC6845 { | ||||
| 	private: | ||||
| 		inline void perform_bus_cycle_phase1() { | ||||
| 			// Skew theory of operation: keep a history of the last three states, and apply whichever is selected. | ||||
| 			character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_); | ||||
| 			bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_; | ||||
| 			character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | unsigned(character_is_visible_); | ||||
| 			bus_state_.display_enable = (int(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_; | ||||
| 			bus_handler_.perform_bus_cycle_phase1(bus_state_); | ||||
| 		} | ||||
|  | ||||
| @@ -240,7 +240,7 @@ template <class T> class CRTC6845 { | ||||
| 		inline void do_end_of_frame() { | ||||
| 			line_counter_ = 0; | ||||
| 			line_is_visible_ = true; | ||||
| 			line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]); | ||||
| 			line_address_ = uint16_t((registers_[12] << 8) | registers_[13]); | ||||
| 			bus_state_.refresh_address = line_address_; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -120,7 +120,7 @@ void ACIA::consider_transmission() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ClockingHint::Preference ACIA::preferred_clocking() { | ||||
| ClockingHint::Preference ACIA::preferred_clocking() const { | ||||
| 	// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever | ||||
| 	// is on the receiving end. | ||||
| 	if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime; | ||||
| @@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) { | ||||
| 	return value ^ (parity_ == Parity::Even); | ||||
| } | ||||
|  | ||||
| bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) { | ||||
| bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) { | ||||
| 	// Shift this bit into the 11-bit input register; this is big enough to hold | ||||
| 	// the largest transmission symbol. | ||||
| 	++bits_received_; | ||||
|   | ||||
| @@ -86,7 +86,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate { | ||||
| 		Serial::Line request_to_send; | ||||
|  | ||||
| 		// ClockingHint::Source. | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
| 		ClockingHint::Preference preferred_clocking() const final; | ||||
|  | ||||
| 		struct InterruptDelegate { | ||||
| 			virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0; | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  | ||||
| using namespace Motorola::MFP68901; | ||||
|  | ||||
| ClockingHint::Preference MFP68901::preferred_clocking() { | ||||
| ClockingHint::Preference MFP68901::preferred_clocking() const { | ||||
| 	// Rule applied: if any timer is actively running and permitted to produce an | ||||
| 	// interrupt, request real-time running. | ||||
| 	return | ||||
|   | ||||
| @@ -76,7 +76,7 @@ class MFP68901: public ClockingHint::Source { | ||||
| 		void set_interrupt_delegate(InterruptDelegate *delegate); | ||||
|  | ||||
| 		// ClockingHint::Source. | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
| 		ClockingHint::Preference preferred_clocking() const final; | ||||
|  | ||||
| 	private: | ||||
| 		// MARK: - Timers | ||||
|   | ||||
| @@ -9,13 +9,15 @@ | ||||
| #ifndef i8255_hpp | ||||
| #define i8255_hpp | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace Intel { | ||||
| namespace i8255 { | ||||
|  | ||||
| class PortHandler { | ||||
| 	public: | ||||
| 		void set_value(int port, uint8_t value) {} | ||||
| 		uint8_t get_value(int port) { return 0xff; } | ||||
| 		void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {} | ||||
| 		uint8_t get_value([[maybe_unused]] int port) { return 0xff; } | ||||
| }; | ||||
|  | ||||
| // TODO: Modes 1 and 2. | ||||
|   | ||||
| @@ -79,10 +79,14 @@ namespace { | ||||
| i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : | ||||
| 	Storage::Disk::MFMController(clock_rate), | ||||
| 	bus_handler_(bus_handler) { | ||||
| 	posit_event(static_cast<int>(Event8272::CommandByte)); | ||||
| 	posit_event(int(Event8272::CommandByte)); | ||||
|  | ||||
| 	// TODO: implement DMA, etc. I have a vague intention to implement the IBM PC | ||||
| 	// one day, that should help to force that stuff. | ||||
| 	(void)bus_handler_; | ||||
| } | ||||
|  | ||||
| ClockingHint::Preference i8272::preferred_clocking() { | ||||
| ClockingHint::Preference i8272::preferred_clocking() const { | ||||
| 	const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking(); | ||||
| 	if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking; | ||||
| 	return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; | ||||
| @@ -97,7 +101,7 @@ void i8272::run_for(Cycles cycles) { | ||||
| 	if(delay_time_ > 0) { | ||||
| 		if(cycles.as_integral() >= delay_time_) { | ||||
| 			delay_time_ = 0; | ||||
| 			posit_event(static_cast<int>(Event8272::Timer)); | ||||
| 			posit_event(int(Event8272::Timer)); | ||||
| 		} else { | ||||
| 			delay_time_ -= cycles.as_integral(); | ||||
| 		} | ||||
| @@ -114,7 +118,7 @@ void i8272::run_for(Cycles cycles) { | ||||
| 				while(steps--) { | ||||
| 					// Perform a step. | ||||
| 					int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; | ||||
| 					LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position)); | ||||
| 					LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << int(drives_[c].head_position)); | ||||
| 					select_drive(c); | ||||
| 					get_drive().step(Storage::Disk::HeadPosition(direction)); | ||||
| 					if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; | ||||
| @@ -156,7 +160,7 @@ void i8272::run_for(Cycles cycles) { | ||||
|  | ||||
| 	// check for busy plus ready disabled | ||||
| 	if(is_executing_ && !get_drive().get_is_ready()) { | ||||
| 		posit_event(static_cast<int>(Event8272::NoLongerReady)); | ||||
| 		posit_event(int(Event8272::NoLongerReady)); | ||||
| 	} | ||||
|  | ||||
| 	is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; | ||||
| @@ -177,7 +181,7 @@ void i8272::write(int address, uint8_t value) { | ||||
| 	} else { | ||||
| 		// accumulate latest byte in the command byte sequence | ||||
| 		command_.push_back(value); | ||||
| 		posit_event(static_cast<int>(Event8272::CommandByte)); | ||||
| 		posit_event(int(Event8272::CommandByte)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -186,7 +190,7 @@ uint8_t i8272::read(int address) { | ||||
| 		if(result_stack_.empty()) return 0xff; | ||||
| 		uint8_t result = result_stack_.back(); | ||||
| 		result_stack_.pop_back(); | ||||
| 		if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty)); | ||||
| 		if(result_stack_.empty()) posit_event(int(Event8272::ResultEmpty)); | ||||
|  | ||||
| 		return result; | ||||
| 	} else { | ||||
| @@ -198,16 +202,16 @@ uint8_t i8272::read(int address) { | ||||
| #define END_SECTION()	} | ||||
|  | ||||
| #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_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return; | ||||
| #define WAIT_FOR_EVENT(mask)	resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__: | ||||
| #define WAIT_FOR_TIME(ms)		resume_point_ = __LINE__; interesting_event_mask_ = int(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return; | ||||
|  | ||||
| #define PASTE(x, y) x##y | ||||
| #define CONCAT(x, y) PASTE(x, y) | ||||
|  | ||||
| #define FIND_HEADER()	\ | ||||
| 	set_data_mode(DataMode::Scanning);	\ | ||||
| 	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \ | ||||
| 	if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; }	\ | ||||
| 	CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \ | ||||
| 	if(event_type == int(Event::IndexHole)) { index_hole_limit_--; }	\ | ||||
| 	else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__);	\ | ||||
| 	\ | ||||
| 	if(index_hole_limit_) goto CONCAT(find_header, __LINE__);	\ | ||||
| @@ -215,8 +219,8 @@ uint8_t i8272::read(int address) { | ||||
|  | ||||
| #define FIND_DATA()	\ | ||||
| 	set_data_mode(DataMode::Scanning);	\ | ||||
| 	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \ | ||||
| 	if(event_type == static_cast<int>(Event::Token)) { \ | ||||
| 	CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \ | ||||
| 	if(event_type == int(Event::Token)) { \ | ||||
| 		if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__);	\ | ||||
| 	} | ||||
|  | ||||
| @@ -264,8 +268,8 @@ uint8_t i8272::read(int address) { | ||||
| 	} | ||||
|  | ||||
| void i8272::posit_event(int event_type) { | ||||
| 	if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++; | ||||
| 	if(event_type == static_cast<int>(Event8272::NoLongerReady)) { | ||||
| 	if(event_type == int(Event::IndexHole)) index_hole_count_++; | ||||
| 	if(event_type == int(Event8272::NoLongerReady)) { | ||||
| 		SetNotReady(); | ||||
| 		goto abort; | ||||
| 	} | ||||
| @@ -425,12 +429,12 @@ void i8272::posit_event(int event_type) { | ||||
| 	// Performs the read data or read deleted data command. | ||||
| 	read_data: | ||||
| 			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]) << "]"); | ||||
| 				<< int(command_[2]) << " " | ||||
| 				<< int(command_[3]) << " " | ||||
| 				<< int(command_[4]) << " " | ||||
| 				<< int(command_[5]) << " ... " | ||||
| 				<< int(command_[6]) << " " | ||||
| 				<< int(command_[8]) << "]"); | ||||
| 		read_next_data: | ||||
| 			goto read_write_find_header; | ||||
|  | ||||
| @@ -439,7 +443,7 @@ void i8272::posit_event(int event_type) { | ||||
| 		read_data_found_header: | ||||
| 			FIND_DATA(); | ||||
| 			ClearControlMark(); | ||||
| 			if(event_type == static_cast<int>(Event::Token)) { | ||||
| 			if(event_type == int(Event::Token)) { | ||||
| 				if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) { | ||||
| 					// Something other than a data mark came next, impliedly an ID or index mark. | ||||
| 					SetMissingAddressMark(); | ||||
| @@ -470,24 +474,24 @@ void i8272::posit_event(int event_type) { | ||||
| 		// | ||||
| 		// TODO: consider DTL. | ||||
| 		read_data_get_byte: | ||||
| 			WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); | ||||
| 			if(event_type == static_cast<int>(Event::Token)) { | ||||
| 			WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); | ||||
| 			if(event_type == int(Event::Token)) { | ||||
| 				result_stack_.push_back(get_latest_token().byte_value); | ||||
| 				distance_into_section_++; | ||||
| 				SetDataRequest(); | ||||
| 				SetDataDirectionToProcessor(); | ||||
| 				WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); | ||||
| 				WAIT_FOR_EVENT(int(Event8272::ResultEmpty) | int(Event::Token) | int(Event::IndexHole)); | ||||
| 			} | ||||
| 			switch(event_type) { | ||||
| 				case static_cast<int>(Event8272::ResultEmpty):	// The caller read the byte in time; proceed as normal. | ||||
| 				case int(Event8272::ResultEmpty):	// The caller read the byte in time; proceed as normal. | ||||
| 					ResetDataRequest(); | ||||
| 					if(distance_into_section_ < (128 << size_)) goto read_data_get_byte; | ||||
| 				break; | ||||
| 				case static_cast<int>(Event::Token):				// The caller hasn't read the old byte yet and a new one has arrived | ||||
| 				case int(Event::Token):				// The caller hasn't read the old byte yet and a new one has arrived | ||||
| 					SetOverrun(); | ||||
| 					goto abort; | ||||
| 				break; | ||||
| 				case static_cast<int>(Event::IndexHole): | ||||
| 				case int(Event::IndexHole): | ||||
| 					SetEndOfCylinder(); | ||||
| 					goto abort; | ||||
| 				break; | ||||
| @@ -515,12 +519,12 @@ void i8272::posit_event(int event_type) { | ||||
|  | ||||
| 	write_data: | ||||
| 			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]) << "]"); | ||||
| 				<< int(command_[2]) << " " | ||||
| 				<< int(command_[3]) << " " | ||||
| 				<< int(command_[4]) << " " | ||||
| 				<< int(command_[5]) << " ... " | ||||
| 				<< int(command_[6]) << " " | ||||
| 				<< int(command_[8]) << "]"); | ||||
|  | ||||
| 			if(get_drive().get_is_read_only()) { | ||||
| 				SetNotWriteable(); | ||||
| @@ -571,7 +575,7 @@ void i8272::posit_event(int event_type) { | ||||
| 	// Performs the read ID command. | ||||
| 	read_id: | ||||
| 		// Establishes the drive and head being addressed, and whether in double density mode. | ||||
| 			LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]"); | ||||
| 			LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << int(command_[1]) << "]"); | ||||
|  | ||||
| 		// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. | ||||
| 		// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. | ||||
| @@ -594,10 +598,10 @@ void i8272::posit_event(int event_type) { | ||||
| 	// Performs read track. | ||||
| 	read_track: | ||||
| 			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]) << "]"); | ||||
| 				<< int(command_[2]) << " " | ||||
| 				<< int(command_[3]) << " " | ||||
| 				<< int(command_[4]) << " " | ||||
| 				<< int(command_[5]) << "]"); | ||||
|  | ||||
| 			// Wait for the index hole. | ||||
| 			WAIT_FOR_EVENT(Event::IndexHole); | ||||
| @@ -627,7 +631,7 @@ void i8272::posit_event(int event_type) { | ||||
| 			distance_into_section_++; | ||||
| 			SetDataRequest(); | ||||
| 			// TODO: other possible exit conditions; find a way to merge with the read_data version of this. | ||||
| 			WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty)); | ||||
| 			WAIT_FOR_EVENT(int(Event8272::ResultEmpty)); | ||||
| 			ResetDataRequest(); | ||||
| 			if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; | ||||
|  | ||||
| @@ -664,13 +668,13 @@ void i8272::posit_event(int event_type) { | ||||
| 			expects_input_ = true; | ||||
| 			distance_into_section_ = 0; | ||||
| 		format_track_write_header: | ||||
| 			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole)); | ||||
| 			WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole)); | ||||
| 			switch(event_type) { | ||||
| 				case static_cast<int>(Event::IndexHole): | ||||
| 				case int(Event::IndexHole): | ||||
| 					SetOverrun(); | ||||
| 					goto abort; | ||||
| 				break; | ||||
| 				case static_cast<int>(Event::DataWritten): | ||||
| 				case int(Event::DataWritten): | ||||
| 					header_[distance_into_section_] = input_; | ||||
| 					write_byte(input_); | ||||
| 					has_input_ = false; | ||||
| @@ -683,10 +687,10 @@ void i8272::posit_event(int event_type) { | ||||
| 			} | ||||
|  | ||||
| 			LOG(PADHEX(2) << "W:" | ||||
| 				<< static_cast<int>(header_[0]) << " " | ||||
| 				<< static_cast<int>(header_[1]) << " " | ||||
| 				<< static_cast<int>(header_[2]) << " " | ||||
| 				<< static_cast<int>(header_[3]) << ", " | ||||
| 				<< int(header_[0]) << " " | ||||
| 				<< int(header_[1]) << " " | ||||
| 				<< int(header_[2]) << " " | ||||
| 				<< int(header_[3]) << ", " | ||||
| 				<< get_crc_generator().get_value()); | ||||
| 			write_crc(); | ||||
|  | ||||
| @@ -706,8 +710,8 @@ void i8272::posit_event(int event_type) { | ||||
| 			// Otherwise, pad out to the index hole. | ||||
| 		format_track_pad: | ||||
| 			write_byte(get_is_double_density() ? 0x4e : 0xff); | ||||
| 			WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole)); | ||||
| 			if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad; | ||||
| 			WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole)); | ||||
| 			if(event_type != int(Event::IndexHole)) goto format_track_pad; | ||||
|  | ||||
| 			end_writing(); | ||||
|  | ||||
| @@ -758,7 +762,7 @@ void i8272::posit_event(int event_type) { | ||||
| 				// up in run_for understands to mean 'keep going until track 0 is active'). | ||||
| 				if(command_.size() > 2) { | ||||
| 					drives_[drive].target_head_position = command_[2]; | ||||
| 					LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2])); | ||||
| 					LOG(PADHEX(2) << "Seek to " << int(command_[2])); | ||||
| 				} else { | ||||
| 					drives_[drive].target_head_position = -1; | ||||
| 					drives_[drive].head_position = 0; | ||||
| @@ -789,7 +793,7 @@ void i8272::posit_event(int event_type) { | ||||
| 				// If a drive was found, return its results. Otherwise return a single 0x80. | ||||
| 				if(found_drive != -1) { | ||||
| 					drives_[found_drive].phase = Drive::NotSeeking; | ||||
| 					status_[0] = static_cast<uint8_t>(found_drive); | ||||
| 					status_[0] = uint8_t(found_drive); | ||||
| 					main_status_ &= ~(1 << found_drive); | ||||
| 					SetSeekEnd(); | ||||
|  | ||||
| @@ -819,7 +823,7 @@ void i8272::posit_event(int event_type) { | ||||
| 				int drive = command_[1] & 3; | ||||
| 				select_drive(drive); | ||||
| 				result_stack_= { | ||||
| 					static_cast<uint8_t>( | ||||
| 					uint8_t( | ||||
| 						(command_[1] & 7) |	// drive and head number | ||||
| 						0x08 |				// single sided | ||||
| 						(get_drive().get_is_track_zero() ? 0x10 : 0x00)	| | ||||
| @@ -853,9 +857,9 @@ void i8272::posit_event(int event_type) { | ||||
| 	// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the | ||||
| 	// last thing in it will be returned first. | ||||
| 	post_result: | ||||
| 			LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; "); | ||||
| 			LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; "); | ||||
| 			for(std::size_t c = 0; c < result_stack_.size(); c++) { | ||||
| 				LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c])); | ||||
| 				LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c])); | ||||
| 			} | ||||
| 			LOGNBR(std::endl); | ||||
|  | ||||
| @@ -880,13 +884,13 @@ bool i8272::seek_is_satisfied(int drive) { | ||||
| 			(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero()); | ||||
| } | ||||
|  | ||||
| void i8272::set_dma_acknowledge(bool dack) { | ||||
| void i8272::set_dma_acknowledge(bool) { | ||||
| } | ||||
|  | ||||
| void i8272::set_terminal_count(bool tc) { | ||||
| void i8272::set_terminal_count(bool) { | ||||
| } | ||||
|  | ||||
| void i8272::set_data_input(uint8_t value) { | ||||
| void i8272::set_data_input(uint8_t) { | ||||
| } | ||||
|  | ||||
| uint8_t i8272::get_data_output() { | ||||
|   | ||||
| @@ -20,8 +20,9 @@ namespace i8272 { | ||||
|  | ||||
| class BusHandler { | ||||
| 	public: | ||||
| 		virtual void set_dma_data_request(bool drq) {} | ||||
| 		virtual void set_interrupt(bool irq) {} | ||||
| 		virtual ~BusHandler() {} | ||||
| 		virtual void set_dma_data_request([[maybe_unused]] bool drq) {} | ||||
| 		virtual void set_interrupt([[maybe_unused]] bool irq) {} | ||||
| }; | ||||
|  | ||||
| class i8272 : public Storage::Disk::MFMController { | ||||
| @@ -39,13 +40,13 @@ class i8272 : public Storage::Disk::MFMController { | ||||
| 		void set_dma_acknowledge(bool dack); | ||||
| 		void set_terminal_count(bool tc); | ||||
|  | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
| 		ClockingHint::Preference preferred_clocking() const final; | ||||
|  | ||||
| 	protected: | ||||
| 		virtual void select_drive(int number) = 0; | ||||
|  | ||||
| 	private: | ||||
| 		// The bus handler, for interrupt and DMA-driven usage. | ||||
| 		// The bus handler, for interrupt and DMA-driven usage. [TODO] | ||||
| 		BusHandler &bus_handler_; | ||||
| 		std::unique_ptr<BusHandler> allocated_bus_handler_; | ||||
|  | ||||
| @@ -68,7 +69,7 @@ class i8272 : public Storage::Disk::MFMController { | ||||
| 			NoLongerReady = (1 << 6) | ||||
| 		}; | ||||
| 		void posit_event(int type) final; | ||||
| 		int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte); | ||||
| 		int interesting_event_mask_ = int(Event8272::CommandByte); | ||||
| 		int resume_point_ = 0; | ||||
| 		bool is_access_command_ = false; | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|  | ||||
| #include "z8530.hpp" | ||||
|  | ||||
| #define LOG_PREFIX "[SCC] " | ||||
| #include "../../Outputs/Log.hpp" | ||||
|  | ||||
| using namespace Zilog::SCC; | ||||
| @@ -16,7 +17,7 @@ void z8530::reset() { | ||||
| 	// TODO. | ||||
| } | ||||
|  | ||||
| bool z8530::get_interrupt_line() { | ||||
| bool z8530::get_interrupt_line() const { | ||||
| 	return | ||||
| 		(master_interrupt_control_ & 0x8) && | ||||
| 		( | ||||
| @@ -25,22 +26,30 @@ bool z8530::get_interrupt_line() { | ||||
| 		); | ||||
| } | ||||
|  | ||||
| /* | ||||
| 	Per the standard defined in the header file, this implementation follows | ||||
| 	an addressing convention of: | ||||
|  | ||||
| 	A0 = A/B		(i.e. channel select) | ||||
| 	A1 = C/D		(i.e. control or data) | ||||
| */ | ||||
|  | ||||
| std::uint8_t z8530::read(int address) { | ||||
| 	if(address & 2) { | ||||
| 		// Read data register for channel | ||||
| 		return 0x00; | ||||
| 		// Read data register for channel. | ||||
| 		return channels_[address & 1].read(true, pointer_); | ||||
| 	} else { | ||||
| 		// Read control register for channel. | ||||
| 		uint8_t result = 0; | ||||
|  | ||||
| 		switch(pointer_) { | ||||
| 			default: | ||||
| 				result = channels_[address & 1].read(address & 2, pointer_); | ||||
| 				result = channels_[address & 1].read(false, pointer_); | ||||
| 			break; | ||||
|  | ||||
| 			case 2:		// Handled non-symmetrically between channels. | ||||
| 				if(address & 1) { | ||||
| 					LOG("[SCC] Unimplemented: register 2 status bits"); | ||||
| 					LOG("Unimplemented: register 2 status bits"); | ||||
| 				} else { | ||||
| 					result = interrupt_vector_; | ||||
|  | ||||
| @@ -63,7 +72,11 @@ std::uint8_t z8530::read(int address) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		// Cf. the two-step control register selection process in ::write. Since this | ||||
| 		// definitely wasn't a *write* to register 0, it follows that the next selected | ||||
| 		// control register will be 0. | ||||
| 		pointer_ = 0; | ||||
|  | ||||
| 		update_delegate(); | ||||
| 		return result; | ||||
| 	} | ||||
| @@ -73,24 +86,31 @@ std::uint8_t z8530::read(int address) { | ||||
|  | ||||
| void z8530::write(int address, std::uint8_t value) { | ||||
| 	if(address & 2) { | ||||
| 		// Write data register for channel. | ||||
| 		// Write data register for channel. This is completely independent | ||||
| 		// of whatever is going on over in the control realm. | ||||
| 		channels_[address & 1].write(true, pointer_, value); | ||||
| 	} else { | ||||
| 		// Write control register for channel. | ||||
| 		// Write control register for channel; there's a two-step sequence | ||||
| 		// here for the programmer. Initially the selected register | ||||
| 		// (i.e. `pointer_`) is zero. That register includes a field to | ||||
| 		// set the next selected register. After any other register has | ||||
| 		// been written to, register 0 is selected again. | ||||
|  | ||||
| 		// Most registers are per channel, but a couple are shared; sever | ||||
| 		// them here. | ||||
| 		// Most registers are per channel, but a couple are shared; | ||||
| 		// sever them here, send the rest to the appropriate chnanel. | ||||
| 		switch(pointer_) { | ||||
| 			default: | ||||
| 				channels_[address & 1].write(address & 2, pointer_, value); | ||||
| 				channels_[address & 1].write(false, pointer_, value); | ||||
| 			break; | ||||
|  | ||||
| 			case 2:	// Interrupt vector register; shared between both channels. | ||||
| 			case 2:	// Interrupt vector register; used only by Channel B. | ||||
| 					// So there's only one of these. | ||||
| 				interrupt_vector_ = value; | ||||
| 				LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value)); | ||||
| 				LOG("Interrupt vector set to " << PADHEX(2) << int(value)); | ||||
| 			break; | ||||
|  | ||||
| 			case 9:	// Master interrupt and reset register; also shared between both channels. | ||||
| 				LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value)); | ||||
| 			case 9:	// Master interrupt and reset register; there is also only one of these. | ||||
| 				LOG("Master interrupt and reset register: " << PADHEX(2) << int(value)); | ||||
| 				master_interrupt_control_ = value; | ||||
| 			break; | ||||
| 		} | ||||
| @@ -105,7 +125,8 @@ void z8530::write(int address, std::uint8_t value) { | ||||
| 			pointer_ = value & 7; | ||||
|  | ||||
| 			// If the command part of the byte is a 'point high', also set the | ||||
| 			// top bit of the pointer. | ||||
| 			// top bit of the pointer. Channels themselves therefore need not | ||||
| 			// (/should not) respond to the point high command. | ||||
| 			if(((value >> 3)&7) == 1) { | ||||
| 				pointer_ |= 8; | ||||
| 			} | ||||
| @@ -126,16 +147,79 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) { | ||||
| 	if(data) { | ||||
| 		return data_; | ||||
| 	} else { | ||||
| 		LOG("Control read from register " << int(pointer)); | ||||
| 		// Otherwise, this is a control read... | ||||
| 		switch(pointer) { | ||||
| 			default: | ||||
| 				LOG("[SCC] Unrecognised control read from register " << int(pointer)); | ||||
| 			return 0x00; | ||||
|  | ||||
| 			case 0: | ||||
| 			case 0x0:	// Read Register 0; see p.37 (PDF p.45). | ||||
| 				// b0: Rx character available. | ||||
| 				// b1: zero count. | ||||
| 				// b2: Tx buffer empty. | ||||
| 				// b3: DCD. | ||||
| 				// b4: sync/hunt. | ||||
| 				// b5: CTS. | ||||
| 				// b6: Tx underrun/EOM. | ||||
| 				// b7: break/abort. | ||||
| 			return dcd_ ? 0x8 : 0x0; | ||||
|  | ||||
| 			case 0xf: | ||||
| 			case 0x1:	// Read Register 1; see p.37 (PDF p.45). | ||||
| 				// b0: all sent. | ||||
| 				// b1: residue code 0. | ||||
| 				// b2: residue code 1. | ||||
| 				// b3: residue code 2. | ||||
| 				// b4: parity error. | ||||
| 				// b5: Rx overrun error. | ||||
| 				// b6: CRC/framing error. | ||||
| 				// b7: end of frame (SDLC). | ||||
| 			return 0x01; | ||||
|  | ||||
| 			case 0x2:	// Read Register 2; see p.37 (PDF p.45). | ||||
| 				// Interrupt vector — modified by status information in B channel. | ||||
| 			return 0x00; | ||||
|  | ||||
| 			case 0x3:	// Read Register 3; see p.37 (PDF p.45). | ||||
| 				// B channel: all bits are 0. | ||||
| 				// A channel: | ||||
| 				// b0: Channel B ext/status IP. | ||||
| 				// b1: Channel B Tx IP. | ||||
| 				// b2: Channel B Rx IP. | ||||
| 				// b3: Channel A ext/status IP. | ||||
| 				// b4: Channel A Tx IP. | ||||
| 				// b5: Channel A Rx IP. | ||||
| 				// b6, b7: 0. | ||||
| 			return 0x00; | ||||
|  | ||||
| 			case 0xa:	// Read Register 10; see p.37 (PDF p.45). | ||||
| 				// b0: 0 | ||||
| 				// b1: On loop. | ||||
| 				// b2: 0 | ||||
| 				// b3: 0 | ||||
| 				// b4: Loop sending. | ||||
| 				// b5: 0 | ||||
| 				// b6: Two clocks missing. | ||||
| 				// b7: One clock missing. | ||||
| 			return 0x00; | ||||
|  | ||||
| 			case 0xc:	// Read Register 12; see p.37 (PDF p.45). | ||||
| 				// Lower byte of time constant. | ||||
| 			return 0x00; | ||||
|  | ||||
| 			case 0xd:	// Read Register 13; see p.38 (PDF p.46). | ||||
| 				// Upper byte of time constant. | ||||
| 			return 0x00; | ||||
|  | ||||
| 			case 0xf:	// Read Register 15; see p.38 (PDF p.46). | ||||
| 				// External interrupt status: | ||||
| 				// b0: 0 | ||||
| 				// b1: Zero count. | ||||
| 				// b2: 0 | ||||
| 				// b3: DCD. | ||||
| 				// b4: Sync/hunt. | ||||
| 				// b5: CTS. | ||||
| 				// b6: Tx underrun/EOM. | ||||
| 				// b7: Break/abort. | ||||
| 			return external_interrupt_status_; | ||||
| 		} | ||||
| 	} | ||||
| @@ -148,9 +232,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { | ||||
| 		data_ = value; | ||||
| 		return; | ||||
| 	} else { | ||||
| 		LOG("Control write: " << PADHEX(2) << int(value) << " to register " << int(pointer)); | ||||
| 		switch(pointer) { | ||||
| 			default: | ||||
| 				LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer)); | ||||
| 				LOG("Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer)); | ||||
| 			break; | ||||
|  | ||||
| 			case 0x0:	// Write register 0 — CRC reset and other functions. | ||||
| @@ -158,13 +243,13 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { | ||||
| 				switch(value >> 6) { | ||||
| 					default:	/* Do nothing. */		break; | ||||
| 					case 1: | ||||
| 						LOG("[SCC] TODO: reset Rx CRC checker."); | ||||
| 						LOG("TODO: reset Rx CRC checker."); | ||||
| 					break; | ||||
| 					case 2: | ||||
| 						LOG("[SCC] TODO: reset Tx CRC checker."); | ||||
| 						LOG("TODO: reset Tx CRC checker."); | ||||
| 					break; | ||||
| 					case 3: | ||||
| 						LOG("[SCC] TODO: reset Tx underrun/EOM latch."); | ||||
| 						LOG("TODO: reset Tx underrun/EOM latch."); | ||||
| 					break; | ||||
| 				} | ||||
|  | ||||
| @@ -172,32 +257,82 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { | ||||
| 				switch((value >> 3)&7) { | ||||
| 					default:	/* Do nothing. */		break; | ||||
| 					case 2: | ||||
| //						LOG("[SCC] reset ext/status interrupts."); | ||||
| //						LOG("reset ext/status interrupts."); | ||||
| 						external_status_interrupt_ = false; | ||||
| 						external_interrupt_status_ = 0; | ||||
| 					break; | ||||
| 					case 3: | ||||
| 						LOG("[SCC] TODO: send abort (SDLC)."); | ||||
| 						LOG("TODO: send abort (SDLC)."); | ||||
| 					break; | ||||
| 					case 4: | ||||
| 						LOG("[SCC] TODO: enable interrupt on next Rx character."); | ||||
| 						LOG("TODO: enable interrupt on next Rx character."); | ||||
| 					break; | ||||
| 					case 5: | ||||
| 						LOG("[SCC] TODO: reset Tx interrupt pending."); | ||||
| 						LOG("TODO: reset Tx interrupt pending."); | ||||
| 					break; | ||||
| 					case 6: | ||||
| 						LOG("[SCC] TODO: reset error."); | ||||
| 						LOG("TODO: reset error."); | ||||
| 					break; | ||||
| 					case 7: | ||||
| 						LOG("[SCC] TODO: reset highest IUS."); | ||||
| 						LOG("TODO: reset highest IUS."); | ||||
| 					break; | ||||
| 				} | ||||
| 			break; | ||||
|  | ||||
| 			case 0x1:	// Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition. | ||||
| 				interrupt_mask_ = value; | ||||
|  | ||||
| 				/* | ||||
| 					b7 = 0 => Wait/Request output is inactive; 1 => output is informative. | ||||
| 					b6 = Wait/request output is for... | ||||
| 						0 => wait: floating when inactive, low if CPU is attempting to transfer data the SCC isn't yet ready for. | ||||
| 						1 => request: high if inactive, low if SCC is ready to transfer data. | ||||
| 					b5 = 1 => wait/request is relative to read buffer; 0 => relative to write buffer. | ||||
|  | ||||
| 					b4/b3: | ||||
| 						00 = disable receive interrupt | ||||
| 						01 = interrupt on first character or special condition | ||||
| 						10 = interrupt on all characters and special conditions | ||||
| 						11 = interrupt only upon special conditions. | ||||
|  | ||||
| 					b2 = 1 => parity error is a special condition; 0 => it isn't. | ||||
| 					b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't. | ||||
| 					b0 = 1 => external interrupt is enabled; 0 => it isn't. | ||||
| 				*/ | ||||
| 				LOG("Interrupt mask: " << PADHEX(2) << int(value)); | ||||
| 			break; | ||||
|  | ||||
| 			case 0x2:	// Write register 2 - interrupt vector. | ||||
| 			break; | ||||
|  | ||||
| 			case 0x3: {	// Write register 3 — Receive Parameters and Control. | ||||
| 				// Get bit count. | ||||
| 				int receive_bit_count = 8; | ||||
| 				switch(value >> 6) { | ||||
| 					default:	receive_bit_count = 5;	break; | ||||
| 					case 1:		receive_bit_count = 7;	break; | ||||
| 					case 2:		receive_bit_count = 6;	break; | ||||
| 					case 3:		receive_bit_count = 8;	break; | ||||
| 				} | ||||
| 				LOG("Receive bit count: " << receive_bit_count); | ||||
|  | ||||
| 				/* | ||||
| 					b7,b6: | ||||
| 						00 = 5 receive bits per character | ||||
| 						01 = 7 bits | ||||
| 						10 = 6 bits | ||||
| 						11 = 8 bits | ||||
|  | ||||
| 					b5 = 1 => DCD and CTS outputs are set automatically; 0 => they're inputs to read register 0. | ||||
| 								(DCD is ignored in local loopback; CTS is ignored in both auto echo and local loopback). | ||||
| 					b4: enter hunt mode (if set to 1, presumably?) | ||||
| 					b3 = 1 => enable receiver CRC generation; 0 => don't. | ||||
| 					b2: address search mode (SDLC) | ||||
| 					b1: sync character load inhibit. | ||||
| 					b0: Rx enable. | ||||
| 				*/ | ||||
| 			} break; | ||||
|  | ||||
| 			case 0x4:	// Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes. | ||||
| 				// Bits 0 and 1 select parity mode. | ||||
| 				if(!(value&1)) { | ||||
| @@ -236,6 +371,23 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { | ||||
| 				} | ||||
| 			break; | ||||
|  | ||||
| 			case 0x5: | ||||
| 				// b7: DTR | ||||
| 				// b6/b5: | ||||
| 				//	00 = Tx 5 bits (or less) per character | ||||
| 				//	01 = Tx 7 bits per character | ||||
| 				//	10 = Tx 6 bits per character | ||||
| 				//	11 = Tx 8 bits per character | ||||
| 				// b4: send break. | ||||
| 				// b3: Tx enable. | ||||
| 				// b2: SDLC (if 0) / CRC-16 (if 1) | ||||
| 				// b1: RTS | ||||
| 				// b0: Tx CRC enable. | ||||
| 			break; | ||||
|  | ||||
| 			case 0x6: | ||||
| 			break; | ||||
|  | ||||
| 			case 0xf:	// Write register 15 — External/Status Interrupt Control. | ||||
| 				external_interrupt_mask_ = value; | ||||
| 			break; | ||||
| @@ -253,12 +405,17 @@ void z8530::Channel::set_dcd(bool level) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool z8530::Channel::get_interrupt_line() { | ||||
| bool z8530::Channel::get_interrupt_line() const { | ||||
| 	return | ||||
| 		(interrupt_mask_ & 1) && external_status_interrupt_; | ||||
| 	// TODO: other potential causes of an interrupt. | ||||
| } | ||||
|  | ||||
| /*! | ||||
| 	Evaluates the new level of the interrupt line and notifies the delegate if | ||||
| 	both: (i) there is one; and (ii) the interrupt line has changed since last | ||||
| 	the delegate was notified. | ||||
| */ | ||||
| void z8530::update_delegate() { | ||||
| 	const bool interrupt_line = get_interrupt_line(); | ||||
| 	if(interrupt_line != previous_interrupt_line_) { | ||||
|   | ||||
| @@ -30,16 +30,33 @@ class z8530 { | ||||
| 				A/B = A0 | ||||
| 				C/D = A1 | ||||
| 		*/ | ||||
|  | ||||
| 		/// Performs a read from the SCC; see above for conventions as to 'address'. | ||||
| 		std::uint8_t read(int address); | ||||
| 		/// Performs a write to the SCC; see above for conventions as to 'address'. | ||||
| 		void write(int address, std::uint8_t value); | ||||
| 		/// Resets the SCC. | ||||
| 		void reset(); | ||||
| 		bool get_interrupt_line(); | ||||
|  | ||||
| 		/// @returns The current value of the status output: @c true for active; @c false for inactive. | ||||
| 		bool get_interrupt_line() const; | ||||
|  | ||||
| 		struct Delegate { | ||||
| 			virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0; | ||||
| 			/*! | ||||
| 				Communicates that @c scc now has the interrupt line status @c new_status. | ||||
| 			*/ | ||||
| 			virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0; | ||||
| 		}; | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the delegate for this SCC. If this is a new delegate it is sent | ||||
| 			an immediate did_change_interrupt_status message, to get it | ||||
| 			up to speed. | ||||
| 		*/ | ||||
| 		void set_delegate(Delegate *delegate) { | ||||
| 			if(delegate_ == delegate) return; | ||||
| 			delegate_ = delegate; | ||||
| 			delegate_->did_change_interrupt_status(this, get_interrupt_line()); | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| @@ -53,7 +70,7 @@ class z8530 { | ||||
| 				uint8_t read(bool data, uint8_t pointer); | ||||
| 				void write(bool data, uint8_t pointer, uint8_t value); | ||||
| 				void set_dcd(bool level); | ||||
| 				bool get_interrupt_line(); | ||||
| 				bool get_interrupt_line() const; | ||||
|  | ||||
| 			private: | ||||
| 				uint8_t data_ = 0xff; | ||||
|   | ||||
| @@ -33,7 +33,7 @@ struct ReverseTable { | ||||
|  | ||||
| 	ReverseTable() { | ||||
| 		for(int c = 0; c < 256; ++c) { | ||||
| 			map[c] = static_cast<uint8_t>( | ||||
| 			map[c] = uint8_t( | ||||
| 				((c & 0x80) >> 7) | | ||||
| 				((c & 0x40) >> 5) | | ||||
| 				((c & 0x20) >> 3) | | ||||
| @@ -129,6 +129,10 @@ void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) { | ||||
| 	crt_.set_display_type(display_type); | ||||
| } | ||||
|  | ||||
| Outputs::Display::DisplayType TMS9918::get_display_type() const { | ||||
| 	return crt_.get_display_type(); | ||||
| } | ||||
|  | ||||
| void Base::LineBuffer::reset_sprite_collection() { | ||||
| 	sprites_stopped = false; | ||||
| 	active_sprite_slot = 0; | ||||
| @@ -140,7 +144,7 @@ void Base::LineBuffer::reset_sprite_collection() { | ||||
|  | ||||
| void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) { | ||||
| 	if(!(status_ & StatusSpriteOverflow)) { | ||||
| 		status_ = static_cast<uint8_t>((status_ & ~0x1f) | (sprite_number & 0x1f)); | ||||
| 		status_ = uint8_t((status_ & ~0x1f) | (sprite_number & 0x1f)); | ||||
| 	} | ||||
| 	if(buffer.sprites_stopped) | ||||
| 		return; | ||||
| @@ -527,7 +531,7 @@ void TMS9918::write(int address, uint8_t value) { | ||||
|  | ||||
| 	// The RAM pointer is always set on a second write, regardless of | ||||
| 	// whether the caller is intending to enqueue a VDP operation. | ||||
| 	ram_pointer_ = (ram_pointer_ & 0x00ff) | static_cast<uint16_t>(value << 8); | ||||
| 	ram_pointer_ = (ram_pointer_ & 0x00ff) | uint16_t(value << 8); | ||||
|  | ||||
| 	write_phase_ = false; | ||||
| 	if(value & 0x80) { | ||||
| @@ -661,7 +665,7 @@ uint8_t TMS9918::get_current_line() { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return static_cast<uint8_t>(source_row); | ||||
| 	return uint8_t(source_row); | ||||
| } | ||||
|  | ||||
| uint8_t TMS9918::get_latched_horizontal_counter() { | ||||
|   | ||||
| @@ -50,6 +50,9 @@ class TMS9918: public Base { | ||||
| 		/*! Sets the type of display the CRT will request. */ | ||||
| 		void set_display_type(Outputs::Display::DisplayType); | ||||
|  | ||||
| 		/*! Gets the type of display the CRT will request. */ | ||||
| 		Outputs::Display::DisplayType get_display_type() const; | ||||
|  | ||||
| 		/*! | ||||
| 			Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code | ||||
| 			that the input clock rate is 3579545 Hz, the NTSC colour clock rate. | ||||
|   | ||||
| @@ -40,7 +40,7 @@ enum class TVStandard { | ||||
|  | ||||
| class Base { | ||||
| 	public: | ||||
| 		static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { | ||||
| 		static uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { | ||||
| 			uint32_t result = 0; | ||||
| 			uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result); | ||||
| 			result_ptr[0] = r; | ||||
| @@ -51,7 +51,7 @@ class Base { | ||||
| 		} | ||||
|  | ||||
| 	protected: | ||||
| 		const static int output_lag = 11;	// i.e. pixel output will occur 11 cycles after corresponding data read. | ||||
| 		static constexpr int output_lag = 11;	// i.e. pixel output will occur 11 cycles after corresponding data read. | ||||
|  | ||||
| 		// The default TMS palette. | ||||
| 		const uint32_t palette[16] = { | ||||
| @@ -352,9 +352,9 @@ class Base { | ||||
| 					if(master_system_.cram_is_selected) { | ||||
| 						// Adjust the palette. | ||||
| 						master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack( | ||||
| 							static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3), | ||||
| 							static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3), | ||||
| 							static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3) | ||||
| 							uint8_t(((read_ahead_buffer_ >> 0) & 3) * 255 / 3), | ||||
| 							uint8_t(((read_ahead_buffer_ >> 2) & 3) * 255 / 3), | ||||
| 							uint8_t(((read_ahead_buffer_ >> 4) & 3) * 255 / 3) | ||||
| 						); | ||||
|  | ||||
| 						// Schedule a CRAM dot; this is scheduled for wherever it should appear | ||||
| @@ -422,6 +422,7 @@ class Base { | ||||
|  | ||||
| #define slot(n)	\ | ||||
| 		if(use_end && end == n) return;	\ | ||||
| 		[[fallthrough]];				\ | ||||
| 		case n | ||||
|  | ||||
| #define external_slot(n)	\ | ||||
| @@ -518,7 +519,7 @@ class Base { | ||||
| 	fetch_columns_4(location+12, column+4); | ||||
|  | ||||
| 			LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; | ||||
| 			const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40); | ||||
| 			const size_t row_base = pattern_name_address_ & (0x3c00 | size_t(write_pointer_.row >> 3) * 40); | ||||
| 			const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7)); | ||||
|  | ||||
| 			switch(start) { | ||||
| @@ -731,7 +732,7 @@ class Base { | ||||
| 		const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ | ||||
| 		const size_t address = row_info.pattern_address_base + (scrolled_column << 1);	\ | ||||
| 		line_buffer.names[column].flags = ram_[address+1];	\ | ||||
| 		line_buffer.names[column].offset = static_cast<size_t>(	\ | ||||
| 		line_buffer.names[column].offset = size_t(	\ | ||||
| 			(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5	\ | ||||
| 		) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2];	\ | ||||
| 	} | ||||
| @@ -785,7 +786,7 @@ class Base { | ||||
| 			}; | ||||
| 			const RowInfo scrolled_row_info = { | ||||
| 				(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset, | ||||
| 				{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)} | ||||
| 				{size_t((scrolled_row & 7) << 2), 28 ^ size_t((scrolled_row & 7) << 2)} | ||||
| 			}; | ||||
| 			RowInfo row_info; | ||||
| 			if(master_system_.vertical_scroll_lock) { | ||||
|   | ||||
| @@ -6,13 +6,17 @@ | ||||
| //  Copyright 2016 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include <cmath> | ||||
|  | ||||
| #include "AY38910.hpp" | ||||
|  | ||||
| #include <cmath> | ||||
| //namespace GI { | ||||
| //namespace AY38910 { | ||||
|  | ||||
| using namespace GI::AY38910; | ||||
|  | ||||
| AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { | ||||
| template <bool is_stereo> | ||||
| AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { | ||||
| 	// Don't use the low bit of the envelope position if this is an AY. | ||||
| 	envelope_position_mask_ |= personality == Personality::AY38910; | ||||
|  | ||||
| @@ -70,18 +74,34 @@ AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue & | ||||
| 	set_sample_volume_range(0); | ||||
| } | ||||
|  | ||||
| void AY38910::set_sample_volume_range(std::int16_t range) { | ||||
| 	// set up volume lookup table | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::int16_t range) { | ||||
| 	// Set up volume lookup table; the function below is based on a combination of the graph | ||||
| 	// from the YM's datasheet, showing a clear power curve, and fitting that to observed | ||||
| 	// values reported elsewhere. | ||||
| 	const float max_volume = float(range) / 3.0f;	// As there are three channels. | ||||
| 	constexpr float root_two = 1.414213562373095f; | ||||
| 	for(int v = 0; v < 32; v++) { | ||||
| 		volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f)); | ||||
| 		volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f)); | ||||
| 	} | ||||
| 	volumes_[0] = 0;	// Tie level 0 to silence. | ||||
|  | ||||
| 	// Tie level 0 to silence. | ||||
| 	for(int v = 31; v >= 0; --v) { | ||||
| 		volumes_[v] -= volumes_[0]; | ||||
| 	} | ||||
|  | ||||
| 	evaluate_output_volume(); | ||||
| } | ||||
|  | ||||
| void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) { | ||||
| 	a_left_ = uint8_t(a_left * 255.0f); | ||||
| 	b_left_ = uint8_t(b_left * 255.0f); | ||||
| 	c_left_ = uint8_t(c_left * 255.0f); | ||||
| 	a_right_ = uint8_t(a_right * 255.0f); | ||||
| 	b_right_ = uint8_t(b_right * 255.0f); | ||||
| 	c_right_ = uint8_t(c_right * 255.0f); | ||||
| } | ||||
|  | ||||
| template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
| 	// Note on structure below: the real AY has a built-in divider of 8 | ||||
| 	// prior to applying its tone and noise dividers. But the YM fills the | ||||
| 	// same total periods for noise and tone with double-precision envelopes. | ||||
| @@ -93,7 +113,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
|  | ||||
| 	std::size_t c = 0; | ||||
| 	while((master_divider_&3) && c < number_of_samples) { | ||||
| 		target[c] = output_volume_; | ||||
| 		if constexpr (is_stereo) { | ||||
| 			reinterpret_cast<uint32_t *>(target)[c] = output_volume_; | ||||
| 		} else { | ||||
| 			target[c] = int16_t(output_volume_); | ||||
| 		} | ||||
| 		master_divider_++; | ||||
| 		c++; | ||||
| 	} | ||||
| @@ -135,7 +159,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
| 		evaluate_output_volume(); | ||||
|  | ||||
| 		for(int ic = 0; ic < 4 && c < number_of_samples; ic++) { | ||||
| 			target[c] = output_volume_; | ||||
| 			if constexpr (is_stereo) { | ||||
| 				reinterpret_cast<uint32_t *>(target)[c] = output_volume_; | ||||
| 			} else { | ||||
| 				target[c] = int16_t(output_volume_); | ||||
| 			} | ||||
| 			c++; | ||||
| 			master_divider_++; | ||||
| 		} | ||||
| @@ -144,7 +172,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { | ||||
| 	master_divider_ &= 3; | ||||
| } | ||||
|  | ||||
| void AY38910::evaluate_output_volume() { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() { | ||||
| 	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_]; | ||||
|  | ||||
| 	// The output level for a channel is: | ||||
| @@ -184,34 +212,47 @@ void AY38910::evaluate_output_volume() { | ||||
| 	}; | ||||
| #undef channel_volume | ||||
|  | ||||
| 	// Mix additively. | ||||
| 	output_volume_ = static_cast<int16_t>( | ||||
| 	// Mix additively, weighting if in stereo. | ||||
| 	if constexpr (is_stereo) { | ||||
| 		int16_t *const output_volumes = reinterpret_cast<int16_t *>(&output_volume_); | ||||
| 		output_volumes[0] = int16_t(( | ||||
| 			volumes_[volumes[0]] * channel_levels[0] * a_left_ + | ||||
| 			volumes_[volumes[1]] * channel_levels[1] * b_left_ + | ||||
| 			volumes_[volumes[2]] * channel_levels[2] * c_left_ | ||||
| 		) >> 8); | ||||
| 		output_volumes[1] = int16_t(( | ||||
| 			volumes_[volumes[0]] * channel_levels[0] * a_right_ + | ||||
| 			volumes_[volumes[1]] * channel_levels[1] * b_right_ + | ||||
| 			volumes_[volumes[2]] * channel_levels[2] * c_right_ | ||||
| 		) >> 8); | ||||
| 	} else { | ||||
| 		output_volume_ = uint32_t( | ||||
| 			volumes_[volumes[0]] * channel_levels[0] + | ||||
| 			volumes_[volumes[1]] * channel_levels[1] + | ||||
| 			volumes_[volumes[2]] * channel_levels[2] | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool AY38910::is_zero_level() { | ||||
| template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() const { | ||||
| 	// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero. | ||||
| 	return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0; | ||||
| } | ||||
|  | ||||
| // MARK: - Register manipulation | ||||
|  | ||||
| void AY38910::select_register(uint8_t r) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::select_register(uint8_t r) { | ||||
| 	selected_register_ = r; | ||||
| } | ||||
|  | ||||
| void AY38910::set_register_value(uint8_t value) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t value) { | ||||
| 	// There are only 16 registers. | ||||
| 	if(selected_register_ > 15) return; | ||||
|  | ||||
| 	// If this is a register that affects audio output, enqueue a mutation onto the | ||||
| 	// audio generation thread. | ||||
| 	if(selected_register_ < 14) { | ||||
| 		const int selected_register = selected_register_; | ||||
| 		task_queue_.defer([=] () { | ||||
| 		task_queue_.defer([this, selected_register = selected_register_, value] () { | ||||
| 			// Perform any register-specific mutation to output generation. | ||||
| 			uint8_t masked_value = value; | ||||
| 			switch(selected_register) { | ||||
| @@ -220,7 +261,7 @@ void AY38910::set_register_value(uint8_t value) { | ||||
| 					int channel = selected_register >> 1; | ||||
|  | ||||
| 					if(selected_register & 1) | ||||
| 						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8); | ||||
| 						tone_periods_[channel] = (tone_periods_[channel] & 0xff) | uint16_t((value&0xf) << 8); | ||||
| 					else | ||||
| 						tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value; | ||||
| 				} | ||||
| @@ -235,7 +276,7 @@ void AY38910::set_register_value(uint8_t value) { | ||||
| 				break; | ||||
|  | ||||
| 				case 12: | ||||
| 					envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8); | ||||
| 					envelope_period_ = (envelope_period_ & 0xff) | int(value << 8); | ||||
| 				break; | ||||
|  | ||||
| 				case 13: | ||||
| @@ -273,7 +314,7 @@ void AY38910::set_register_value(uint8_t value) { | ||||
| 	if(update_port_a) set_port_output(false); | ||||
| } | ||||
|  | ||||
| uint8_t AY38910::get_register_value() { | ||||
| template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() { | ||||
| 	// This table ensures that bits that aren't defined within the AY are returned as 0s | ||||
| 	// when read, conforming to CPC-sourced unit tests. | ||||
| 	const uint8_t register_masks[16] = { | ||||
| @@ -287,24 +328,24 @@ uint8_t AY38910::get_register_value() { | ||||
|  | ||||
| // MARK: - Port querying | ||||
|  | ||||
| uint8_t AY38910::get_port_output(bool port_b) { | ||||
| template <bool is_stereo> uint8_t AY38910<is_stereo>::get_port_output(bool port_b) { | ||||
| 	return registers_[port_b ? 15 : 14]; | ||||
| } | ||||
|  | ||||
| // MARK: - Bus handling | ||||
|  | ||||
| void AY38910::set_port_handler(PortHandler *handler) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_port_handler(PortHandler *handler) { | ||||
| 	port_handler_ = handler; | ||||
| 	set_port_output(true); | ||||
| 	set_port_output(false); | ||||
| } | ||||
|  | ||||
| void AY38910::set_data_input(uint8_t r) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_data_input(uint8_t r) { | ||||
| 	data_input_ = r; | ||||
| 	update_bus(); | ||||
| } | ||||
|  | ||||
| void AY38910::set_port_output(bool port_b) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b) { | ||||
| 	// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor, | ||||
| 	// so that when in the "input" mode, all pins will read normally high". Therefore, | ||||
| 	// report programmer selection of input mode as creating an output of 0xff. | ||||
| @@ -314,7 +355,7 @@ void AY38910::set_port_output(bool port_b) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| uint8_t AY38910::get_data_output() { | ||||
| template <bool is_stereo> uint8_t AY38910<is_stereo>::get_data_output() { | ||||
| 	if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) { | ||||
| 		// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the | ||||
| 		// value returned to the CPU when reading it is the and of the output value and any input. | ||||
| @@ -330,22 +371,22 @@ uint8_t AY38910::get_data_output() { | ||||
| 	return data_output_; | ||||
| } | ||||
|  | ||||
| void AY38910::set_control_lines(ControlLines control_lines) { | ||||
| 	switch(static_cast<int>(control_lines)) { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLines control_lines) { | ||||
| 	switch(int(control_lines)) { | ||||
| 		default:					control_state_ = Inactive;		break; | ||||
|  | ||||
| 		case static_cast<int>(BDIR | BC2 | BC1): | ||||
| 		case int(BDIR | BC2 | BC1): | ||||
| 		case BDIR: | ||||
| 		case BC1:					control_state_ = LatchAddress;	break; | ||||
|  | ||||
| 		case static_cast<int>(BC2 | BC1):		control_state_ = Read;			break; | ||||
| 		case static_cast<int>(BDIR | BC2):		control_state_ = Write;			break; | ||||
| 		case int(BC2 | BC1):		control_state_ = Read;			break; | ||||
| 		case int(BDIR | BC2):		control_state_ = Write;			break; | ||||
| 	} | ||||
|  | ||||
| 	update_bus(); | ||||
| } | ||||
|  | ||||
| void AY38910::update_bus() { | ||||
| template <bool is_stereo> void AY38910<is_stereo>::update_bus() { | ||||
| 	// Assume no output, unless this turns out to be a read. | ||||
| 	data_output_ = 0xff; | ||||
| 	switch(control_state_) { | ||||
| @@ -355,3 +396,7 @@ void AY38910::update_bus() { | ||||
| 		case Read:			data_output_ = get_register_value();	break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Ensure both mono and stereo versions of the AY are built. | ||||
| template class GI::AY38910::AY38910<true>; | ||||
| template class GI::AY38910::AY38910<false>; | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class PortHandler { | ||||
|  | ||||
| 			@param port_b @c true if the input being queried is Port B. @c false if it is Port A. | ||||
| 		*/ | ||||
| 		virtual uint8_t get_port_input(bool port_b) { | ||||
| 		virtual uint8_t get_port_input([[maybe_unused]] bool port_b) { | ||||
| 			return 0xff; | ||||
| 		} | ||||
|  | ||||
| @@ -40,7 +40,7 @@ class PortHandler { | ||||
| 			@param port_b @c true if the output being posted is Port B. @c false if it is Port A. | ||||
| 			@param value the value now being output. | ||||
| 		*/ | ||||
| 		virtual void set_port_output(bool port_b, uint8_t value) {} | ||||
| 		virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {} | ||||
| }; | ||||
|  | ||||
| /*! | ||||
| @@ -63,8 +63,10 @@ enum class Personality { | ||||
| 	Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a | ||||
| 	noise generator and a volume envelope generator, which also provides two bidirectional | ||||
| 	interface ports. | ||||
|  | ||||
| 	This AY has an attached mono or stereo mixer. | ||||
| */ | ||||
| class AY38910: public ::Outputs::Speaker::SampleSource { | ||||
| template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource { | ||||
| 	public: | ||||
| 		/// Creates a new AY38910. | ||||
| 		AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &); | ||||
| @@ -91,10 +93,23 @@ class AY38910: public ::Outputs::Speaker::SampleSource { | ||||
| 		*/ | ||||
| 		void set_port_handler(PortHandler *); | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's | ||||
| 			channels in each of the output channels. | ||||
|  | ||||
| 			If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono. | ||||
|  | ||||
| 			a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left. | ||||
|  | ||||
| 			a_left = 0.5, a_right = 0.5 will make A half volume on both outputs. | ||||
| 		*/ | ||||
| 		void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0); | ||||
|  | ||||
| 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. | ||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||
| 		bool is_zero_level(); | ||||
| 		bool is_zero_level() const; | ||||
| 		void set_sample_volume_range(std::int16_t range); | ||||
| 		static constexpr bool get_is_stereo() { return is_stereo; } | ||||
|  | ||||
| 	private: | ||||
| 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||
| @@ -135,14 +150,21 @@ class AY38910: public ::Outputs::Speaker::SampleSource { | ||||
|  | ||||
| 		uint8_t data_input_, data_output_; | ||||
|  | ||||
| 		int16_t output_volume_; | ||||
| 		void evaluate_output_volume(); | ||||
| 		uint32_t output_volume_; | ||||
|  | ||||
| 		void update_bus(); | ||||
| 		PortHandler *port_handler_ = nullptr; | ||||
| 		void set_port_output(bool port_b); | ||||
|  | ||||
| 		void evaluate_output_volume(); | ||||
|  | ||||
| 		// Output mixing control. | ||||
| 		uint8_t a_left_ = 255, a_right_ = 255; | ||||
| 		uint8_t b_left_ = 255, b_right_ = 255; | ||||
| 		uint8_t c_left_ = 255, c_right_ = 255; | ||||
| }; | ||||
|  | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,16 +23,16 @@ void Toggle::set_sample_volume_range(std::int16_t range) { | ||||
| 	volume_ = range; | ||||
| } | ||||
|  | ||||
| void Toggle::skip_samples(const std::size_t number_of_samples) {} | ||||
| void Toggle::skip_samples(std::size_t) {} | ||||
|  | ||||
| void Toggle::set_output(bool enabled) { | ||||
| 	if(is_enabled_ == enabled) return; | ||||
| 	is_enabled_ = enabled; | ||||
| 	audio_queue_.defer([=] { | ||||
| 	audio_queue_.defer([this, enabled] { | ||||
| 		level_ = enabled ? volume_ : 0; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| bool Toggle::get_output() { | ||||
| bool Toggle::get_output() const { | ||||
| 	return is_enabled_; | ||||
| } | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class Toggle: public Outputs::Speaker::SampleSource { | ||||
| 		void skip_samples(const std::size_t number_of_samples); | ||||
|  | ||||
| 		void set_output(bool enabled); | ||||
| 		bool get_output(); | ||||
| 		bool get_output() const; | ||||
|  | ||||
| 	private: | ||||
| 		// Accessed on the calling thread. | ||||
|   | ||||
| @@ -85,13 +85,13 @@ void DiskII::run_for(const Cycles cycles) { | ||||
| 			--flux_duration_; | ||||
| 			if(!flux_duration_) inputs_ |= input_flux; | ||||
| 		} | ||||
| 		state_ = state_machine_[static_cast<std::size_t>(address)]; | ||||
| 		state_ = state_machine_[size_t(address)]; | ||||
| 		switch(state_ & 0xf) { | ||||
| 			default:	shift_register_ = 0;										break;	// clear | ||||
| 			case 0x8:																break;	// nop | ||||
|  | ||||
| 			case 0x9:	shift_register_ = static_cast<uint8_t>(shift_register_ << 1);			break;	// shift left, bringing in a zero | ||||
| 			case 0xd:	shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1);		break;	// shift left, bringing in a one | ||||
| 			case 0x9:	shift_register_ = uint8_t(shift_register_ << 1);			break;	// shift left, bringing in a zero | ||||
| 			case 0xd:	shift_register_ = uint8_t((shift_register_ << 1) | 1);		break;	// shift left, bringing in a one | ||||
|  | ||||
| 			case 0xa:	// shift right, bringing in write protected status | ||||
| 				shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); | ||||
| @@ -191,7 +191,6 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) { | ||||
| 				((source_address&0x02) ? 0x02 : 0x00); | ||||
| 			uint8_t source_value = state_machine[source_address]; | ||||
|  | ||||
| 			// Remap into Beneath Apple Pro-DOS value form. | ||||
| 			source_value = | ||||
| 				((source_value & 0x80) ? 0x10 : 0x0) | | ||||
| 				((source_value & 0x40) ? 0x20 : 0x0) | | ||||
| @@ -219,13 +218,13 @@ void DiskII::process_event(const Storage::Disk::Drive::Event &event) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { | ||||
| void DiskII::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) { | ||||
| 	drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None; | ||||
| 	drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None; | ||||
| 	decide_clocking_preference(); | ||||
| } | ||||
|  | ||||
| ClockingHint::Preference DiskII::preferred_clocking() { | ||||
| ClockingHint::Preference DiskII::preferred_clocking() const { | ||||
| 	return clocking_preference_; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -76,7 +76,7 @@ class DiskII : | ||||
| 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | ||||
|  | ||||
| 		// As per Sleeper. | ||||
| 		ClockingHint::Preference preferred_clocking() final; | ||||
| 		ClockingHint::Preference preferred_clocking() const final; | ||||
|  | ||||
| 		// The Disk II functions as a potential target for @c Activity::Sources. | ||||
| 		void set_activity_observer(Activity::Observer *observer); | ||||
|   | ||||
| @@ -307,8 +307,8 @@ void IWM::run_for(const Cycles cycles) { | ||||
| 			} else { | ||||
| 				shift_register_ = sense(); | ||||
| 			} | ||||
| 			[[fallthrough]]; | ||||
|  | ||||
| 		/* Deliberate fallthrough. */ | ||||
| 		default: | ||||
| 			if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles); | ||||
| 		break; | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace Apple { | ||||
| 	Defines the drive interface used by the IWM, derived from the external pinout as | ||||
| 	per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml | ||||
|  | ||||
| 	These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports, | ||||
| 	These are subclasses of Storage::Disk::Drive, so accept any disk the emulator supports, | ||||
| 	and provide the usual read/write interface for on-disk data. | ||||
| */ | ||||
| struct IWMDrive: public Storage::Disk::Drive { | ||||
| @@ -82,8 +82,9 @@ class IWM: | ||||
|  | ||||
| 		uint8_t data_register_ = 0; | ||||
| 		uint8_t mode_ = 0; | ||||
| 		bool read_write_ready_ = true; | ||||
| 		bool write_overran_ = false; | ||||
| 		// These related to functionality not-yet implemented. | ||||
| 		// bool read_write_ready_ = true; | ||||
| 		// bool write_overran_ = false; | ||||
|  | ||||
| 		int state_ = 0; | ||||
|  | ||||
|   | ||||
| @@ -15,7 +15,7 @@ using namespace Konami; | ||||
| SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : | ||||
| 	task_queue_(task_queue) {} | ||||
|  | ||||
| bool SCC::is_zero_level() { | ||||
| bool SCC::is_zero_level() const { | ||||
| 	return !(channel_enable_ & 0x1f); | ||||
| } | ||||
|  | ||||
| @@ -55,7 +55,7 @@ void SCC::write(uint16_t address, uint8_t value) { | ||||
| 	address &= 0xff; | ||||
| 	if(address < 0x80) ram_[address] = value; | ||||
|  | ||||
| 	task_queue_.defer([=] { | ||||
| 	task_queue_.defer([this, address, value] { | ||||
| 		// Check for a write into waveform memory. | ||||
| 		if(address < 0x80) { | ||||
| 			waves_[address >> 5].samples[address & 0x1f] = value; | ||||
| @@ -87,13 +87,13 @@ void SCC::write(uint16_t address, uint8_t value) { | ||||
|  | ||||
| void SCC::evaluate_output_volume() { | ||||
| 	transient_output_level_ = | ||||
| 		static_cast<int16_t>( | ||||
| 		int16_t( | ||||
| 			(( | ||||
| 				(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 | ||||
| 				(channel_enable_ & 0x01) ? int8_t(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x02) ? int8_t(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x04) ? int8_t(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x08) ? int8_t(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + | ||||
| 				(channel_enable_ & 0x10) ? int8_t(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 | ||||
| 			) * master_volume_) / (255*15*5) | ||||
| 			// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5. | ||||
| 		); | ||||
|   | ||||
| @@ -27,11 +27,12 @@ class SCC: public ::Outputs::Speaker::SampleSource { | ||||
| 		SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||
|  | ||||
| 		/// As per ::SampleSource; provides a broadphase test for silence. | ||||
| 		bool is_zero_level(); | ||||
| 		bool is_zero_level() const; | ||||
|  | ||||
| 		/// As per ::SampleSource; provides audio output. | ||||
| 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||
| 		void set_sample_volume_range(std::int16_t range); | ||||
| 		static constexpr bool get_is_stereo() { return false; } | ||||
|  | ||||
| 		/// Writes to the SCC. | ||||
| 		void write(uint16_t address, uint8_t value); | ||||
| @@ -60,7 +61,6 @@ class SCC: public ::Outputs::Speaker::SampleSource { | ||||
| 		} waves_[4]; | ||||
|  | ||||
| 		std::uint8_t channel_enable_ = 0; | ||||
| 		std::uint8_t test_register_ = 0; | ||||
|  | ||||
| 		void evaluate_output_volume(); | ||||
|  | ||||
|   | ||||
							
								
								
									
										264
									
								
								Components/OPx/Implementation/EnvelopeGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								Components/OPx/Implementation/EnvelopeGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | ||||
| // | ||||
| //  EnvelopeGenerator.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 01/05/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef EnvelopeGenerator_h | ||||
| #define EnvelopeGenerator_h | ||||
|  | ||||
| #include <optional> | ||||
| #include <functional> | ||||
| #include "LowFrequencyOscillator.hpp" | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| /*! | ||||
| 	Models an OPL-style envelope generator. | ||||
|  | ||||
| 	Damping is optional; if damping is enabled then if there is a transition to key-on while | ||||
| 	attenuation is less than maximum then attenuation will be quickly transitioned to maximum | ||||
| 	before the attack phase can begin. | ||||
|  | ||||
| 	in real hardware damping is used by the envelope generators associated with | ||||
| 	carriers, with phases being reset upon the transition from damping to attack. | ||||
|  | ||||
| 	This code considers application of tremolo to be a function of the envelope generator; | ||||
| 	this is largely for logical conformity with the phase generator that necessarily has to | ||||
| 	apply vibrato. | ||||
|  | ||||
| 	TODO: use envelope_precision. | ||||
| */ | ||||
| template <int envelope_precision, int period_precision> class EnvelopeGenerator { | ||||
| 	public: | ||||
| 		/*! | ||||
| 			Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator. | ||||
| 		*/ | ||||
| 		void update(const LowFrequencyOscillator &oscillator) { | ||||
| 			// Apply tremolo, which is fairly easy. | ||||
| 			tremolo_ = tremolo_enable_ * oscillator.tremolo << 4; | ||||
|  | ||||
| 			// Something something something... | ||||
| 			const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_; | ||||
| 			switch(phase_) { | ||||
| 				case Phase::Damp: | ||||
| 					update_decay(oscillator, 12 << 2); | ||||
| 					if(attenuation_ == 511) { | ||||
| 						(*will_attack_)(); | ||||
| 						phase_ = Phase::Attack; | ||||
| 					} | ||||
| 				break; | ||||
|  | ||||
| 				case Phase::Attack: | ||||
| 					update_attack(oscillator, attack_rate_ + key_scaling_rate); | ||||
|  | ||||
| 					// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached. | ||||
| 					if(attenuation_ <= 0) { | ||||
| 						attenuation_ = 0; | ||||
| 						phase_ = Phase::Decay; | ||||
| 					} | ||||
| 				break; | ||||
|  | ||||
| 				case Phase::Decay: | ||||
| 					update_decay(oscillator, decay_rate_ + key_scaling_rate); | ||||
| 					if(attenuation_ >= sustain_level_) { | ||||
| 						attenuation_ = sustain_level_; | ||||
| 						phase_ = use_sustain_level_ ? Phase::Sustain : Phase::Release; | ||||
| 					} | ||||
| 				break; | ||||
|  | ||||
| 				case Phase::Sustain: | ||||
| 					// Nothing to do. | ||||
| 				break; | ||||
|  | ||||
| 				case Phase::Release: | ||||
| 					update_decay(oscillator, release_rate_ + key_scaling_rate); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns The current attenuation from this envelope generator. This is independent of the envelope precision. | ||||
| 		*/ | ||||
| 		int attenuation() const { | ||||
| 			// TODO: if this envelope is fully released, should tremolo still be able to vocalise it? | ||||
| 			return (attenuation_ << 3) + tremolo_; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will | ||||
| 			use the damping phase when necessary (i.e. when transitioning to key on if attenuation is not already at maximum) | ||||
| 			and in any case will call @c will_attack before transitioning from any other state to attack. | ||||
|  | ||||
| 			@param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode. | ||||
| 		*/ | ||||
| 		void set_should_damp(const std::optional<std::function<void(void)>> &will_attack) { | ||||
| 			will_attack_ = will_attack; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the current state of the key-on input. | ||||
| 		*/ | ||||
| 		void set_key_on(bool key_on) { | ||||
| 			// Do nothing if this is not a leading or trailing edge. | ||||
| 			if(key_on == key_on_) return; | ||||
| 			key_on_ = key_on; | ||||
|  | ||||
| 			// Always transition to release upon a key off. | ||||
| 			if(!key_on_) { | ||||
| 				phase_ = Phase::Release; | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			// On key on: if this is an envelope generator with damping, and damping is required, | ||||
| 			// schedule that. If damping is not required, announce a pending attack now and | ||||
| 			// transition to attack. | ||||
| 			if(will_attack_) { | ||||
| 				if(attenuation_ != 511) { | ||||
| 					phase_ = Phase::Damp; | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				(*will_attack_)(); | ||||
| 			} | ||||
| 			phase_ = Phase::Attack; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the attack rate, which should be in the range 0–15. | ||||
| 		*/ | ||||
| 		void set_attack_rate(int rate) { | ||||
| 			attack_rate_ = rate << 2; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the decay rate, which should be in the range 0–15. | ||||
| 		*/ | ||||
| 		void set_decay_rate(int rate) { | ||||
| 			decay_rate_ = rate << 2; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the release rate, which should be in the range 0–15. | ||||
| 		*/ | ||||
| 		void set_release_rate(int rate) { | ||||
| 			release_rate_ = rate << 2; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the sustain level, which should be in the range 0–15. | ||||
| 		*/ | ||||
| 		void set_sustain_level(int level) { | ||||
| 			sustain_level_ = level << 3; | ||||
| 			// TODO: verify the shift level here. Especially re: precision. | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables use of the sustain level. If this is disabled, the envelope proceeds | ||||
| 			directly from decay to release. | ||||
| 		*/ | ||||
| 		void set_use_sustain_level(bool use) { | ||||
| 			use_sustain_level_ = use; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables key-rate scaling. | ||||
| 		*/ | ||||
| 		void set_key_scaling_rate_enabled(bool enabled) { | ||||
| 			key_scale_rate_shift_ = int(enabled) * 2; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables application of the low-frequency oscillator's tremolo. | ||||
| 		*/ | ||||
| 		void set_tremolo_enabled(bool enabled) { | ||||
| 			tremolo_enable_ = int(enabled); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the current period associated with the channel that owns this envelope generator; | ||||
| 			this is used to select a key scaling rate if key-rate scaling is enabled. | ||||
| 		*/ | ||||
| 		void set_period(int period, int octave) { | ||||
| 			key_scale_rate_ = (octave << 1) | (period >> (period_precision - 1)); | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		enum class Phase { | ||||
| 			Attack, Decay, Sustain, Release, Damp | ||||
| 		} phase_ = Phase::Release; | ||||
| 		int attenuation_ = 511, tremolo_ = 0; | ||||
|  | ||||
| 		bool key_on_ = false; | ||||
| 		std::optional<std::function<void(void)>> will_attack_; | ||||
|  | ||||
| 		int key_scale_rate_ = 0; | ||||
| 		int key_scale_rate_shift_ = 0; | ||||
|  | ||||
| 		int tremolo_enable_ = 0; | ||||
|  | ||||
| 		int attack_rate_ = 0; | ||||
| 		int decay_rate_ = 0; | ||||
| 		int release_rate_ = 0; | ||||
| 		int sustain_level_ = 0; | ||||
| 		bool use_sustain_level_ = false; | ||||
|  | ||||
| 		static constexpr int dithering_patterns[4][8] = { | ||||
| 			{0, 1, 0, 1, 0, 1, 0, 1}, | ||||
| 			{0, 1, 0, 1, 1, 1, 0, 1}, | ||||
| 			{0, 1, 1, 1, 0, 1, 1, 1}, | ||||
| 			{0, 1, 1, 1, 1, 1, 1, 1}, | ||||
| 		}; | ||||
|  | ||||
| 		void update_attack(const LowFrequencyOscillator &oscillator, int rate) { | ||||
| 			// Special case: no attack. | ||||
| 			if(rate < 4) { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			// Special case: instant attack. | ||||
| 			if(rate >= 60) { | ||||
| 				attenuation_ = 0; | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			// Work out the number of cycles between each adjustment tick, and stop now | ||||
| 			// if not at the next adjustment boundary. | ||||
| 			const int shift_size = 13 - (std::min(rate, 52) >> 2); | ||||
| 			if(oscillator.counter & ((1 << shift_size) - 1)) { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			// Apply dithered adjustment. | ||||
| 			const int rate_shift = (rate > 55); | ||||
| 			const int step = dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7]; | ||||
| 			attenuation_ -= ((attenuation_ >> (3 - rate_shift)) + 1) * step; | ||||
| 		} | ||||
|  | ||||
| 		void update_decay(const LowFrequencyOscillator &oscillator, int rate) { | ||||
| 			// Special case: no decay. | ||||
| 			if(rate < 4) { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			// Work out the number of cycles between each adjustment tick, and stop now | ||||
| 			// if not at the next adjustment boundary. | ||||
| 			const int shift_size = 13 - (std::min(rate, 52) >> 2); | ||||
| 			if(oscillator.counter & ((1 << shift_size) - 1)) { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			// Apply dithered adjustment and clamp. | ||||
| 			const int rate_shift = 1 + (rate > 59) + (rate > 55); | ||||
| 			attenuation_ += dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7] * (4 << rate_shift); | ||||
| 			attenuation_ = std::min(attenuation_, 511); | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* EnvelopeGenerator_h */ | ||||
							
								
								
									
										58
									
								
								Components/OPx/Implementation/KeyLevelScaler.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								Components/OPx/Implementation/KeyLevelScaler.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // | ||||
| //  KeyLevelScaler.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 02/05/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef KeyLevelScaler_h | ||||
| #define KeyLevelScaler_h | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| template <int frequency_precision> class KeyLevelScaler { | ||||
| 	public: | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the current period associated with the channel that owns this envelope generator; | ||||
| 			this is used to select a key scaling rate if key-rate scaling is enabled. | ||||
| 		*/ | ||||
| 		void set_period(int period, int octave) { | ||||
| 			constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112}; | ||||
| 			constexpr int masks[2] = {~0, 0}; | ||||
|  | ||||
| 			// A two's complement assumption is embedded below; the use of masks relies | ||||
| 			// on the sign bit to clamp to zero. | ||||
| 			level_ = key_level_scales[period >> (frequency_precision - 4)]; | ||||
| 			level_ -= 16 * (octave ^ 7); | ||||
| 			level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1]; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables key-rate scaling. | ||||
| 		*/ | ||||
| 		void set_key_scaling_level(int level) { | ||||
| 			// '7' is just a number large enough to render all possible scaling coefficients as 0. | ||||
| 			constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0}; | ||||
| 			shift_ = key_level_scale_shifts[level]; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns The current attenuation level due to key-level scaling. | ||||
| 		*/ | ||||
| 		int attenuation() const { | ||||
| 			return level_ >> shift_; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		int level_ = 0; | ||||
| 		int shift_ = 0; | ||||
| }; | ||||
|  | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* KeyLevelScaler_h */ | ||||
							
								
								
									
										68
									
								
								Components/OPx/Implementation/LowFrequencyOscillator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Components/OPx/Implementation/LowFrequencyOscillator.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| // | ||||
| //  LowFrequencyOscillator.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 23/04/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef LowFrequencyOscillator_hpp | ||||
| #define LowFrequencyOscillator_hpp | ||||
|  | ||||
| #include "../../../Numeric/LFSR.hpp" | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| /*! | ||||
| 	Models the output of the OPL low-frequency oscillator, which provides a couple of optional fixed-frequency | ||||
| 	modifications to an operator: tremolo and vibrato. Also exposes a global time counter, which oscillators use | ||||
| 	as part of their ADSR envelope. | ||||
| */ | ||||
| class LowFrequencyOscillator { | ||||
| 	public: | ||||
| 		/// Current attenuation due to tremolo / amplitude modulation, between 0 and 26. | ||||
| 		int tremolo = 0; | ||||
|  | ||||
| 		/// A number between 0 and 7 indicating the current vibrato offset; this should be combined by operators | ||||
| 		/// with their frequency number to get the actual vibrato. | ||||
| 		int vibrato = 0; | ||||
|  | ||||
| 		/// A counter of the number of operator update cycles (i.e. input clock / 72) since an arbitrary time. | ||||
| 		int counter = 0; | ||||
|  | ||||
| 		/// Describes the current output of the LFSR; will be either 0 or 1. | ||||
| 		int lfsr = 0; | ||||
|  | ||||
| 		/// Updates the oscillator outputs. Should be called at the (input clock/72) rate. | ||||
| 		void update() { | ||||
| 			++counter; | ||||
|  | ||||
| 			// This produces output of: | ||||
| 			// | ||||
| 			// four instances of 0, four instances of 1... _three_ instances of 26, | ||||
| 			// four instances of 25, four instances of 24... _three_ instances of 0. | ||||
| 			// | ||||
| 			// ... advancing once every 64th update. | ||||
| 			const int tremolo_index = (counter >> 6) % 210; | ||||
| 			const int tremolo_levels[2] = {tremolo_index >> 2, 52 - ((tremolo_index+1) >> 2)}; | ||||
| 			tremolo = tremolo_levels[tremolo_index / 107]; | ||||
|  | ||||
| 			// Vibrato is relatively simple: it's just three bits from the counter. | ||||
| 			vibrato = (counter >> 10) & 7; | ||||
| 		} | ||||
|  | ||||
| 		/// Updartes the LFSR output. Should be called at the input clock rate. | ||||
| 		void update_lfsr() { | ||||
| 			lfsr = noise_source_.next();		 | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		// This is the correct LSFR per forums.submarine.org.uk. | ||||
| 		Numeric::LFSR<int, 0x800302> noise_source_; | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* LowFrequencyOscillator_hpp */ | ||||
							
								
								
									
										40
									
								
								Components/OPx/Implementation/OPLBase.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Components/OPx/Implementation/OPLBase.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // | ||||
| //  OPLBase.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/05/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef OPLBase_h | ||||
| #define OPLBase_h | ||||
|  | ||||
| #include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||
| #include "../../../Concurrency/AsyncTaskQueue.hpp" | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource { | ||||
| 	public: | ||||
| 		void write(uint16_t address, uint8_t value) { | ||||
| 			if(address & 1) { | ||||
| 				static_cast<Child *>(this)->write_register(selected_register_, value); | ||||
| 			} else { | ||||
| 				selected_register_ = value; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	protected: | ||||
| 		OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} | ||||
|  | ||||
| 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t selected_register_ = 0; | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* OPLBase_h */ | ||||
							
								
								
									
										125
									
								
								Components/OPx/Implementation/PhaseGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								Components/OPx/Implementation/PhaseGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| // | ||||
| //  PhaseGenerator.h | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 30/04/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef PhaseGenerator_h | ||||
| #define PhaseGenerator_h | ||||
|  | ||||
| #include <cassert> | ||||
| #include "LowFrequencyOscillator.hpp" | ||||
| #include "Tables.hpp" | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| /*! | ||||
| 	Models an OPL-style phase generator of templated precision; having been told its period ('f-num'), octave ('block') and | ||||
| 	multiple, and whether to apply vibrato, this will then appropriately update and return phase. | ||||
| */ | ||||
| template <int precision> class PhaseGenerator { | ||||
| 	public: | ||||
| 		/*! | ||||
| 			Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator. | ||||
| 		*/ | ||||
| 		void update(const LowFrequencyOscillator &oscillator) { | ||||
| 			constexpr int vibrato_shifts[4] = {3, 1, 0, 1}; | ||||
| 			constexpr int vibrato_signs[2] = {1, -1}; | ||||
|  | ||||
| 			// Get just the top three bits of the period_. | ||||
| 			const int top_freq = period_ >> (precision - 3); | ||||
|  | ||||
| 			// Cacluaute applicable vibrato as a function of (i) the top three bits of the | ||||
| 			// oscillator period; (ii) the current low-frequency oscillator vibrato output; and | ||||
| 			// (iii) whether vibrato is enabled. | ||||
| 			const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_; | ||||
|  | ||||
| 			// Apply phase update with vibrato from the low-frequency oscillator. | ||||
| 			phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1; | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		/*! | ||||
| 			@returns Current phase; real hardware provides only the low ten bits of this result. | ||||
| 		*/ | ||||
| 		int phase() const { | ||||
| 			// My table if multipliers is multiplied by two, so shift by one more | ||||
| 			// than the stated precision. | ||||
| 			return phase_ >> precision_shift; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns Current phase, scaled up by (1 << precision). | ||||
| 		*/ | ||||
| 		int scaled_phase() const { | ||||
| 			return phase_ >> 1; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Applies feedback based on two historic samples of a total output level, | ||||
| 			plus the degree of feedback to apply | ||||
| 		*/ | ||||
| 		void apply_feedback(LogSign first, LogSign second, int level) { | ||||
| 			constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0}; | ||||
| 			phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level]; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the multiple for this phase generator, in the same terms as an OPL programmer, | ||||
| 			i.e. a 4-bit number that is used as a lookup into the internal multiples table. | ||||
| 		*/ | ||||
| 		void set_multiple(int multiple) { | ||||
| 			// This encodes the MUL -> multiple table given on page 12, | ||||
| 			// multiplied by two. | ||||
| 			constexpr int multipliers[] = { | ||||
| 				1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 | ||||
| 			}; | ||||
| 			assert(multiple < 16); | ||||
| 			multiple_ = multipliers[multiple]; | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Sets the period of this generator, along with its current octave. | ||||
|  | ||||
| 			Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave. | ||||
| 		*/ | ||||
| 		void set_period(int period, int octave) { | ||||
| 			period_ = period; | ||||
| 			octave_ = octave; | ||||
|  | ||||
| 			assert(octave_ < 8); | ||||
| 			assert(period_ < (1 << precision)); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Enables or disables vibrato. | ||||
| 		*/ | ||||
| 		void set_vibrato_enabled(bool enabled) { | ||||
| 			enable_vibrato_ = int(enabled); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			Resets the current phase. | ||||
| 		*/ | ||||
| 		void reset() { | ||||
| 			phase_ = 0; | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		static constexpr int precision_shift =  1 + precision; | ||||
|  | ||||
| 		int phase_ = 0; | ||||
|  | ||||
| 		int multiple_ = 0; | ||||
| 		int period_ = 0; | ||||
| 		int octave_ = 0; | ||||
| 		int enable_vibrato_ = 0; | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* PhaseGenerator_h */ | ||||
							
								
								
									
										227
									
								
								Components/OPx/Implementation/Tables.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								Components/OPx/Implementation/Tables.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | ||||
| // | ||||
| //  Tables.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 15/04/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef Tables_hpp | ||||
| #define Tables_hpp | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| /* | ||||
| 	These are the OPL's built-in log-sin and exponentiation tables, as recovered by | ||||
| 	Matthew Gambrell and Olli Niemitalo in 'OPLx decapsulated'. Despite the formulas | ||||
| 	being well known, I've elected not to generate these at runtime because even if I | ||||
| 	did, I'd just end up with the proper values laid out in full in a unit test, and | ||||
| 	they're very compact. | ||||
| */ | ||||
|  | ||||
| /*! | ||||
| 	Represents both the logarithm of a value and its sign. | ||||
|  | ||||
| 	It's actually the negative logarithm, in base two, in fixed point. | ||||
| */ | ||||
| struct LogSign { | ||||
| 	int log; | ||||
| 	int sign; | ||||
|  | ||||
| 	void reset() { | ||||
| 		log = 0; | ||||
| 		sign = 1; | ||||
| 	} | ||||
|  | ||||
| 	LogSign &operator +=(int attenuation) { | ||||
| 		log += attenuation; | ||||
| 		return *this; | ||||
| 	} | ||||
|  | ||||
| 	LogSign &operator +=(LogSign log_sign) { | ||||
| 		log += log_sign.log; | ||||
| 		sign *= log_sign.sign; | ||||
| 		return *this; | ||||
| 	} | ||||
|  | ||||
| 	int level(int fractional = 0) const; | ||||
| }; | ||||
|  | ||||
| /*! | ||||
| 	@returns Negative log sin of x, assuming a 1024-unit circle. | ||||
| */ | ||||
| constexpr LogSign negative_log_sin(int x) { | ||||
| 	/// Defines the first quadrant of 1024-unit negative log to the base two of  sine (that conveniently misses sin(0)). | ||||
| 	/// | ||||
| 	/// Expected branchless usage for a full 1024 unit output: | ||||
| 	/// | ||||
| 	///	constexpr int multiplier[] = { 1, -1 }; | ||||
| 	///	constexpr int mask[] = { 0, 255 }; | ||||
| 	/// | ||||
| 	/// value = exp( log_sin[angle & 255] ^ mask[(angle >> 8) & 1]) * multitplier[(angle >> 9) & 1] | ||||
| 	/// | ||||
| 	/// ... where exp(x) = 2 ^ -x / 256 | ||||
| 	constexpr int16_t log_sin[] = { | ||||
| 		2137,	1731,	1543,	1419,	1326,	1252,	1190,	1137, | ||||
| 		1091,	1050,	1013,	979,	949,	920,	894,	869, | ||||
| 		846,	825,	804,	785,	767,	749,	732,	717, | ||||
| 		701,	687,	672,	659,	646,	633,	621,	609, | ||||
| 		598,	587,	576,	566,	556,	546,	536,	527, | ||||
| 		518,	509,	501,	492,	484,	476,	468,	461, | ||||
| 		453,	446,	439,	432,	425,	418,	411,	405, | ||||
| 		399,	392,	386,	380,	375,	369,	363,	358, | ||||
| 		352,	347,	341,	336,	331,	326,	321,	316, | ||||
| 		311,	307,	302,	297,	293,	289,	284,	280, | ||||
| 		276,	271,	267,	263,	259,	255,	251,	248, | ||||
| 		244,	240,	236,	233,	229,	226,	222,	219, | ||||
| 		215,	212,	209,	205,	202,	199,	196,	193, | ||||
| 		190,	187,	184,	181,	178,	175,	172,	169, | ||||
| 		167,	164,	161,	159,	156,	153,	151,	148, | ||||
| 		146,	143,	141,	138,	136,	134,	131,	129, | ||||
| 		127,	125,	122,	120,	118,	116,	114,	112, | ||||
| 		110,	108,	106,	104,	102,	100,	98,		96, | ||||
| 		94,		92,		91,		89,		87,		85,		83,		82, | ||||
| 		80,		78,		77,		75,		74,		72,		70,		69, | ||||
| 		67,		66,		64,		63,		62,		60,		59,		57, | ||||
| 		56,		55,		53,		52,		51,		49,		48,		47, | ||||
| 		46,		45,		43,		42,		41,		40,		39,		38, | ||||
| 		37,		36,		35,		34,		33,		32,		31,		30, | ||||
| 		29,		28,		27,		26,		25,		24,		23,		23, | ||||
| 		22,		21,		20,		20,		19,		18,		17,		17, | ||||
| 		16,		15,		15,		14,		13,		13,		12,		12, | ||||
| 		11,		10,		10,		9,		9,		8,		8,		7, | ||||
| 		7,		7,		6,		6,		5,		5,		5,		4, | ||||
| 		4,		4,		3,		3,		3,		2,		2,		2, | ||||
| 		2,		1,		1,		1,		1,		1,		1,		1, | ||||
| 		0,		0,		0,		0,		0,		0,		0,		0 | ||||
| 	}; | ||||
| 	constexpr int16_t sign[] = { 1, -1 }; | ||||
| 	constexpr int16_t mask[] = { 0, 255 }; | ||||
|  | ||||
| 	return { | ||||
| 		.log = log_sin[(x & 255) ^ mask[(x >> 8) & 1]], | ||||
| 		.sign = sign[(x >> 9) & 1] | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| /*! | ||||
| 	Computes the linear value represented by the log-sign @c ls, shifted left by @c fractional prior | ||||
| 	to loss of precision. | ||||
| */ | ||||
| constexpr int power_two(LogSign ls, int fractional = 0) { | ||||
| 	/// A derivative of the exponent table in a real OPL2; mapped_exp[x] = (source[c ^ 0xff] << 1) | 0x800. | ||||
| 	/// | ||||
| 	/// The ahead-of-time transformation represents fixed work the OPL2 does when reading its table | ||||
| 	/// independent on the input. | ||||
| 	/// | ||||
| 	/// The original table is a 0.10 fixed-point representation of 2^x - 1 with bit 10 implicitly set, where x is | ||||
| 	/// in 0.8 fixed point. | ||||
| 	/// | ||||
| 	/// Since the log_sin table represents sine in a negative base-2 logarithm, values from it would need | ||||
| 	/// to be negatived before being put into the original table. That's haned with the ^ 0xff. The | 0x800 is to | ||||
| 	/// set the implicit bit 10 (subject to the shift). | ||||
| 	/// | ||||
| 	/// The shift by 1 is to allow the chip's exploitation of the recursive symmetry of the exponential table to | ||||
| 	/// be achieved more easily. Specifically, to convert a logarithmic attenuation to a linear one, just perform: | ||||
| 	/// | ||||
| 	///	result = mapped_exp[x & 0xff] >> (x >> 8) | ||||
| 	constexpr int16_t mapped_exp[] = { | ||||
| 		4084,	4074,	4062,	4052,	4040,	4030,	4020,	4008, | ||||
| 		3998,	3986,	3976,	3966,	3954,	3944,	3932,	3922, | ||||
| 		3912,	3902,	3890,	3880,	3870,	3860,	3848,	3838, | ||||
| 		3828,	3818,	3808,	3796,	3786,	3776,	3766,	3756, | ||||
| 		3746,	3736,	3726,	3716,	3706,	3696,	3686,	3676, | ||||
| 		3666,	3656,	3646,	3636,	3626,	3616,	3606,	3596, | ||||
| 		3588,	3578,	3568,	3558,	3548,	3538,	3530,	3520, | ||||
| 		3510,	3500,	3492,	3482,	3472,	3464,	3454,	3444, | ||||
| 		3434,	3426,	3416,	3408,	3398,	3388,	3380,	3370, | ||||
| 		3362,	3352,	3344,	3334,	3326,	3316,	3308,	3298, | ||||
| 		3290,	3280,	3272,	3262,	3254,	3246,	3236,	3228, | ||||
| 		3218,	3210,	3202,	3192,	3184,	3176,	3168,	3158, | ||||
| 		3150,	3142,	3132,	3124,	3116,	3108,	3100,	3090, | ||||
| 		3082,	3074,	3066,	3058,	3050,	3040,	3032,	3024, | ||||
| 		3016,	3008,	3000,	2992,	2984,	2976,	2968,	2960, | ||||
| 		2952,	2944,	2936,	2928,	2920,	2912,	2904,	2896, | ||||
| 		2888,	2880,	2872,	2866,	2858,	2850,	2842,	2834, | ||||
| 		2826,	2818,	2812,	2804,	2796,	2788,	2782,	2774, | ||||
| 		2766,	2758,	2752,	2744,	2736,	2728,	2722,	2714, | ||||
| 		2706,	2700,	2692,	2684,	2678,	2670,	2664,	2656, | ||||
| 		2648,	2642,	2634,	2628,	2620,	2614,	2606,	2600, | ||||
| 		2592,	2584,	2578,	2572,	2564,	2558,	2550,	2544, | ||||
| 		2536,	2530,	2522,	2516,	2510,	2502,	2496,	2488, | ||||
| 		2482,	2476,	2468,	2462,	2456,	2448,	2442,	2436, | ||||
| 		2428,	2422,	2416,	2410,	2402,	2396,	2390,	2384, | ||||
| 		2376,	2370,	2364,	2358,	2352,	2344,	2338,	2332, | ||||
| 		2326,	2320,	2314,	2308,	2300,	2294,	2288,	2282, | ||||
| 		2276,	2270,	2264,	2258,	2252,	2246,	2240,	2234, | ||||
| 		2228,	2222,	2216,	2210,	2204,	2198,	2192,	2186, | ||||
| 		2180,	2174,	2168,	2162,	2156,	2150,	2144,	2138, | ||||
| 		2132,	2128,	2122,	2116,	2110,	2104,	2098,	2092, | ||||
| 		2088,	2082,	2076,	2070,	2064,	2060,	2054,	2048, | ||||
| 	}; | ||||
|  | ||||
| 	return ((mapped_exp[ls.log & 0xff] << fractional) >> (ls.log >> 8)) * ls.sign; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  | ||||
| 	Credit for the fixed register lists goes to Nuke.YKT; I found them at: | ||||
| 	https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instrument_rom | ||||
|  | ||||
| 	The arrays below begin with channel 1, then each line is a single channel defined | ||||
| 	in exactly the same terms as the OPL's user-defined channel. | ||||
|  | ||||
| */ | ||||
|  | ||||
| constexpr uint8_t opll_patch_set[] = { | ||||
| 	0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17, | ||||
| 	0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13, | ||||
| 	0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x11, 0x23, | ||||
| 	0x31, 0x61, 0x0e, 0x07, 0xa8, 0x64, 0x70, 0x27, | ||||
| 	0x32, 0x21, 0x1e, 0x06, 0xe0, 0x76, 0x00, 0x28, | ||||
| 	0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18, | ||||
| 	0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x10, 0x07, | ||||
| 	0x23, 0x21, 0x2d, 0x14, 0xa2, 0x72, 0x00, 0x07, | ||||
| 	0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17, | ||||
| 	0x41, 0x61, 0x0b, 0x18, 0x85, 0xf7, 0x71, 0x07, | ||||
| 	0x13, 0x01, 0x83, 0x11, 0xfa, 0xe4, 0x10, 0x04, | ||||
| 	0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12, | ||||
| 	0x61, 0x50, 0x0c, 0x05, 0xc2, 0xf5, 0x20, 0x42, | ||||
| 	0x01, 0x01, 0x55, 0x03, 0xc9, 0x95, 0x03, 0x02, | ||||
| 	0x61, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0x40, 0x13, | ||||
| }; | ||||
|  | ||||
| constexpr uint8_t vrc7_patch_set[] = { | ||||
| 	0x03, 0x21, 0x05, 0x06, 0xe8, 0x81, 0x42, 0x27, | ||||
| 	0x13, 0x41, 0x14, 0x0d, 0xd8, 0xf6, 0x23, 0x12, | ||||
| 	0x11, 0x11, 0x08, 0x08, 0xfa, 0xb2, 0x20, 0x12, | ||||
| 	0x31, 0x61, 0x0c, 0x07, 0xa8, 0x64, 0x61, 0x27, | ||||
| 	0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28, | ||||
| 	0x02, 0x01, 0x06, 0x00, 0xa3, 0xe2, 0xf4, 0xf4, | ||||
| 	0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07, | ||||
| 	0x23, 0x21, 0x22, 0x17, 0xa2, 0x72, 0x01, 0x17, | ||||
| 	0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01, | ||||
| 	0xb5, 0x01, 0x0f, 0x0f, 0xa8, 0xa5, 0x51, 0x02, | ||||
| 	0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12, | ||||
| 	0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16, | ||||
| 	0x01, 0x02, 0xd3, 0x05, 0xc9, 0x95, 0x03, 0x02, | ||||
| 	0x61, 0x63, 0x0c, 0x00, 0x94, 0xc0, 0x33, 0xf6, | ||||
| 	0x21, 0x72, 0x0d, 0x00, 0xc1, 0xd5, 0x56, 0x06, | ||||
| }; | ||||
|  | ||||
| constexpr uint8_t percussion_patch_set[] = { | ||||
| 	0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d, | ||||
| 	0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x48, | ||||
| 	0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55, | ||||
| }; | ||||
|  | ||||
|  | ||||
| inline int LogSign::level(int fractional) const { | ||||
| 	return power_two(*this, fractional); | ||||
| } | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* Tables_hpp */ | ||||
							
								
								
									
										92
									
								
								Components/OPx/Implementation/WaveformGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Components/OPx/Implementation/WaveformGenerator.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // | ||||
| //  WaveformGenerator.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/05/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef WaveformGenerator_h | ||||
| #define WaveformGenerator_h | ||||
|  | ||||
| #include "Tables.hpp" | ||||
| #include "LowFrequencyOscillator.hpp" | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| enum class Waveform { | ||||
| 	Sine, HalfSine, AbsSine, PulseSine | ||||
| }; | ||||
|  | ||||
| template <int phase_precision> class WaveformGenerator { | ||||
| 	public: | ||||
| 		/*! | ||||
| 			@returns The output of waveform @c form at [integral] phase @c phase. | ||||
| 		*/ | ||||
| 		static constexpr LogSign wave(Waveform form, int phase) { | ||||
| 			constexpr int waveforms[4][4] = { | ||||
| 				{1023, 1023, 1023, 1023},	// Sine: don't mask in any quadrant. | ||||
| 				{511, 511, 0, 0},			// Half sine: keep the first half intact, lock to 0 in the second half. | ||||
| 				{511, 511, 511, 511},		// AbsSine: endlessly repeat the first half of the sine wave. | ||||
| 				{255, 0, 255, 0},			// PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0. | ||||
| 			}; | ||||
| 			return negative_log_sin(phase & waveforms[int(form)][(phase >> 8) & 3]); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns The output of waveform @c form at [scaled] phase @c scaled_phase given the modulation input @c modulation. | ||||
| 		*/ | ||||
| 		static constexpr LogSign wave(Waveform form, int scaled_phase, LogSign modulation) { | ||||
| 			const int scaled_phase_offset = modulation.level(phase_precision); | ||||
| 			const int phase = (scaled_phase + scaled_phase_offset) >> phase_precision; | ||||
| 			return wave(form, phase); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns Snare output, calculated from the current LFSR state as captured in @c oscillator and an operator's phase. | ||||
| 		*/ | ||||
| 		static constexpr LogSign snare(const LowFrequencyOscillator &oscillator, int phase) { | ||||
| 			// If noise is 0, output is positive. | ||||
| 			// If noise is 1, output is negative. | ||||
| 			// If (noise ^ sign) is 0, output is 0. Otherwise it is max. | ||||
| 			const int sign = phase & 0x200; | ||||
| 			const int level = ((phase >> 9) & 1) ^ oscillator.lfsr; | ||||
| 			return negative_log_sin(sign + (level << 8)); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns Cymbal output, calculated from an operator's phase and a modulator's phase. | ||||
| 		*/ | ||||
| 		static constexpr LogSign cymbal(int carrier_phase, int modulator_phase) { | ||||
| 			return negative_log_sin(256 + (phase_combination(carrier_phase, modulator_phase) << 9)); | ||||
| 		} | ||||
|  | ||||
| 		/*! | ||||
| 			@returns High-hat output, calculated from the current LFSR state as captured in @c oscillator, an operator's phase and a modulator's phase. | ||||
| 		*/ | ||||
| 		static constexpr LogSign high_hat(const LowFrequencyOscillator &oscillator, int carrier_phase, int modulator_phase) { | ||||
| 			constexpr int angles[] = {0x234, 0xd0, 0x2d0, 0x34}; | ||||
| 			return negative_log_sin(angles[ | ||||
| 				phase_combination(carrier_phase, modulator_phase) | | ||||
| 				(oscillator.lfsr << 1) | ||||
| 			]); | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		/*! | ||||
| 			@returns The phase bit used for cymbal and high-hat generation, which is a function of two operators' phases. | ||||
| 		*/ | ||||
| 		static constexpr int phase_combination(int carrier_phase, int modulator_phase) { | ||||
| 			return ( | ||||
| 				((carrier_phase >> 5) ^ (carrier_phase >> 3)) & | ||||
| 				((modulator_phase >> 7) ^ (modulator_phase >> 2)) & | ||||
| 				((carrier_phase >> 5) ^ (modulator_phase >> 3)) | ||||
| 			) & 1; | ||||
| 		} | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* WaveformGenerator_h */ | ||||
							
								
								
									
										443
									
								
								Components/OPx/OPLL.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								Components/OPx/OPLL.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,443 @@ | ||||
| // | ||||
| //  OPLL.cpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/05/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #include "OPLL.hpp" | ||||
|  | ||||
| #include <cassert> | ||||
|  | ||||
| using namespace Yamaha::OPL; | ||||
|  | ||||
| OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7): | ||||
| 	OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) { | ||||
| 	// Due to the way that sound mixing works on the OPLL, the audio divider may not | ||||
| 	// be larger than 4. | ||||
| 	assert(audio_divider <= 4); | ||||
|  | ||||
| 	// Setup the rhythm envelope generators. | ||||
|  | ||||
| 	// Treat the bass exactly as if it were a melodic channel. | ||||
| 	rhythm_envelope_generators_[BassCarrier].set_should_damp([this] { | ||||
| 		// Propagate attack mode to the modulator, and reset both phases. | ||||
| 		rhythm_envelope_generators_[BassModulator].set_key_on(true); | ||||
| 		phase_generators_[6 + 0].reset(); | ||||
| 		phase_generators_[6 + 9].reset(); | ||||
| 	}); | ||||
|  | ||||
| 	// Set the other drums to damp, but only the TomTom to affect phase. | ||||
| 	rhythm_envelope_generators_[TomTom].set_should_damp([this] { | ||||
| 		phase_generators_[8 + 9].reset(); | ||||
| 	}); | ||||
| 	rhythm_envelope_generators_[Snare].set_should_damp({}); | ||||
| 	rhythm_envelope_generators_[Cymbal].set_should_damp({}); | ||||
| 	rhythm_envelope_generators_[HighHat].set_should_damp({}); | ||||
|  | ||||
| 	// Crib the proper rhythm envelope generator settings by installing | ||||
| 	// the rhythm instruments and copying them over. | ||||
| 	rhythm_mode_enabled_ = true; | ||||
| 	install_instrument(6); | ||||
| 	install_instrument(7); | ||||
| 	install_instrument(8); | ||||
|  | ||||
| 	rhythm_envelope_generators_[BassCarrier] = envelope_generators_[6]; | ||||
| 	rhythm_envelope_generators_[BassModulator] = envelope_generators_[6 + 9]; | ||||
| 	rhythm_envelope_generators_[HighHat] = envelope_generators_[7 + 9]; | ||||
| 	rhythm_envelope_generators_[Cymbal] = envelope_generators_[8]; | ||||
| 	rhythm_envelope_generators_[TomTom] = envelope_generators_[8 + 9]; | ||||
| 	rhythm_envelope_generators_[Snare] = envelope_generators_[7]; | ||||
|  | ||||
| 	// Return to ordinary default mode. | ||||
| 	rhythm_mode_enabled_ = false; | ||||
|  | ||||
| 	// Set up damping for the melodic channels. | ||||
| 	for(int c = 0; c < 9; ++c) { | ||||
| 		envelope_generators_[c].set_should_damp([this, c] { | ||||
| 			// Propagate attack mode to the modulator, and reset both phases. | ||||
| 			envelope_generators_[c + 9].set_key_on(true); | ||||
| 			phase_generators_[c + 0].reset(); | ||||
| 			phase_generators_[c + 9].reset(); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	// Set default instrument. | ||||
| 	for(int c = 0; c < 9; ++c) { | ||||
| 		install_instrument(c); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // MARK: - Machine-facing programmatic input. | ||||
|  | ||||
| void OPLL::write_register(uint8_t address, uint8_t value) { | ||||
| 	// The OPLL doesn't have timers or other non-audio functions, so all writes | ||||
| 	// go to the audio queue. | ||||
| 	task_queue_.defer([this, address, value] { | ||||
| 		// The first 8 locations are used to define the custom instrument, and have | ||||
| 		// exactly the same format as the patch set arrays at the head of this file. | ||||
| 		if(address < 8) { | ||||
| 			custom_instrument_[address] = value; | ||||
|  | ||||
| 			// Update all channels that refer to instrument 0. | ||||
| 			for(int c = 0; c < 9; ++c) { | ||||
| 				if(!channels_[c].instrument) { | ||||
| 					install_instrument(c); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// Register 0xe enables or disables rhythm mode and contains the | ||||
| 		// percussion key-on bits. | ||||
| 		if(address == 0xe) { | ||||
| 			const bool old_rhythm_mode = rhythm_mode_enabled_; | ||||
| 			rhythm_mode_enabled_ = value & 0x20; | ||||
| 			if(old_rhythm_mode != rhythm_mode_enabled_) { | ||||
| 				// Change the instlled instruments for channels 6, 7 and 8 | ||||
| 				// if this was a transition into or out of rhythm mode. | ||||
| 				install_instrument(6); | ||||
| 				install_instrument(7); | ||||
| 				install_instrument(8); | ||||
| 			} | ||||
| 			rhythm_envelope_generators_[HighHat].set_key_on(value & 0x01); | ||||
| 			rhythm_envelope_generators_[Cymbal].set_key_on(value & 0x02); | ||||
| 			rhythm_envelope_generators_[TomTom].set_key_on(value & 0x04); | ||||
| 			rhythm_envelope_generators_[Snare].set_key_on(value & 0x08); | ||||
| 			if(value & 0x10) { | ||||
| 				rhythm_envelope_generators_[BassCarrier].set_key_on(true); | ||||
| 			} else { | ||||
| 				rhythm_envelope_generators_[BassCarrier].set_key_on(false); | ||||
| 				rhythm_envelope_generators_[BassModulator].set_key_on(false); | ||||
|  | ||||
| 			} | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// That leaves only per-channel selections, for which the addressing | ||||
| 		// is completely orthogonal; check that a valid channel is being requested. | ||||
| 		const auto index = address & 0xf; | ||||
| 		if(index > 8) return; | ||||
|  | ||||
| 		switch(address & 0xf0) { | ||||
| 			default: break; | ||||
|  | ||||
| 			// Address 1x sets the low 8 bits of the period for channel x. | ||||
| 			case 0x10: | ||||
| 				channels_[index].period = (channels_[index].period & ~0xff) | value; | ||||
| 				set_channel_period(index); | ||||
| 			return; | ||||
|  | ||||
| 			// Address 2x Sets the octave and a single bit of the frequency, as well | ||||
| 			// as setting key on and sustain mode. | ||||
| 			case 0x20: | ||||
| 				channels_[index].period = (channels_[index].period & 0xff) | ((value & 1) << 8); | ||||
| 				channels_[index].octave = (value >> 1) & 7; | ||||
| 				set_channel_period(index); | ||||
|  | ||||
| 				// In this implementation the first 9 envelope generators are for | ||||
| 				// channel carriers, and their will_attack callback is used to trigger | ||||
| 				// key-on for modulators. But key-off needs to be set to both envelope | ||||
| 				// generators now. | ||||
| 				if(value & 0x10) { | ||||
| 					envelope_generators_[index].set_key_on(true); | ||||
| 				} else { | ||||
| 					envelope_generators_[index + 0].set_key_on(false); | ||||
| 					envelope_generators_[index + 9].set_key_on(false); | ||||
| 				} | ||||
|  | ||||
| 				// Set sustain bit to both the relevant operators. | ||||
| 				channels_[index].use_sustain = value & 0x20; | ||||
| 				set_use_sustain(index); | ||||
| 			return; | ||||
|  | ||||
| 			// Address 3x selects the instrument and attenuation for a channel; | ||||
| 			// in rhythm mode some of the nibbles that ordinarily identify instruments | ||||
| 			// instead nominate additional attenuations. This code reads those back | ||||
| 			// from the stored instrument values. | ||||
| 			case 0x30: | ||||
| 				channels_[index].attenuation = value & 0xf; | ||||
|  | ||||
| 				// Install an instrument only if it's new. | ||||
| 				if(channels_[index].instrument != value >> 4) { | ||||
| 					channels_[index].instrument = value >> 4; | ||||
| 					if(index < 6 || !rhythm_mode_enabled_) { | ||||
| 						install_instrument(index); | ||||
| 					} | ||||
| 				} | ||||
| 			return; | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void OPLL::set_channel_period(int channel) { | ||||
| 	phase_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave); | ||||
| 	phase_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave); | ||||
|  | ||||
| 	envelope_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave); | ||||
| 	envelope_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave); | ||||
|  | ||||
| 	key_level_scalers_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave); | ||||
| 	key_level_scalers_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave); | ||||
| } | ||||
|  | ||||
| const uint8_t *OPLL::instrument_definition(int instrument, int channel) { | ||||
| 	// Divert to the appropriate rhythm instrument if in rhythm mode. | ||||
| 	if(channel >= 6 && rhythm_mode_enabled_) { | ||||
| 		return &percussion_patch_set[(channel - 6) * 8]; | ||||
| 	} | ||||
|  | ||||
| 	// Instrument 0 is the custom instrument. | ||||
| 	if(!instrument) return custom_instrument_; | ||||
|  | ||||
| 	// Instruments other than 0 are taken from the fixed set. | ||||
| 	const int index = (instrument - 1) * 8; | ||||
| 	return is_vrc7_ ? &vrc7_patch_set[index] : &opll_patch_set[index]; | ||||
| } | ||||
|  | ||||
| void OPLL::install_instrument(int channel) { | ||||
| 	auto &carrier_envelope = envelope_generators_[channel + 0]; | ||||
| 	auto &carrier_phase = phase_generators_[channel + 0]; | ||||
| 	auto &carrier_scaler = key_level_scalers_[channel + 0]; | ||||
|  | ||||
| 	auto &modulator_envelope = envelope_generators_[channel + 9]; | ||||
| 	auto &modulator_phase = phase_generators_[channel + 9]; | ||||
| 	auto &modulator_scaler = key_level_scalers_[channel + 9]; | ||||
|  | ||||
| 	const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel); | ||||
|  | ||||
| 	// Bytes 0 (modulator) and 1 (carrier): | ||||
| 	// | ||||
| 	//	b0-b3:	multiplier; | ||||
| 	//	b4:		key-scale rate enable; | ||||
| 	//	b5:		sustain-level enable; | ||||
| 	//	b6:		vibrato enable; | ||||
| 	//	b7:		tremolo enable. | ||||
| 	modulator_phase.set_multiple(instrument[0] & 0xf); | ||||
| 	channels_[channel].modulator_key_rate_scale_multiplier = (instrument[0] >> 4) & 1; | ||||
| 	modulator_phase.set_vibrato_enabled(instrument[0] & 0x40); | ||||
| 	modulator_envelope.set_tremolo_enabled(instrument[0] & 0x80); | ||||
|  | ||||
| 	carrier_phase.set_multiple(instrument[1] & 0xf); | ||||
| 	channels_[channel].carrier_key_rate_scale_multiplier = (instrument[1] >> 4) & 1; | ||||
| 	carrier_phase.set_vibrato_enabled(instrument[1] & 0x40); | ||||
| 	carrier_envelope.set_tremolo_enabled(instrument[1] & 0x80); | ||||
|  | ||||
| 	// Pass off bit 5. | ||||
| 	set_use_sustain(channel); | ||||
|  | ||||
| 	// Byte 2: | ||||
| 	// | ||||
| 	//	b0–b5:	modulator attenuation; | ||||
| 	//	b6–b7:	modulator key-scale level. | ||||
| 	modulator_scaler.set_key_scaling_level(instrument[3] >> 6); | ||||
| 	channels_[channel].modulator_attenuation = instrument[2] & 0x3f; | ||||
|  | ||||
| 	// Byte 3: | ||||
| 	// | ||||
| 	//	b0–b2:	modulator feedback level; | ||||
| 	//	b3:		modulator waveform selection; | ||||
| 	//	b4:		carrier waveform selection; | ||||
| 	//	b5:		[unused] | ||||
| 	//	b6–b7:	carrier key-scale level. | ||||
| 	channels_[channel].modulator_feedback = instrument[3] & 7; | ||||
| 	channels_[channel].modulator_waveform = Waveform((instrument[3] >> 3) & 1); | ||||
| 	channels_[channel].carrier_waveform = Waveform((instrument[3] >> 4) & 1); | ||||
| 	carrier_scaler.set_key_scaling_level(instrument[3] >> 6); | ||||
|  | ||||
| 	// Bytes 4 (modulator) and 5 (carrier): | ||||
| 	// | ||||
| 	//	b0–b3:	decay rate; | ||||
| 	//	b4–b7:	attack rate. | ||||
| 	modulator_envelope.set_decay_rate(instrument[4] & 0xf); | ||||
| 	modulator_envelope.set_attack_rate(instrument[4] >> 4); | ||||
| 	carrier_envelope.set_decay_rate(instrument[5] & 0xf); | ||||
| 	carrier_envelope.set_attack_rate(instrument[5] >> 4); | ||||
|  | ||||
| 	// Bytes 6 (modulator) and 7 (carrier): | ||||
| 	// | ||||
| 	//	b0–b3:	release rate; | ||||
| 	//	b4–b7:	sustain level. | ||||
| 	modulator_envelope.set_release_rate(instrument[6] & 0xf); | ||||
| 	modulator_envelope.set_sustain_level(instrument[6] >> 4); | ||||
| 	carrier_envelope.set_release_rate(instrument[7] & 0xf); | ||||
| 	carrier_envelope.set_sustain_level(instrument[7] >> 4); | ||||
| } | ||||
|  | ||||
| void OPLL::set_use_sustain(int channel) { | ||||
| 	const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel); | ||||
| 	envelope_generators_[channel + 0].set_use_sustain_level((instrument[1] & 0x20) || channels_[channel].use_sustain); | ||||
| 	envelope_generators_[channel + 9].set_use_sustain_level((instrument[0] & 0x20) || channels_[channel].use_sustain); | ||||
| } | ||||
|  | ||||
| // MARK: - Output generation. | ||||
|  | ||||
| void OPLL::set_sample_volume_range(std::int16_t range) { | ||||
| 	total_volume_ = range; | ||||
| } | ||||
|  | ||||
| void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) { | ||||
| 	// Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency; | ||||
| 	// unlike the OPL2 the OPLL time-divides the output for 'mixing'. | ||||
|  | ||||
| 	const int update_period = 72 / audio_divider_; | ||||
| 	const int channel_output_period = 4 / audio_divider_; | ||||
|  | ||||
| 	// TODO: the conditional below is terrible. Fix. | ||||
| 	while(number_of_samples--) { | ||||
| 		if(!audio_offset_) update_all_channels(); | ||||
|  | ||||
| 		*target = output_levels_[audio_offset_ / channel_output_period]; | ||||
| 		++target; | ||||
| 		audio_offset_ = (audio_offset_ + 1) % update_period; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OPLL::update_all_channels() { | ||||
| 	oscillator_.update(); | ||||
|  | ||||
| 	// Update all phase generators. That's guaranteed. | ||||
| 	for(int c = 0; c < 18; ++c) { | ||||
| 		phase_generators_[c].update(oscillator_); | ||||
| 	} | ||||
|  | ||||
| 	// Update the ADSR envelopes that are guaranteed to be melodic. | ||||
| 	for(int c = 0; c < 6; ++c) { | ||||
| 		envelope_generators_[c + 0].update(oscillator_); | ||||
| 		envelope_generators_[c + 9].update(oscillator_); | ||||
| 	} | ||||
|  | ||||
| #define VOLUME(x)	int16_t(((x) * total_volume_) >> 12) | ||||
|  | ||||
| 	if(rhythm_mode_enabled_) { | ||||
| 		// Advance the rhythm envelope generators. | ||||
| 		for(int c = 0; c < 6; ++c) { | ||||
| 			rhythm_envelope_generators_[c].update(oscillator_); | ||||
| 		} | ||||
|  | ||||
| 		// Fill in the melodic channels. | ||||
| 		output_levels_[3] = VOLUME(melodic_output(0)); | ||||
| 		output_levels_[4] = VOLUME(melodic_output(1)); | ||||
| 		output_levels_[5] = VOLUME(melodic_output(2)); | ||||
|  | ||||
| 		output_levels_[9] = VOLUME(melodic_output(3)); | ||||
| 		output_levels_[10] = VOLUME(melodic_output(4)); | ||||
| 		output_levels_[11] = VOLUME(melodic_output(5)); | ||||
|  | ||||
| 		// Bass drum, which is a regular FM effect. | ||||
| 		output_levels_[2] = output_levels_[15] = VOLUME(bass_drum()); | ||||
| 		oscillator_.update_lfsr(); | ||||
|  | ||||
| 		// Tom tom, which is a single operator. | ||||
| 		output_levels_[1] = output_levels_[14] = VOLUME(tom_tom()); | ||||
| 		oscillator_.update_lfsr(); | ||||
|  | ||||
| 		// Snare. | ||||
| 		output_levels_[6] = output_levels_[16] = VOLUME(snare_drum()); | ||||
| 		oscillator_.update_lfsr(); | ||||
|  | ||||
| 		// Cymbal. | ||||
| 		output_levels_[7] = output_levels_[17] = VOLUME(cymbal()); | ||||
| 		oscillator_.update_lfsr(); | ||||
|  | ||||
| 		// High-hat. | ||||
| 		output_levels_[0] = output_levels_[13] = VOLUME(high_hat()); | ||||
| 		oscillator_.update_lfsr(); | ||||
|  | ||||
| 		// Unutilised slots. | ||||
| 		output_levels_[8] = output_levels_[12] = 0; | ||||
| 		oscillator_.update_lfsr(); | ||||
| 	} else { | ||||
| 		for(int c = 6; c < 9; ++c) { | ||||
| 			envelope_generators_[c + 0].update(oscillator_); | ||||
| 			envelope_generators_[c + 9].update(oscillator_); | ||||
| 		} | ||||
|  | ||||
| 		// All melodic. Fairly easy. | ||||
| 		output_levels_[0] = output_levels_[1] = output_levels_[2] = | ||||
| 		output_levels_[6] = output_levels_[7] = output_levels_[8] = | ||||
| 		output_levels_[12] = output_levels_[13] = output_levels_[14] = 0; | ||||
|  | ||||
| 		output_levels_[3] = VOLUME(melodic_output(0)); | ||||
| 		output_levels_[4] = VOLUME(melodic_output(1)); | ||||
| 		output_levels_[5] = VOLUME(melodic_output(2)); | ||||
|  | ||||
| 		output_levels_[9] = VOLUME(melodic_output(3)); | ||||
| 		output_levels_[10] = VOLUME(melodic_output(4)); | ||||
| 		output_levels_[11] = VOLUME(melodic_output(5)); | ||||
|  | ||||
| 		output_levels_[15] = VOLUME(melodic_output(6)); | ||||
| 		output_levels_[16] = VOLUME(melodic_output(7)); | ||||
| 		output_levels_[17] = VOLUME(melodic_output(8)); | ||||
| 	} | ||||
|  | ||||
| #undef VOLUME | ||||
|  | ||||
| 	// TODO: batch updates of the LFSR. | ||||
| } | ||||
|  | ||||
| // TODO: verify attenuation scales pervasively below. | ||||
|  | ||||
| #define ATTENUATION(x)	((x) << 7) | ||||
|  | ||||
| int OPLL::melodic_output(int channel) { | ||||
| 	// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value. | ||||
| 	auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output); | ||||
| 	carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation(); | ||||
|  | ||||
| 	// Get the modulator's new value. | ||||
| 	auto modulation = WaveformGenerator<period_precision>::wave(channels_[channel].modulator_waveform, phase_generators_[channel + 9].phase()); | ||||
| 	modulation += envelope_generators_[channel + 9].attenuation() + (channels_[channel].modulator_attenuation << 5) + key_level_scalers_[channel + 9].attenuation(); | ||||
|  | ||||
| 	// Apply feedback, if any. | ||||
| 	phase_generators_[channel + 9].apply_feedback(channels_[channel].modulator_output, modulation, channels_[channel].modulator_feedback); | ||||
| 	channels_[channel].modulator_output = modulation; | ||||
|  | ||||
| 	return carrier.level(); | ||||
| } | ||||
|  | ||||
| int OPLL::bass_drum() { | ||||
| 	// Use modulator 6 and carrier 6, attenuated as per the bass-specific envelope generators and the attenuation level for channel 6. | ||||
| 	auto modulation = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6 + 9].phase()); | ||||
| 	modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation(); | ||||
|  | ||||
| 	auto carrier = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6].scaled_phase(), modulation); | ||||
| 	carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + ATTENUATION(channels_[6].attenuation); | ||||
| 	return carrier.level(); | ||||
| } | ||||
|  | ||||
| int OPLL::tom_tom() { | ||||
| 	// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation. | ||||
| 	auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase()); | ||||
| 	tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation(); | ||||
| 	tom_tom += ATTENUATION(channels_[8].instrument); | ||||
| 	return tom_tom.level(); | ||||
| } | ||||
|  | ||||
| int OPLL::snare_drum() { | ||||
| 	// Use modulator 7 and the carrier attenuation level for channel 7. | ||||
| 	LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase()); | ||||
| 	snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation(); | ||||
| 	snare += ATTENUATION(channels_[7].attenuation); | ||||
| 	return snare.level(); | ||||
| } | ||||
|  | ||||
| int OPLL::cymbal() { | ||||
| 	// Use modulator 7, carrier 8 and the attenuation level for channel 8. | ||||
| 	LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase()); | ||||
| 	cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation(); | ||||
| 	cymbal += ATTENUATION(channels_[8].attenuation); | ||||
| 	return cymbal.level(); | ||||
| } | ||||
|  | ||||
| int OPLL::high_hat() { | ||||
| 	// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation. | ||||
| 	LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase()); | ||||
| 	high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation(); | ||||
| 	high_hat += ATTENUATION(channels_[7].instrument); | ||||
| 	return high_hat.level(); | ||||
| } | ||||
|  | ||||
| #undef ATTENUATION | ||||
							
								
								
									
										131
									
								
								Components/OPx/OPLL.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								Components/OPx/OPLL.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| // | ||||
| //  OPLL.hpp | ||||
| //  Clock Signal | ||||
| // | ||||
| //  Created by Thomas Harte on 03/05/2020. | ||||
| //  Copyright © 2020 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #ifndef OPLL_hpp | ||||
| #define OPLL_hpp | ||||
|  | ||||
| #include "Implementation/OPLBase.hpp" | ||||
| #include "Implementation/EnvelopeGenerator.hpp" | ||||
| #include "Implementation/KeyLevelScaler.hpp" | ||||
| #include "Implementation/PhaseGenerator.hpp" | ||||
| #include "Implementation/LowFrequencyOscillator.hpp" | ||||
| #include "Implementation/WaveformGenerator.hpp" | ||||
|  | ||||
| #include <atomic> | ||||
|  | ||||
| namespace Yamaha { | ||||
| namespace OPL { | ||||
|  | ||||
| class OPLL: public OPLBase<OPLL> { | ||||
| 	public: | ||||
| 		/// Creates a new OPLL or VRC7. | ||||
| 		OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false); | ||||
|  | ||||
| 		/// As per ::SampleSource; provides audio output. | ||||
| 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||
| 		void set_sample_volume_range(std::int16_t range); | ||||
|  | ||||
| 		// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in | ||||
| 		// rhythm mode, but it's correct for melodic output. | ||||
| 		double get_average_output_peak() const { return 0.5; } | ||||
|  | ||||
| 		/// Reads from the OPL. | ||||
| 		uint8_t read(uint16_t address); | ||||
|  | ||||
| 	private: | ||||
| 		friend OPLBase<OPLL>; | ||||
| 		void write_register(uint8_t address, uint8_t value); | ||||
|  | ||||
| 		int audio_divider_ = 0; | ||||
| 		int audio_offset_ = 0; | ||||
| 		std::atomic<int> total_volume_; | ||||
|  | ||||
| 		int16_t output_levels_[18]; | ||||
| 		void update_all_channels(); | ||||
|  | ||||
| 		int melodic_output(int channel); | ||||
| 		int bass_drum(); | ||||
| 		int tom_tom(); | ||||
| 		int snare_drum(); | ||||
| 		int cymbal(); | ||||
| 		int high_hat(); | ||||
|  | ||||
| 		static constexpr int period_precision = 9; | ||||
| 		static constexpr int envelope_precision = 7; | ||||
|  | ||||
| 		// Standard melodic phase and envelope generators; | ||||
| 		// | ||||
| 		// These are assigned as: | ||||
| 		// | ||||
| 		//		[x], 0 <= x < 9		= carrier for channel x; | ||||
| 		//		[x+9]				= modulator for channel x. | ||||
| 		// | ||||
| 		PhaseGenerator<period_precision> phase_generators_[18]; | ||||
| 		EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18]; | ||||
| 		KeyLevelScaler<period_precision> key_level_scalers_[18]; | ||||
|  | ||||
| 		// Dedicated rhythm envelope generators and attenuations. | ||||
| 		EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6]; | ||||
| 		enum RhythmIndices { | ||||
| 			HighHat = 0, | ||||
| 			Cymbal = 1, | ||||
| 			TomTom = 2, | ||||
| 			Snare = 3, | ||||
| 			BassCarrier = 4, | ||||
| 			BassModulator = 5 | ||||
| 		}; | ||||
|  | ||||
| 		// Channel specifications. | ||||
| 		struct Channel { | ||||
| 			int octave = 0; | ||||
| 			int period = 0; | ||||
| 			int instrument = 0; | ||||
|  | ||||
| 			int attenuation = 0; | ||||
| 			int modulator_attenuation = 0; | ||||
|  | ||||
| 			Waveform carrier_waveform = Waveform::Sine; | ||||
| 			Waveform modulator_waveform = Waveform::Sine; | ||||
|  | ||||
| 			int carrier_key_rate_scale_multiplier = 0; | ||||
| 			int modulator_key_rate_scale_multiplier = 0; | ||||
|  | ||||
| 			LogSign modulator_output; | ||||
| 			int modulator_feedback = 0; | ||||
|  | ||||
| 			bool use_sustain = false; | ||||
| 		} channels_[9]; | ||||
|  | ||||
| 		// The low-frequency oscillator. | ||||
| 		LowFrequencyOscillator oscillator_; | ||||
| 		bool rhythm_mode_enabled_ = false; | ||||
| 		bool is_vrc7_ = false; | ||||
|  | ||||
| 		// Contains the current configuration of the custom instrument. | ||||
| 		uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||||
|  | ||||
| 		// Helpers to push per-channel information. | ||||
|  | ||||
| 		/// Pushes the current octave and period to channel @c channel. | ||||
| 		void set_channel_period(int channel); | ||||
|  | ||||
| 		/// Installs the appropriate instrument on channel @c channel. | ||||
| 		void install_instrument(int channel); | ||||
|  | ||||
| 		/// Sets whether the sustain level is used for channel @c channel based on its current instrument | ||||
| 		/// and the user's selection. | ||||
| 		void set_use_sustain(int channel); | ||||
|  | ||||
| 		/// @returns The 8-byte definition of instrument @c instrument. | ||||
| 		const uint8_t *instrument_definition(int instrument, int channel); | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* OPLL_hpp */ | ||||
| @@ -39,9 +39,9 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue & | ||||
| void SN76489::set_sample_volume_range(std::int16_t range) { | ||||
| 	// Build a volume table. | ||||
| 	double multiplier = pow(10.0, -0.1); | ||||
| 	double volume = static_cast<float>(range) / 4.0f;	// As there are four channels. | ||||
| 	double volume = float(range) / 4.0f;	// As there are four channels. | ||||
| 	for(int c = 0; c < 16; ++c) { | ||||
| 		volumes_[c] = (int)round(volume); | ||||
| 		volumes_[c] = int(round(volume)); | ||||
| 		volume *= multiplier; | ||||
| 	} | ||||
| 	volumes_[15] = 0; | ||||
| @@ -65,7 +65,7 @@ void SN76489::write(uint8_t value) { | ||||
| 				if(value & 0x80) { | ||||
| 					channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf); | ||||
| 				} else { | ||||
| 					channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4)); | ||||
| 					channels_[channel].divider = uint16_t((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4)); | ||||
| 				} | ||||
| 			} else { | ||||
| 				// writes to the noise register always reset the shifter | ||||
| @@ -77,7 +77,7 @@ void SN76489::write(uint8_t value) { | ||||
| 					noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15; | ||||
| 				} | ||||
|  | ||||
| 				channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3)); | ||||
| 				channels_[3].divider = uint16_t(0x10 << (value & 3)); | ||||
| 				// Special case: if these bits are both set, the noise channel should track channel 2, | ||||
| 				// which is marked with a divider of 0xffff. | ||||
| 				if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff; | ||||
| @@ -86,12 +86,12 @@ void SN76489::write(uint8_t value) { | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| bool SN76489::is_zero_level() { | ||||
| bool SN76489::is_zero_level() const { | ||||
| 	return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf; | ||||
| } | ||||
|  | ||||
| void SN76489::evaluate_output_volume() { | ||||
| 	output_volume_ = static_cast<int16_t>( | ||||
| 	output_volume_ = int16_t( | ||||
| 		channels_[0].level * volumes_[channels_[0].volume] + | ||||
| 		channels_[1].level * volumes_[channels_[1].volume] + | ||||
| 		channels_[2].level * volumes_[channels_[2].volume] + | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user