1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

591 Commits

Author SHA1 Message Date
Thomas Harte
15394358df Merge pull request #126 from TomHarte/GCRAnalysis
Corrects infinite loop when performing GCR analysis
2017-05-16 20:54:24 -04:00
Thomas Harte
df4d4467b3 Ensured GCR parser spins the disk. 2017-05-16 20:53:06 -04:00
Thomas Harte
67ec0b9e6c Merge pull request #125 from TomHarte/SampledComposite
Formalises reasoning for the colour phase clamp and offset...
2017-05-16 20:47:20 -04:00
Thomas Harte
2ee8a7056e Corrected TIA no longer to assume phase is an automatic quarter askew. 2017-05-16 20:43:28 -04:00
Thomas Harte
a5075d9eb5 Formalised the reasoning behind the colour phase fix-up and made it an opt-in per-caller value. Only the Oric currently needs to opt in. 2017-05-16 20:31:39 -04:00
Thomas Harte
abeaedf16f Merge pull request #123 from TomHarte/RemovedDeadOption
Formally withdraws the 'load automatically' option for the Vic
2017-05-14 17:03:33 -04:00
Thomas Harte
8e35e913bb Formally withdrew the 'load automatically' option for the Vic, having removed that option elsewhere. 2017-05-14 16:59:24 -04:00
Thomas Harte
81c5f4ab19 Merge pull request #122 from TomHarte/16bit6560
Increases VIC-I intermediate format to 16 bit and corrects output colours
2017-05-13 22:03:08 -04:00
Thomas Harte
e270b726b3 Tweaked blue, increased saturation. 2017-05-13 22:01:02 -04:00
Thomas Harte
c2b5a9bb1f Minor fix: given that phase is now a function of position, stop nudging position. 2017-05-13 21:50:48 -04:00
Thomas Harte
44ce7fa54c Corrected luminances across the board, and PAL colours. 2017-05-13 21:50:09 -04:00
Thomas Harte
b0142cf050 Made an updated stab at NTSC colours. 2017-05-13 14:29:36 -04:00
Thomas Harte
a340331229 Introduced 1-bit of saturation, returning black and white as black and white. 2017-05-11 21:31:58 -04:00
Thomas Harte
b14c892740 Switched to a safer RAII approach to this lock. 2017-05-10 21:29:39 -04:00
Thomas Harte
15d17c12d5 Switched the 6560 to two bytes per pixel, since one isn't sufficient for precision and because mixing up the implementation might help me to figure out what's amiss. 2017-05-09 21:22:01 -04:00
Thomas Harte
99800d9840 Merge pull request #121 from TomHarte/VicColours
Permits ROM-area located PRGs that are not a power-of-two in size
2017-05-08 22:18:47 -04:00
Thomas Harte
5d91a2600d Permitted ROM-style PRGs that are not a power-of-two in size, and added extra safety checks on loading data from a tape. 2017-05-08 22:15:35 -04:00
Thomas Harte
cb66c7e2dc Performed some minor tidying. 2017-05-08 21:05:35 -04:00
Thomas Harte
58488c93be Merge pull request #120 from TomHarte/Vic20Colours
Corrects — and improves — Vic-20 tape loading
2017-05-08 20:59:29 -04:00
Thomas Harte
61f8f2f18c Switched to a more straightforward way of exiting from tape data loading. 2017-05-08 20:58:55 -04:00
Thomas Harte
7b43ae0a92 Implemented a catch for loading the data portion of files. 2017-05-07 22:22:59 -04:00
Thomas Harte
2807e3134f Implemented speedy header finding. So that's half of it. 2017-05-07 20:32:48 -04:00
Thomas Harte
0771363f3b Removed one piece of unnecessary logging. 2017-05-06 22:22:03 -04:00
Thomas Harte
1f56e85f6d Centralised resetting of tape files within the static analyser, having implemented it patchily. 2017-05-06 22:19:08 -04:00
Thomas Harte
2edf73908c Temporarily disabled the existing fast loading implementation in pursuit of another, and started trying to correct the lack of connection between the userport VIA and the tape drive. 2017-05-06 22:00:12 -04:00
Thomas Harte
6a37a02eee Switched to std::ostringstream to avoid the need to make a string length count (and I was one off when loading a disk). 2017-05-06 19:55:42 -04:00
Thomas Harte
5998123868 Added some consts, for a minor safety improvement. 2017-05-06 19:53:24 -04:00
Thomas Harte
26cb903b08 Merge pull request #119 from TomHarte/C11
Ejects the deprecated OSAtomicTestAnd[Re]Set test-and-[re]set in favour of C11's atomic_flag.
2017-04-15 21:45:48 -04:00
Thomas Harte
92a8b68859 Dumped Mach-specific test-and-set in favour of ordinary C11. 2017-04-15 21:41:59 -04:00
Thomas Harte
4127350abe Merge pull request #118 from TomHarte/HighResolutionAtariAudio
Introduces a higher source sampling rate for Atari audio
2017-04-15 21:21:58 -04:00
Thomas Harte
ed6b135015 Made final switch to permit high-sampling rate Atari audio. 2017-04-15 21:18:00 -04:00
Thomas Harte
f95015c7f6 Pulled out the divisor for audio. 2017-04-03 21:16:39 -04:00
Thomas Harte
defec2c9b0 Fixed: operation reads now fulfil the promise of seeding the value to be read with 0xff. 2017-03-26 20:56:27 -04:00
Thomas Harte
04921e64de Merge pull request #117 from TomHarte/AudioRace
Works around what appears to be a bug in Apple's AudioQueue.
2017-03-26 20:29:41 -04:00
Thomas Harte
bdd432fe1d Added an ugly workaround for the empirical sound shutdown issues. 2017-03-26 20:28:04 -04:00
Thomas Harte
6e9ab9f330 Merge pull request #116 from TomHarte/YarsBug
Fixes a bug in which TIA access during the border would flush all motion steps.
2017-03-26 18:38:51 -04:00
Thomas Harte
814c0ada13 Fixed action counts for border motion. 2017-03-26 18:33:05 -04:00
Thomas Harte
dfc468f220 Locked down all initial state. 2017-03-26 15:47:04 -04:00
Thomas Harte
6817b38322 Merge pull request #115 from TomHarte/Style
Settles my C++ curly bracket style indecision
2017-03-26 14:37:23 -04:00
Thomas Harte
e01f3f06c8 Completed curly bracket movement. 2017-03-26 14:34:47 -04:00
Thomas Harte
3229502fa1 Standardised curly bracket placement across the Atari. 2017-03-23 21:59:16 -04:00
Thomas Harte
a4c5eebd1e The latest Atari Age-discovered numbers suggest this starts up in 1024T mode. 2017-03-21 18:22:50 -04:00
Thomas Harte
a3c22d5abb Merge pull request #114 from TomHarte/Pitfall2
Adds support for the Pitfall 2 DPC. And therefore for Pitfall 2.
2017-03-20 20:44:43 -04:00
Thomas Harte
a26b87f348 Fixed: mistake was failure to count ready cycles. 2017-03-20 20:44:03 -04:00
Thomas Harte
4c3cc42c91 This gives a very noisy version of the real audio. 2017-03-20 20:38:29 -04:00
Thomas Harte
f3f4e1a541 Made a first, hacky, attempt at audio. 2017-03-20 19:35:51 -04:00
Thomas Harte
4722f6b5c4 Fixed sprite disappearance: test should be applied predecrement, not post — it relates to the address being used this access, not the next one. 2017-03-19 18:58:35 -04:00
Thomas Harte
7d8d1c7828 Fixed 'random' number generator. 2017-03-19 18:54:35 -04:00
Thomas Harte
4bb70e7d31 Resetting the mask upon low byte write appears to resolve some issues. 2017-03-19 18:49:37 -04:00
Thomas Harte
321030bb44 Added a slightly faulty but seemingly 'close' version of masking. 2017-03-19 18:28:06 -04:00
Thomas Harte
6c161b1150 This gives something that might be the correct background. 2017-03-19 17:49:48 -04:00
Thomas Harte
d5c37c8619 Shushed a little, so as to be able to see a reasonable amount of output during my lifetime. 2017-03-19 17:38:54 -04:00
Thomas Harte
c445eaec3e Switched startup values, following a comment on AtariAge. May or may not be correct, the thread was speculative. 2017-03-19 17:38:26 -04:00
Thomas Harte
7c66c36d3f Attempted at least to manage appropriate data storage. 2017-03-19 17:31:08 -04:00
Thomas Harte
031a68000a Added a class to contain the Pitfall 2 pager and a skeleton of initial work. 2017-03-18 22:08:47 -04:00
Thomas Harte
7d7b665be8 Merge pull request #113 from TomHarte/PagingTemplates
Pulls the Atari 2600 paging schemes out into individual classes
2017-03-18 21:05:01 -04:00
Thomas Harte
c3d82f88a5 Tidied up and commented on the Activision stack implementation. 2017-03-18 21:01:58 -04:00
Thomas Harte
c033bad0b9 Here's MNetwork! 2017-03-18 20:51:49 -04:00
Thomas Harte
c31d85f820 Re-emplaced the MegaBoy. Also cut detritus from the main Atari header. 2017-03-18 19:02:34 -04:00
Thomas Harte
217fbf257e CBS RAM Plus returns. 2017-03-18 18:56:20 -04:00
Thomas Harte
0b611a14b9 Tigervision paging returns. 2017-03-18 18:50:13 -04:00
Thomas Harte
df6861c9dc Parker Bros paging is back. 2017-03-18 18:21:01 -04:00
Thomas Harte
a4cd12394e Reinstated the Activision stack pager. 2017-03-18 18:03:48 -04:00
Thomas Harte
e0bca1e37b Reinstated the 16 and 32 kb Atari pagers, and ensured the 6532 always starts in a valid state. 2017-03-18 17:34:34 -04:00
Thomas Harte
55ce851bb2 Fixed types of the 8k cartridges, ensured the 6502 starts without an IRQ request history. 2017-03-18 17:04:01 -04:00
Thomas Harte
e8d34f2eb4 Having farmed out the bus, the Atari itself no longer is/owns a 6502. 2017-03-18 16:34:41 -04:00
Thomas Harte
bb3daaa99b Sought to reintroduce the Atari 8k paging scheme, at the same time deciding to do away with the copy and paste of holding on to ROM data. 2017-03-18 15:04:01 -04:00
Thomas Harte
36b58d03b7 Formalised read bus value guarantee from the 6502, fixed missing clock signal wiring on the Atari cartridge class, reintroduced CommaVid support. 2017-03-18 14:46:46 -04:00
Thomas Harte
7958459db9 In theory unpaged cartridges should now work. Time to debug. 2017-03-18 14:01:04 -04:00
Thomas Harte
14a76af0d3 Started trying to float out bus control to cartridges. 2017-03-17 20:28:07 -04:00
Thomas Harte
a04a58e01f Merge pull request #112 from TomHarte/LineTiming
Fixes declared Atari line length.
2017-03-14 21:34:19 -04:00
Thomas Harte
afbd9fd41b Fixed declared line length. 2017-03-14 21:33:38 -04:00
Thomas Harte
3d53d4e55e Merge pull request #111 from TomHarte/ActivisionStack
Implements the Activision stack paging scheme.
2017-03-14 20:41:04 -04:00
Thomas Harte
7302703039 Implemented the Activision stack paging scheme. 2017-03-14 20:24:05 -04:00
Thomas Harte
97a8a96593 Rejigged how the memory map is handled and implemented MNetwork support. 2017-03-14 20:07:54 -04:00
Thomas Harte
be2e99077e Merge pull request #110 from TomHarte/MegaBoy
Adds emulation of the MegaBoy paging scheme.
2017-03-14 17:40:41 -04:00
Thomas Harte
3b29276228 Implemented the MegaBoy paging scheme. 2017-03-14 17:40:01 -04:00
Thomas Harte
4a528b9ecb Merge pull request #109 from TomHarte/CBSRamPlus
Adds emulation of the CBS RAM Plus paging scheme.
2017-03-14 17:26:09 -04:00
Thomas Harte
b3632a4e86 Institutes CBS RAM Plus emulation. 2017-03-14 17:25:10 -04:00
Thomas Harte
8a659e3117 Merge pull request #108 from TomHarte/PitfallIIDetection
Adds tested detection of the Pitfall II and MegaBoy paging schemes.
2017-03-13 20:44:28 -04:00
Thomas Harte
a6897ebde0 Added an attempt to distinguish the MegaBoy (now with proper capitalisation) and a test for it. 2017-03-13 20:43:12 -04:00
Thomas Harte
582da14a14 Added an enumerated type and detection of Pitfall 2. 2017-03-13 08:15:36 -04:00
Thomas Harte
b81bf6b547 Merge pull request #107 from TomHarte/PagingTests
Introduces unit tests for the Atari static analyser
2017-03-12 22:17:17 -04:00
Thomas Harte
8e147444d5 Added a readme, as is traditional for folders I'm excluding from Git. 2017-03-12 22:16:12 -04:00
Thomas Harte
a9964ee0c8 Simplified CommaVid test. 2017-03-12 22:14:39 -04:00
Thomas Harte
b671df9906 'Perfected' the Activision stack paging detector. 2017-03-12 22:11:58 -04:00
Thomas Harte
0bcf9c30de Added an attempt, at least, at spotting the Activision titles. Which reduces back to only the one breakage, but this is getting a bit too much like spotting the hard-coded bytes. 2017-03-12 21:14:12 -04:00
Thomas Harte
2c07cce282 Had the wrong paging scheme listed for Robot Tank and Thwocker. Better to get this right before trying to come up with a test for the Activision stack scheme. 2017-03-12 21:03:10 -04:00
Thomas Harte
8c5e39c0c5 Adjusted Super Chip test to look for two portions of 128 bytes. It now spots Dig Dug and doesn't acquire any further false positives amongst the tested set. So failure to spot the Activision stack-based paging mechanism is now the only remaining failure. 2017-03-12 20:39:45 -04:00
Thomas Harte
cae48aaa95 Added an MNetwork test. Reduces failures to two of the set that I have: Dig Dug, which uses a Super Chip but has inconsistent bytes in its image, and Decathlon, which uses the Activision stack paging mechanism for which I don't yet have detection code. 2017-03-12 18:54:49 -04:00
Thomas Harte
37f4f6ba14 Cut down to one disassembly, now I know a bit more about how the non-Atari schemes work. 2017-03-12 17:50:24 -04:00
Thomas Harte
597bd97b01 Corrected two more table errors. 2017-03-12 15:46:25 -04:00
Thomas Harte
38de5300e5 Elevator Action seemingly uses a Super Chip. 2017-03-12 15:43:42 -04:00
Thomas Harte
62b3c9dda8 Corrected 8k Tigervision test, putting detection failures below 10 (i.e. at 9) for the first time. 2017-03-12 15:41:48 -04:00
Thomas Harte
146f3ea0f5 Fixed: Crystal Castles is 16kb. 2017-03-12 15:39:07 -04:00
Thomas Harte
af9b7fbc30 Switched to a voting method for classifying 8kb ROMs. 2017-03-12 15:36:01 -04:00
Thomas Harte
78213f1e95 Fixed a couple more table entries, introduced per-size tests (plus a catch-all), to speed up the development/testing cycle. 2017-03-12 15:35:36 -04:00
Thomas Harte
de347ad7c8 Improved CBS RAM Plus and Super Chip detection exclusion, reducing error count to 15. 2017-03-12 14:03:17 -04:00
Thomas Harte
a4bba8a92e Made a couple of lookup table fixes and corrected RAM region detection windows; failures now down to 19. 2017-03-11 23:18:30 -05:00
Thomas Harte
fcacfc2726 Tidied up spacing, slightly. 2017-03-11 23:01:42 -05:00
Thomas Harte
bab464e765 I'm far from confident, but this should reduce the deviations close to those that result from mistakes by the static analyser, rather than table errors. 2017-03-11 22:58:11 -05:00
Thomas Harte
2879763c34 Reduced to 84 failures through more accurate tabulation. 2017-03-11 21:52:52 -05:00
Thomas Harte
ea2ea30193 Fleshed entire table out with most common values. Exceptions now to fix. 2017-03-11 21:11:25 -05:00
Thomas Harte
608569cc48 Typed out all the 'A's that I am aware of. So about 5% done. 2017-03-11 20:58:38 -05:00
Thomas Harte
c7e973aab4 Extended test set a little, corrected current failures. 2017-03-11 20:51:25 -05:00
Thomas Harte
443d57bc32 Slimmed output and added first six tests. Acid Drop fails since I'm not yet declaring Atari 16k and Atari 32k. 2017-03-11 20:43:19 -05:00
Thomas Harte
57ec756f5b Started speccing out a unit test for Atari ROM analysis. 2017-03-11 20:33:58 -05:00
Thomas Harte
9286a5ba73 Added a new folder to the exclusion list, to contain commercial Atari images, to unit test my paging hardware selection logic. 2017-03-11 19:42:23 -05:00
Thomas Harte
1c9dffe41f Added an earlier exit. 2017-03-11 19:38:05 -05:00
Thomas Harte
8c7f724ce4 Merge pull request #106 from TomHarte/Tigervision
Adds detection and emulation of the Tigervision paging scheme
2017-03-11 18:18:29 -05:00
Thomas Harte
b193248056 Ensured that queue is not touched at all outside of the critical section. 2017-03-11 18:17:09 -05:00
Thomas Harte
f0d944847b Fixed setting of the second 1kb. 2017-03-11 18:16:29 -05:00
Thomas Harte
a72d70e707 Enabled code coverage calculation for unit tests. 2017-03-11 17:44:56 -05:00
Thomas Harte
add14fb43a Made an attempt to implement Tigervision paging. 2017-03-11 17:44:35 -05:00
Thomas Harte
38ce4dc56c Fixed potential deadlock, if a delegate decided to dealloc the queue as a result of its prompting. 2017-03-11 17:44:02 -05:00
Thomas Harte
bce5abd33b Made an attempt to spot Tigervision paging requests. 2017-03-11 13:12:23 -05:00
Thomas Harte
3f36eeb071 Merge pull request #105 from TomHarte/ParkerBros
Introduces detection and emulation of the Parker Bros paging scheme
2017-03-11 13:05:17 -05:00
Thomas Harte
33bda2d40c Switched to image inspection for RAM guesses rather than disassembly. Which fixes the other Parker Bros titles. 2017-03-11 13:04:23 -05:00
Thomas Harte
2b5e3a600e Made a first attempt at implementing the Parker Bros pager within the emulation. 2017-03-11 12:43:12 -05:00
Thomas Harte
8dbf9fd302 Started adding an attempt to distinguish between Atari and Parker Bros paging schemes. 2017-03-11 11:41:18 -05:00
Thomas Harte
9c72ce5bd2 Ensured a settling delay is permitted before an NTSC/PAL decision is made. To avoid false switches just due to startup. 2017-03-06 19:37:35 -05:00
Thomas Harte
ec2762509b Merge pull request #104 from TomHarte/SyncDetection
Improves vertical sync recognition
2017-03-06 19:18:52 -05:00
Thomas Harte
e63229a5e5 Pulled vertical sync detection entirely outside the loop, and gave it greater perspective. 2017-03-06 19:15:33 -05:00
Thomas Harte
ad73379d1c Took vertical sync detection logic entirely out of the loop. 2017-03-05 20:17:55 -05:00
Thomas Harte
abd4d2c42a Extended has-RAM test to check all banks. 2017-03-05 11:58:52 -05:00
Thomas Harte
79784a8e57 Followed tip: missiles are locked to the position after four output pixels, not four input pixels. 2017-03-04 22:23:50 -05:00
Thomas Harte
61b8fc1e2f Merge pull request #103 from TomHarte/CRTCounting
Fixes NTSC colour cycle count
2017-03-04 17:32:45 -05:00
Thomas Harte
4751615623 Fixed NTSC colour cycle count, and hence the 2600's reported line lengths and phase offset. 2017-03-04 17:31:39 -05:00
Thomas Harte
cccdc558e7 Merge pull request #102 from TomHarte/6532Accuracy
Improves 6532 counting accuracy
2017-03-04 17:04:43 -05:00
Thomas Harte
d3257c345a Tested against public ROMs and corrected. Also moved the deferred adjustment into a more canonical place. 2017-03-04 17:00:28 -05:00
Thomas Harte
e09b76bf32 Fixed 'same value, then immediate increment, then proper counting increments' behaviour and ensured it takes one cycle to commit a value. Adjusted tests to match. 2017-03-04 15:57:54 -05:00
Thomas Harte
837cccdf83 Switched to deferred updates for the 6532. 2017-03-04 14:58:28 -05:00
Thomas Harte
a3fcd15980 Loosened sync charge level requirement. 2017-03-01 22:16:56 -05:00
Thomas Harte
93d1573481 Added a fix for certain homebrews. 2017-03-01 07:59:25 -05:00
Thomas Harte
893a5dd007 Added an artificial low pass filter, attempting to capture post-digital effects. 2017-02-28 21:29:55 -05:00
Thomas Harte
026b418b4a Ensured filtered 1:1 audio resampling is applied. 2017-02-28 21:27:38 -05:00
Thomas Harte
06dd98b23c Pulled the reset time for horizontal blank extend up to position 224. 2017-02-28 20:28:54 -05:00
Thomas Harte
2a81ae1dec Now required: at least four stores for RAM to be detected. 2017-02-28 20:28:14 -05:00
Thomas Harte
1625b9c7f9 Merge pull request #101 from TomHarte/CommaVid
Adds detection and emulation of the CommaVid paging scheme
2017-02-27 20:53:19 -05:00
Thomas Harte
184c8ae707 Extended to emulate the CommaVid. 2017-02-27 20:50:59 -05:00
Thomas Harte
8f8b103224 Slightly tidier. Also in the interim: confirmed no remaining false positives or negatives from the existing published set. 2017-02-27 08:42:43 -05:00
Thomas Harte
1af415a88e Okay, this is a bit desperate, but worth investigation. 2017-02-27 08:39:53 -05:00
Thomas Harte
fe07cd0248 Made another attempt to distinguish. 2017-02-27 08:06:57 -05:00
Thomas Harte
a3d339092e Corrected callers of the 6502 disassembler. 2017-02-27 07:56:59 -05:00
Thomas Harte
837216ee9a Adjusted semantics to allow for more complicated mappings, e.g. whereby supplied data repeats itself within a range. 2017-02-27 07:49:33 -05:00
Thomas Harte
dcd0c90283 Switched time of best-effort updater delegate setting, to avoid a callback before setupClockRate has happened, and therefore before it's clear what should be going on with audio. 2017-02-26 21:58:59 -05:00
Thomas Harte
b24cd00a39 Switched time of best-effort updater delegate setting, to avoid a callback before setupClockRate has happened, and therefore before it's clear what should be going on with audio. 2017-02-26 21:58:43 -05:00
Thomas Harte
0273860018 Sought to weed out further false positives on CommaVid, partly by introducing a record of internal calls, to pair with the now confusingly named outward_calls. 2017-02-26 21:58:09 -05:00
Thomas Harte
82c089cde4 Factored out paging type detection and, from that, took out 2k cartridge detection. Added an attempt at a CommaVid detector, which does not currently work. 2017-02-26 21:24:54 -05:00
Thomas Harte
997707a45b Merge pull request #100 from TomHarte/SuperChipDetection
Adds detection and emulation of the Super Chip
2017-02-26 18:04:50 -05:00
Thomas Harte
9d7985c1e1 Added Super Chip emulation. 2017-02-26 17:47:29 -05:00
Thomas Harte
8b1ec827e0 Made an initial, very naive attempt to recognise two types of expanded cartridge: those with a superchip and those with a CBS RAM+, in both cases by looking for instructions that appear to write into cartridge space. 2017-02-26 17:11:57 -05:00
Thomas Harte
153525f23d Extended quality of known address read/write/modify mapping, and started recording internal accesses in addition to external. 2017-02-26 17:10:33 -05:00
Thomas Harte
3101dc94a7 Merge pull request #98 from TomHarte/TIAImprovements
Reimplements the Atari TIA, as a discrete component
2017-02-26 15:14:59 -05:00
Thomas Harte
e6a84fd26b Attempted a hardware-correct implementation of missile-to-player latching. This completes the last of the **knowing** inaccuracies. The rest are as-of-yet unwitting. 2017-02-26 15:12:31 -05:00
Thomas Harte
440467ea3e Started communicating which copy is being requested. 2017-02-26 13:39:25 -05:00
Thomas Harte
98376de9ad Started returning 'no effect' for pot ports, rather than doing nothing. Still very much TODO though. 2017-02-25 22:58:58 -05:00
Thomas Harte
e61e355251 Moved to the maximum possibly required queue length of 4. Though the emulated 2600 should never need more than 2 slots as per the current calling pattern, it's not a contractual guarantee. 2017-02-25 17:25:10 -05:00
Thomas Harte
c898c8a99e Ensured the missiles and ball don't attempt to enqueue. Because I don't think they're supposed to. 2017-02-25 17:13:22 -05:00
Thomas Harte
8c9062857c Added a single-slot queue for player objects to defer drawing, thereby deferring pixel lookup. Which I think is correct. Though more slots might be needed. 2017-02-25 17:10:24 -05:00
Thomas Harte
77ed4ddc05 Slightly simplified ready line release logic. 2017-02-23 21:08:32 -05:00
Thomas Harte
82f392fada This should be the other way around. I want whichever is later. 2017-02-22 21:54:49 -05:00
Thomas Harte
2f0c923c29 Switched away from @synchronized as it appears possibly to be the lock used during -dealloc, creating deadlock with the CSAudioQueueDeallocLock. 2017-02-22 21:42:10 -05:00
Thomas Harte
8291a63d5f Fixed loss of audio when switching to PAL. 2017-02-22 21:15:37 -05:00
Thomas Harte
4c947ad553 Attempted to resolve risk of an audio callback being in progress when -dealloc is received. 2017-02-22 21:12:59 -05:00
Thomas Harte
6120dae61a While I'm using the hacky approach to player/missile synchronisation, I need to seed adder. 2017-02-22 07:39:11 -05:00
Thomas Harte
1d03793f22 Fixed potential race condition: ensure the queue is disposed of synchronously because otherwise there'll be a potential dangling reference to self. 2017-02-22 07:35:09 -05:00
Thomas Harte
4f5f191cd6 Fixed: will no longer attempt to output pixels from before the pixel part of a line on which sync was disabled abruptly. 2017-02-22 07:33:36 -05:00
Thomas Harte
21abf4e9fc Enshrined a terminology switch, albeit without any flow change behind it. 2017-02-22 07:29:48 -05:00
Thomas Harte
144d6b70d9 Minor cleaning. 2017-02-22 07:14:30 -05:00
Thomas Harte
b769f22ca0 Switched back to the collision_buffer_ being part of the TIA object, added one more assert. 2017-02-21 22:26:20 -05:00
Thomas Harte
7019d396d0 Threw in some asserts, discovering a bug in missile positioning. 2017-02-21 22:04:27 -05:00
Thomas Harte
f4447fd9cd Attempted to fix failure of sprites properly to wrap when performing motion. 2017-02-21 21:53:09 -05:00
Thomas Harte
36396b3d62 Made a slightly better, albeit still inaccurate, version of missile-player lock.Enough for Combat to do reasonable things. 2017-02-21 20:45:20 -05:00
Thomas Harte
d1dbf8c21f Missile to player lock is supposed to be a toggle; also factored out the commonalities of missile and ball drawing. 2017-02-21 07:58:37 -05:00
Thomas Harte
1bde0fed6f Simplified relationship between Objects and the usage-specific components through inheritance. 2017-02-21 07:37:20 -05:00
Thomas Harte
7ab2358bba Made an attempt to reintroduce missiles. 2017-02-20 22:22:39 -05:00
Thomas Harte
99547181f1 Attempted to template this thing. Without yet a plan in place for pixel lookup timing. 2017-02-20 21:42:59 -05:00
Thomas Harte
2bf784535c Simplified calllng. 2017-02-20 18:04:40 -05:00
Thomas Harte
57f434c199 Reorganised state, with an eye towards unifying object motion and triggers. 2017-02-20 17:58:28 -05:00
Thomas Harte
87afa9140e Took some provision steps towards paging type autodetection and communication. But I think this is a distraction. 2017-02-20 17:44:36 -05:00
Thomas Harte
d19f26887d Performed a very naive shuffling of output builder sets onto the OpenGL queue. Which makes the frequency switcher work properly from it's possibly-contextless thread. 2017-02-20 10:39:31 -05:00
Thomas Harte
6cb95b4fc5 Switched to passing around std::strings rather than char *s, because they should be easier to capture. 2017-02-20 10:35:33 -05:00
Thomas Harte
d979a822ac Introduced a deferred task list for the OpenGL thread. 2017-02-19 21:46:07 -05:00
Thomas Harte
fccdce65b9 Switched to lock guards. 2017-02-19 21:45:28 -05:00
Thomas Harte
99a35266e1 Attempted to bring frequency-switching logic into the cross-platform realm. Which for now creates an issue with the OpenGL context. 2017-02-19 21:20:37 -05:00
Thomas Harte
51bcaea60c Disabled incorrect 'optimisations'. 2017-02-19 12:00:04 -05:00
Thomas Harte
e00339ef0a Attempted to reintroduce the ball. 2017-02-19 08:02:54 -05:00
Thomas Harte
53cd125712 Added stub calls to draw the missiles and ball. 2017-02-19 07:28:24 -05:00
Thomas Harte
04693b067c Fixed failure of the optimised route to pump the pixel clock; removed optimisation entirely for now. 2017-02-18 21:36:48 -05:00
Thomas Harte
cd7876a746 Reintroduced the extra clocking delay. 2017-02-18 20:18:50 -05:00
Thomas Harte
ed5ff49ef5 Fixed vertical delay, retreated from my previous thought about adding the one extra cycle of sprite delay, at least temporarily. 2017-02-16 20:52:01 -05:00
Thomas Harte
8d502a0b03 Decided not to run before I can walk and switched to storing the motion time and next step explicitly per object. 2017-02-16 20:28:37 -05:00
Thomas Harte
5ea232310f Added a check against negative runs. 2017-02-16 18:55:58 -05:00
Thomas Harte
09309aa74f Attempted to prevent extraneous moves. 2017-02-16 18:52:39 -05:00
Thomas Harte
b5357860b9 Made an attempt to split things apart so as to be able to introduce the proper sprite latency. 2017-02-14 20:56:16 -05:00
Thomas Harte
dd17459687 Added my first failing test: delay is incorrect when resetting outside of the play area. 2017-02-12 20:42:49 -05:00
Thomas Harte
cd90118a0f Added two, extraordinarily simple tests. 2017-02-12 20:32:53 -05:00
Thomas Harte
25776de59d I think unit testing this thing is the only way forwards. Started adding appropriate hooks. 2017-02-12 19:55:02 -05:00
Thomas Harte
600bdc9af7 In C++, I think the implicit cast to bool negates the need for any manual collapsing? 2017-02-12 18:18:35 -05:00
Thomas Harte
0c9be2b09e Shunted the collisions buffer onto a separate area of the heap for the time being, as a debugging aid. Also added a few more initial values. 2017-02-12 18:16:50 -05:00
Thomas Harte
df8a5cbe6d Made attempts (i) to respect the delay flag; and (ii) to account for border-region sprite clocking. 2017-02-12 17:35:09 -05:00
Thomas Harte
9ce68c38ae Made an effort to implement proper pixel output for sprites. 2017-02-12 14:01:50 -05:00
Thomas Harte
40954d6a2a Attempted to factor out parts I expect to reuse for missiles and the ball. 2017-02-12 11:31:17 -05:00
Thomas Harte
ac444a3f34 Corrected both position increments and target time calculation. 2017-02-11 21:24:14 -05:00
Thomas Harte
b8abeced6d Made an attempt to introduce the proper eventful loop for player output. With debugging yet to occur. 2017-02-11 21:01:58 -05:00
Thomas Harte
aeff59addc Implemented motion 'correctly', for programs written to do all work outside of the pixel area. 2017-02-11 20:25:49 -05:00
Thomas Harte
7ab6023a0c Ensured no attempt to call strcmp on null if a file name without an extension got into here. 2017-02-11 13:36:36 -05:00
Thomas Harte
97cdfea9e9 Resolved spurious static analyser complaint: input_size and output_size aren't supposed to have defined values if input or output is null. But whatever. 2017-02-11 13:36:09 -05:00
Thomas Harte
aff69dbc34 Resolved spurious static analyser issue; screen mode will always be 0–6 but it doesn't know that. Setting a non-zero divider doesn't feel worth worrying about for a cleaner compile. 2017-02-11 13:35:22 -05:00
Thomas Harte
6381e4e1b0 All that's happened to position is that numbers have been added to it. So it can't be negative, given that it wasn't before. So a regular modulo will do. 2017-02-11 13:34:36 -05:00
Thomas Harte
c8e595d9aa Merge branch 'master' into TIAImprovements 2017-02-11 13:20:52 -05:00
Thomas Harte
8c88fd4261 Merge pull request #99 from TomHarte/StartupRace
Resolves a race condition on machine startup
2017-02-11 13:18:19 -05:00
Thomas Harte
a86a6367b5 Slightly shuffled to avoid a race condition on the best-effort updater. 2017-02-11 13:17:11 -05:00
Thomas Harte
905ed1f87b Switched to the more natural type, which is also signed, making my logic less prone to error. 2017-02-11 13:16:53 -05:00
Thomas Harte
8de6caf6ff Started trying to get into a proper structure here. Chickened out. 2017-02-11 12:59:13 -05:00
Thomas Harte
327c19a222 Slightly shuffled to avoid a race condition on the best-effort updater. 2017-02-11 12:58:47 -05:00
Thomas Harte
40d3f5f7f6 Attempted properly to respect start. 2017-02-11 08:26:09 -05:00
Thomas Harte
64d5712d1d Added an incorrectly-coded version of horizontal move, at least so that I can verify that information is going into the correct slots. 2017-02-10 07:23:43 -05:00
Thomas Harte
3b20d862f0 Made an initial attempt to mark sprite positions. But without hmove implemented, they're all over the place. 2017-02-09 20:53:42 -05:00
Thomas Harte
2e9ef2b0ef Took a shot at reinstating the horizontal blank extend flag. 2017-02-09 18:37:19 -05:00
Thomas Harte
70745286a5 Ensured this array is properly aligned for the uin32_t accesses I intend to make for background drawing. 2017-02-08 20:25:23 -05:00
Thomas Harte
dcb7584060 Added the four-cycle playfield output latency and ensured you can't get smaller-than-usual pixels by rapid register value changing. 2017-02-08 07:30:32 -05:00
Thomas Harte
a477499724 Got a bit more explicit with range returned by get_cycles_until_horizontal_blank and hence attempted a more thorough (/correct) version of WSYNC. 2017-02-07 22:14:45 -05:00
Thomas Harte
944d835eea Switched explicitly to an accumulation model for filling the collision buffer. 2017-02-06 21:59:28 -05:00
Thomas Harte
8f5039130c Changed index naming order to ensure no out-of-bounds accesses. 2017-02-06 21:48:41 -05:00
Thomas Harte
ba165bb70a Made an attempt properly to populate collision registers from the collision buffer. 2017-02-06 21:15:55 -05:00
Thomas Harte
474e2e8d2c Fixed once again to respect mid-line palette changes. 2017-02-06 20:09:12 -05:00
Thomas Harte
8b8eb787df Fixed complete invisibility. 2017-02-06 18:42:58 -05:00
Thomas Harte
66bcdd36f3 Made an attempt to introduce an intermediate buffer that ends up with a bit mask of all graphical components present on it, and to use that to infer collision flags and colours, based on playfield priority and colour palette. Immediately yielding: a blank screen. Good work! 2017-02-06 18:29:00 -05:00
Thomas Harte
fcf8cafb5d Sought to ensure that communicating a colour burst in multiple parts doesn't ruin the phase. 2017-02-06 18:27:44 -05:00
Thomas Harte
6bcf95042c Started trying to be a bit more explicit about usage, and to divide up drawing responsibility. 2017-02-05 17:51:56 -05:00
Thomas Harte
23f3ccd77a Made a further attempt to prevent overwrites. 2017-02-05 17:47:34 -05:00
Thomas Harte
f2437cb257 Added some additional documentation, started making steps towards returning sprites, fixed a counter bug that would exhibit as incorrect sync. 2017-01-31 20:30:32 -05:00
Thomas Harte
abe04334c2 Attempted to retain more player information, and removed the output cursor from class storage as I think it's acceptable as a temporary. 2017-01-30 22:42:27 -05:00
Thomas Harte
8545707b54 Reinstituted the playfield. Probably needs more buffering though. Time to look into delays. 2017-01-30 21:38:58 -05:00
Thomas Harte
2b08758b2b Started capturing playfield/ball and background colours. 2017-01-30 08:08:03 -05:00
Thomas Harte
764b528891 Made a first attempt at switching to a model that respects blank and sync. 2017-01-30 07:19:19 -05:00
Thomas Harte
92754ace7a Some mild fixes get me up to having a rolling screen of vertical lines. Which is what I was hoping for right now! 2017-01-29 22:16:23 -05:00
Thomas Harte
1cc13b2799 Merge branch 'master' into TIAImprovements 2017-01-29 16:13:46 -05:00
Thomas Harte
38f944bc34 This needs to be a memmove as the areas may overlap. 2017-01-29 16:13:33 -05:00
Thomas Harte
427175b9c0 Added an extra flag to avoid potential race condition on is_full_, being reset from the background despite a write area not having been allocated. 2017-01-29 16:13:28 -05:00
Thomas Harte
ebde955356 This needs to be a memmove as the areas may overlap. 2017-01-29 16:12:48 -05:00
Thomas Harte
7fd02e7f4c Added an extra flag to avoid potential race condition on is_full_, being reset from the background despite a write area not having been allocated. 2017-01-29 16:11:29 -05:00
Thomas Harte
d51f185dc7 Made an attempt to reintroduce the basic horizontal loop. 2017-01-29 15:43:57 -05:00
Thomas Harte
2390358c24 Prevented unbounded CPU usage, albeit without yet deciding who has authority for the clock rate. 2017-01-29 14:19:26 -05:00
Thomas Harte
2432a3b4d7 Fixed condition — >= is smarter. 2017-01-29 14:00:01 -05:00
Thomas Harte
9c3597c7e3 Attempted to reintroduce enough logic to handle [most of] line timing, such that WSYNC works. Initial objective is to get back to having a working background. 2017-01-29 13:47:36 -05:00
Thomas Harte
fba6baaa9c Stubbed and disabled to get back to building. 2017-01-28 21:56:01 -05:00
Thomas Harte
a246530953 Supposing the TIA were implemented, this is (more or less) what the Atari 2600 would now look like. 2017-01-28 21:46:40 -05:00
Thomas Harte
0ffded72a6 Created a placeholder class for a factored-out TIA. There's a bit more it'll need to do, like vending (or receiving) a CRT but this is the full hardware stuff, I think. 2017-01-28 16:19:08 -05:00
Thomas Harte
acadfbabec Merge pull request #97 from TomHarte/Icons
Introduces some file association icons
2017-01-27 21:36:13 -05:00
Thomas Harte
9001cc3fc2 Added a cartridge image. 2017-01-27 21:26:11 -05:00
Thomas Harte
015b2b49f9 Introduced an incomplete set of file association icons. 2017-01-26 22:21:55 -05:00
Thomas Harte
92f928ca42 Merge pull request #96 from TomHarte/PhaseAlignedSampling
Optimises existing composite flow
2017-01-25 21:51:11 -05:00
Thomas Harte
6d087ca054 Restored 2600 audio. 2017-01-25 21:29:19 -05:00
Thomas Harte
c2d7e36c8f Ensured logic for whether composite output is in use is consistent. 2017-01-25 21:25:03 -05:00
Thomas Harte
4d6e78e641 Reinstated temporary Oric-related fix. 2017-01-24 22:16:15 -05:00
Thomas Harte
5761c8267b [Re-]Eliminated connection between colour subcarrier frequency and monitor output mode. 2017-01-24 20:48:54 -05:00
Thomas Harte
a66a8c31b2 Merge branch 'master' into PhaseAlignedSampling 2017-01-24 07:29:18 -05:00
Thomas Harte
19e4ee12e1 Merge branch 'PhaseAlignedSampling' of github.com:TomHarte/CLK into PhaseAlignedSampling 2017-01-24 07:29:14 -05:00
Thomas Harte
4871572a33 Optimised images. 2017-01-23 21:28:13 -05:00
Thomas Harte
2e744a95e4 Merge branch 'master' into PhaseAlignedSampling 2017-01-23 21:11:14 -05:00
Thomas Harte
ff87f1390d Merge pull request #95 from TomHarte/ReadmeImages
Added some example composite images
2017-01-23 20:49:16 -05:00
Thomas Harte
76ca30c26d This version works better. 2017-01-23 20:47:48 -05:00
Thomas Harte
7c2685cb34 Made an attempt at reducing displayed image size. 2017-01-23 20:46:35 -05:00
Thomas Harte
8cf25a2d70 Went tabular. 2017-01-23 20:44:42 -05:00
Thomas Harte
8d69dd30f3 Testing a table. 2017-01-23 20:43:43 -05:00
Thomas Harte
ae8068b86f Added Stormlord images. 2017-01-23 20:38:30 -05:00
Thomas Harte
baeb0ee89f Reduced image sizes. 2017-01-23 20:34:15 -05:00
Thomas Harte
c07993bb0a Added more images. 2017-01-23 20:33:00 -05:00
Thomas Harte
7680cbf9c3 Testing this Markdown implementation for image sizing support. 2017-01-23 20:26:57 -05:00
Thomas Harte
4920fe6701 Added a grab of the Repton title screen. 2017-01-23 20:23:49 -05:00
Thomas Harte
55fe0176bd Added a space. Probably need to hold for a better example though. 2017-01-12 22:12:37 -05:00
Thomas Harte
99fcbb55d1 Attempted to improve layout. 2017-01-12 22:11:25 -05:00
Thomas Harte
6f78ecd12b Added a small pictorial example. Hardly the best, but a step in the right direction. 2017-01-12 22:06:45 -05:00
Thomas Harte
ced644b103 It seems likely that an AY divides its clock by 8, not 16. I had conflated wave frequency and counter clock. 2017-01-11 22:03:01 -05:00
Thomas Harte
be1cb2a551 Fixed NTSC phase. 2017-01-11 21:31:24 -05:00
Thomas Harte
b4159295f6 Switched to using quads for intermediate draws. The specific concern is the flexibility offered in the GL spec as to line drawing algorithms. And even if a driver implements exactly to spec then it should omit the final pixel. 2017-01-11 21:18:41 -05:00
Thomas Harte
d0a93409e6 Made an attempt to simplify in-shader phase calculation, now that output position is a direct multiple of phase. 2017-01-11 08:18:00 -05:00
Thomas Harte
4c3669f210 Reduced precision of input phase, but I'm not necessarily persuaded by it as a move. However it's clear that something is off in that whole area. But if phase is locked by output position, do I need to retain this level of complexity? Also ensured that intermediate buffers prior to the final are sampled using the nearest sampling mode, also to reduce precision errors. 2017-01-10 22:08:07 -05:00
Thomas Harte
eeb646868b Switched off filtering, at least temporarily, to try to ensure that sampling is all where it should be. 2017-01-08 19:53:08 -05:00
Thomas Harte
3d789732a2 Switched back to full buffer clearing. Until I can figure out the source of noise. 2017-01-08 19:50:31 -05:00
Thomas Harte
d2a7d39749 Ensured the output lock isn't held while talking to the delegate. 2017-01-08 19:49:21 -05:00
Thomas Harte
9521718120 Colour phase is multiplied by 255, not 256. 2017-01-08 17:21:26 -05:00
Thomas Harte
28909e33ca Eliminated phaseCyclesPerTick as implied. 2017-01-08 16:48:02 -05:00
Thomas Harte
79632b1d34 Instituted de-escalating phase-related extensions, definitively to kill rounding error edges. 2017-01-08 16:24:22 -05:00
Thomas Harte
cf6d03e35c Merge branch 'master' into PhaseAlignedSampling 2017-01-08 14:49:40 -05:00
Thomas Harte
4a4b31a15c Merge pull request #94 from TomHarte/ElectronDisks
Fixes the Electron's ability automatically to launch a disk
2017-01-08 14:48:58 -05:00
Thomas Harte
f3d9aec8fc Fixed Electron's support for automatically booting floppy disks. 2017-01-08 14:47:41 -05:00
Thomas Harte
7ad64ff16b Made further efforts to support throughput via memory barrier. 2017-01-08 14:47:16 -05:00
Thomas Harte
6153ada33b Fixed Electron's support for automatically booting floppy disks. 2017-01-08 14:46:19 -05:00
Thomas Harte
be48c950b4 Started taking steps towards using a texture barrier where possible to reduce all of my framebuffer binds. Some output appears, but it's not correct. 2017-01-08 11:13:20 -05:00
Thomas Harte
0487b8c178 Definitively eliminated the additional y filtering step; if I'm going to work to ensure always four samples per colour cycle, I can put the channel separation coefficients directly into their shaders, cutting down on samples. 2017-01-07 16:02:33 -05:00
Thomas Harte
5740015f56 Temporarily disabled composite processing to show the pure stream. Fixed both automatic calculations of phase — per line and, at input, per pixel. 2017-01-07 12:38:00 -05:00
Thomas Harte
c84004bfa3 Fixed: colour_cycle_numerator_ doesn't need to be multiplied by the time multiplier because it'll get that for free from the calculation of next_run_length. 2017-01-06 21:36:19 -05:00
Thomas Harte
c746a3711f Temporarily disabled my attempt to be clever with bilinear filtering when applying a lowpass filter. Will need to investigate. 2017-01-04 08:06:18 -05:00
Thomas Harte
aa7774a9a6 Experimental: up the chroma accuracy, just let the luma go straight through. Subject to figuring out how I'm still losing so much precision. 2017-01-03 22:41:34 -05:00
Thomas Harte
a836120945 Restored proper colour separation, but somewhere a massive hit in horizontal resolution is happening — much greater than one would expect from the sample size picked. So investigation to come. 2017-01-03 22:32:07 -05:00
Thomas Harte
7d60df9075 Added the option for both intermediate and output shaders to use only a portion of the input/output texture; made an attempt to pick an appropriate proportion in order to align signal sampling with the colour subcarrier. 2017-01-03 22:16:52 -05:00
Thomas Harte
f2b8b26bc4 Started throwing some comments into my shaders. 2017-01-03 21:16:38 -05:00
Thomas Harte
9d60172571 Merge pull request #93 from TomHarte/ReadWriteTrack
Implements the Type 3 WD177x commands
2017-01-01 21:01:56 -05:00
Thomas Harte
eca3995481 Added a CRC check for read address, ensured CRC, lost data and record not found are initially reset. 2017-01-01 21:00:25 -05:00
Thomas Harte
044c920a5b Made it more explicit that there are no unhandled cases. 2017-01-01 20:56:52 -05:00
Thomas Harte
0df9ce5a76 Made an attempt at read address. So superficially that leaves only the force interrupts. 2017-01-01 20:55:09 -05:00
Thomas Harte
f94f34f053 Made an attempt at read track. Which means process_input_bit can't just swallow syncs any more; it now reports them as tokens of type ::Sync. 2017-01-01 20:39:19 -05:00
Thomas Harte
4ad2d2bedd Merge branch 'master' into ReadWriteTrack 2017-01-01 20:04:22 -05:00
Thomas Harte
e28f72d919 Merge pull request #90 from TomHarte/TravisCI
Introduces a shared Xcode scheme plus a first attempt at Travis CI integration
2017-01-01 20:03:26 -05:00
Thomas Harte
c994fa39f6 Ensured spin-up doesn't occur if there's no motor line. 2016-12-31 16:18:30 -05:00
Thomas Harte
1ea4f0d79d Made an attempt to implement 'write track' and ensure that 'write sector' can't end without announcing that it has ended writing. 2016-12-31 16:01:44 -05:00
Thomas Harte
0689df1349 Merge pull request #92 from TomHarte/MFMCleanup
Adds some documentation and tidies some of the new MFM infrastructure
2016-12-31 15:32:11 -05:00
Thomas Harte
b3c33d993a Made an attempt to explain the requirements placed upon Disk subclasses that wish to support writing. 2016-12-31 15:30:48 -05:00
Thomas Harte
8eb21c6702 The "MFM...Byte"s aren't MFM-specific, they're relevant to both FM and MFM encoding. So renamed them. Also slimmed syntax within MFM.cpp mostly where emigration from the Acorn disk analyser had left a residue of lengthy namespace specification. 2016-12-31 15:25:11 -05:00
Thomas Harte
4c62487e6e Merge pull request #91 from TomHarte/TableCRC
Switches to a table-based implementation of CRC generation
2016-12-31 14:16:17 -05:00
Thomas Harte
a147d56ce6 Switched to a table-based implementation of CRC generation, adding construction cost to cheapen running cost. 2016-12-31 14:15:20 -05:00
Thomas Harte
7b696b0962 Switched scheme to shared. 2016-12-31 13:11:07 -05:00
Thomas Harte
57bb771fb7 It looks like spaces are automatically escaped (?) 2016-12-31 13:05:55 -05:00
Thomas Harte
5201a59c44 Attempted to introduce Travis CI. 2016-12-31 13:03:32 -05:00
Thomas Harte
df6e98fa52 Merge pull request #89 from TomHarte/OricDiskWrites
Adds write support for the Oric .DSK file format
2016-12-31 12:53:43 -05:00
Thomas Harte
52b850a3f5 Quick extra: make sure parsed tracks don't overflow the 6400 byte space available in an MFM disk. Which might be better expressed as 6250? 2016-12-31 12:51:52 -05:00
Thomas Harte
cfbab1448c Switched to a track parsing that disallows synchronisation values within sector contents. 2016-12-31 12:23:08 -05:00
Thomas Harte
12549ff412 Might as well get the file offset before entering the critical section; also moved the lock guard down more explicitly to group with the second set of actions. 2016-12-31 11:48:46 -05:00
Thomas Harte
6f0b5427e4 Made an attempt to avoid repetition of sync bytes. 2016-12-31 00:20:00 -05:00
Thomas Harte
0123b37213 Made an attempt to include sync values in the stream and properly to align. 2016-12-31 00:11:31 -05:00
Thomas Harte
ea4d85e1cd The virtual disk constructed is the same across all tracks. So why not just request zero? 2016-12-31 00:10:35 -05:00
Thomas Harte
f217d508b8 Completed first attempt at write support for Oric disk images. 2016-12-30 23:12:46 -05:00
Thomas Harte
1f625fad66 Decided that if this is an [M]FM parsing function then it should be something more intelligent than a mere PLL record. Which I guess conveniently implies Oric DSK-esque behaviour. But properly defined, rather than very vaguely. 2016-12-30 23:10:52 -05:00
Thomas Harte
632b3c63b1 Added the infrastructure necessary for Oric disks to appear writeable to the machine and to receive changed tracks. 2016-12-30 22:51:48 -05:00
Thomas Harte
d581294479 Added get_track to get the PLL output for a complete track. 2016-12-30 19:59:23 -05:00
Thomas Harte
0f399b0a0c Made type conversion explicit. 2016-12-30 19:59:01 -05:00
Thomas Harte
c6fcc40ac5 Merge pull request #88 from TomHarte/WDWrites
Implements write support for the WD.
2016-12-30 18:11:00 -05:00
Thomas Harte
3b29e6a473 Ensured SSD and ADFs are grown if required. 2016-12-30 18:08:12 -05:00
Thomas Harte
07dacff42d Added writing for Acorn ADF disks, plus appropriate TODOs in both similar bits of boilerplate. 2016-12-30 18:03:30 -05:00
Thomas Harte
c85450648f Fix: make sure copies have proper event lengths. Also made it much clearer what's going on with the initial copy to the heap. 2016-12-30 17:55:46 -05:00
Thomas Harte
c740d9655a Fixed: index_count_ may have been left high by a previous call; reset it just in case. 2016-12-30 17:55:06 -05:00
Thomas Harte
d09e7ac1e8 Made an attempt at reacting appropriately if the very first thing that looks like a sector doesn't pan out. 2016-12-30 17:44:35 -05:00
Thomas Harte
5d63556870 Periods need a custom copy constructor too, if they're going to avoid sharing an event_source. 2016-12-30 17:39:52 -05:00
Thomas Harte
e5cc77f22d Added an extra sanity check. 2016-12-30 17:29:51 -05:00
Thomas Harte
81a3cbac45 Ensured a copy is passed for writing back rather than the original. 2016-12-30 17:26:44 -05:00
Thomas Harte
63ff5165a4 After a quick bit of reading, discovered the virtual copy constructor pattern really is only a convention in C++, and conformed to it. Which hopefully gives copyable tracks. 2016-12-30 17:25:39 -05:00
Thomas Harte
71dbd78cf2 If asynchronous background processing is to occur on tracks then, given that they inherently have state, they'll need to be copyable, and ideally 'cheaply' (though it's not too great a priority). So started implementing appropriate copy constructors. Also introduced an extra level of indirection to PCMSegmentEventSource so that it can copy itself without copying the underlying PCMSegment, which is 95% of the heft of a track in all currently-implemented cases. 2016-12-30 14:23:26 -05:00
Thomas Harte
f88f3c65e9 Removed duplicated newline. 2016-12-30 14:21:36 -05:00
Thomas Harte
82bb78fb2d Ensured that get_sector copes even if any invalid sectors are encountered. 2016-12-30 14:21:14 -05:00
Thomas Harte
6fc692cd34 Attempted to switch to an asynchronous means for continuous file updates. Testing with SSD, as usual. 2016-12-29 22:15:58 -05:00
Thomas Harte
bbd94749f4 ... and I guess an instant maximal simplification is also easy if length ends up being 0 2016-12-29 11:02:21 -05:00
Thomas Harte
54900ca3fb Addition and subtraction can end immediately without performing any extra work if the operand is 0 2016-12-29 11:00:47 -05:00
Thomas Harte
a8bc9d830e Removed leftover very temporary debugging aid. 2016-12-28 23:03:05 -05:00
Thomas Harte
b9fad184d7 Added just enough for a complete manual test of writing to a .ADF with the 1770 then getting the correct result parsing it back on the host side in order potentially to update a file.
... which means that now it's time to worry about when and how mounted files should actually update themselves. Which will make for some fun with threading, I dare say.
2016-12-28 23:00:47 -05:00
Thomas Harte
af1b396c9e Found an ugly issue with Storage::Time as implemented (i) to be unsigned; and (ii) automatically to simplify. Will need to fix. Here's a quick workaround for this one segment of code. 2016-12-28 22:57:11 -05:00
Thomas Harte
9cb902cc4f Experimentally marked ADF as writable too, immediately discovering a mistake in the analysing MFM decoder. 2016-12-28 22:34:22 -05:00
Thomas Harte
e4000bd060 Added some even more verbose logging; slightly simplified write loop logic, and decided it's definitely write_byte that's responsible for CRC generator feeding. 2016-12-28 21:24:19 -05:00
Thomas Harte
ce814c9e99 These can be const. 2016-12-28 21:23:22 -05:00
Thomas Harte
bfe6c0a0c1 Ensured that FileHolder gets a writeable file reference if one is possible, and records whether the file in hand is read-only. So now the SSD class can answer honestly. 2016-12-28 20:09:14 -05:00
Thomas Harte
4adcb46665 Fixed FM-mode CRC generation. 2016-12-28 19:51:27 -05:00
Thomas Harte
46a93d2e12 Fixed errors to ensure that FM disks, at least, follow the same CRC generation rules when being built from sectors and when being parsed. 2016-12-28 19:48:46 -05:00
Thomas Harte
1277a67f9a Introduced data_mode_ to replace is_reading_data_, representing that there are now three possible modes. When writing, any input from the read head won't affect the CRC generator. 2016-12-28 19:26:21 -05:00
Thomas Harte
720b1e5802 Attempted to ensure proper CRC generation for FM-format input. 2016-12-28 18:56:53 -05:00
Thomas Harte
8cd1575891 Similar fix to that over in Oric land: ensure a known, effective initial value for the Plus 3's control register. 2016-12-28 18:52:36 -05:00
Thomas Harte
3a9ad3fb08 Fixed Oric .DSK handling, per my latest understanding. Which creates a desire to write shorts directly to the disk surface, so exposed that in the encoder. 2016-12-28 18:50:28 -05:00
Thomas Harte
90151e2094 Fixed to ensure a known initial control register value, which has taken effect. 2016-12-28 18:49:32 -05:00
Thomas Harte
7a627b782d Reintroduced writing of MFM sync marks when writing a sector. 2016-12-28 18:48:50 -05:00
Thomas Harte
a568172758 Made steps towards proper CRC generation. Am currently comparing against Oric disk images, as — amongst other things — they include precomputed CRCs. 2016-12-28 18:29:37 -05:00
Thomas Harte
99993a1b24 Since it's about to become important that objective results match, added a couple of objective-result tests for the CRC generator. 2016-12-27 19:03:46 -05:00
Thomas Harte
9c0f622a2e Started working CRC checking into the 1770. Discovered immediately that my generated CRC does not match that built into the Oric disk images. So mine is pretty-much certainly wrong. An opportunity for learning! 2016-12-26 16:46:26 -05:00
Thomas Harte
0490a47058 Worked on the all-around framework for decoding sectors back from tracks when closing down a file. Hit the wall that the parser is more observant of CRCs than the WD. No, really. So I guess I have to stop avoiding that whole issue. 2016-12-26 14:24:33 -05:00
Thomas Harte
83c433c142 Deviated from the data sheet, which seems likely to be correct. Hence removed a whole load of the temporary logging. 2016-12-26 12:48:49 -05:00
Thomas Harte
742c5df367 With lots of logging arising temporarily, fixed bug whereby conversion to a patched track would lead to holding a track with a distinct measure of time, leading to improperly-placed patches. 2016-12-25 22:00:39 -05:00
Thomas Harte
b538ee5bd8 Fixed discovery of correct active period and setting of track time, when seeking. 2016-12-25 21:32:50 -05:00
Thomas Harte
a6d038cad9 Eliminated special case that doesn't seek properly and isn't needed. Added TODO. 2016-12-25 21:32:14 -05:00
Thomas Harte
4fca30b81f Made the Plus 3 less chatty, documented invalidate_track. 2016-12-25 21:06:58 -05:00
Thomas Harte
26710c988d Modified SSD to ensure a fully-formatted surface is represented even if no track data is in the source file. This corrects the controller's sense of write success. 2016-12-25 20:40:06 -05:00
Thomas Harte
acc35885cd Attempted to reduce track invalidations. 2016-12-25 20:38:25 -05:00
Thomas Harte
c0a1264ab0 Slightly improved legibility. 2016-12-25 20:19:47 -05:00
Thomas Harte
e2b829f68e Made an attempt to write the proper address mark. 2016-12-25 20:15:07 -05:00
Thomas Harte
beaa868079 Factored the MFM parser out into encodings. 2016-12-25 20:00:57 -05:00
Thomas Harte
1349e85d83 [Mostly] fixed track write-back. 2016-12-25 19:19:22 -05:00
Thomas Harte
74e98fd097 Made an attempt to write actual data (albeit that CRC calculation is still missing). 2016-12-25 19:18:45 -05:00
Thomas Harte
007c13ec16 Fixed: cycles_per_bit_ isn't a function of the rotational multiplier, it's absolute. Also made sure that exactly hitting the end of a bit counts. 2016-12-25 16:35:39 -05:00
Thomas Harte
98be6ede45 Shuffled a little to reduce risk of overflow, ensured writing is a loop, still seem to be writing too quickly for some reason. 2016-12-25 16:13:05 -05:00
Thomas Harte
d2ad2c756e Added enough shovelling to write rubbish for an entire sector. 2016-12-25 15:46:49 -05:00
Thomas Harte
ec55a25620 It makes sense to simplify these ahead of time. 2016-12-25 12:32:25 -05:00
Thomas Harte
aceb7e3b6b Started implementing write sector on the 1770, immediately deciding it would be useful to have a callback for end-of-queued-data-written from disk controller. So had a go at implementing that, naively. More investigation required. 2016-12-25 12:31:38 -05:00
Thomas Harte
901f19f89c Added enough stuff that SSDs attached to a 1770 will now reach the entry point for writing. 2016-12-25 09:46:12 -05:00
Thomas Harte
e56beb3e9c Merge pull request #86 from TomHarte/DiskWrites
Implements backing work for in-memory disk writes
2016-12-25 09:37:20 -05:00
Thomas Harte
9d555c4a02 Let's try just declining to pump the PLL while in write mode. Added documentation to explain. 2016-12-25 09:19:18 -05:00
Thomas Harte
b57038edc5 Actually, at least index holes will still be receivable while writing, so this wasn't entirely correct. Probably best to leave it in. 2016-12-25 09:16:09 -05:00
Thomas Harte
d606bd7ce5 Added saturation test, fixed code as indicated. 2016-12-24 23:29:37 -05:00
Thomas Harte
09ff9d6a26 Introduced a couple more floating-point conversion tests, fixed errors uncovered. 2016-12-24 23:21:19 -05:00
Thomas Harte
e25195a718 Added a single test for Storage::Time, discovering that I had the wrong sign on float conversions. 2016-12-24 22:59:01 -05:00
Thomas Harte
af69b21033 This is almost complete, except that it doesn't act appropriately if some bits are written but not enough to cover the entire writing period. 2016-12-24 22:51:26 -05:00
Thomas Harte
f601d796f5 Added documentation. 2016-12-24 22:37:20 -05:00
Thomas Harte
6e94d0c19f Extended Storage::Disk::Disk to permit write-back of modified tracks, exposed some interface via Storage::Disk::Drive. 2016-12-24 22:11:31 -05:00
Thomas Harte
7f303cfceb Continued the baby steps. 2016-12-24 21:54:43 -05:00
Thomas Harte
afc6f4129c Withdrew unused tally. 2016-12-24 21:47:57 -05:00
Thomas Harte
1e416d4af0 Withdrew now-unused and never-implemented API from TimedEventLoop, and the redundant track time count from DiskController. 2016-12-24 21:02:10 -05:00
Thomas Harte
bedea48d03 This is a much better way of dealing with being partway into an incoming event. Subject to eliminating overruns, of course. 2016-12-24 20:54:27 -05:00
Thomas Harte
4cb17143ef Messing around trying to lock down timing precisely. Which includes formal initial conditions. 2016-12-24 15:18:46 -05:00
Thomas Harte
4d4852bb78 Ensured that Times start life in their simplest form. 2016-12-24 15:18:03 -05:00
Thomas Harte
4728bda0a2 Added an additional constructor to make sure that regular ints go to the correct place. 2016-12-24 13:27:57 -05:00
Thomas Harte
1e970a9772 Started stepping slowly towards allowing writing on the disk controller, taking the opportunity to introduce self-simplifying behaviour to Storage::Time. 2016-12-24 13:07:23 -05:00
Thomas Harte
42f25cdffc Merge branch 'master' into DiskWrites 2016-12-22 22:47:19 -05:00
Thomas Harte
393dc5c64f Merge pull request #87 from TomHarte/ElectronVideoFix
Ensures the Electron's video base address is set properly at construction.
2016-12-22 22:46:55 -05:00
Thomas Harte
3805e3d17d Ensured base address is set properly at construction. 2016-12-22 22:46:02 -05:00
Thomas Harte
7028f57336 Simplified a little further. 2016-12-22 18:13:10 -05:00
Thomas Harte
e4e0347638 Attempted to consolidate some of the repetition. 2016-12-21 22:17:00 -05:00
Thomas Harte
72ca06cf8d Added some extra tests, performed some basic tidying. Probably should do more. 2016-12-21 19:54:19 -05:00
Thomas Harte
6a0c7f22ee Added a few more tests. All passing. 2016-12-20 21:46:34 -05:00
Thomas Harte
03579f33f1 Fixed multi-coverage insertion, via an appropriate test. 2016-12-20 21:38:32 -05:00
Thomas Harte
7eca910cc5 Fixed insertion location finding logic, working on the relevant test. 2016-12-20 21:14:05 -05:00
Thomas Harte
c180340474 Added two more passing tests and one that crashes. 2016-12-20 19:25:58 -05:00
Thomas Harte
823ab9bc34 Completed initial non-trivial test, fixing revealed errors. 2016-12-20 19:15:36 -05:00
Thomas Harte
5a508ea0df Attempted properly to cover the exactly-equal starts and ends cases, and to improve meaning. 2016-12-20 18:32:49 -05:00
Thomas Harte
63d861a2f3 Switched from C-in-the-brain manual offset counting to using iterators like an ordinary C++ person. 2016-12-20 18:17:54 -05:00
Thomas Harte
6f17076003 Switched to much more logical shared_ptr ownership of PCMSegmentEventSources by Periods. 2016-12-20 18:13:10 -05:00
Thomas Harte
497b2ae4dd Still by manual inspection: the time for the next event should be provisional until proven acceptable, allowing a proper measurement of time until exiting the period to be taken; also fixed the accumulated period error when seeking back onto the underlying track. 2016-12-20 08:14:16 -05:00
Thomas Harte
6bdde542c5 Edging towards functioning automatic tests, fixed right-period adjustment and slightly decreased searching cost while in the process of adding a test. 2016-12-20 07:52:14 -05:00
Thomas Harte
ec624eaab1 Made an attempt fully to implement PCMPatchedTrack. Which now requires tests. 2016-12-20 07:30:57 -05:00
Thomas Harte
1ef1f6ec69 Attempted to implemnt seek_to and to finish add_segment. Started doing a little of get_next_event but ran out of time for the day. 2016-12-19 21:46:02 -05:00
Thomas Harte
8f937ceac8 Made an attempt to come up with a data structure that actually makes sense (though perhaps this is textbook list rather than vector stuff? I guess it depends on the frequency I expect inserts to occur versus reads) and to implement inserts. Though the Periods aren't yet honoured. 2016-12-19 07:42:43 -05:00
Thomas Harte
1df478d250 Removed dead header file. 2016-12-18 23:04:16 -05:00
Thomas Harte
e081f224b6 Implemented a very basic PCMTrack test, nevertheless revealing an oversight in PCMSegmentEventSource related to improperly counting to the index hole if the final bit is set. Took that as a message that I should comment and document the event source. 2016-12-18 22:53:24 -05:00
Thomas Harte
a6354ebb01 Reimplemented PCMTrack to use PCMSegmentEventSource, eliminating code duplication. 2016-12-18 21:37:05 -05:00
Thomas Harte
f9a5595dad Added seeking tests, correcting such errors as uncovered. 2016-12-18 10:19:24 -05:00
Thomas Harte
3297f6d545 Made an attempt to implement seek_to on PCMSegmentEventSource, taking account of off-by-half counting. 2016-12-17 22:44:33 -05:00
Thomas Harte
3116a2cf4c Realised I was actually testing PCMSegmentEventSource, not PCMSegment; implemented a spread of tests; hence fixed PCMSegmentEventSource. 2016-12-17 21:47:13 -05:00
Thomas Harte
254cc41fd6 Made an attempt to separate and isolate the stuff of creating flux events from a PCMSegment, eventually to factor that out of PCMTrack and make it available also to PCMPatchedTrack. 2016-12-17 21:13:57 -05:00
Thomas Harte
313db75303 Ensured the patchable track owns its underlying track. 2016-12-17 18:17:22 -05:00
Thomas Harte
3017062e89 Maybe TDD is the way to get over my activity block on this thing? Fixed the existing ArrayBuilder tests so that the tests target builds again, added an extremely trivial PCMTrack test, heading towards PCMPatchedTrack tests. 2016-12-17 17:05:49 -05:00
Thomas Harte
f1a08b7ab5 Opted to pass times by reference and added enough to PCMPatchedTrack that it could start being used by the disk controller, albeit that it doesn't work. 2016-12-17 16:26:45 -05:00
Thomas Harte
dc08a23ceb This is going to be a slow walk, I think. This class attempts to be the scratchpad which will hold in-memory track modifications. 2016-12-16 19:20:38 -05:00
Thomas Harte
1e757d1039 Merge branch 'master' into DiskWrites 2016-12-15 19:53:39 -05:00
Thomas Harte
ea1b3d447b Merge pull request #85 from TomHarte/ElectronRefactor
Applies a healthy cleaning to the Electron implementation
2016-12-15 19:53:07 -05:00
Thomas Harte
63107cd492 Tidied, very slightly. 2016-12-15 19:49:25 -05:00
Thomas Harte
a555c5762a Rearranged code, hopefully into a more logical grouping. 2016-12-15 19:47:04 -05:00
Thomas Harte
4a7ddaf2e9 Added documentation and a quick note to self. 2016-12-15 19:43:04 -05:00
Thomas Harte
f61176cd7d Reinstituted something of the don't-do-pixel-work-until-an-affecting-write-occurs optimisation. 2016-12-15 19:20:14 -05:00
Thomas Harte
c1c70a767a Attempted fully to reinstate proper timing. 2016-12-15 18:52:16 -05:00
Thomas Harte
0326316bb8 Reinstated whole-frame counting. Thereby to reinstate proper interrupts. 2016-12-15 18:09:49 -05:00
Thomas Harte
b58b11fc93 Switched to a table-based dispatch of line-by-line actions, primarily to simplify. 2016-12-15 18:07:46 -05:00
Thomas Harte
fd541e1142 An early draft; dealing with the issue that not all cycles are necessarily consumed in a single call. Incomplete; broken. Committing for cross-machine visibility. 2016-12-12 08:01:10 -05:00
Thomas Harte
be7e05e109 Started attempting to move total responsibility for display-related interrupts and RAM timing into the video. 2016-12-11 18:34:49 -05:00
Thomas Harte
c5cf8d9531 Ensured the video subsystem correctly handles requests to run over a frame boundary. 2016-12-11 16:17:51 -05:00
Thomas Harte
52028432e1 Restored some semblance of output. 2016-12-10 22:19:10 -05:00
Thomas Harte
0aae1bd1ef Fixed calculation of termination cycle. 2016-12-10 21:35:41 -05:00
Thomas Harte
c43e481a33 Started factoring video out of the Electron. 2016-12-10 21:07:52 -05:00
Thomas Harte
54b5056c74 Merge branch 'master' into DiskWrites 2016-12-10 19:37:48 -05:00
Thomas Harte
0653770c63 Merge pull request #84 from TomHarte/OricColour
Switches to using the original Oric colour ROM to generate Oric composite values
2016-12-10 19:37:17 -05:00
Thomas Harte
e62be03673 Removed endianness assumption. 2016-12-10 19:10:33 -05:00
Thomas Harte
34d213dec4 Decreased Y resolution, again also hopefully temporarily. 2016-12-10 15:35:38 -05:00
Thomas Harte
81a102d951 Upped intermediate buffer size, at least temporarily, while I look for the source of the interference patterns I'm seeing. 2016-12-10 15:20:10 -05:00
Thomas Harte
a5683dfb21 Removed now untrue comment. 2016-12-10 15:19:48 -05:00
Thomas Harte
0e71802b92 Reduced Oric video to single nibble constants. Removed attempt at asynchronous flush as no longer required. 2016-12-10 14:17:46 -05:00
Thomas Harte
580f347727 Fixed Oric SCART mode by having it change what it's giving to the CRT based on which shader it knows will be active. 2016-12-10 13:55:56 -05:00
Thomas Harte
a549fd1ecc Introduced the ability simply to piggy-back off the CRT's natural phase for the colour burst, thereby eliminating a couple of redundant independent attempts in the Oric and Electron. 2016-12-10 13:42:34 -05:00
Thomas Harte
e359441e2f Added a readme.txt for the omitted Oric ROMs. 2016-12-09 22:18:11 -05:00
Thomas Harte
6cdd41e5a9 Added direct use of the colour ROM, uploading 16 bits per pixel to contain the entire ROM composite wave. 2016-12-09 22:17:10 -05:00
Thomas Harte
3b5962b171 This is an initial attempt at using the actual Oric colour ROM values for composite video generation. 2016-12-09 20:01:27 -05:00
Thomas Harte
c4041b06a8 This'll do as a write interface, won't it? 2016-12-07 22:19:20 -05:00
Thomas Harte
46ebae7e4b Merge pull request #83 from TomHarte/InterruptLine
Corrects interrupt line handling of the WD and Microdisc
2016-12-06 21:21:35 -05:00
Thomas Harte
c304db0f5a Deintegrated the busy flag and the interrupt request line, as the latter is reset by status reads. Which also means I can start reporting the WD INTRQ line status directly from the Microdisc. That appears to be correct, rather than honouring the Microdisc IRQ select there. 2016-12-06 21:16:29 -05:00
Thomas Harte
4d3bdf8c7c Fixed failure to initialise the Microdisc flag if loading a tape. 2016-12-06 20:29:05 -05:00
Thomas Harte
0300ae4ec0 Merge pull request #82 from TomHarte/VideoRacing
Pulls the texture builder into flush/submit orthodoxy
2016-12-06 19:13:05 -05:00
Thomas Harte
5216dda675 Added some brief extra exposition to the texture builder, cut all internal tex_x/y and source_divider stuff from the CRT. 2016-12-06 19:08:55 -05:00
Thomas Harte
33d52bb573 Ensured no over-moving. 2016-12-06 19:02:18 -05:00
Thomas Harte
4ff33254e1 Sought to shift locking back up to the CRT. And to be a bit more RAII-ish. 2016-12-06 18:48:30 -05:00
Thomas Harte
60f9ddfde8 Fixed start test and added incrementation of start locations. 2016-12-06 08:08:57 -05:00
Thomas Harte
f388ba11cc Missed an initialisation. Fixed! 2016-12-06 07:26:23 -05:00
Thomas Harte
0fee8096c1 Made an attempt to shuffle the texture builder to a similar flush/submit pattern as the input builder. Don't care about thread safety yet, as it's obvious I'm going to need to move that back up to the CRT. 2016-12-06 07:24:07 -05:00
Thomas Harte
0edc043378 Started introducing an extra layer of indirection so as to be able to bind the texture builder to the same flush and submit patern as the array builder. 2016-12-03 20:47:19 -05:00
Thomas Harte
cb3c837e30 Simplified interface by baking in last-minute-only updates. 2016-12-03 18:19:12 -05:00
Thomas Harte
ca50606e1d Restored Vic audio. 2016-12-03 17:10:47 -05:00
Thomas Harte
0220d33562 Resolved failure of initial state setting, which was causing tests sometimes to fail. 2016-12-03 16:42:10 -05:00
Thomas Harte
d17751787a The remainder of this test isn't necessarily safe to perform if the array length isn't as expected. But in that case the test has already failed, so it's not worth worrying about a partial validation. 2016-12-03 16:06:15 -05:00
Thomas Harte
875e6619cc Merge pull request #81 from TomHarte/Underscores
Switches hopefully all C++ classes to postfix underscores
2016-12-03 14:19:47 -05:00
Thomas Harte
ebb62a2d78 Switched the 2600 to postfix and non-camel-case instance variable names. 2016-12-03 14:07:38 -05:00
Thomas Harte
b81c058c0a Factored out the Atari 2600's 6532 connection, as a low-hanging fruit. 2016-12-03 13:41:55 -05:00
Thomas Harte
3361d6b93a Factored out the Atari 2600 speaker and adjusted it to postfix underscores. 2016-12-03 13:39:46 -05:00
Thomas Harte
1b1a8d3e52 Brought the Vic-20 into suffix naming. 2016-12-03 13:30:27 -05:00
Thomas Harte
063a62372f The Commodore serial bus and C1540 are now postfix underscorers. 2016-12-03 13:14:03 -05:00
Thomas Harte
eb3a1fbfb7 Commuted remaining Electron underscores. It would be nice also to factor out the video, but the time hasn't come yet. 2016-12-03 13:01:01 -05:00
Thomas Harte
4fac538a57 Factored out the Electron's speaker and adjusted instance variable naming. 2016-12-03 12:41:02 -05:00
Thomas Harte
d1d93829cf Factored out the Tape and switched it to postfix underscores. 2016-12-03 12:18:08 -05:00
Thomas Harte
3f7f2c6117 'Tape' has joined the new underscore orthodoxy. 2016-12-03 12:05:19 -05:00
Thomas Harte
0dc2aa6454 Commuted all of 'Storage' other than 'Tape' to postfix underscores. 2016-12-03 11:59:28 -05:00
Thomas Harte
5be22e2f8d Switched to suffix underscores and underscores in general for instance variables. 2016-12-03 11:38:53 -05:00
Thomas Harte
c5016a3eaa Completed flight of 'Outputs' to postfix underscores. 2016-12-03 11:02:34 -05:00
Thomas Harte
2003b514aa Switched the typer to postfix underscores. 2016-12-03 10:55:50 -05:00
Thomas Harte
36bc558798 Converted all 'Components' to postfix underscores. 2016-12-03 10:51:09 -05:00
Thomas Harte
a0043ec336 Merge pull request #80 from TomHarte/Microdisc
Adds support for the Microdisc interface to the Oric emulation
2016-12-03 23:25:44 +08:00
Thomas Harte
81ee834530 As well as a bunch of logging, reinstated rotation position preservation across tracks. 2016-12-02 18:36:47 -05:00
Thomas Harte
93c573bfa9 Implemented missing status bits (other than the index hole), and a head loading delay for the Microdisc. 2016-12-01 21:13:16 -05:00
Thomas Harte
0a0775c3bd Removed earlier hacky solution. 2016-12-01 20:16:11 -05:00
Thomas Harte
442986ee2c Introduced a head loading path for 1793 machines. 2016-12-01 20:12:22 -05:00
Thomas Harte
82899f2f47 Ensured flag setting is atomic, removed duplication of interrupt request versus busy, found better names for the personality testers, unified delegate protocol. 2016-12-01 07:41:52 -05:00
Thomas Harte
b31fd11470 Fixed reporting of data request line, initial status values. 2016-11-30 22:39:55 -05:00
Thomas Harte
2222cb65d6 Split the status up into flags, assembled into a register upon demand. Attempted to implement some of the differences between the 1770/1772 and 1773/1793. Albeit with a motor fix still in place. 2016-11-30 22:26:02 -05:00
Thomas Harte
9b6c5e814a Now that it can be more explicit, this should admit that it's '93-based, not '73. 2016-11-28 16:22:35 -05:00
Thomas Harte
84cb07613d Checked some documentation more thoroughly; the 1793 has quite different spin-up (/head load) semantics. So it's another distinct personality. Grrr. 2016-11-27 20:39:08 -08:00
Thomas Harte
02ba1f220f The '72 seems to be a '70 with altered timing. So worth differentiating. 2016-11-27 21:06:17 +08:00
Thomas Harte
2c01f9dbed Added meaningful TODOs. 2016-11-27 08:42:39 +08:00
Thomas Harte
f44542c18c Improved naming: this now explains what, not the mechanics of how. 2016-11-26 23:35:11 +08:00
Thomas Harte
2f459690d4 It would appear the 1770 and 1773 actually differ in relation to the (non-sensical) ability not to spin-up for a Type 2, and whether a side compare can occur. So the WD1770 class now requires a personality to be specified. Which it singly fails to honour. 2016-11-26 23:29:30 +08:00
Thomas Harte
d8ecc52de8 Temporarily disabled spin-down as harmful to the status register if following anything other than a Type 1 command. 2016-11-26 22:27:20 +08:00
Thomas Harte
5c8ecd3051 It probably needs a better name, but hastily implemented track caching at the Disk level. 2016-11-26 14:27:06 +08:00
Thomas Harte
2f86b07cfa Added a parser for Oric-format MFM disks. Causing my first disk to load! 2016-11-26 13:40:10 +08:00
Thomas Harte
dcfdd73077 Switched to more conventional runtime polymorphism, also now allowing encoders (shifters as were) to be obtained, as a slightly lower-level interface than vector-of-sectors. 2016-11-26 13:39:20 +08:00
Thomas Harte
b180f04c87 Okay, so this file format wasn't what I hoped it was. It's another hack. Lots of work to do. 2016-11-26 10:19:10 +08:00
Thomas Harte
7613755f94 Fixed addressing: types are 1 and 2, not 0 and 1. 2016-11-26 10:13:12 +08:00
Thomas Harte
b9677c9927 Consolidated interrupt request setting. 2016-11-26 09:41:53 +08:00
Thomas Harte
e9d6566e9c Of course, changing the IRQ enable may immediately change the IRQ line. Signal if so. 2016-11-26 09:35:44 +08:00
Thomas Harte
73d30b9c00 Corrected typo. 2016-11-25 21:30:45 +08:00
Thomas Harte
12956901d6 Filled in some register mirrors. 2016-11-25 21:28:11 +08:00
Thomas Harte
54246c8f1a Interrupt enabling works the other way around I think, and both registers with only one bit defined should probably return '1' in all other places? 2016-11-25 21:24:59 +08:00
Thomas Harte
d5f9e0aa3b Ensured there's no such thing as a zero-cycle operation, even if i don't yet know exactly what I should be doing. 2016-11-25 21:24:25 +08:00
Thomas Harte
8be81f6ebd Supplied disks are given to the Microdisc. 2016-11-25 20:53:38 +08:00
Thomas Harte
4af678d2ed Gave the Microdisc a clock signal, added just enough of force interrupt to avoid a spurious belief that a type 3 command has started. 2016-11-25 20:51:39 +08:00
Thomas Harte
5c019ad1c0 Okay, so it looks like both ROM paging flags are the opposite of what I previously had. 2016-11-25 20:42:40 +08:00
Thomas Harte
5be45c6c50 Ensured proper default behaviour. 2016-11-25 20:30:27 +08:00
Thomas Harte
d33f3b9224 This is the broad strokes effort at enabling Microdisc emulation. 2016-11-25 20:15:48 +08:00
Thomas Harte
7c2d9f3752 This seems to be right, per http://wiki.defence-force.org/doku.php?id=oric:hardware:floppy_disk_controller_wd1793 2016-11-22 22:35:43 +08:00
Thomas Harte
5ebc1c63ff Switched video to postfix underscores, for consistency. 2016-11-22 22:28:45 +08:00
Thomas Harte
707763f80b Added Microdisc storage to the Oric class, switching all instance storage to postfix underscore while I'm here. 2016-11-22 22:22:00 +08:00
Thomas Harte
0c3644f350 Made a second parse at logic. We'll see. 2016-11-22 22:12:32 +08:00
Thomas Harte
03843bf934 Unified delegates. Let's keep it easy for the caller. 2016-11-22 22:11:11 +08:00
Thomas Harte
13a608a8c2 Added what may be correct paging logic. 2016-11-22 22:09:52 +08:00
Thomas Harte
363db695e8 Started implementation of the Microdisc selection logic. 2016-11-22 08:12:53 +08:00
Thomas Harte
59e2a09107 Added assumption that Microdisc => BASIC 1.1. 2016-11-22 08:12:24 +08:00
Thomas Harte
09f965e6a9 Fixed potential bug whereby inserting a disk into a drive that hadn't been lazily allocated yet but had already been selected wouldn't take effect. 2016-11-22 08:11:57 +08:00
Thomas Harte
ea33a28695 Any Oric-format disks that are inserted now make it all the way to the Oric, along with a request to emulate the Microdisc. It has received a copy of the ROM. The ball is entirely in its court now. 2016-11-21 20:59:25 +08:00
Thomas Harte
fc1afe9351 Merge pull request #79 from TomHarte/OricDSK
Creates a base class for file format implementations which seeks to collect boilerplate stuff
2016-11-21 20:51:48 +08:00
Thomas Harte
8499783b14 Dragged multibyte primitives and signature checks up to the base class. Implemented support for Oric MFM-style .DSK, at the file format level. 2016-11-21 20:47:16 +08:00
Thomas Harte
31c2548804 Created a base class for the boilerplate fopen stuff, switched as many classes as possible to its use, switched to postfix underscores and non-camelCase names. 2016-11-21 20:14:09 +08:00
Thomas Harte
efb53c292c Merge pull request #78 from TomHarte/WD1772Pins
Attempts properly to expose the data and interrupt request lines from the WD1770
2016-11-21 13:25:49 +08:00
Thomas Harte
d4a1961378 Added getters for the IRQ and DRQ lines plus a delegate to receive changes; adjusted code so that the two lines signal. 2016-11-21 13:21:49 +08:00
Thomas Harte
4ec042fad1 Merge pull request #77 from TomHarte/LineBuffer
Makes input and output OpenGL data submission atomic, while cleaning code generally
2016-11-21 12:24:43 +08:00
Thomas Harte
7c85cb62e4 Moved underscores, removed indirections where they're not necessary, converted those names that were still looking very Objective-C and moved the GL fence variable into the private area, where it should always have been. 2016-11-21 12:14:52 +08:00
Thomas Harte
bc03e12dc5 Switched to suffix underscores. 2016-11-21 11:57:45 +08:00
Thomas Harte
340607e13e Switched the flywheel to suffix underscores. 2016-11-21 11:48:31 +08:00
Thomas Harte
e1285028aa Removed a redundant clear and some dead code. 2016-11-21 11:42:45 +08:00
Thomas Harte
7b38247ab3 Updated outdated comments. 2016-11-21 11:40:13 +08:00
Thomas Harte
d7d0ed378a Only the final intermediate buffer needs clearing, on reflection. 2016-11-21 11:26:07 +08:00
Thomas Harte
c89345c639 Reduced buffer size. 2016-11-21 11:21:14 +08:00
Thomas Harte
32dbfe947d With hindsight, the elimination of this might have been overzealous. 2016-11-20 10:53:35 +08:00
Thomas Harte
ef0367d4a4 Corrected typo. 2016-11-20 09:12:43 +08:00
Thomas Harte
5bc165960a Completed refactoring. 2016-11-20 09:12:06 +08:00
Thomas Harte
fda90c5aef Documented. 2016-11-20 09:11:24 +08:00
Thomas Harte
c2349ee3f4 This thing has clearly becoma a real class. 2016-11-19 20:12:40 +08:00
Thomas Harte
7857ef774f Ensured resets genuinely kill outstanding data. 2016-11-19 20:09:38 +08:00
Thomas Harte
a4c7b00ecd Fixed code that was causing the failing test. 2016-11-19 19:55:30 +08:00
Thomas Harte
be60eaa120 Added a test for pointer continuity over a submit. Which fails. 2016-11-19 19:48:16 +08:00
Thomas Harte
274ec9efb8 Added a test for interceding submit. 2016-11-19 08:59:21 +08:00
Thomas Harte
22cb8ecd75 Started building some tests of the array builder. 2016-11-19 08:27:08 +08:00
Thomas Harte
f59537bce9 Added a testing hook, but as of yet no tests. 2016-11-17 14:06:16 +08:00
Thomas Harte
aca1fa0577 Returned some video output. Enough significantly to reduce my paranoia. 2016-11-17 13:37:53 +08:00
Thomas Harte
1f91d29434 Progressed to usage of an array builder and a texture builder up to the point of a successful build. 2016-11-17 12:26:04 +08:00
Thomas Harte
0f3b02edb7 Switched to postfix underscores and gave this class ownership of a texture builder and an array builder, though it presently uses neither. 2016-11-17 11:00:11 +08:00
Thomas Harte
57f0648742 Fleshed out first implementation of ArrayBuilder, albeit that I need to implement exhaustion properly, as soon as I think of a sensible way to handle synchronisation. 2016-11-17 10:39:30 +08:00
Thomas Harte
324a1de43d Started pulling out array construction as a separate task. 2016-11-17 09:20:49 +08:00
Thomas Harte
c04a116a05 Fixed comment. 2016-11-16 23:14:56 +08:00
Thomas Harte
edeafd4d94 Wait, OpenGL textures go the other way. 2016-11-16 23:14:15 +08:00
Thomas Harte
6ac20e0066 Pushed responsibility for submitting texture contents up to the texture builder, simplifying the interface. 2016-11-16 23:13:06 +08:00
Thomas Harte
5c5e44874f Even better: why include the 'Input' prefix when there's only one? 2016-11-16 22:57:17 +08:00
Thomas Harte
04b2688683 Attempted to reduce allocations. 2016-11-16 13:25:50 +08:00
Thomas Harte
c1a509910d Documented this interface, albeit that the English could do with a second pass, and very sightly simplified inline with current usage. 2016-11-16 13:15:50 +08:00
Thomas Harte
4d0d5eb919 Renamed the 'input buffer builder' to the 'input texture builder' to be explicit about what sort of buffer, and killed the prefix since it's namespaced. Also switched to std::vector. 2016-11-16 12:31:32 +08:00
Thomas Harte
4ee4400801 Removed dead code. 2016-11-16 11:57:22 +08:00
Thomas Harte
6cb4950db4 Improved run-off area. 2016-11-16 11:53:15 +08:00
Thomas Harte
9ee11d7765 At the expense of API simplicity, at least for now, resolved the most glaring cause of dirty lines. 2016-11-16 11:34:05 +08:00
Thomas Harte
44d3fd6d5b Fixed mistimed reset of the target output line. Now all that's left is occasional noise. 2016-11-16 11:22:12 +08:00
Thomas Harte
7a737e0790 Both pathways start with a buffer that clears to black. So no need to keep going on about it. 2016-11-16 11:12:08 +08:00
Thomas Harte
f63e849092 Sought completely to eliminate the outgoing concept of 'clearing zones'. 2016-11-16 11:10:34 +08:00
Thomas Harte
311f8c0b47 Restored audio. 2016-11-16 11:10:07 +08:00
Thomas Harte
294adde344 Eliminated zoned clearing, in favour of a complete buffer clear on each load and attempted to reduce locking. But imperfectly. 2016-11-16 10:59:12 +08:00
Thomas Harte
ccedb6bea6 Introduced an intermediate buffer that collects lines before flushing them to the output buffer. 2016-11-16 10:49:18 +08:00
Thomas Harte
ba2adf8bb1 Normalised on std::vector rather than std::unique_ptr<uint8_t *> as (probably?) more idiomatic. Improved structure slightly. Introduced a container for line data, albeit one that isn't yet used. 2016-11-16 10:34:54 +08:00
Thomas Harte
f84a28b566 Merge pull request #76 from TomHarte/Disassembler
Introduces a first attempt at 6502 disassembly
2016-11-15 19:52:12 -05:00
Thomas Harte
97811fe590 Made minor fix to ensure that a header that appears to extend beyond the end of an Oric .TAP doesn't create an ostensibly endless tape. 2016-11-15 12:02:03 +08:00
Thomas Harte
8b40ae03ca Extended analysis to spot IRQ and NMI vector changes, for titles that simply adjust those then exit. Ensured Oric emulator can type and load quickly on an Oric 1 just as well as on an Atmos. 2016-11-15 11:05:53 +08:00
Thomas Harte
e2cdfae8a7 The emulated Oric now has access to both versions of the BASIC ROM and picks between them based on the static analyser's recommendation. 2016-11-15 10:39:16 +08:00
Thomas Harte
ab64731d51 Fixed off-by-one recording of disasssmbled addresses; added logic to the Oric static analyser to try to guess between BASIC 1.1 (the preferred option in a tie-break or if no information is found) and BASIC 1.0. 2016-11-15 10:31:47 +08:00
Thomas Harte
1bffc70494 In theory that's a full disassembler, at least as far as I want to go for now. Time to test. 2016-11-15 08:19:57 +08:00
Thomas Harte
a5fc45e66e Started fleshing this out; addressing modes are completely decoded, along with a bunch of opcodes, but appropriate address advancement isn't in yet, and neither is filling in derived metadata. 2016-11-14 12:19:09 +08:00
Thomas Harte
f08e87c6c1 Merge branch 'master' into Disassembler 2016-11-11 20:32:18 -05:00
Thomas Harte
7eeaac23e7 Reversed myself. I once again do not think the clock is divided by 256 for envelopes. 2016-11-11 20:31:48 -05:00
Thomas Harte
1b66847647 Started trying to implement something sufficient of a 6502 disassembler. 2016-11-11 20:10:58 -05:00
Thomas Harte
4ba39d13b5 Merge pull request #75 from TomHarte/AudioUpdates
Reintroduced a separate thread for audio processing and made various AY fixes
2016-11-10 20:32:57 -05:00
Thomas Harte
77987bf31e Decided to go with divide by 256 for the envelope counter after all. 2016-11-09 21:51:56 -05:00
Thomas Harte
77ce200fbb Simplified/corrected AY tone/noise mixer logic, and made a new guess at the effect of reading registers that are smaller than 8 bits. 2016-11-09 21:21:17 -05:00
Thomas Harte
9c550c594a Moved audio work back into its own thread, but this time it queues up an all happens only upon a flush. Hopefully to resolve synchronisation cost concerns. 2016-11-09 21:17:50 -05:00
Thomas Harte
f3c8c57043 Merge pull request #74 from TomHarte/WaveLengthOric
Switches to Oric tape parsing by full-cycle length only
2016-11-09 06:41:56 -05:00
Thomas Harte
fce48b9b8c I am instructed that the Oric actually catches only positive transitions, and compares the distance between those to a threshold. So here's an altered version of the tape parser that does that. 2016-11-09 06:37:10 -05:00
198 changed files with 13090 additions and 7670 deletions

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ DerivedData
# Exclude system ROMs
ROMImages/*
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
# CocoaPods
#

5
.travis.yml Normal file
View File

@@ -0,0 +1,5 @@
language: objective-c
osx_image: xcode8.2
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
xcode_scheme: Clock Signal
xcode_sdk: macosx10.12

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,19 @@
#define _770_hpp
#include "../../Storage/Disk/DiskController.hpp"
#include "../../NumberTheory/CRC.hpp"
namespace WD {
class WD1770: public Storage::Disk::Controller {
public:
WD1770();
enum Personality {
P1770, // implies automatic motor-on management with Type 2 commands offering a spin-up disable
P1772, // as per the 1770, with different stepping rates
P1773, // implements the side number-testing logic of the 1793; omits spin-up/loading logic
P1793 // implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT
};
WD1770(Personality p);
void set_is_double_density(bool is_double_density);
void set_register(int address, uint8_t value);
@@ -24,10 +31,12 @@ class WD1770: public Storage::Disk::Controller {
void run_for_cycles(unsigned int number_of_cycles);
enum Flag: uint8_t {
NotReady = 0x80,
MotorOn = 0x80,
WriteProtect = 0x40,
RecordType = 0x20,
SpinUp = 0x20,
HeadLoaded = 0x20,
RecordNotFound = 0x10,
SeekError = 0x10,
CRCError = 0x08,
@@ -38,8 +47,39 @@ class WD1770: public Storage::Disk::Controller {
Busy = 0x01
};
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
inline bool get_data_request_line() { return status_.data_request; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected:
virtual void set_head_load_request(bool head_load);
void set_head_loaded(bool head_loaded);
private:
uint8_t status_;
Personality personality_;
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
inline bool has_head_load_line() { return (personality_ == P1793 ); }
struct Status {
Status();
bool write_protect;
bool record_type;
bool spin_up;
bool record_not_found;
bool crc_error;
bool seek_error;
bool lost_data;
bool data_request;
bool interrupt_request;
bool busy;
enum {
One, Two, Three
} type;
} status_;
uint8_t track_;
uint8_t sector_;
uint8_t data_;
@@ -52,15 +92,19 @@ class WD1770: public Storage::Disk::Controller {
bool is_awaiting_marker_value_;
int step_direction_;
void set_interrupt_request(bool interrupt_request) {}
void update_status(std::function<void(Status &)> updater);
// Tokeniser
bool is_reading_data_;
enum DataMode {
Scanning,
Reading,
Writing
} data_mode_;
bool is_double_density_;
int shift_register_;
struct Token {
enum Type {
Index, ID, Data, DeletedData, Byte
Index, ID, Data, DeletedData, Sync, Byte
} type;
uint8_t byte_value;
} latest_token_;
@@ -70,21 +114,39 @@ class WD1770: public Storage::Disk::Controller {
Command = (1 << 0), // Indicates receipt of a new command.
Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
IndexHole = (1 << 2), // Indicates the passing of a physical index hole.
HeadLoad = (1 << 3), // Indicates the head has been loaded (1973 only).
DataWritten = (1 << 4), // Indicates that all queued bits have been written
Timer = (1 << 3), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 4) // Indicates that index_hole_count_ has reached index_hole_count_target_.
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
};
void posit_event(Event type);
int interesting_event_mask_;
int resume_point_;
int delay_time_;
// ID buffer
uint8_t header[6];
// Output
int last_bit_;
void write_bit(int bit);
void write_byte(uint8_t byte);
void write_raw_short(uint16_t value);
//
// ID buffer
uint8_t header_[6];
// CRC generator
NumberTheory::CRC16 crc_generator_;
// 1793 head-loading logic
bool head_is_loaded_;
// delegate
Delegate *delegate_;
// Storage::Disk::Controller
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
virtual void process_write_completed();
};
}

View File

@@ -50,86 +50,79 @@ template <class T> class MOS6522 {
};
/*! Sets a register value. */
inline void set_register(int address, uint8_t value)
{
inline void set_register(int address, uint8_t value) {
address &= 0xf;
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
switch(address)
{
switch(address) {
case 0x0:
_registers.output[1] = value;
static_cast<T *>(this)->set_port_output(Port::B, value, _registers.data_direction[1]); // TODO: handshake
registers_.output[1] = value;
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
_registers.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((_registers.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
_registers.output[0] = value;
static_cast<T *>(this)->set_port_output(Port::A, value, _registers.data_direction[0]); // TODO: handshake
registers_.output[0] = value;
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
_registers.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((_registers.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
// // No handshake, so write directly
// _registers.output[0] = value;
// registers_.output[0] = value;
// static_cast<T *>(this)->set_port_output(0, value);
// break;
case 0x2:
_registers.data_direction[1] = value;
registers_.data_direction[1] = value;
break;
case 0x3:
_registers.data_direction[0] = value;
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: _registers.timer_latch[0] = (_registers.timer_latch[0]&0xff00) | value; break;
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) | (uint16_t)(value << 8);
_registers.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05)
{
_registers.next_timer[0] = _registers.timer_latch[0];
_timer_is_running[0] = true;
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];
timer_is_running_[0] = true;
}
reevaluate_interrupts();
break;
// Timer 2
case 0x8: _registers.timer_latch[1] = value; break;
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
_registers.interrupt_flags &= ~InterruptFlag::Timer2;
_registers.next_timer[1] = _registers.timer_latch[1] | (uint16_t)(value << 8);
_timer_is_running[1] = true;
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
// Shift
case 0xa: _registers.shift = value; break;
case 0xa: registers_.shift = value; break;
// Control
case 0xb:
_registers.auxiliary_control = value;
registers_.auxiliary_control = value;
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
_registers.peripheral_control = value;
registers_.peripheral_control = value;
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
if(value & 0x08)
{
switch(value & 0x0e)
{
if(value & 0x08) {
switch(value & 0x0e) {
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
}
}
if(value & 0x80)
{
switch(value & 0xe0)
{
if(value & 0x80) {
switch(value & 0xe0) {
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
@@ -139,131 +132,122 @@ template <class T> class MOS6522 {
// Interrupt control
case 0xd:
_registers.interrupt_flags &= ~value;
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
if(value&0x80)
_registers.interrupt_enable |= value;
registers_.interrupt_enable |= value;
else
_registers.interrupt_enable &= ~value;
registers_.interrupt_enable &= ~value;
reevaluate_interrupts();
break;
}
}
/*! Gets a register value. */
inline uint8_t get_register(int address)
{
inline uint8_t get_register(int address) {
address &= 0xf;
// printf("6522 %p: %d\n", this, address);
switch(address)
{
switch(address) {
case 0x0:
_registers.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, _registers.data_direction[1], _registers.output[1]);
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
case 0xf: // TODO: handshake, latching
case 0x1:
_registers.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, _registers.data_direction[0], _registers.output[0]);
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
case 0x2: return _registers.data_direction[1];
case 0x3: return _registers.data_direction[0];
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
// Timer 1
case 0x4:
_registers.interrupt_flags &= ~InterruptFlag::Timer1;
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return _registers.timer[0] & 0x00ff;
case 0x5: return _registers.timer[0] >> 8;
case 0x6: return _registers.timer_latch[0] & 0x00ff;
case 0x7: return _registers.timer_latch[0] >> 8;
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
// Timer 2
case 0x8:
_registers.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return _registers.timer[1] & 0x00ff;
case 0x9: return _registers.timer[1] >> 8;
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return _registers.shift;
case 0xa: return registers_.shift;
case 0xb: return _registers.auxiliary_control;
case 0xc: return _registers.peripheral_control;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xd: return _registers.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return _registers.interrupt_enable | 0x80;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
}
return 0xff;
}
inline void set_control_line_input(Port port, Line line, bool value)
{
switch(line)
{
inline void set_control_line_input(Port port, Line line, bool value) {
switch(line) {
case Line::One:
if( value != _control_inputs[port].line_one &&
value == !!(_registers.peripheral_control & (port ? 0x10 : 0x01))
)
{
_registers.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
if( value != control_inputs_[port].line_one &&
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
reevaluate_interrupts();
}
_control_inputs[port].line_one = value;
control_inputs_[port].line_one = value;
break;
case Line::Two:
// TODO: output modes, but probably elsewhere?
if( value != _control_inputs[port].line_two && // i.e. value has changed ...
!(_registers.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
value == !!(_registers.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
)
{
_registers.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
reevaluate_interrupts();
}
_control_inputs[port].line_two = value;
control_inputs_[port].line_two = value;
break;
}
}
#define phase2() \
_registers.last_timer[0] = _registers.timer[0];\
_registers.last_timer[1] = _registers.timer[1];\
registers_.last_timer[0] = registers_.timer[0];\
registers_.last_timer[1] = registers_.timer[1];\
\
if(_registers.timer_needs_reload)\
{\
_registers.timer_needs_reload = false;\
_registers.timer[0] = _registers.timer_latch[0];\
if(registers_.timer_needs_reload) {\
registers_.timer_needs_reload = false;\
registers_.timer[0] = registers_.timer_latch[0];\
}\
else\
_registers.timer[0] --;\
registers_.timer[0] --;\
\
_registers.timer[1] --; \
if(_registers.next_timer[0] >= 0) { _registers.timer[0] = (uint16_t)_registers.next_timer[0]; _registers.next_timer[0] = -1; }\
if(_registers.next_timer[1] >= 0) { _registers.timer[1] = (uint16_t)_registers.next_timer[1]; _registers.next_timer[1] = -1; }\
registers_.timer[1] --; \
if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
// IRQ is raised on the half cycle after overflow
#define phase1() \
if((_registers.timer[1] == 0xffff) && !_registers.last_timer[1] && _timer_is_running[1])\
{\
_timer_is_running[1] = false;\
_registers.interrupt_flags |= InterruptFlag::Timer2;\
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\
timer_is_running_[1] = false;\
registers_.interrupt_flags |= InterruptFlag::Timer2;\
reevaluate_interrupts();\
}\
\
if((_registers.timer[0] == 0xffff) && !_registers.last_timer[0] && _timer_is_running[0])\
{\
_registers.interrupt_flags |= InterruptFlag::Timer1;\
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
registers_.interrupt_flags |= InterruptFlag::Timer1;\
reevaluate_interrupts();\
\
if(_registers.auxiliary_control&0x40)\
_registers.timer_needs_reload = true;\
if(registers_.auxiliary_control&0x40)\
registers_.timer_needs_reload = true;\
else\
_timer_is_running[0] = false;\
timer_is_running_[0] = false;\
}
/*!
@@ -279,29 +263,23 @@ template <class T> class MOS6522 {
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_half_cycles(unsigned int number_of_cycles)
{
if(_is_phase2)
{
inline void run_for_half_cycles(unsigned int number_of_cycles) {
if(is_phase2_) {
phase2();
number_of_cycles--;
}
while(number_of_cycles >= 2)
{
while(number_of_cycles >= 2) {
phase1();
phase2();
number_of_cycles -= 2;
}
if(number_of_cycles)
{
if(number_of_cycles) {
phase1();
_is_phase2 = true;
}
else
{
_is_phase2 = false;
is_phase2_ = true;
} else {
is_phase2_ = false;
}
}
@@ -311,10 +289,8 @@ template <class T> class MOS6522 {
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_cycles(unsigned int number_of_cycles)
{
while(number_of_cycles--)
{
inline void run_for_cycles(unsigned int number_of_cycles) {
while(number_of_cycles--) {
phase1();
phase2();
}
@@ -324,17 +300,15 @@ template <class T> class MOS6522 {
#undef phase2
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
inline bool get_interrupt_line()
{
uint8_t interrupt_status = _registers.interrupt_flags & _registers.interrupt_enable & 0x7f;
inline bool get_interrupt_line() {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}
MOS6522() :
_timer_is_running{false, false},
_last_posted_interrupt_status(false),
_is_phase2(false)
{}
timer_is_running_{false, false},
last_posted_interrupt_status_(false),
is_phase2_(false) {}
private:
// Expected to be overridden
@@ -344,23 +318,20 @@ template <class T> class MOS6522 {
void set_interrupt_status(bool status) {}
// Input/output multiplexer
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
{
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
// Phase toggle
bool _is_phase2;
bool is_phase2_;
// Delegate and communications
bool _last_posted_interrupt_status;
inline void reevaluate_interrupts()
{
bool last_posted_interrupt_status_;
inline void reevaluate_interrupts() {
bool new_interrupt_status = get_interrupt_line();
if(new_interrupt_status != _last_posted_interrupt_status)
{
_last_posted_interrupt_status = new_interrupt_status;
if(new_interrupt_status != last_posted_interrupt_status_) {
last_posted_interrupt_status_ = new_interrupt_status;
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
}
}
@@ -382,15 +353,15 @@ template <class T> class MOS6522 {
interrupt_flags(0), interrupt_enable(0),
last_timer{0, 0}, timer_needs_reload(false),
next_timer{-1, -1} {}
} _registers;
} registers_;
// control state
struct {
bool line_one, line_two;
} _control_inputs[2];
} control_inputs_[2];
// Internal state other than the registers
bool _timer_is_running[2];
bool timer_is_running_[2];
};
/*!
@@ -404,18 +375,16 @@ class MOS6522IRQDelegate {
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
void set_interrupt_delegate(Delegate *delegate)
{
_delegate = delegate;
inline void set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void set_interrupt_status(bool new_status)
{
if(_delegate) _delegate->mos6522_did_change_interrupt_status(this);
inline void set_interrupt_status(bool new_status) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}
private:
Delegate *_delegate;
Delegate *delegate_;
};
}

View File

@@ -27,82 +27,74 @@ namespace MOS {
*/
template <class T> class MOS6532 {
public:
inline void set_ram(uint16_t address, uint8_t value) { _ram[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return _ram[address & 0x7f]; }
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
inline void set_register(int address, uint8_t value)
{
inline void set_register(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
case 0x00: case 0x02:
_port[decodedAddress / 2].output = value;
static_cast<T *>(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask);
port_[decodedAddress / 2].output = value;
static_cast<T *>(this)->set_port_output(decodedAddress / 2, port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask);
set_port_did_change(decodedAddress / 2);
break;
case 0x01: case 0x03:
_port[decodedAddress / 2].output_mask = value;
static_cast<T *>(this)->set_port_output(decodedAddress / 2, _port[decodedAddress/2].output, _port[decodedAddress / 2].output_mask);
port_[decodedAddress / 2].output_mask = value;
static_cast<T *>(this)->set_port_output(decodedAddress / 2, port_[decodedAddress/2].output, port_[decodedAddress / 2].output_mask);
set_port_did_change(decodedAddress / 2);
break;
// The timer and edge detect control
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 = ((unsigned int)(value) << _timer.activeShift) | ((1 << _timer.activeShift)-1);
_timer.interrupt_enabled = !!(address&0x08);
_interrupt_status &= ~InterruptFlag::Timer;
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = ((unsigned int)value << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
}
else
{
_a7_interrupt.enabled = !!(address&0x2);
_a7_interrupt.active_on_positive = !!(address & 0x01);
} else {
a7_interrupt_.enabled = !!(address&0x2);
a7_interrupt_.active_on_positive = !!(address & 0x01);
}
break;
}
}
inline uint8_t get_register(int address)
{
inline uint8_t get_register(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
case 0x00: case 0x02:
{
case 0x00: case 0x02: {
const int port = decodedAddress / 2;
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~_port[port].output_mask) | (_port[port].output & _port[port].output_mask);
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
}
break;
case 0x01: case 0x03:
return _port[decodedAddress / 2].output_mask;
return port_[decodedAddress / 2].output_mask;
break;
// Timer and interrupt control
case 0x04: case 0x06:
{
uint8_t value = (uint8_t)(_timer.value >> _timer.activeShift);
_timer.interrupt_enabled = !!(address&0x08);
_interrupt_status &= ~InterruptFlag::Timer;
case 0x04: case 0x06: {
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
if(_timer.activeShift != _timer.writtenShift) {
unsigned int shift = _timer.writtenShift - _timer.activeShift;
_timer.value = (_timer.value << shift) | ((1 << shift) - 1);
_timer.activeShift = _timer.writtenShift;
if(timer_.activeShift != timer_.writtenShift) {
unsigned int shift = timer_.writtenShift - timer_.activeShift;
timer_.value = (timer_.value << shift) | ((1 << shift) - 1);
timer_.activeShift = timer_.writtenShift;
}
return value;
}
break;
case 0x05: case 0x07:
{
uint8_t value = _interrupt_status;
_interrupt_status &= ~InterruptFlag::PA7;
case 0x05: case 0x07: {
uint8_t value = interrupt_status_;
interrupt_status_ &= ~InterruptFlag::PA7;
evaluate_interrupts();
return value;
}
@@ -112,90 +104,83 @@ template <class T> class MOS6532 {
return 0xff;
}
inline void run_for_cycles(unsigned int number_of_cycles)
{
inline void run_for_cycles(unsigned int number_of_cycles) {
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(_timer.value >= number_of_cycles) {
_timer.value -= number_of_cycles;
if(timer_.value >= number_of_cycles) {
timer_.value -= number_of_cycles;
} else {
number_of_cycles -= _timer.value;
_timer.value = 0x100 - number_of_cycles;
_timer.activeShift = 0;
_interrupt_status |= InterruptFlag::Timer;
number_of_cycles -= timer_.value;
timer_.value = (0x100 - number_of_cycles) & 0xff;
timer_.activeShift = 0;
interrupt_status_ |= InterruptFlag::Timer;
evaluate_interrupts();
}
}
MOS6532() :
_interrupt_status(0),
_port{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
_a7_interrupt({.last_port_value = 0, .enabled = false}),
_interrupt_line(false)
{}
interrupt_status_(0),
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
a7_interrupt_({.last_port_value = 0, .enabled = false}),
interrupt_line_(false),
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
inline void set_port_did_change(int port)
{
if(!port)
{
uint8_t new_port_a_value = (get_port_input(0) & ~_port[0].output_mask) | (_port[0].output & _port[0].output_mask);
uint8_t difference = new_port_a_value ^ _a7_interrupt.last_port_value;
_a7_interrupt.last_port_value = new_port_a_value;
if(difference&0x80)
{
inline void set_port_did_change(int port) {
if(!port) {
uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
a7_interrupt_.last_port_value = new_port_a_value;
if(difference&0x80) {
if(
((new_port_a_value&0x80) && _a7_interrupt.active_on_positive) ||
(!(new_port_a_value&0x80) && !_a7_interrupt.active_on_positive)
)
{
_interrupt_status |= InterruptFlag::PA7;
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
) {
interrupt_status_ |= InterruptFlag::PA7;
evaluate_interrupts();
}
}
}
}
inline bool get_inerrupt_line()
{
return _interrupt_line;
inline bool get_inerrupt_line() {
return interrupt_line_;
}
private:
uint8_t _ram[128];
uint8_t ram_[128];
struct {
unsigned int value;
unsigned int activeShift, writtenShift;
bool interrupt_enabled;
} _timer;
} timer_;
struct {
bool enabled;
bool active_on_positive;
uint8_t last_port_value;
} _a7_interrupt;
} a7_interrupt_;
struct {
uint8_t output_mask, output;
} _port[2];
} port_[2];
uint8_t _interrupt_status;
uint8_t interrupt_status_;
enum InterruptFlag: uint8_t {
Timer = 0x80,
PA7 = 0x40
};
bool _interrupt_line;
bool interrupt_line_;
// 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) {}
inline void evaluate_interrupts()
{
_interrupt_line =
((_interrupt_status&InterruptFlag::Timer) && _timer.interrupt_enabled) ||
((_interrupt_status&InterruptFlag::PA7) && _a7_interrupt.enabled);
set_irq_line(_interrupt_line);
inline void evaluate_interrupts() {
interrupt_line_ =
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);
set_irq_line(interrupt_line_);
}
};

View File

@@ -11,23 +11,20 @@
using namespace MOS;
Speaker::Speaker() :
_volume(0),
_control_registers{0, 0, 0, 0},
_shift_registers{0, 0, 0, 0},
_counters{2, 1, 0, 0} // create a slight phase offset for the three channels
{}
volume_(0),
control_registers_{0, 0, 0, 0},
shift_registers_{0, 0, 0, 0},
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
void Speaker::set_volume(uint8_t volume)
{
void Speaker::set_volume(uint8_t volume) {
enqueue([=]() {
_volume = volume;
volume_ = volume;
});
}
void Speaker::set_control(int channel, uint8_t value)
{
void Speaker::set_control(int channel, uint8_t value) {
enqueue([=]() {
_control_registers[channel] = value;
control_registers_[channel] = value;
});
}
@@ -99,19 +96,17 @@ static uint8_t noise_pattern[] = {
0xf0, 0xe1, 0xe0, 0x78, 0x70, 0x38, 0x3c, 0x3e, 0x1e, 0x3c, 0x1e, 0x1c, 0x70, 0x3c, 0x38, 0x3f,
};
#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] = (unsigned int)(_control_registers[r]&0x7f) << m; }
#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] = (unsigned int)(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
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
// means every second cycle, etc.
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
{
for(unsigned int c = 0; c < number_of_samples; c++)
{
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);
@@ -120,18 +115,16 @@ void Speaker::get_samples(unsigned int 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] = (
(_shift_registers[0]&1) +
(_shift_registers[1]&1) +
(_shift_registers[2]&1) +
((noise_pattern[_shift_registers[3] >> 3] >> (_shift_registers[3]&7))&(_control_registers[3] >> 7)&1)
) * _volume * 700 + _volume * 44;
(shift_registers_[0]&1) +
(shift_registers_[1]&1) +
(shift_registers_[2]&1) +
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
) * volume_ * 700 + volume_ * 44;
}
}
void Speaker::skip_samples(unsigned int number_of_samples)
{
for(unsigned int c = 0; c < number_of_samples; c++)
{
void Speaker::skip_samples(unsigned int number_of_samples) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);

View File

@@ -26,10 +26,10 @@ class Speaker: public ::Outputs::Filter<Speaker> {
void skip_samples(unsigned int number_of_samples);
private:
unsigned int _counters[4];
unsigned int _shift_registers[4];
uint8_t _control_registers[4];
uint8_t _volume;
unsigned int counters_[4];
unsigned int shift_registers_[4];
uint8_t control_registers_[4];
uint8_t volume_;
};
/*!
@@ -43,37 +43,33 @@ class Speaker: public ::Outputs::Filter<Speaker> {
template <class T> class MOS6560 {
public:
MOS6560() :
_crt(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
_speaker(new Speaker),
_horizontal_counter(0),
_vertical_counter(0),
_cycles_since_speaker_update(0),
_is_odd_frame(false),
_is_odd_line(false)
{
_crt->set_composite_sampling_function(
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
speaker_(new Speaker),
horizontal_counter_(0),
vertical_counter_(0),
cycles_since_speaker_update_(0),
is_odd_frame_(false),
is_odd_line_(false) {
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"float y = float(c >> 4) / 4.0;"
"uint yC = c & 15u;"
"float phaseOffset = 6.283185308 * float(yC) / 16.0;"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = cos(phase + phaseOffset);"
"return mix(y, step(yC, 14) * chroma, amplitude);"
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
"}");
// default to NTSC
set_output_mode(OutputMode::NTSC);
}
void set_clock_rate(double clock_rate)
{
_speaker->set_input_rate((float)(clock_rate / 4.0));
void set_clock_rate(double clock_rate) {
speaker_->set_input_rate((float)(clock_rate / 4.0));
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; }
std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; }
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
enum OutputMode {
PAL, NTSC
@@ -82,152 +78,151 @@ template <class T> class MOS6560 {
/*!
Sets the output mode to either PAL or NTSC.
*/
void set_output_mode(OutputMode output_mode)
{
_output_mode = output_mode;
uint8_t luminances[16] = { // range is 04
0, 4, 1, 3, 2, 2, 1, 3,
2, 1, 2, 1, 2, 3, 2, 3
void set_output_mode(OutputMode output_mode) {
output_mode_ = output_mode;
// Lumunances are encoded trivially: on a 0255 scale.
const uint8_t luminances[16] = {
0, 255, 109, 189,
199, 144, 159, 161,
126, 227, 227, 207,
235, 173, 188, 196
};
uint8_t pal_chrominances[16] = { // range is 015; 15 is a special case meaning "no chrominance"
15, 15, 5, 13, 2, 10, 0, 8,
6, 7, 5, 13, 2, 10, 0, 8,
// Chrominances are encoded such that 0128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green.
const uint8_t pal_chrominances[16] = {
255, 255, 40, 112,
8, 88, 120, 56,
40, 48, 40, 112,
8, 88, 120, 56,
};
uint8_t ntsc_chrominances[16] = {
15, 15, 2, 10, 4, 12, 6, 14,
0, 8, 2, 10, 4, 12, 6, 14,
const uint8_t ntsc_chrominances[16] = {
255, 255, 40, 104,
64, 120, 80, 16,
32, 32, 40, 104,
64, 120, 80, 16,
};
uint8_t *chrominances;
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
switch(output_mode)
{
switch(output_mode) {
case OutputMode::PAL:
chrominances = pal_chrominances;
display_type = Outputs::CRT::PAL50;
_timing.cycles_per_line = 71;
_timing.line_counter_increment_offset = 0;
_timing.lines_per_progressive_field = 312;
_timing.supports_interlacing = false;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 0;
timing_.lines_per_progressive_field = 312;
timing_.supports_interlacing = false;
break;
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::CRT::NTSC60;
_timing.cycles_per_line = 65;
_timing.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
_timing.lines_per_progressive_field = 261;
_timing.supports_interlacing = true;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
timing_.lines_per_progressive_field = 261;
timing_.supports_interlacing = true;
break;
}
_crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type);
// _crt->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
// switch(output_mode)
// {
// switch(output_mode) {
// case OutputMode::PAL:
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
// break;
// case OutputMode::NTSC:
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
// break;
// }
for(int c = 0; c < 16; c++)
{
_colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
for(int c = 0; c < 16; c++) {
uint8_t *colour = (uint8_t *)&colours_[c];
colour[0] = luminances[c];
colour[1] = chrominances[c];
}
}
/*!
Runs for cycles. Derr.
*/
inline void run_for_cycles(unsigned int number_of_cycles)
{
inline void run_for_cycles(unsigned int number_of_cycles) {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
_cycles_since_speaker_update += number_of_cycles;
cycles_since_speaker_update_ += number_of_cycles;
while(number_of_cycles--)
{
while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = _vertical_counter;
int previous_vertical_counter = vertical_counter_;
// keep track of internal time relative to this scanline
_horizontal_counter++;
_full_frame_counter++;
if(_horizontal_counter == _timing.cycles_per_line)
{
if(_horizontal_drawing_latch)
{
_current_character_row++;
horizontal_counter_++;
full_frame_counter_++;
if(horizontal_counter_ == timing_.cycles_per_line) {
if(horizontal_drawing_latch_) {
current_character_row_++;
if(
(_current_character_row == 16) ||
(_current_character_row == 8 && !_registers.tall_characters)
(current_character_row_ == 16) ||
(current_character_row_ == 8 && !registers_.tall_characters)
) {
_current_character_row = 0;
_current_row++;
current_character_row_ = 0;
current_row_++;
}
_pixel_line_cycle = -1;
_columns_this_line = -1;
_column_counter = -1;
pixel_line_cycle_ = -1;
columns_this_line_ = -1;
column_counter_ = -1;
}
_horizontal_counter = 0;
if(_output_mode == OutputMode::PAL) _is_odd_line ^= true;
_horizontal_drawing_latch = false;
horizontal_counter_ = 0;
if(output_mode_ == OutputMode::PAL) is_odd_line_ ^= true;
horizontal_drawing_latch_ = false;
_vertical_counter ++;
if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field))
{
_vertical_counter = 0;
_full_frame_counter = 0;
vertical_counter_ ++;
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
vertical_counter_ = 0;
full_frame_counter_ = 0;
if(_output_mode == OutputMode::NTSC) _is_odd_frame ^= true;
_current_row = 0;
_rows_this_field = -1;
_vertical_drawing_latch = false;
_base_video_matrix_address_counter = 0;
_current_character_row = 0;
if(output_mode_ == OutputMode::NTSC) is_odd_frame_ ^= true;
current_row_ = 0;
rows_this_field_ = -1;
vertical_drawing_latch_ = false;
base_video_matrix_address_counter_ = 0;
current_character_row_ = 0;
}
}
// check for vertical starting events
_vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1);
_horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location);
vertical_drawing_latch_ |= registers_.first_row_location == (previous_vertical_counter >> 1);
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
if(_pixel_line_cycle >= 0) _pixel_line_cycle++;
switch(_pixel_line_cycle)
{
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
switch(pixel_line_cycle_) {
case -1:
if(_horizontal_drawing_latch)
{
_pixel_line_cycle = 0;
_video_matrix_address_counter = _base_video_matrix_address_counter;
if(horizontal_drawing_latch_) {
pixel_line_cycle_ = 0;
video_matrix_address_counter_ = base_video_matrix_address_counter_;
}
break;
case 1: _columns_this_line = _registers.number_of_columns; break;
case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break;
case 3: if(_current_row < _rows_this_field) _column_counter = 0; break;
case 1: columns_this_line_ = registers_.number_of_columns; break;
case 2: if(rows_this_field_ < 0) rows_this_field_ = registers_.number_of_rows; break;
case 3: if(current_row_ < rows_this_field_) column_counter_ = 0; break;
}
uint16_t fetch_address = 0x1c;
if(_column_counter >= 0 && _column_counter < _columns_this_line*2)
{
if(_column_counter&1)
{
fetch_address = _registers.character_cell_start_address + (_character_code*(_registers.tall_characters ? 16 : 8)) + _current_character_row;
}
else
{
fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter);
_video_matrix_address_counter++;
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
} else {
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
video_matrix_address_counter_++;
if(
(_current_character_row == 15) ||
(_current_character_row == 7 && !_registers.tall_characters)
(current_character_row_ == 15) ||
(current_character_row_ == 7 && !registers_.tall_characters)
) {
_base_video_matrix_address_counter = _video_matrix_address_counter;
base_video_matrix_address_counter_ = video_matrix_address_counter_;
}
}
}
@@ -242,99 +237,85 @@ template <class T> class MOS6560 {
// divide the byte it is set for 3:1 and then continue as usual.
// determine output state; colour burst and sync timing are currently a guess
if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst;
else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync;
else
{
_this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border;
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync;
else {
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
}
// apply vertical sync
if(
(_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) ||
(_registers.interlaced &&
(vertical_counter_ < 3 && (is_odd_frame_ || !registers_.interlaced)) ||
(registers_.interlaced &&
(
(_vertical_counter == 0 && _horizontal_counter > 32) ||
(_vertical_counter == 1) || (_vertical_counter == 2) ||
(_vertical_counter == 3 && _horizontal_counter <= 32)
(vertical_counter_ == 0 && horizontal_counter_ > 32) ||
(vertical_counter_ == 1) || (vertical_counter_ == 2) ||
(vertical_counter_ == 3 && horizontal_counter_ <= 32)
)
))
_this_state = State::Sync;
this_state_ = State::Sync;
// update the CRT
if(_this_state != _output_state)
{
switch(_output_state)
{
case State::Sync: _crt->output_sync(_cycles_in_state * 4); break;
case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, (_is_odd_frame || _is_odd_line) ? 128 : 0, 0); break;
case State::Border: output_border(_cycles_in_state * 4); break;
case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break;
if(this_state_ != output_state_) {
switch(output_state_) {
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0); break;
case State::Border: output_border(cycles_in_state_ * 4); break;
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
}
_output_state = _this_state;
_cycles_in_state = 0;
output_state_ = this_state_;
cycles_in_state_ = 0;
pixel_pointer = nullptr;
if(_output_state == State::Pixels)
{
pixel_pointer = _crt->allocate_write_area(260);
if(output_state_ == State::Pixels) {
pixel_pointer = (uint16_t *)crt_->allocate_write_area(260);
}
}
_cycles_in_state++;
cycles_in_state_++;
if(_this_state == State::Pixels)
{
if(_column_counter&1)
{
_character_value = pixel_data;
if(this_state_ == State::Pixels) {
if(column_counter_&1) {
character_value_ = pixel_data;
if(pixel_pointer)
{
uint8_t cell_colour = _colours[_character_colour & 0x7];
if(!(_character_colour&0x8))
{
uint8_t colours[2];
if(_registers.invertedCells)
{
if(pixel_pointer) {
uint16_t cell_colour = colours_[character_colour_ & 0x7];
if(!(character_colour_&0x8)) {
uint16_t colours[2];
if(registers_.invertedCells) {
colours[0] = cell_colour;
colours[1] = _registers.backgroundColour;
}
else
{
colours[0] = _registers.backgroundColour;
colours[1] = registers_.backgroundColour;
} else {
colours[0] = registers_.backgroundColour;
colours[1] = cell_colour;
}
pixel_pointer[0] = colours[(_character_value >> 7)&1];
pixel_pointer[1] = colours[(_character_value >> 6)&1];
pixel_pointer[2] = colours[(_character_value >> 5)&1];
pixel_pointer[3] = colours[(_character_value >> 4)&1];
pixel_pointer[4] = colours[(_character_value >> 3)&1];
pixel_pointer[5] = colours[(_character_value >> 2)&1];
pixel_pointer[6] = colours[(_character_value >> 1)&1];
pixel_pointer[7] = colours[(_character_value >> 0)&1];
}
else
{
uint8_t colours[4] = {_registers.backgroundColour, _registers.borderColour, cell_colour, _registers.auxiliary_colour};
pixel_pointer[0] = colours[(character_value_ >> 7)&1];
pixel_pointer[1] = colours[(character_value_ >> 6)&1];
pixel_pointer[2] = colours[(character_value_ >> 5)&1];
pixel_pointer[3] = colours[(character_value_ >> 4)&1];
pixel_pointer[4] = colours[(character_value_ >> 3)&1];
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
} else {
uint16_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
pixel_pointer[0] =
pixel_pointer[1] = colours[(_character_value >> 6)&3];
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
pixel_pointer[2] =
pixel_pointer[3] = colours[(_character_value >> 4)&3];
pixel_pointer[3] = colours[(character_value_ >> 4)&3];
pixel_pointer[4] =
pixel_pointer[5] = colours[(_character_value >> 2)&3];
pixel_pointer[5] = colours[(character_value_ >> 2)&3];
pixel_pointer[6] =
pixel_pointer[7] = colours[(_character_value >> 0)&3];
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
}
pixel_pointer += 8;
}
}
else
{
_character_code = pixel_data;
_character_colour = colour_data;
} else {
character_code_ = pixel_data;
character_colour_ = colour_data;
}
_column_counter++;
column_counter_++;
}
}
}
@@ -342,39 +323,37 @@ template <class T> class MOS6560 {
/*!
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
*/
inline void synchronise() { update_audio(); }
inline void synchronise() { update_audio(); speaker_->flush(); }
/*!
Writes to a 6560 register.
*/
void set_register(int address, uint8_t value)
{
void set_register(int address, uint8_t value) {
address &= 0xf;
_registers.direct_values[address] = value;
switch(address)
{
registers_.direct_values[address] = value;
switch(address) {
case 0x0:
_registers.interlaced = !!(value&0x80) && _timing.supports_interlacing;
_registers.first_column_location = value & 0x7f;
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
registers_.first_column_location = value & 0x7f;
break;
case 0x1:
_registers.first_row_location = value;
registers_.first_row_location = value;
break;
case 0x2:
_registers.number_of_columns = value & 0x7f;
_registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
break;
case 0x3:
_registers.number_of_rows = (value >> 1)&0x3f;
_registers.tall_characters = !!(value&0x01);
registers_.number_of_rows = (value >> 1)&0x3f;
registers_.tall_characters = !!(value&0x01);
break;
case 0x5:
_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));
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:
@@ -382,26 +361,24 @@ template <class T> class MOS6560 {
case 0xc:
case 0xd:
update_audio();
_speaker->set_control(address - 0xa, value);
speaker_->set_control(address - 0xa, value);
break;
case 0xe:
update_audio();
_registers.auxiliary_colour = _colours[value >> 4];
_speaker->set_volume(value & 0xf);
registers_.auxiliary_colour = colours_[value >> 4];
speaker_->set_volume(value & 0xf);
break;
case 0xf:
{
uint8_t new_border_colour = _colours[value & 0x07];
if(_this_state == State::Border && new_border_colour != _registers.borderColour)
{
output_border(_cycles_in_state * 4);
_cycles_in_state = 0;
case 0xf: {
uint16_t new_border_colour = colours_[value & 0x07];
if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
output_border(cycles_in_state_ * 4);
cycles_in_state_ = 0;
}
_registers.invertedCells = !((value >> 3)&1);
_registers.borderColour = new_border_colour;
_registers.backgroundColour = _colours[value >> 4];
registers_.invertedCells = !((value >> 3)&1);
registers_.borderColour = new_border_colour;
registers_.backgroundColour = colours_[value >> 4];
}
break;
@@ -415,27 +392,24 @@ template <class T> class MOS6560 {
/*
Reads from a 6560 register.
*/
uint8_t get_register(int address)
{
uint8_t get_register(int address) {
address &= 0xf;
int current_line = (_full_frame_counter + _timing.line_counter_increment_offset) / _timing.cycles_per_line;
switch(address)
{
default: return _registers.direct_values[address];
case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f);
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (current_line >> 1) & 0xff;
}
}
private:
std::shared_ptr<Outputs::CRT::CRT> _crt;
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::shared_ptr<Speaker> _speaker;
unsigned int _cycles_since_speaker_update;
void update_audio()
{
_speaker->run_for_cycles(_cycles_since_speaker_update >> 2);
_cycles_since_speaker_update &= 3;
std::shared_ptr<Speaker> speaker_;
unsigned int cycles_since_speaker_update_;
void update_audio() {
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
cycles_since_speaker_update_ &= 3;
}
// register state
@@ -444,45 +418,44 @@ template <class T> class MOS6560 {
uint8_t first_column_location, first_row_location;
uint8_t number_of_columns, number_of_rows;
uint16_t character_cell_start_address, video_matrix_start_address;
uint8_t backgroundColour, borderColour, auxiliary_colour;
uint16_t backgroundColour, borderColour, auxiliary_colour;
bool invertedCells;
uint8_t direct_values[16];
} _registers;
} registers_;
// output state
enum State {
Sync, ColourBurst, Border, Pixels
} _this_state, _output_state;
unsigned int _cycles_in_state;
} this_state_, output_state_;
unsigned int cycles_in_state_;
// counters that cover an entire field
int _horizontal_counter, _vertical_counter, _full_frame_counter;
int horizontal_counter_, vertical_counter_, full_frame_counter_;
// latches dictating start and length of drawing
bool _vertical_drawing_latch, _horizontal_drawing_latch;
int _rows_this_field, _columns_this_line;
bool vertical_drawing_latch_, horizontal_drawing_latch_;
int rows_this_field_, columns_this_line_;
// current drawing position counter
int _pixel_line_cycle, _column_counter;
int _current_row;
uint16_t _current_character_row;
uint16_t _video_matrix_address_counter, _base_video_matrix_address_counter;
int pixel_line_cycle_, column_counter_;
int current_row_;
uint16_t current_character_row_;
uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
// data latched from the bus
uint8_t _character_code, _character_colour, _character_value;
uint8_t character_code_, character_colour_, character_value_;
bool _is_odd_frame, _is_odd_line;
bool is_odd_frame_, is_odd_line_;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint8_t _colours[16];
uint16_t colours_[16];
uint8_t *pixel_pointer;
void output_border(unsigned int number_of_cycles)
{
uint8_t *colour_pointer = _crt->allocate_write_area(1);
if(colour_pointer) *colour_pointer = _registers.borderColour;
_crt->output_level(number_of_cycles);
uint16_t *pixel_pointer;
void output_border(unsigned int number_of_cycles) {
uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1);
if(colour_pointer) *colour_pointer = registers_.borderColour;
crt_->output_level(number_of_cycles);
}
struct {
@@ -490,8 +463,8 @@ template <class T> class MOS6560 {
int line_counter_increment_offset;
int lines_per_progressive_field;
bool supports_interlacing;
} _timing;
OutputMode _output_mode;
} timing_;
OutputMode output_mode_;
};
}

View File

@@ -11,56 +11,52 @@
using namespace GI;
AY38910::AY38910() :
_selected_register(0),
_tone_counters{0, 0, 0}, _tone_periods{0, 0, 0}, _tone_outputs{0, 0, 0},
_noise_shift_register(0xffff), _noise_period(0), _noise_counter(0), _noise_output(0),
_envelope_divider(0), _envelope_period(0), _envelope_position(0),
_master_divider(0),
_output_registers{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
{
_output_registers[8] = _output_registers[9] = _output_registers[10] = 0;
selected_register_(0),
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
master_divider_(0),
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} {
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
// set up envelope lookup tables
for(int c = 0; c < 16; c++)
{
for(int p = 0; p < 32; p++)
{
switch(c)
{
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; p++) {
switch(c) {
case 0: case 1: case 2: case 3: case 9:
_envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0;
_envelope_overflow_masks[c] = 0x1f;
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
envelope_overflow_masks_[c] = 0x1f;
break;
case 4: case 5: case 6: case 7: case 15:
_envelope_shapes[c][p] = (p < 16) ? p : 0;
_envelope_overflow_masks[c] = 0x1f;
envelope_shapes_[c][p] = (p < 16) ? p : 0;
envelope_overflow_masks_[c] = 0x1f;
break;
case 8:
_envelope_shapes[c][p] = (p & 0xf) ^ 0xf;
_envelope_overflow_masks[c] = 0x00;
envelope_shapes_[c][p] = (p & 0xf) ^ 0xf;
envelope_overflow_masks_[c] = 0x00;
break;
case 12:
_envelope_shapes[c][p] = (p & 0xf);
_envelope_overflow_masks[c] = 0x00;
envelope_shapes_[c][p] = (p & 0xf);
envelope_overflow_masks_[c] = 0x00;
break;
case 10:
_envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
_envelope_overflow_masks[c] = 0x00;
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
envelope_overflow_masks_[c] = 0x00;
break;
case 14:
_envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
_envelope_overflow_masks[c] = 0x00;
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
envelope_overflow_masks_[c] = 0x00;
break;
case 11:
_envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0xf;
_envelope_overflow_masks[c] = 0x1f;
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf;
envelope_overflow_masks_[c] = 0x1f;
break;
case 13:
_envelope_shapes[c][p] = (p < 16) ? p : 0xf;
_envelope_overflow_masks[c] = 0x1f;
envelope_shapes_[c][p] = (p < 16) ? p : 0xf;
envelope_overflow_masks_[c] = 0x1f;
break;
}
}
@@ -69,36 +65,30 @@ AY38910::AY38910() :
// set up volume lookup table
float max_volume = 8192;
float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++)
{
_volumes[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
for(int v = 0; v < 16; v++) {
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
}
_volumes[0] = 0;
volumes_[0] = 0;
}
void AY38910::set_clock_rate(double clock_rate)
{
void AY38910::set_clock_rate(double clock_rate) {
set_input_rate((float)clock_rate);
}
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
{
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
int c = 0;
while((_master_divider&15) && c < number_of_samples)
{
target[c] = _output_volume;
_master_divider++;
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
}
while(c < number_of_samples)
{
while(c < number_of_samples) {
#define step_channel(c) \
if(_tone_counters[c]) _tone_counters[c]--;\
else\
{\
_tone_outputs[c] ^= 1;\
_tone_counters[c] = _tone_periods[c];\
if(tone_counters_[c]) tone_counters_[c]--;\
else {\
tone_outputs_[c] ^= 1;\
tone_counters_[c] = tone_periods_[c];\
}
// update the tone channels
@@ -110,50 +100,47 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
// it into the official 17 upon divider underflow.
if(_noise_counter) _noise_counter--;
else
{
_noise_counter = _noise_period;
_noise_output ^= _noise_shift_register&1;
_noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17;
_noise_shift_register >>= 1;
if(noise_counter_) noise_counter_--;
else {
noise_counter_ = noise_period_;
noise_output_ ^= noise_shift_register_&1;
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
noise_shift_register_ >>= 1;
}
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
// implementing non-repeating patterns by locking them to table position 0x1f.
if(_envelope_divider) _envelope_divider--;
else
{
_envelope_divider = _envelope_period;
_envelope_position ++;
if(_envelope_position == 32) _envelope_position = _envelope_overflow_masks[_output_registers[13]];
if(envelope_divider_) envelope_divider_--;
else {
envelope_divider_ = envelope_period_;
envelope_position_ ++;
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
}
evaluate_output_volume();
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
{
target[c] = _output_volume;
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
target[c] = output_volume_;
c++;
_master_divider++;
master_divider_++;
}
}
_master_divider &= 15;
master_divider_ &= 7;
}
void AY38910::evaluate_output_volume()
{
int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position];
void AY38910::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
// The output level for a channel is:
// 1 if neither tone nor noise is enabled;
// 0 if either tone or noise is enabled and its value is low.
// (which is implemented here with reverse logic, assuming _channel_output and _noise_output are already inverted)
#define level(c, tb, nb) \
(((((_output_registers[7] >> tb)&1)^1) & _tone_outputs[c]) | ((((_output_registers[7] >> nb)&1)^1) & _noise_output)) ^ 1
// The tone/noise enable bits use inverse logic — 0 = on, 1 = off — permitting the OR logic below.
#define tone_level(c, tone_bit) (tone_outputs_[c] | (output_registers_[7] >> tone_bit))
#define noise_level(c, noise_bit) (noise_output_ | (output_registers_[7] >> noise_bit))
int channel_levels[3] = {
#define level(c, tone_bit, noise_bit) tone_level(c, tone_bit) & noise_level(c, noise_bit) & 1
const int channel_levels[3] = {
level(0, 0, 3),
level(1, 1, 4),
level(2, 2, 5),
@@ -162,9 +149,9 @@ void AY38910::evaluate_output_volume()
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
#define channel_volume(c) \
((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0xf)
((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf)
int volumes[3] = {
const int volumes[3] = {
channel_volume(8),
channel_volume(9),
channel_volume(10)
@@ -172,92 +159,89 @@ void AY38910::evaluate_output_volume()
#undef channel_volume
// Mix additively.
_output_volume = (int16_t)(
_volumes[volumes[0]] * channel_levels[0] +
_volumes[volumes[1]] * channel_levels[1] +
_volumes[volumes[2]] * channel_levels[2]
output_volume_ = (int16_t)(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
}
void AY38910::select_register(uint8_t r)
{
_selected_register = r & 0xf;
void AY38910::select_register(uint8_t r) {
selected_register_ = r & 0xf;
}
void AY38910::set_register_value(uint8_t value)
{
_registers[_selected_register] = value;
if(_selected_register < 14)
{
int selected_register = _selected_register;
void AY38910::set_register_value(uint8_t value) {
registers_[selected_register_] = value;
if(selected_register_ < 14) {
int selected_register = selected_register_;
enqueue([=] () {
uint8_t masked_value = value;
switch(selected_register)
{
switch(selected_register) {
case 0: case 2: case 4:
case 1: case 3: case 5:
{
case 1: case 3: case 5: {
int channel = selected_register >> 1;
if(selected_register & 1)
_tone_periods[channel] = (_tone_periods[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
else
_tone_periods[channel] = (_tone_periods[channel] & ~0xff) | value;
_tone_counters[channel] = _tone_periods[channel];
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
tone_counters_[channel] = tone_periods_[channel];
}
break;
case 6:
_noise_period = value & 0x1f;
_noise_counter = _noise_period;
noise_period_ = value & 0x1f;
noise_counter_ = noise_period_;
break;
case 11:
_envelope_period = (_envelope_period & ~0xff) | value;
_envelope_divider = _envelope_period;
envelope_period_ = (envelope_period_ & ~0xff) | value;
envelope_divider_ = envelope_period_;
break;
case 12:
_envelope_period = (_envelope_period & 0xff) | (int)(value << 8);
_envelope_divider = _envelope_period;
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
envelope_divider_ = envelope_period_;
break;
case 13:
masked_value &= 0xf;
_envelope_position = 0;
envelope_position_ = 0;
break;
}
_output_registers[selected_register] = masked_value;
output_registers_[selected_register] = masked_value;
evaluate_output_volume();
});
}
}
uint8_t AY38910::get_register_value()
{
return _registers[_selected_register];
uint8_t AY38910::get_register_value() {
// This table ensures that bits that aren't defined within the AY are returned as 1s
// when read. I can't find documentation on this and don't have a machine to test, so
// this is provisionally a guess. TODO: investigate.
const uint8_t register_masks[16] = {
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0xe0, 0x00,
0xe0, 0xe0, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x00
};
return registers_[selected_register_] | register_masks[selected_register_];
}
uint8_t AY38910::get_port_output(bool port_b)
{
return _registers[port_b ? 15 : 14];
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
void AY38910::set_data_input(uint8_t r)
{
_data_input = r;
void AY38910::set_data_input(uint8_t r) {
data_input_ = r;
}
uint8_t AY38910::get_data_output()
{
return _data_output;
uint8_t AY38910::get_data_output() {
return data_output_;
}
void AY38910::set_control_lines(ControlLines control_lines)
{
void AY38910::set_control_lines(ControlLines control_lines) {
ControlState new_state;
switch((int)control_lines)
{
switch((int)control_lines) {
default: new_state = Inactive; break;
case (int)(BCDIR | BC2 | BC1):
@@ -268,15 +252,13 @@ void AY38910::set_control_lines(ControlLines control_lines)
case (int)(BCDIR | BC2): new_state = Write; break;
}
if(new_state != _control_state)
{
_control_state = new_state;
switch(new_state)
{
if(new_state != control_state_) {
control_state_ = new_state;
switch(new_state) {
default: break;
case LatchAddress: select_register(_data_input); break;
case Write: set_register_value(_data_input); break;
case Read: _data_output = get_register_value(); break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;
}
}
}

View File

@@ -51,42 +51,42 @@ class AY38910: public ::Outputs::Filter<AY38910> {
void get_samples(unsigned int number_of_samples, int16_t *target);
private:
int _selected_register;
uint8_t _registers[16], _output_registers[16];
int selected_register_;
uint8_t registers_[16], output_registers_[16];
int _master_divider;
int master_divider_;
int _tone_periods[3];
int _tone_counters[3];
int _tone_outputs[3];
int tone_periods_[3];
int tone_counters_[3];
int tone_outputs_[3];
int _noise_period;
int _noise_counter;
int _noise_shift_register;
int _noise_output;
int noise_period_;
int noise_counter_;
int noise_shift_register_;
int noise_output_;
int _envelope_period;
int _envelope_divider;
int _envelope_position;
int _envelope_shapes[16][32];
int _envelope_overflow_masks[16];
int envelope_period_;
int envelope_divider_;
int envelope_position_;
int envelope_shapes_[16][32];
int envelope_overflow_masks_[16];
int _volumes[16];
int volumes_[16];
enum ControlState {
Inactive,
LatchAddress,
Read,
Write
} _control_state;
} control_state_;
void select_register(uint8_t r);
void set_register_value(uint8_t value);
uint8_t get_register_value();
uint8_t _data_input, _data_output;
uint8_t data_input_, data_output_;
int16_t _output_volume;
int16_t output_volume_;
inline void evaluate_output_volume();
};

View File

@@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue()
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#else
thread_.reset(new std::thread([this]() {
while(!should_destruct_)
{
while(!should_destruct_) {
std::function<void(void)> next_function;
// Take lock, check for a new task
std::unique_lock<std::mutex> lock(queue_mutex_);
if(!pending_tasks_.empty())
{
if(!pending_tasks_.empty()) {
next_function = pending_tasks_.front();
pending_tasks_.pop_front();
}
if(next_function)
{
if(next_function) {
// If there is a task, release lock and perform it
lock.unlock();
next_function();
}
else
{
} else {
// If there isn't a task, atomically block on the processing condition and release the lock
// until there's something pending (and then release it again via scope)
processing_condition_.wait(lock);
@@ -48,8 +43,7 @@ AsyncTaskQueue::AsyncTaskQueue()
#endif
}
AsyncTaskQueue::~AsyncTaskQueue()
{
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
dispatch_release(serial_dispatch_queue_);
#else
@@ -60,8 +54,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
#endif
}
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
{
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
#ifdef __APPLE__
dispatch_async(serial_dispatch_queue_, ^{function();});
#else
@@ -71,8 +64,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
#endif
}
void AsyncTaskQueue::flush()
{
void AsyncTaskQueue::flush() {
#ifdef __APPLE__
dispatch_sync(serial_dispatch_queue_, ^{});
#else

File diff suppressed because it is too large Load Diff

View File

@@ -12,71 +12,21 @@
#include <stdint.h>
#include "../../Processors/6502/CPU6502.hpp"
#include "../../Components/6532/6532.hpp"
#include "../CRTMachine.hpp"
#include "Bus.hpp"
#include "PIA.hpp"
#include "Speaker.hpp"
#include "TIA.hpp"
#include "../ConfigurationTarget.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
const unsigned int number_of_upcoming_events = 6;
const unsigned int number_of_recorded_counters = 7;
class Speaker: public ::Outputs::Filter<Speaker> {
public:
Speaker();
~Speaker();
void set_volume(int channel, uint8_t volume);
void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control);
void get_samples(unsigned int number_of_samples, int16_t *target);
private:
uint8_t _volume[2];
uint8_t _divider[2];
uint8_t _control[2];
int _poly4_counter[2];
int _poly5_counter[2];
int _poly9_counter[2];
int _output_state[2];
int _divider_counter[2];
int _pattern_periods[16];
int _patterns[16][512];
};
class PIA: public MOS::MOS6532<PIA> {
public:
inline uint8_t get_port_input(int port)
{
return _portValues[port];
}
inline void update_port_input(int port, uint8_t mask, bool set)
{
if(set) _portValues[port] &= ~mask; else _portValues[port] |= mask;
set_port_did_change(port);
}
PIA() :
_portValues{0xff, 0xff}
{}
private:
uint8_t _portValues[2];
};
class Machine:
public CPU6502::Processor<Machine>,
public CRTMachine::Machine,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public Outputs::CRT::Delegate {
public:
Machine();
@@ -87,138 +37,31 @@ class Machine:
void set_digital_input(Atari2600DigitalInput input, bool state);
void set_switch_is_enabled(Atari2600Switch input, bool state);
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
void synchronise();
void set_reset_line(bool state) { bus_->set_reset_line(state); }
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; }
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
// TODO: different rate for PAL
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); }
// to satisfy Outputs::CRT::Delegate
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);
private:
uint8_t *_rom, *_romPages[4];
size_t _rom_size;
// the bus
std::unique_ptr<Bus> bus_;
// the RIOT
PIA _mos6532;
// output frame rate tracker
struct FrameRecord {
unsigned int number_of_frames;
unsigned int number_of_unexpected_vertical_syncs;
// playfield registers
uint8_t _playfieldControl;
uint8_t _playfieldColour;
uint8_t _backgroundColour;
uint8_t _playfield[41];
// ... and derivatives
int _ballSize, _missileSize[2];
// delayed clock events
enum OutputState {
Sync,
Blank,
ColourBurst,
Pixel
};
struct Event {
enum Action {
Playfield = 1 << 0,
ResetCounter = 1 << 1,
HMoveSetup = 1 << 2,
HMoveCompare = 1 << 3,
HMoveDecrement = 1 << 4,
};
int updates;
OutputState state;
uint8_t playfieldPixel;
int counter;
Event() : updates(0), playfieldPixel(0) {}
} _upcomingEvents[number_of_upcoming_events];
unsigned int _upcomingEventsPointer;
// object counters
struct ObjectCounter {
int count; // the counter value, multiplied by four, counting phase
int pixel; // for non-sprite objects, a count of cycles since the last counter reset; for sprite objects a count of pixels so far elapsed
int broad_pixel; // for sprite objects, a count of cycles since the last counter reset; otherwise unused
ObjectCounter() : count(0), pixel(0), broad_pixel(0) {}
} _objectCounter[number_of_recorded_counters][5];
unsigned int _objectCounterPointer;
// the latched playfield output
uint8_t _playfieldOutput, _nextPlayfieldOutput;
// player registers
uint8_t _playerColour[2];
uint8_t _playerReflectionMask[2];
uint8_t _playerGraphics[2][2];
uint8_t _playerGraphicsSelector[2];
bool _playerStart[2];
// object flags
bool _hasSecondCopy[2];
bool _hasThirdCopy[2];
bool _hasFourthCopy[2];
uint8_t _objectMotion[5]; // the value stored to this counter's motion register
// player + missile registers
uint8_t _playerAndMissileSize[2];
// missile registers
uint8_t _missileGraphicsEnable[2];
bool _missileGraphicsReset[2];
// ball registers
uint8_t _ballGraphicsEnable[2];
uint8_t _ballGraphicsSelector;
// graphics output
unsigned int _horizontalTimer;
bool _vSyncEnabled, _vBlankEnabled;
// horizontal motion control
uint8_t _hMoveCounter;
uint8_t _hMoveFlags;
// joystick state
uint8_t _tiaInputValue[2];
// collisions
uint8_t _collisions[8];
void output_pixels(unsigned int count);
uint8_t get_output_pixel();
void update_timers(int mask);
// outputs
std::shared_ptr<Outputs::CRT::CRT> _crt;
std::shared_ptr<Speaker> _speaker;
// current mode
bool _is_pal_region;
// speaker backlog accumlation counter
unsigned int _cycles_since_speaker_update;
void update_audio();
// latched output state
unsigned int _lastOutputStateDuration;
OutputState _stateByExtendTime[2][57];
OutputState *_stateByTime;
OutputState _lastOutputState;
uint8_t *_outputBuffer;
// lookup table for collision reporting
uint8_t _reportedCollisions[64][8];
void setup_reported_collisions();
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
} frame_records_[4];
unsigned int frame_record_pointer_;
bool is_ntsc_;
};
}

View File

@@ -0,0 +1,64 @@
//
// Bus.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_Bus_hpp
#define Atari2600_Bus_hpp
#include "Atari2600.hpp"
#include "PIA.hpp"
#include "Speaker.hpp"
#include "TIA.hpp"
namespace Atari2600 {
class Bus {
public:
Bus() :
tia_input_value_{0xff, 0xff},
cycles_since_speaker_update_(0),
cycles_since_video_update_(0),
cycles_since_6532_update_(0) {}
virtual void run_for_cycles(int number_of_cycles) = 0;
virtual void set_reset_line(bool state) = 0;
// the RIOT, TIA and speaker
PIA mos6532_;
std::shared_ptr<TIA> tia_;
std::shared_ptr<Speaker> speaker_;
// joystick state
uint8_t tia_input_value_[2];
protected:
// speaker backlog accumlation counter
unsigned int cycles_since_speaker_update_;
inline void update_audio() {
unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3);
cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3);
speaker_->run_for_cycles(audio_cycles);
}
// video backlog accumulation counter
unsigned int cycles_since_video_update_;
inline void update_video() {
tia_->run_for_cycles((int)cycles_since_video_update_);
cycles_since_video_update_ = 0;
}
// RIOT backlog accumulation counter
unsigned int cycles_since_6532_update_;
inline void update_6532() {
mos6532_.run_for_cycles(cycles_since_6532_update_);
cycles_since_6532_update_ = 0;
}
};
}
#endif /* Atari2600_Bus_hpp */

View File

@@ -0,0 +1,176 @@
//
// Cartridge.h
// Clock Signal
//
// Created by Thomas Harte on 17/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_Cartridge_hpp
#define Atari2600_Cartridge_hpp
#include "../../../Processors/6502/CPU6502.hpp"
#include "../Bus.hpp"
namespace Atari2600 {
template<class T> class Cartridge:
public CPU6502::Processor<Cartridge<T>>,
public Bus {
public:
Cartridge(const std::vector<uint8_t> &rom) :
rom_(rom) {}
void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); }
void set_reset_line(bool state) { CPU6502::Processor<Cartridge<T>>::set_reset_line(state); }
void advance_cycles(unsigned int cycles) {}
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
uint8_t returnValue = 0xff;
unsigned int cycles_run_for = 3;
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
// skips to the end of the line.
if(operation == CPU6502::BusOperation::Ready)
cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_since_speaker_update_ += cycles_run_for;
cycles_since_video_update_ += cycles_run_for;
cycles_since_6532_update_ += (cycles_run_for / 3);
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
if(operation != CPU6502::BusOperation::Ready) {
// give the cartridge a chance to respond to the bus access
static_cast<T *>(this)->perform_bus_operation(operation, address, value);
// check for a RIOT RAM access
if((address&0x1280) == 0x80) {
if(isReadOperation(operation)) {
returnValue &= mos6532_.get_ram(address);
} else {
mos6532_.set_ram(address, *value);
}
}
// check for a TIA access
if(!(address&0x1080)) {
if(isReadOperation(operation)) {
const uint16_t decodedAddress = address & 0xf;
switch(decodedAddress) {
case 0x00: // missile 0 / player collisions
case 0x01: // missile 1 / player collisions
case 0x02: // player 0 / playfield / ball collisions
case 0x03: // player 1 / playfield / ball collisions
case 0x04: // missile 0 / playfield / ball collisions
case 0x05: // missile 1 / playfield / ball collisions
case 0x06: // ball / playfield collisions
case 0x07: // player / player, missile / missile collisions
returnValue &= tia_->get_collision_flags(decodedAddress);
break;
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
// TODO: pot ports
returnValue &= 0;
break;
case 0x0c:
case 0x0d:
returnValue &= tia_input_value_[decodedAddress - 0x0c];
break;
}
} else {
const uint16_t decodedAddress = address & 0x3f;
switch(decodedAddress) {
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x02: CPU6502::Processor<Cartridge<T>>::set_ready_line(true); break;
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
// TODO: audio will now be out of synchronisation — fix
case 0x04:
case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x06:
case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_->set_background_colour(*value); break;
case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break;
case 0x0b:
case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0d:
case 0x0e:
case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break;
case 0x10:
case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break;
case 0x12:
case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_->set_ball_position(); break;
case 0x1b:
case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1d:
case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_->set_ball_enable((*value)&2); break;
case 0x20:
case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break;
case 0x22:
case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_->set_ball_motion(*value); break;
case 0x25:
case 0x26: tia_->set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_->set_ball_delay((*value)&1); break;
case 0x28:
case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_->move(); break;
case 0x2b: update_video(); tia_->clear_motion(); break;
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break;
case 0x17:
case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break;
case 0x19:
case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break;
}
}
}
// check for a PIA access
if((address&0x1280) == 0x280) {
update_6532();
if(isReadOperation(operation)) {
returnValue &= mos6532_.get_register(address);
} else {
mos6532_.set_register(address, *value);
}
}
if(isReadOperation(operation)) {
*value &= returnValue;
}
}
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU6502::Processor<Cartridge<T>>::set_ready_line(false);
return cycles_run_for / 3;
}
void synchronise() {
update_audio();
update_video();
speaker_->flush();
}
protected:
std::vector<uint8_t> rom_;
};
}
#endif /* Atari2600_Cartridge_hpp */

View File

@@ -0,0 +1,50 @@
//
// CartridgeActivisionStack.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeActivisionStack_hpp
#define Atari2600_CartridgeActivisionStack_hpp
namespace Atari2600 {
class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
public:
CartridgeActivisionStack(const std::vector<uint8_t> &rom) :
Cartridge(rom),
last_opcode_(0x00) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
// RST or JSR.
if(operation == CPU6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
if(address & 0x2000) {
rom_ptr_ = rom_.data();
} else {
rom_ptr_ = rom_.data() + 4096;
}
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(operation == CPU6502::BusOperation::ReadOpcode) last_opcode_ = *value;
}
private:
uint8_t *rom_ptr_;
uint8_t last_opcode_;
};
}
#endif /* Atari2600_CartridgeActivisionStack_hpp */

View File

@@ -0,0 +1,66 @@
//
// CartridgeAtari8k.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeAtari16k_hpp
#define Atari2600_CartridgeAtari16k_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
public:
CartridgeAtari16k(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
};
class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
public:
CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[128];
};
}
#endif /* Atari2600_CartridgeAtari16k_hpp */

View File

@@ -0,0 +1,66 @@
//
// CartridgeAtari8k.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeAtari32k_hpp
#define Atari2600_CartridgeAtari32k_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
public:
CartridgeAtari32k(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
};
class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
public:
CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[128];
};
}
#endif /* Atari2600_CartridgeAtari32k_hpp */

View File

@@ -0,0 +1,68 @@
//
// CartridgeAtari8k.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeAtari8k_hpp
#define Atari2600_CartridgeAtari8k_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
public:
CartridgeAtari8k(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff8) rom_ptr_ = rom_.data();
else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
};
class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
public:
CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff8) rom_ptr_ = rom_.data();
if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1080) ram_[address & 0x7f] = *value;
else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[128];
};
}
#endif /* Atari2600_CartridgeAtari8k_hpp */

View File

@@ -0,0 +1,44 @@
//
// CartridgeCBSRAMPlus.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeCBSRAMPlus_hpp
#define Atari2600_CartridgeCBSRAMPlus_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
public:
CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
if(address < 0x1100) ram_[address & 0xff] = *value;
else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff];
}
private:
uint8_t *rom_ptr_;
uint8_t ram_[256];
};
}
#endif /* Atari2600_CartridgeCBSRAMPlus_hpp */

View File

@@ -0,0 +1,42 @@
//
// CartridgeCommaVid.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeCommaVid_hpp
#define Atari2600_CartridgeCommaVid_hpp
namespace Atari2600 {
class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
public:
CartridgeCommaVid(const std::vector<uint8_t> &rom) :
Cartridge(rom) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
address &= 0x1fff;
if(address < 0x1400) {
if(isReadOperation(operation)) *value = ram_[address & 1023];
return;
}
if(address < 0x1800) {
ram_[address & 1023] = *value;
return;
}
if(isReadOperation(operation)) *value = rom_[address & 2047];
}
private:
uint8_t ram_[1024];
};
}
#endif /* Atari2600_CartridgeCommaVid_hpp */

View File

@@ -0,0 +1,68 @@
//
// CartridgeMNetwork.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeMNetwork_hpp
#define Atari2600_CartridgeMNetwork_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
public:
CartridgeMNetwork(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
high_ram_ptr_ = high_ram_;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address <= 0x1fe6) {
rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048;
} else if(address == 0x1fe7) {
rom_ptr_[0] = nullptr;
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
int offset = (address - 0x1ff8) * 256;
high_ram_ptr_ = &high_ram_[offset];
}
if(address & 0x800) {
if(address < 0x1900) {
high_ram_ptr_[address & 255] = *value;
} else if(address < 0x1a00) {
if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255];
} else {
if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047];
}
} else {
if(rom_ptr_[0]) {
if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047];
} else {
if(address < 0x1400) {
low_ram_[address & 1023] = *value;
} else {
if(isReadOperation(operation)) *value = low_ram_[address & 1023];
}
}
}
}
private:
uint8_t *rom_ptr_[2];
uint8_t *high_ram_ptr_;
uint8_t low_ram_[1024], high_ram_[1024];
};
}
#endif /* Atari2600_CartridgeMNetwork_hpp */

View File

@@ -0,0 +1,45 @@
//
// CartridgeMegaBoy.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeMegaBoy_hpp
#define Atari2600_CartridgeMegaBoy_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
public:
CartridgeMegaBoy(const std::vector<uint8_t> &rom) :
Cartridge(rom),
current_page_(0) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff0) {
current_page_ = (current_page_ + 1) & 15;
rom_ptr_ = rom_.data() + current_page_ * 4096;
}
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
}
private:
uint8_t *rom_ptr_;
uint8_t current_page_;
};
}
#endif /* CartridgeMegaBoy_h */

View File

@@ -0,0 +1,46 @@
//
// CartridgeParkerBros.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeParkerBros_hpp
#define Atari2600_CartridgeParkerBros_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
public:
CartridgeParkerBros(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + 4096;
rom_ptr_[1] = rom_ptr_[0] + 1024;
rom_ptr_[2] = rom_ptr_[1] + 1024;
rom_ptr_[3] = rom_ptr_[2] + 1024;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address < 0x1ff8) {
int slot = (address >> 3)&3;
rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024);
}
if(isReadOperation(operation)) {
*value = rom_ptr_[(address >> 10)&3][address & 1023];
}
}
private:
uint8_t *rom_ptr_[4];
};
}
#endif /* Atari2600_CartridgeParkerBros_hpp */

View File

@@ -0,0 +1,134 @@
//
// CartridgePitfall2.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgePitfall2_hpp
#define Atari2600_CartridgePitfall2_hpp
namespace Atari2600 {
class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
public:
CartridgePitfall2(const std::vector<uint8_t> &rom) :
Cartridge(rom),
random_number_generator_(0),
featcher_address_{0, 0, 0, 0, 0, 0, 0, 0},
mask_{0, 0, 0, 0, 0, 0, 0, 0},
cycles_since_audio_update_(0) {
rom_ptr_ = rom_.data();
}
void advance_cycles(unsigned int cycles) {
cycles_since_audio_update_ += cycles;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
switch(address) {
#pragma mark - Reads
// The random number generator
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
if(isReadOperation(operation)) {
*value = random_number_generator_;
}
random_number_generator_ = (uint8_t)(
(random_number_generator_ << 1) |
(~( (random_number_generator_ >> 7) ^
(random_number_generator_ >> 5) ^
(random_number_generator_ >> 4) ^
(random_number_generator_ >> 3)
) & 1));
break;
case 0x1005: case 0x1006: case 0x1007:
*value = update_audio();
break;
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
*value = rom_[8192 + address_for_counter(address & 7)];
break;
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
break;
#pragma mark - Writes
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
top_[address & 7] = *value;
break;
case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f:
bottom_[address & 7] = *value;
break;
case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value;
mask_[address & 7] = 0x00;
break;
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8);
break;
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
random_number_generator_ = 0;
break;
#pragma mark - Paging
case 0x1ff8: rom_ptr_ = rom_.data(); break;
case 0x1ff9: rom_ptr_ = rom_.data() + 4096; break;
#pragma mark - Business as usual
default:
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
}
break;
}
}
private:
inline uint16_t address_for_counter(int counter) {
uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047;
if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff;
if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00;
featcher_address_[counter]--;
return fetch_address;
}
inline uint8_t update_audio() {
const unsigned int clock_divisor = 57;
unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor;
cycles_since_audio_update_ %= clock_divisor;
int table_position = 0;
for(int c = 0; c < 3; c++) {
audio_channel_[c] = (audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]);
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
table_position |= 0x4 >> c;
}
}
static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf };
return level_table[table_position];
}
uint16_t featcher_address_[8];
uint8_t top_[8], bottom_[8], mask_[8];
uint8_t music_mode_[3];
uint8_t random_number_generator_;
uint8_t *rom_ptr_;
uint8_t audio_channel_[3];
unsigned int cycles_since_audio_update_;
};
}
#endif /* Atari2600_CartridgePitfall2_hpp */

View File

@@ -0,0 +1,40 @@
//
// CartridgeTigervision.h
// Clock Signal
//
// Created by Thomas Harte on 18/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeTigervision_hpp
#define Atari2600_CartridgeTigervision_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
public:
CartridgeTigervision(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if((address&0x1fff) == 0x3f) {
int offset = ((*value) * 2048) & (rom_.size() - 1);
rom_ptr_[0] = rom_.data() + offset;
return;
} else if((address&0x1000) && isReadOperation(operation)) {
*value = rom_ptr_[(address >> 11)&1][address & 2047];
}
}
private:
uint8_t *rom_ptr_[2];
};
}
#endif /* Atari2600_CartridgeTigervision_hpp */

View File

@@ -0,0 +1,30 @@
//
// CartridgeUnpaged.h
// Clock Signal
//
// Created by Thomas Harte on 17/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeUnpaged_hpp
#define Atari2600_CartridgeUnpaged_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> {
public:
CartridgeUnpaged(const std::vector<uint8_t> &rom) :
Cartridge(rom) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(isReadOperation(operation) && (address & 0x1000)) {
*value = rom_[address & (rom_.size() - 1)];
}
}
};
}
#endif /* Atari2600_CartridgeUnpaged_hpp */

View File

@@ -0,0 +1,38 @@
//
// PIA.h
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_PIA_h
#define Atari2600_PIA_h
#include "../../Components/6532/6532.hpp"
namespace Atari2600 {
class PIA: public MOS::MOS6532<PIA> {
public:
inline uint8_t get_port_input(int port) {
return port_values_[port];
}
inline void update_port_input(int port, uint8_t mask, bool set) {
if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask;
set_port_did_change(port);
}
PIA() :
port_values_{0xff, 0xff}
{}
private:
uint8_t port_values_[2];
};
}
#endif /* PIA_h */

View File

@@ -0,0 +1,124 @@
//
// Speaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Speaker.hpp"
using namespace Atari2600;
Atari2600::Speaker::Speaker() :
poly4_counter_{0x00f, 0x00f},
poly5_counter_{0x01f, 0x01f},
poly9_counter_{0x1ff, 0x1ff}
{}
void Atari2600::Speaker::set_volume(int channel, uint8_t volume) {
enqueue([=]() {
volume_[channel] = volume & 0xf;
});
}
void Atari2600::Speaker::set_divider(int channel, uint8_t divider) {
enqueue([=]() {
divider_[channel] = divider & 0x1f;
divider_counter_[channel] = 0;
});
}
void Atari2600::Speaker::set_control(int channel, uint8_t control) {
enqueue([=]() {
control_[channel] = control & 0xf;
});
}
#define advance_poly4(c) poly4_counter_[channel] = (poly4_counter_[channel] >> 1) | (((poly4_counter_[channel] << 3) ^ (poly4_counter_[channel] << 2))&0x008)
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
target[c] = 0;
for(int channel = 0; channel < 2; channel++) {
divider_counter_[channel] ++;
int divider_value = divider_counter_[channel] / (38 / CPUTicksPerAudioTick);
int level = 0;
switch(control_[channel]) {
case 0x0: case 0xb: // constant 1
level = 1;
break;
case 0x4: case 0x5: // div2 tone
level = (divider_value / (divider_[channel]+1))&1;
break;
case 0xc: case 0xd: // div6 tone
level = (divider_value / ((divider_[channel]+1)*3))&1;
break;
case 0x6: case 0xa: // div31 tone
level = (divider_value / (divider_[channel]+1))%30 <= 18;
break;
case 0xe: // div93 tone
level = (divider_value / ((divider_[channel]+1)*3))%30 <= 18;
break;
case 0x1: // 4-bit poly
level = poly4_counter_[channel]&1;
if(divider_value == divider_[channel]+1) {
divider_counter_[channel] = 0;
advance_poly4(channel);
}
break;
case 0x2: // 4-bit poly div31
level = poly4_counter_[channel]&1;
if(divider_value%(30*(divider_[channel]+1)) == 18) {
advance_poly4(channel);
}
break;
case 0x3: // 5/4-bit poly
level = output_state_[channel];
if(divider_value == divider_[channel]+1) {
if(poly5_counter_[channel]&1) {
output_state_[channel] = poly4_counter_[channel]&1;
advance_poly4(channel);
}
advance_poly5(channel);
}
break;
case 0x7: case 0x9: // 5-bit poly
level = poly5_counter_[channel]&1;
if(divider_value == divider_[channel]+1) {
divider_counter_[channel] = 0;
advance_poly5(channel);
}
break;
case 0xf: // 5-bit poly div6
level = poly5_counter_[channel]&1;
if(divider_value == (divider_[channel]+1)*3) {
divider_counter_[channel] = 0;
advance_poly5(channel);
}
break;
case 0x8: // 9-bit poly
level = poly9_counter_[channel]&1;
if(divider_value == divider_[channel]+1) {
divider_counter_[channel] = 0;
advance_poly9(channel);
}
break;
}
target[c] += volume_[channel] * 1024 * level;
}
}
}

View File

@@ -0,0 +1,45 @@
//
// Speaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_Speaker_hpp
#define Atari2600_Speaker_hpp
#include "../../Outputs/Speaker.hpp"
namespace Atari2600 {
// This should be a divisor of 38; audio counters are updated every 38 cycles — though lesser dividers
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
const int CPUTicksPerAudioTick = 2;
class Speaker: public ::Outputs::Filter<Speaker> {
public:
Speaker();
void set_volume(int channel, uint8_t volume);
void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control);
void get_samples(unsigned int number_of_samples, int16_t *target);
private:
uint8_t volume_[2];
uint8_t divider_[2];
uint8_t control_[2];
int poly4_counter_[2];
int poly5_counter_[2];
int poly9_counter_[2];
int output_state_[2];
int divider_counter_[2];
};
}
#endif /* Speaker_hpp */

679
Machines/Atari2600/TIA.cpp Normal file
View File

@@ -0,0 +1,679 @@
//
// TIA.cpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "TIA.hpp"
#include <cassert>
using namespace Atari2600;
namespace {
const int cycles_per_line = 228;
const int first_pixel_cycle = 68;
const int sync_flag = 0x1;
const int blank_flag = 0x2;
uint8_t reverse_table[256];
}
TIA::TIA(bool create_crt) :
horizontal_counter_(0),
pixels_start_location_(0),
output_mode_(0),
pixel_target_(nullptr),
background_{0, 0},
background_half_mask_(0),
horizontal_blank_extend_(false),
collision_flags_(0)
{
if(create_crt) {
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
crt_->set_output_device(Outputs::CRT::Television);
set_output_mode(OutputMode::NTSC);
}
for(int c = 0; c < 256; c++) {
reverse_table[c] = (uint8_t)(
((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) |
((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7)
);
}
for(int c = 0; c < 64; c++) {
bool has_playfield = c & (int)(CollisionType::Playfield);
bool has_ball = c & (int)(CollisionType::Ball);
bool has_player0 = c & (int)(CollisionType::Player0);
bool has_player1 = c & (int)(CollisionType::Player1);
bool has_missile0 = c & (int)(CollisionType::Missile0);
bool has_missile1 = c & (int)(CollisionType::Missile1);
uint8_t collision_registers[8];
collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00);
collision_registers[1] = ((has_missile1 && has_player0) ? 0x80 : 0x00) | ((has_missile1 && has_player1) ? 0x40 : 0x00);
collision_registers[2] = ((has_playfield && has_player0) ? 0x80 : 0x00) | ((has_ball && has_player0) ? 0x40 : 0x00);
collision_registers[3] = ((has_playfield && has_player1) ? 0x80 : 0x00) | ((has_ball && has_player1) ? 0x40 : 0x00);
collision_registers[4] = ((has_playfield && has_missile0) ? 0x80 : 0x00) | ((has_ball && has_missile0) ? 0x40 : 0x00);
collision_registers[5] = ((has_playfield && has_missile1) ? 0x80 : 0x00) | ((has_ball && has_missile1) ? 0x40 : 0x00);
collision_registers[6] = ((has_playfield && has_ball) ? 0x80 : 0x00);
collision_registers[7] = ((has_player0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_missile1) ? 0x40 : 0x00);
collision_flags_by_buffer_vaules_[c] =
(collision_registers[0] >> 6) |
(collision_registers[1] >> 4) |
(collision_registers[2] >> 2) |
(collision_registers[3] >> 0) |
(collision_registers[4] << 2) |
(collision_registers[5] << 4) |
(collision_registers[6] << 6) |
(collision_registers[7] << 8);
// all priority modes show the background if nothing else is present
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background;
// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour
if(has_playfield || has_ball) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall;
}
// test 1 for score mode: if there is a ball pixel, plot that colour
if(has_ball) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall;
}
// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour
if(has_player1 || has_missile1) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1;
}
// in the right-hand side of score mode, the playfield has the same priority as player 1
if(has_playfield) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1;
}
// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead
if(has_player0 || has_missile0) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0;
}
// if this is the left-hand side of score mode, the playfield has the same priority as player 0
if(has_playfield) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0;
}
// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others
if(has_playfield || has_ball) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall;
}
}
}
TIA::TIA() : TIA(true) {}
TIA::TIA(std::function<void(uint8_t *output_buffer)> line_end_function) : TIA(false) {
line_end_function_ = line_end_function;
}
void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
Outputs::CRT::DisplayType display_type;
if(output_mode == OutputMode::NTSC) {
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);"
"}");
display_type = Outputs::CRT::DisplayType::NTSC60;
} else {
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"uint direction = iPhase & 1u;"
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
"phaseOffset *= 6.283185308 / 12.0;"
"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);"
"}");
display_type = Outputs::CRT::DisplayType::PAL50;
}
// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari
// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled
// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply
// cycles_per_line * 2 cycles of information from one sync edge to the next
crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type);
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
}
void TIA::run_for_cycles(int number_of_cycles)
{
// if part way through a line, definitely perform a partial, at most up to the end of the line
if(horizontal_counter_) {
int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
output_for_cycles(cycles);
number_of_cycles -= cycles;
}
// output full lines for as long as possible
while(number_of_cycles >= cycles_per_line) {
output_line();
number_of_cycles -= cycles_per_line;
}
// partly start a new line if necessary
if(number_of_cycles) {
output_for_cycles(number_of_cycles);
}
}
void TIA::set_sync(bool sync) {
output_mode_ = (output_mode_ & ~sync_flag) | (sync ? sync_flag : 0);
}
void TIA::set_blank(bool blank) {
output_mode_ = (output_mode_ & ~blank_flag) | (blank ? blank_flag : 0);
}
void TIA::reset_horizontal_counter() {
}
int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) {
return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line;
}
void TIA::set_background_colour(uint8_t colour) {
colour_palette_[(int)ColourIndex::Background] = colour;
}
void TIA::set_playfield(uint16_t offset, uint8_t value) {
assert(offset >= 0 && offset < 3);
switch(offset) {
case 0:
background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16);
background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4);
break;
case 1:
background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8);
background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4);
break;
case 2:
background_[1] = (background_[1] & 0xfff00) | reverse_table[value];
background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12);
break;
}
}
void TIA::set_playfield_control_and_ball_size(uint8_t value) {
background_half_mask_ = value & 1;
switch(value & 6) {
case 0:
playfield_priority_ = PlayfieldPriority::Standard;
break;
case 2:
playfield_priority_ = PlayfieldPriority::Score;
break;
case 4:
case 6:
playfield_priority_ = PlayfieldPriority::OnTop;
break;
}
ball_.size = 1 << ((value >> 4)&3);
}
void TIA::set_playfield_ball_colour(uint8_t colour) {
colour_palette_[(int)ColourIndex::PlayfieldBall] = colour;
}
void TIA::set_player_number_and_size(int player, uint8_t value) {
assert(player >= 0 && player < 2);
int size = 0;
switch(value & 7) {
case 0: case 1: case 2: case 3: case 4:
player_[player].copy_flags = value & 7;
break;
case 5:
size = 1;
player_[player].copy_flags = 0;
break;
case 6:
player_[player].copy_flags = 6;
break;
case 7:
size = 2;
player_[player].copy_flags = 0;
break;
}
missile_[player].size = 1 << ((value >> 4)&3);
missile_[player].copy_flags = player_[player].copy_flags;
player_[player].adder = 4 >> size;
}
void TIA::set_player_graphic(int player, uint8_t value) {
assert(player >= 0 && player < 2);
player_[player].graphic[1] = value;
player_[player^1].graphic[0] = player_[player^1].graphic[1];
if(player) ball_.enabled[0] = ball_.enabled[1];
}
void TIA::set_player_reflected(int player, bool reflected) {
assert(player >= 0 && player < 2);
player_[player].reverse_mask = reflected ? 7 : 0;
}
void TIA::set_player_delay(int player, bool delay) {
assert(player >= 0 && player < 2);
player_[player].graphic_index = delay ? 0 : 1;
}
void TIA::set_player_position(int player) {
assert(player >= 0 && player < 2);
// players have an extra clock of delay before output and don't display upon reset;
// both aims are achieved by setting to -1 because: (i) it causes the clock to be
// one behind its real hardware value, creating the extra delay; and (ii) the player
// code is written to start a draw upon wraparound from 159 to 0, so -1 is the
// correct option rather than 159.
player_[player].position = -1;
}
void TIA::set_player_motion(int player, uint8_t motion) {
assert(player >= 0 && player < 2);
player_[player].motion = (motion >> 4)&0xf;
}
void TIA::set_player_missile_colour(int player, uint8_t colour) {
assert(player >= 0 && player < 2);
colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour;
}
void TIA::set_missile_enable(int missile, bool enabled) {
assert(missile >= 0 && missile < 2);
missile_[missile].enabled = enabled;
}
void TIA::set_missile_position(int missile) {
assert(missile >= 0 && missile < 2);
missile_[missile].position = 0;
}
void TIA::set_missile_position_to_player(int missile, bool lock) {
assert(missile >= 0 && missile < 2);
missile_[missile].locked_to_player = lock;
player_[missile].latched_pixel4_time = -1;
}
void TIA::set_missile_motion(int missile, uint8_t motion) {
assert(missile >= 0 && missile < 2);
missile_[missile].motion = (motion >> 4)&0xf;
}
void TIA::set_ball_enable(bool enabled) {
ball_.enabled[1] = enabled;
}
void TIA::set_ball_delay(bool delay) {
ball_.enabled_index = delay ? 0 : 1;
}
void TIA::set_ball_position() {
ball_.position = 0;
// setting the ball position also triggers a draw
ball_.reset_pixels(0);
}
void TIA::set_ball_motion(uint8_t motion) {
ball_.motion = (motion >> 4) & 0xf;
}
void TIA::move() {
horizontal_blank_extend_ = true;
player_[0].is_moving = player_[1].is_moving = missile_[0].is_moving = missile_[1].is_moving = ball_.is_moving = true;
player_[0].motion_step = player_[1].motion_step = missile_[0].motion_step = missile_[1].motion_step = ball_.motion_step = 15;
player_[0].motion_time = player_[1].motion_time = missile_[0].motion_time = missile_[1].motion_time = ball_.motion_time = (horizontal_counter_ + 3) & ~3;
}
void TIA::clear_motion() {
player_[0].motion = player_[1].motion = missile_[0].motion = missile_[1].motion = ball_.motion = 0;
}
uint8_t TIA::get_collision_flags(int offset) {
return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
}
void TIA::clear_collision_flags() {
collision_flags_ = 0;
}
void TIA::output_for_cycles(int number_of_cycles) {
/*
Line timing is oriented around 0 being the start of the right-hand side vertical blank;
a wsync synchronises the CPU to horizontal_counter_ = 0. All timing below is in terms of the
NTSC colour clock.
Therefore, each line is composed of:
16 cycles: blank ; -> 16
16 cycles: sync ; -> 32
16 cycles: colour burst ; -> 48
20 cycles: blank ; -> 68
8 cycles: blank or pixels, depending on whether the blank extend bit is set
152 cycles: pixels
*/
int output_cursor = horizontal_counter_;
horizontal_counter_ += number_of_cycles;
bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224;
if(!output_cursor) {
if(line_end_function_) line_end_function_(collision_buffer_);
memset(collision_buffer_, 0, sizeof(collision_buffer_));
ball_.motion_time %= 228;
player_[0].motion_time %= 228;
player_[1].motion_time %= 228;
missile_[0].motion_time %= 228;
missile_[1].motion_time %= 228;
}
// accumulate an OR'd version of the output into the collision buffer
int latent_start = output_cursor + 4;
int latent_end = horizontal_counter_ + 4;
draw_playfield(latent_start, latent_end);
draw_object<Player>(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_);
draw_object<Player>(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_);
draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_);
draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_);
draw_object<Ball>(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_);
// convert to television signals
#define Period(function, target) \
if(output_cursor < target) { \
if(horizontal_counter_ <= target) { \
if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \
horizontal_counter_ %= cycles_per_line; \
return; \
} else { \
if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \
output_cursor = target; \
} \
}
switch(output_mode_) {
default:
Period(output_blank, 16)
Period(output_sync, 32)
Period(output_default_colour_burst, 48)
Period(output_blank, 68)
break;
case sync_flag:
case sync_flag | blank_flag:
Period(output_sync, 16)
Period(output_blank, 32)
Period(output_default_colour_burst, 48)
Period(output_sync, 228)
break;
}
#undef Period
if(output_mode_ & blank_flag) {
if(pixel_target_) {
output_pixels(pixels_start_location_, output_cursor);
if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
int duration = std::min(228, horizontal_counter_) - output_cursor;
if(crt_) crt_->output_blank((unsigned int)(duration * 2));
} else {
if(!pixels_start_location_ && crt_) {
pixels_start_location_ = output_cursor;
pixel_target_ = crt_->allocate_write_area(160);
}
// convert that into pixels
if(pixel_target_) output_pixels(output_cursor, horizontal_counter_);
// accumulate collision flags
while(output_cursor < horizontal_counter_) {
collision_flags_ |= collision_flags_by_buffer_vaules_[collision_buffer_[output_cursor - first_pixel_cycle]];
output_cursor++;
}
if(horizontal_counter_ == cycles_per_line && crt_) {
crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
}
if(is_reset) horizontal_blank_extend_ = false;
horizontal_counter_ %= cycles_per_line;
}
void TIA::output_pixels(int start, int end) {
start = std::max(start, pixels_start_location_);
int target_position = start - pixels_start_location_;
if(start < first_pixel_cycle+8 && horizontal_blank_extend_) {
while(start < end && start < first_pixel_cycle+8) {
pixel_target_[target_position] = 0;
start++;
target_position++;
}
}
if(playfield_priority_ == PlayfieldPriority::Score) {
while(start < end && start < first_pixel_cycle + 80) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]];
start++;
target_position++;
}
while(start < end) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]];
start++;
target_position++;
}
} else {
int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
while(start < end) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]];
start++;
target_position++;
}
}
}
void TIA::output_line() {
switch(output_mode_) {
default:
// TODO: optimise special case
output_for_cycles(cycles_per_line);
break;
case sync_flag:
case sync_flag | blank_flag:
if(crt_) {
crt_->output_sync(32);
crt_->output_blank(32);
crt_->output_sync(392);
}
horizontal_blank_extend_ = false;
break;
case blank_flag:
if(crt_) {
crt_->output_blank(32);
crt_->output_sync(32);
crt_->output_default_colour_burst(32);
crt_->output_blank(360);
}
horizontal_blank_extend_ = false;
break;
}
}
#pragma mark - Playfield output
void TIA::draw_playfield(int start, int end) {
// don't do anything if this window ends too early
if(end < first_pixel_cycle) return;
// clip to drawable bounds
start = std::max(start, first_pixel_cycle);
end = std::min(end, 228);
// proceed along four-pixel boundaries, plotting four pixels at a time
int aligned_position = (start + 3)&~3;
while(aligned_position < end) {
int offset = (aligned_position - first_pixel_cycle) >> 2;
uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101;
*(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value;
aligned_position += 4;
}
}
#pragma mark - Motion
template<class T> void TIA::perform_motion_step(T &object) {
if((object.motion_step ^ (object.motion ^ 8)) == 0xf) {
object.is_moving = false;
} else {
if(object.position == 159) object.reset_pixels(0);
else if(object.position == 15 && object.copy_flags&1) object.reset_pixels(1);
else if(object.position == 31 && object.copy_flags&2) object.reset_pixels(2);
else if(object.position == 63 && object.copy_flags&4) object.reset_pixels(3);
else object.skip_pixels(1, object.motion_time);
object.position = (object.position + 1) % 160;
object.motion_step --;
object.motion_time += 4;
}
}
template<class T> void TIA::perform_border_motion(T &object, int start, int end) {
while(object.is_moving && object.motion_time < end)
perform_motion_step<T>(object);
}
template<class T> void TIA::draw_object(T &object, const uint8_t collision_identity, int start, int end) {
int first_pixel = first_pixel_cycle - 4 + (horizontal_blank_extend_ ? 8 : 0);
object.dequeue_pixels(collision_buffer_, collision_identity, end - first_pixel_cycle);
// movement works across the entire screen, so do work that falls outside of the pixel area
if(start < first_pixel) {
perform_border_motion<T>(object, start, std::min(end, first_pixel));
}
// don't continue to do any drawing if this window ends too early
if(end < first_pixel) return;
if(start < first_pixel) start = first_pixel;
if(start >= end) return;
// perform the visible part of the line, if any
if(start < 224) {
draw_object_visible<T>(object, collision_identity, start - first_pixel_cycle + 4, std::min(end - first_pixel_cycle + 4, 160), end - first_pixel_cycle);
}
// move further if required
if(object.is_moving && end >= 224 && object.motion_time < end) {
perform_motion_step<T>(object);
}
}
template<class T> void TIA::draw_object_visible(T &object, const uint8_t collision_identity, int start, int end, int time_now) {
// perform a miniature event loop on (i) triggering draws; (ii) drawing; and (iii) motion
int next_motion_time = object.motion_time - first_pixel_cycle + 4;
while(start < end) {
int next_event_time = end;
// is the next event a movement tick?
if(object.is_moving && next_motion_time < next_event_time) {
next_event_time = next_motion_time;
}
// is the next event a graphics trigger?
int next_copy = 160;
int next_copy_id = 0;
if(object.copy_flags) {
if(object.position < 16 && object.copy_flags&1) {
next_copy = 16;
next_copy_id = 1;
} else if(object.position < 32 && object.copy_flags&2) {
next_copy = 32;
next_copy_id = 2;
} else if(object.position < 64 && object.copy_flags&4) {
next_copy = 64;
next_copy_id = 3;
}
}
int next_copy_time = start + next_copy - object.position;
if(next_copy_time < next_event_time) next_event_time = next_copy_time;
// the decision is to progress by length
const int length = next_event_time - start;
// enqueue a future intention to draw pixels if spitting them out now would violate accuracy;
// otherwise draw them now
if(object.enqueues && next_event_time > time_now) {
if(start < time_now) {
object.output_pixels(&collision_buffer_[start], time_now - start, collision_identity, start + first_pixel_cycle - 4);
object.enqueue_pixels(time_now, next_event_time, time_now + first_pixel_cycle - 4);
} else {
object.enqueue_pixels(start, next_event_time, start + first_pixel_cycle - 4);
}
} else {
object.output_pixels(&collision_buffer_[start], length, collision_identity, start + first_pixel_cycle - 4);
}
// the next interesting event is after next_event_time cycles, so progress
object.position = (object.position + length) % 160;
start = next_event_time;
// if the event is a motion tick, apply; if it's a draw trigger, trigger a draw
if(object.is_moving && start == next_motion_time) {
perform_motion_step(object);
next_motion_time += 4;
} else if(start == next_copy_time) {
object.reset_pixels(next_copy_id);
}
}
}
#pragma mark - Missile drawing
void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) {
if(!missile.locked_to_player || player.latched_pixel4_time < 0) {
draw_object<Missile>(missile, collision_identity, start, end);
} else {
draw_object<Missile>(missile, collision_identity, start, player.latched_pixel4_time);
missile.position = 0;
draw_object<Missile>(missile, collision_identity, player.latched_pixel4_time, end);
player.latched_pixel4_time = -1;
}
}

333
Machines/Atari2600/TIA.hpp Normal file
View File

@@ -0,0 +1,333 @@
//
// TIA.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef TIA_hpp
#define TIA_hpp
#include <cstdint>
#include "../CRTMachine.hpp"
namespace Atari2600 {
class TIA {
public:
TIA();
// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will
// be called with the latest collision buffer upon the conclusion of each line. What's a collision
// buffer? It's an implementation detail. If you're not writing a unit test, leave it alone.
TIA(std::function<void(uint8_t *output_buffer)> line_end_function);
enum class OutputMode {
NTSC, PAL
};
/*!
Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the
first cycle performed.
*/
void run_for_cycles(int number_of_cycles);
void set_output_mode(OutputMode output_mode);
void set_sync(bool sync);
void set_blank(bool blank);
void reset_horizontal_counter(); // Reset is delayed by four cycles.
/*!
@returns the number of cycles between (current TIA time) + from_offset to the current or
next horizontal blanking period. Returns numbers in the range [0, 227].
*/
int get_cycles_until_horizontal_blank(unsigned int from_offset);
void set_background_colour(uint8_t colour);
void set_playfield(uint16_t offset, uint8_t value);
void set_playfield_control_and_ball_size(uint8_t value);
void set_playfield_ball_colour(uint8_t colour);
void set_player_number_and_size(int player, uint8_t value);
void set_player_graphic(int player, uint8_t value);
void set_player_reflected(int player, bool reflected);
void set_player_delay(int player, bool delay);
void set_player_position(int player);
void set_player_motion(int player, uint8_t motion);
void set_player_missile_colour(int player, uint8_t colour);
void set_missile_enable(int missile, bool enabled);
void set_missile_position(int missile);
void set_missile_position_to_player(int missile, bool lock);
void set_missile_motion(int missile, uint8_t motion);
void set_ball_enable(bool enabled);
void set_ball_delay(bool delay);
void set_ball_position();
void set_ball_motion(uint8_t motion);
void move();
void clear_motion();
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
private:
TIA(bool create_crt);
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::function<void(uint8_t *output_buffer)> line_end_function_;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_;
// contains flags to indicate whether sync or blank are currently active
int output_mode_;
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
enum class CollisionType : uint8_t {
Playfield = (1 << 0),
Ball = (1 << 1),
Player0 = (1 << 2),
Player1 = (1 << 3),
Missile0 = (1 << 4),
Missile1 = (1 << 5)
};
int collision_flags_;
int collision_flags_by_buffer_vaules_[64];
// colour mapping tables
enum class ColourMode {
Standard = 0,
ScoreLeft,
ScoreRight,
OnTop
};
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry
enum class ColourIndex {
Background = 0,
PlayfieldBall,
PlayerMissile0,
PlayerMissile1
};
uint8_t colour_palette_[4];
// playfield state
int background_half_mask_;
enum class PlayfieldPriority {
Standard,
Score,
OnTop
} playfield_priority_;
uint32_t background_[2]; // contains two 20-bit bitfields representing the background state;
// at index 0 is the left-hand side of the playfield with bit 0 being
// the first bit to display, bit 1 the second, etc. Index 1 contains
// a mirror image of index 0. If the playfield is being displayed in
// mirroring mode, background_[0] will be output on the left and
// background_[1] on the right; otherwise background_[0] will be
// output twice.
// objects
template<class T> struct Object {
// the two programmer-set values
int position;
int motion;
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
int motion_step;
int motion_time;
// indicates whether this object is currently undergoing motion
bool is_moving;
Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {};
};
// player state
struct Player: public Object<Player> {
Player() :
adder(4),
copy_flags(0),
graphic{0, 0},
reverse_mask(false),
graphic_index(0),
pixel_position(32),
pixel_counter(0),
latched_pixel4_time(-1),
copy_index_(0),
queue_read_pointer_(0),
queue_write_pointer_(0) {}
int adder;
int copy_flags; // a bit field, corresponding to the first few values of NUSIZ
uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current
int reverse_mask; // 7 for a reflected player, 0 for normal
int graphic_index;
int pixel_position, pixel_counter;
int latched_pixel4_time;
const bool enqueues = true;
inline void skip_pixels(const int count, int from_horizontal_counter) {
int old_pixel_counter = pixel_counter;
pixel_position = std::min(32, pixel_position + count * adder);
pixel_counter += count;
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
}
}
inline void reset_pixels(int copy) {
pixel_position = pixel_counter = 0;
copy_index_ = copy;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
skip_pixels(count, from_horizontal_counter);
}
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
while(queue_read_pointer_ != queue_write_pointer_) {
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
if(queue_[queue_read_pointer_].end > time_now) {
const int length = time_now - queue_[queue_read_pointer_].start;
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
queue_[queue_read_pointer_].start = time_now;
return;
} else {
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
}
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
}
}
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
queue_[queue_write_pointer_].start = start;
queue_[queue_write_pointer_].end = end;
queue_[queue_write_pointer_].pixel_position = pixel_position;
queue_[queue_write_pointer_].adder = adder;
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
skip_pixels(end - start, from_horizontal_counter);
}
private:
int copy_index_;
struct QueuedPixels {
int start, end;
int pixel_position;
int adder;
int reverse_mask;
QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {}
} queue_[4];
int queue_read_pointer_, queue_write_pointer_;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) {
if(pixel_position == 32 || !graphic[graphic_index]) return;
int output_cursor = 0;
while(pixel_position < 32 && output_cursor < count) {
int shift = (pixel_position >> 2) ^ reverse_mask;
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
output_cursor++;
pixel_position += adder;
}
}
} player_[2];
// common actor for things that appear as a horizontal run of pixels
struct HorizontalRun: public Object<HorizontalRun> {
int pixel_position;
int size;
const bool enqueues = false;
inline void skip_pixels(const int count, int from_horizontal_counter) {
pixel_position = std::max(0, pixel_position - count);
}
inline void reset_pixels(int copy) {
pixel_position = size;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
int output_cursor = 0;
while(pixel_position && output_cursor < count)
{
target[output_cursor] |= collision_identity;
output_cursor++;
pixel_position--;
}
}
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
HorizontalRun() : pixel_position(0), size(1) {}
};
// missile state
struct Missile: public HorizontalRun {
bool enabled;
bool locked_to_player;
int copy_flags;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled && !locked_to_player) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
Missile() : enabled(false), copy_flags(0) {}
} missile_[2];
// ball state
struct Ball: public HorizontalRun {
bool enabled[2];
int enabled_index;
const int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled[enabled_index]) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
Ball() : enabled{false, false}, enabled_index(0) {}
} ball_;
// motion
bool horizontal_blank_extend_;
template<class T> void perform_border_motion(T &object, int start, int end);
template<class T> void perform_motion_step(T &object);
// drawing methods and state
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
inline void draw_playfield(int start, int end);
inline void output_for_cycles(int number_of_cycles);
inline void output_line();
int pixels_start_location_;
uint8_t *pixel_target_;
inline void output_pixels(int start, int end);
};
}
#endif /* TIA_hpp */

View File

@@ -13,49 +13,28 @@
using namespace Commodore::C1540;
Machine::Machine() :
_shift_register(0),
Storage::Disk::Controller(1000000, 4, 300)
{
// create a serial port and a VIA to run it
_serialPortVIA.reset(new SerialPortVIA);
_serialPort.reset(new SerialPort);
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
serial_port_(new SerialPort),
serial_port_VIA_(new SerialPortVIA) {
// attach the serial port to its VIA and vice versa
_serialPort->set_serial_port_via(_serialPortVIA);
_serialPortVIA->set_serial_port(_serialPort);
serial_port_->set_serial_port_via(serial_port_VIA_);
serial_port_VIA_->set_serial_port(serial_port_);
// set this instance as the delegate to receive interrupt requests from both VIAs
_serialPortVIA->set_interrupt_delegate(this);
_driveVIA.set_interrupt_delegate(this);
_driveVIA.set_delegate(this);
serial_port_VIA_->set_interrupt_delegate(this);
drive_VIA_.set_interrupt_delegate(this);
drive_VIA_.set_delegate(this);
// set a bit rate
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
{
Commodore::Serial::AttachPortAndBus(_serialPort, serial_bus);
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
// static bool log = false;
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xF3C0)) log = true;
// if(operation == CPU6502::BusOperation::ReadOpcode && log) printf("%04x\n", address);
// if(operation == CPU6502::BusOperation::ReadOpcode) printf("%04x\n", address);
// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF510 && address <= 0xF553)) printf("%04x\n", address);
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xE887)) printf("A: %02x\n", get_value_of_register(CPU6502::Register::A));
/* static bool log = false;
if(operation == CPU6502::BusOperation::ReadOpcode)
{
log = (address >= 0xE85B && address <= 0xE907) || (address >= 0xE9C9 && address <= 0xEA2D);
if(log) printf("\n%04x: ", address);
}
if(log) printf("[%c %04x] ", isReadOperation(operation) ? 'r' : 'w', address);*/
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
/*
Memory map (given that I'm unsure yet on any potential mirroring):
@@ -64,93 +43,75 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
0x1c000x1c0f the drive VIA
0xc0000xffff ROM
*/
if(address < 0x800)
{
if(address < 0x800) {
if(isReadOperation(operation))
*value = _ram[address];
*value = ram_[address];
else
_ram[address] = *value;
}
else if(address >= 0xc000)
{
ram_[address] = *value;
} else if(address >= 0xc000) {
if(isReadOperation(operation))
*value = _rom[address & 0x3fff];
}
else if(address >= 0x1800 && address <= 0x180f)
{
*value = rom_[address & 0x3fff];
} else if(address >= 0x1800 && address <= 0x180f) {
if(isReadOperation(operation))
*value = _serialPortVIA->get_register(address);
*value = serial_port_VIA_->get_register(address);
else
_serialPortVIA->set_register(address, *value);
}
else if(address >= 0x1c00 && address <= 0x1c0f)
{
serial_port_VIA_->set_register(address, *value);
} else if(address >= 0x1c00 && address <= 0x1c0f) {
if(isReadOperation(operation))
*value = _driveVIA.get_register(address);
*value = drive_VIA_.get_register(address);
else
_driveVIA.set_register(address, *value);
drive_VIA_.set_register(address, *value);
}
_serialPortVIA->run_for_cycles(1);
_driveVIA.run_for_cycles(1);
serial_port_VIA_->run_for_cycles(1);
drive_VIA_.run_for_cycles(1);
return 1;
}
void Machine::set_rom(const uint8_t *rom)
{
memcpy(_rom, rom, sizeof(_rom));
void Machine::set_rom(const uint8_t *rom) {
memcpy(rom_, rom, sizeof(rom_));
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
{
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
drive->set_disk(disk);
set_drive(drive);
}
void Machine::run_for_cycles(int number_of_cycles)
{
void Machine::run_for_cycles(int number_of_cycles) {
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
set_motor_on(_driveVIA.get_motor_enabled());
if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down
set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
Storage::Disk::Controller::run_for_cycles(number_of_cycles);
}
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
{
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
// both VIAs are connected to the IRQ line
set_irq_line(_serialPortVIA->get_interrupt_line() || _driveVIA.get_interrupt_line());
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
}
#pragma mark - Disk drive
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
_shift_register = (_shift_register << 1) | value;
if((_shift_register & 0x3ff) == 0x3ff)
{
_driveVIA.set_sync_detected(true);
_bit_window_offset = -1; // i.e. this bit isn't the first within a data window, but the next might be
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
shift_register_ = (shift_register_ << 1) | value;
if((shift_register_ & 0x3ff) == 0x3ff) {
drive_VIA_.set_sync_detected(true);
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
} else {
drive_VIA_.set_sync_detected(false);
}
else
{
_driveVIA.set_sync_detected(false);
}
_bit_window_offset++;
if(_bit_window_offset == 8)
{
_driveVIA.set_data_input((uint8_t)_shift_register);
_bit_window_offset = 0;
if(_driveVIA.get_should_set_overflow())
{
bit_window_offset_++;
if(bit_window_offset_ == 8) {
drive_VIA_.set_data_input((uint8_t)shift_register_);
bit_window_offset_ = 0;
if(drive_VIA_.get_should_set_overflow()) {
set_overflow_line(true);
}
}
else
set_overflow_line(false);
else set_overflow_line(false);
}
// the 1540 does not recognise index holes
@@ -158,36 +119,30 @@ void Machine::process_index_hole() {}
#pragma mak - Drive VIA delegate
void Machine::drive_via_did_step_head(void *driveVIA, int direction)
{
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
step(direction);
}
void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
{
void Machine::drive_via_did_set_data_density(void *driveVIA, int density) {
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
}
#pragma mark - SerialPortVIA
SerialPortVIA::SerialPortVIA() :
_portB(0x00), _attention_acknowledge_level(false), _attention_level_input(true), _data_level_output(false)
{}
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {}
uint8_t SerialPortVIA::get_port_input(Port port)
{
if(port) return _portB;
uint8_t SerialPortVIA::get_port_input(Port port) {
if(port) return port_b_;
return 0xff;
}
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
{
if(port)
{
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
if(port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
_attention_acknowledge_level = !(value&0x10);
_data_level_output = (value&0x02);
attention_acknowledge_level_ = !(value&0x10);
data_level_output_ = (value&0x02);
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!(value&0x08));
update_data_line();
@@ -195,109 +150,99 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
}
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
{
switch(line)
{
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
switch(line) {
default: break;
case ::Commodore::Serial::Line::Data: _portB = (_portB & ~0x01) | (value ? 0x00 : 0x01); break;
case ::Commodore::Serial::Line::Clock: _portB = (_portB & ~0x04) | (value ? 0x00 : 0x04); break;
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break;
case ::Commodore::Serial::Line::Attention:
_attention_level_input = !value;
_portB = (_portB & ~0x80) | (value ? 0x00 : 0x80);
attention_level_input_ = !value;
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
set_control_line_input(Port::A, Line::One, !value);
update_data_line();
break;
}
}
void SerialPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
{
_serialPort = serialPort;
void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &serialPort) {
serial_port_ = serialPort;
}
void SerialPortVIA::update_data_line()
{
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
if(serialPort)
{
void SerialPortVIA::update_data_line() {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
serialPort->set_output(::Commodore::Serial::Line::Data,
(::Commodore::Serial::LineLevel)(!_data_level_output
&& (_attention_level_input != _attention_acknowledge_level)));
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
}
}
#pragma mark - DriveVIA
void DriveVIA::set_delegate(Delegate *delegate)
{
_delegate = delegate;
void DriveVIA::set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
// write protect tab uncovered
DriveVIA::DriveVIA() : _port_b(0xff), _port_a(0xff), _delegate(nullptr) {}
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
uint8_t DriveVIA::get_port_input(Port port) {
return port ? _port_b : _port_a;
return port ? port_b_ : port_a_;
}
void DriveVIA::set_sync_detected(bool sync_detected) {
_port_b = (_port_b & 0x7f) | (sync_detected ? 0x00 : 0x80);
port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80);
}
void DriveVIA::set_data_input(uint8_t value) {
_port_a = value;
port_a_ = value;
}
bool DriveVIA::get_should_set_overflow() {
return _should_set_overflow;
return should_set_overflow_;
}
bool DriveVIA::get_motor_enabled() {
return _drive_motor;
return drive_motor_;
}
void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
if(port == Port::A && line == Line::Two) {
_should_set_overflow = value;
should_set_overflow_ = value;
}
}
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port)
{
if(port) {
// record drive motor state
_drive_motor = !!(value&4);
drive_motor_ = !!(value&4);
// check for a head step
int step_difference = ((value&3) - (_previous_port_b_output&3))&3;
if(step_difference)
{
if(_delegate) _delegate->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference) {
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
}
// check for a change in density
int density_difference = (_previous_port_b_output^value) & (3 << 5);
if(density_difference && _delegate)
{
_delegate->drive_via_did_set_data_density(this, (value >> 5)&3);
int density_difference = (previous_port_b_output_^value) & (3 << 5);
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
}
// TODO: something with the drive LED
// printf("LED: %s\n", value&8 ? "On" : "Off");
_previous_port_b_output = value;
previous_port_b_output_ = value;
}
}
#pragma mark - SerialPort
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
std::shared_ptr<SerialPortVIA> serialPortVIA = _serialPortVIA.lock();
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
if(serialPortVIA) serialPortVIA->set_serial_line_state(line, (bool)level);
}
void SerialPort::set_serial_port_via(std::shared_ptr<SerialPortVIA> serialPortVIA) {
_serialPortVIA = serialPortVIA;
void SerialPort::set_serial_port_via(const std::shared_ptr<SerialPortVIA> &serialPortVIA) {
serial_port_VIA_ = serialPortVIA;
}

View File

@@ -41,17 +41,17 @@ class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQD
SerialPortVIA();
uint8_t get_port_input(Port port);
uint8_t get_port_input(Port);
void set_port_output(Port port, uint8_t value, uint8_t mask);
void set_serial_line_state(::Commodore::Serial::Line line, bool value);
void set_port_output(Port, uint8_t value, uint8_t mask);
void set_serial_line_state(::Commodore::Serial::Line, bool);
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
private:
uint8_t _portB;
std::weak_ptr<::Commodore::Serial::Port> _serialPort;
bool _attention_acknowledge_level, _attention_level_input, _data_level_output;
uint8_t port_b_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
void update_data_line();
};
@@ -79,7 +79,7 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
};
void set_delegate(Delegate *delegate);
void set_delegate(Delegate *);
using MOS6522IRQDelegate::set_interrupt_status;
@@ -87,21 +87,21 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
uint8_t get_port_input(Port port);
void set_sync_detected(bool sync_detected);
void set_data_input(uint8_t value);
void set_sync_detected(bool);
void set_data_input(uint8_t);
bool get_should_set_overflow();
bool get_motor_enabled();
void set_control_line_output(Port port, Line line, bool value);
void set_control_line_output(Port, Line, bool value);
void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
void set_port_output(Port, uint8_t value, uint8_t direction_mask);
private:
uint8_t _port_b, _port_a;
bool _should_set_overflow;
bool _drive_motor;
uint8_t _previous_port_b_output;
Delegate *_delegate;
uint8_t port_b_, port_a_;
bool should_set_overflow_;
bool drive_motor_;
uint8_t previous_port_b_output_;
Delegate *delegate_;
};
/*!
@@ -109,11 +109,11 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
*/
class SerialPort : public ::Commodore::Serial::Port {
public:
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level);
void set_serial_port_via(std::shared_ptr<SerialPortVIA> serialPortVIA);
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
private:
std::weak_ptr<SerialPortVIA> _serialPortVIA;
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
};
/*!
@@ -152,16 +152,14 @@ class Machine:
void drive_via_did_set_data_density(void *driveVIA, int density);
private:
uint8_t _ram[0x800];
uint8_t _rom[0x4000];
uint8_t ram_[0x800];
uint8_t rom_[0x4000];
std::shared_ptr<SerialPortVIA> _serialPortVIA;
std::shared_ptr<SerialPort> _serialPort;
DriveVIA _driveVIA;
std::shared_ptr<SerialPortVIA> serial_port_VIA_;
std::shared_ptr<SerialPort> serial_port_;
DriveVIA drive_VIA_;
std::shared_ptr<Storage::Disk::Disk> _disk;
int _shift_register, _bit_window_offset;
int shift_register_, bit_window_offset_;
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
};

View File

@@ -10,10 +10,8 @@
using namespace Commodore::Serial;
const char *::Commodore::Serial::StringForLine(Line line)
{
switch(line)
{
const char *::Commodore::Serial::StringForLine(Line line) {
switch(line) {
case ServiceRequest: return "Service request";
case Attention: return "Attention";
case Clock: return "Clock";
@@ -22,48 +20,39 @@ const char *::Commodore::Serial::StringForLine(Line line)
}
}
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus)
{
void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shared_ptr<Bus> bus) {
port->set_serial_bus(bus);
bus->add_port(port);
}
void Bus::add_port(std::shared_ptr<Port> port)
{
_ports.push_back(port);
for(int line = (int)ServiceRequest; line <= (int)Reset; line++)
{
void Bus::add_port(std::shared_ptr<Port> port) {
ports_.push_back(port);
for(int line = (int)ServiceRequest; line <= (int)Reset; line++) {
// the addition of a new device may change the line output...
set_line_output_did_change((Line)line);
// ... but the new device will need to be told the current state regardless
port->set_input((Line)line, _line_levels[line]);
port->set_input((Line)line, line_levels_[line]);
}
}
void Bus::set_line_output_did_change(Line line)
{
void Bus::set_line_output_did_change(Line line) {
// i.e. I believe these lines to be open collector
LineLevel new_line_level = High;
for(std::weak_ptr<Port> port : _ports)
{
for(std::weak_ptr<Port> port : ports_) {
std::shared_ptr<Port> locked_port = port.lock();
if(locked_port)
{
if(locked_port) {
new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(line));
}
}
// post an update only if one occurred
if(new_line_level != _line_levels[line])
{
_line_levels[line] = new_line_level;
if(new_line_level != line_levels_[line]) {
line_levels_[line] = new_line_level;
for(std::weak_ptr<Port> port : _ports)
{
for(std::weak_ptr<Port> port : ports_) {
std::shared_ptr<Port> locked_port = port.lock();
if(locked_port)
{
if(locked_port) {
locked_port->set_input(line, new_line_level);
}
}
@@ -72,22 +61,17 @@ void Bus::set_line_output_did_change(Line line)
#pragma mark - The debug port
void DebugPort::set_input(Line line, LineLevel value)
{
_input_levels[line] = value;
void DebugPort::set_input(Line line, LineLevel value) {
input_levels_[line] = value;
printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low");
if(!_incoming_count)
{
_incoming_count = (!_input_levels[Line::Clock] && !_input_levels[Line::Data]) ? 8 : 0;
}
else
{
if(line == Line::Clock && value)
{
_incoming_byte = (_incoming_byte >> 1) | (_input_levels[Line::Data] ? 0x80 : 0x00);
if(!incoming_count_) {
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
} else {
if(line == Line::Clock && value) {
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
}
_incoming_count--;
if(_incoming_count == 0) printf("[Bus] Observed %02x\n", _incoming_byte);
incoming_count_--;
if(incoming_count_ == 0) printf("[Bus] Observed %02x\n", incoming_byte_);
}
}

View File

@@ -48,7 +48,7 @@ namespace Serial {
*/
class Bus {
public:
Bus() : _line_levels{High, High, High, High, High} {}
Bus() : line_levels_{High, High, High, High, High} {}
/*!
Adds the supplied port to the bus.
@@ -62,8 +62,8 @@ namespace Serial {
void set_line_output_did_change(Line line);
private:
LineLevel _line_levels[5];
std::vector<std::weak_ptr<Port>> _ports;
LineLevel line_levels_[5];
std::vector<std::weak_ptr<Port>> ports_;
};
/*!
@@ -72,16 +72,15 @@ namespace Serial {
*/
class Port {
public:
Port() : _line_levels{High, High, High, High, High} {}
Port() : line_levels_{High, High, High, High, High} {}
/*!
Sets the current level of an output line on this serial port.
*/
void set_output(Line line, LineLevel level) {
if(_line_levels[line] != level)
{
_line_levels[line] = level;
std::shared_ptr<Bus> bus = _serial_bus.lock();
if(line_levels_[line] != level) {
line_levels_[line] = level;
std::shared_ptr<Bus> bus = serial_bus_.lock();
if(bus) bus->set_line_output_did_change(line);
}
}
@@ -90,7 +89,7 @@ namespace Serial {
Gets the previously set level of an output line.
*/
LineLevel get_output(Line line) {
return _line_levels[line];
return line_levels_[line];
}
/*!
@@ -102,12 +101,12 @@ namespace Serial {
Sets the supplied serial bus as that to which line levels will be communicated.
*/
inline void set_serial_bus(std::shared_ptr<Bus> serial_bus) {
_serial_bus = serial_bus;
serial_bus_ = serial_bus;
}
private:
std::weak_ptr<Bus> _serial_bus;
LineLevel _line_levels[5];
std::weak_ptr<Bus> serial_bus_;
LineLevel line_levels_[5];
};
/*!
@@ -117,12 +116,12 @@ namespace Serial {
public:
void set_input(Line line, LineLevel value);
DebugPort() : _incoming_count(0) {}
DebugPort() : incoming_count_(0) {}
private:
uint8_t _incoming_byte;
int _incoming_count;
LineLevel _input_levels[5];
uint8_t incoming_byte_;
int incoming_count_;
LineLevel input_levels_[5];
};
}

View File

@@ -8,8 +8,7 @@
#include "Vic20.hpp"
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character)
{
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
#define X {NotMapped}

View File

@@ -10,33 +10,34 @@
#include <algorithm>
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
using namespace Commodore::Vic20;
Machine::Machine() :
_rom(nullptr),
_is_running_at_zero_cost(false),
_tape(1022727)
{
// create 6522s, serial port and bus
_userPortVIA.reset(new UserPortVIA);
_keyboardVIA.reset(new KeyboardVIA);
_serialPort.reset(new SerialPort);
_serialBus.reset(new ::Commodore::Serial::Bus);
rom_(nullptr),
is_running_at_zero_cost_(false),
tape_(new Storage::Tape::BinaryTapePlayer(1022727)),
user_port_via_(new UserPortVIA),
keyboard_via_(new KeyboardVIA),
serial_port_(new SerialPort),
serial_bus_(new ::Commodore::Serial::Bus) {
// communicate the tape to the user-port VIA
user_port_via_->set_tape(tape_);
// wire up the serial bus and serial port
Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus);
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
// wire up 6522s and serial port
_userPortVIA->set_serial_port(_serialPort);
_keyboardVIA->set_serial_port(_serialPort);
_serialPort->set_user_port_via(_userPortVIA);
user_port_via_->set_serial_port(serial_port_);
keyboard_via_->set_serial_port(serial_port_);
serial_port_->set_user_port_via(user_port_via_);
// wire up the 6522s, tape and machine
_userPortVIA->set_interrupt_delegate(this);
_keyboardVIA->set_interrupt_delegate(this);
_tape.set_delegate(this);
user_port_via_->set_interrupt_delegate(this);
keyboard_via_->set_interrupt_delegate(this);
tape_->set_delegate(this);
// establish the memory maps
set_memory_size(MemorySize::Default);
@@ -44,90 +45,69 @@ Machine::Machine() :
// set the NTSC clock rate
set_region(NTSC);
// _debugPort.reset(new ::Commodore::Serial::DebugPort);
// _debugPort->set_serial_bus(_serialBus);
// _serialBus->add_port(_debugPort);
// _debugPort->set_serial_bus(serial_bus_);
// serial_bus_->add_port(_debugPort);
}
void Machine::set_memory_size(MemorySize size)
{
memset(_processorReadMemoryMap, 0, sizeof(_processorReadMemoryMap));
memset(_processorWriteMemoryMap, 0, sizeof(_processorWriteMemoryMap));
void Machine::set_memory_size(MemorySize size) {
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
switch(size)
{
switch(size) {
default: break;
case ThreeKB:
write_to_map(_processorReadMemoryMap, _expansionRAM, 0x0000, 0x1000);
write_to_map(_processorWriteMemoryMap, _expansionRAM, 0x0000, 0x1000);
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000);
break;
case ThirtyTwoKB:
write_to_map(_processorReadMemoryMap, _expansionRAM, 0x0000, 0x8000);
write_to_map(_processorWriteMemoryMap, _expansionRAM, 0x0000, 0x8000);
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000);
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000);
break;
}
// install the system ROMs and VIC-visible memory
write_to_map(_processorReadMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory));
write_to_map(_processorReadMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
write_to_map(_processorReadMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
write_to_map(_processorReadMemoryMap, _characterROM, 0x8000, sizeof(_characterROM));
write_to_map(_processorReadMemoryMap, _basicROM, 0xc000, sizeof(_basicROM));
write_to_map(_processorReadMemoryMap, _kernelROM, 0xe000, sizeof(_kernelROM));
write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
write_to_map(processor_read_memory_map_, character_rom_, 0x8000, sizeof(character_rom_));
write_to_map(processor_read_memory_map_, basic_rom_, 0xc000, sizeof(basic_rom_));
write_to_map(processor_read_memory_map_, kernel_rom_, 0xe000, sizeof(kernel_rom_));
write_to_map(_processorWriteMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory));
write_to_map(_processorWriteMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
write_to_map(_processorWriteMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
// install the inserted ROM if there is one
if(_rom)
{
write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length);
if(rom_) {
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
}
}
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length)
{
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
address >>= 10;
length >>= 10;
while(length--)
{
while(length--) {
map[address] = area;
area += 0x400;
address++;
}
}
Machine::~Machine()
{
delete[] _rom;
Machine::~Machine() {
delete[] rom_;
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
// static int logCount = 0;
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
// logCount--;
// printf("%04x\n", address);
// }
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192))
// {
// printf("\n[%04x] <- %02x\n", address, *value);
// }
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
// run the phase-1 part of this cycle, in which the VIC accesses memory
if(!_is_running_at_zero_cost) _mos6560->run_for_cycles(1);
if(!is_running_at_zero_cost_) mos6560_->run_for_cycles(1);
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
if(isReadOperation(operation))
{
uint8_t result = _processorReadMemoryMap[address >> 10] ? _processorReadMemoryMap[address >> 10][address & 0x3ff] : 0xff;
if((address&0xfc00) == 0x9000)
{
if((address&0xff00) == 0x9000) result &= _mos6560->get_register(address);
if((address&0xfc10) == 0x9010) result &= _userPortVIA->get_register(address);
if((address&0xfc20) == 0x9020) result &= _keyboardVIA->get_register(address);
if(isReadOperation(operation)) {
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
}
*value = result;
@@ -135,345 +115,298 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
// CPU or 6560 costs.
if(_use_fast_tape_hack && _tape.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
{
while(!_userPortVIA->get_interrupt_line() && !_keyboardVIA->get_interrupt_line() && !_tape.get_tape()->is_at_end())
{
_userPortVIA->run_for_cycles(1);
_keyboardVIA->run_for_cycles(1);
_tape.run_for_cycles(1);
if(use_fast_tape_hack_ && tape_->has_tape() && operation == CPU6502::BusOperation::ReadOpcode) {
if(address == 0xf7b2) {
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
// So cancel that via a double NOP and fill in the next header programmatically.
Storage::Tape::Commodore::Parser parser;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
// serialise to wherever b2:b3 points
uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8);
if(header) {
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
} else {
// no header found, so store end-of-tape
user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape
}
// clear status and the verify flag
user_basic_memory_[0x90] = 0;
user_basic_memory_[0x93] = 0;
*value = 0x0c; // i.e. NOP abs
} else if(address == 0xf90b) {
uint8_t x = (uint8_t)get_value_of_register(CPU6502::Register::X);
if(x == 0xe) {
Storage::Tape::Commodore::Parser parser;
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
uint16_t start_address, end_address;
start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
// perform a via-processor_write_memory_map_ memcpy
uint8_t *data_ptr = data->data.data();
size_t data_left = data->data.size();
while(data_left && start_address != end_address) {
uint8_t *page = processor_write_memory_map_[start_address >> 10];
if(page) page[start_address & 0x3ff] = *data_ptr;
data_ptr++;
start_address++;
data_left--;
}
// set tape status, carry and flag
user_basic_memory_[0x90] |= 0x40;
uint8_t flags = (uint8_t)get_value_of_register(CPU6502::Register::Flags);
flags &= ~(uint8_t)(CPU6502::Flag::Carry | CPU6502::Flag::Interrupt);
set_value_of_register(CPU6502::Register::Flags, flags);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
// ensure that the PC leaps to 0xfccf
set_value_of_register(CPU6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
}
}
}
}
else
{
uint8_t *ram = _processorWriteMemoryMap[address >> 10];
} else {
uint8_t *ram = processor_write_memory_map_[address >> 10];
if(ram) ram[address & 0x3ff] = *value;
if((address&0xfc00) == 0x9000)
{
if((address&0xff00) == 0x9000) _mos6560->set_register(address, *value);
if((address&0xfc10) == 0x9010) _userPortVIA->set_register(address, *value);
if((address&0xfc20) == 0x9020) _keyboardVIA->set_register(address, *value);
if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
}
}
_userPortVIA->run_for_cycles(1);
_keyboardVIA->run_for_cycles(1);
if(_typer && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E)
{
if(!_typer->type_next_character())
{
user_port_via_->run_for_cycles(1);
keyboard_via_->run_for_cycles(1);
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) {
if(!typer_->type_next_character()) {
clear_all_keys();
_typer.reset();
}
}
_tape.run_for_cycles(1);
if(_c1540) _c1540->run_for_cycles(1);
// If using fast tape then:
// if the PC hits 0xf98e, the ROM's tape loading routine, then begin zero cost processing;
// if the PC heads into RAM
//
// Where 'zero cost processing' is taken to be taking the 6560 off the bus (because I know it's
// expensive, and not relevant) then running the tape, the CPU and both 6522s as usual but not
// counting cycles towards the processing budget. So the limit is the host machine.
//
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
// the CPU out of the action.
if(_use_fast_tape_hack && _tape.has_tape())
{
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
{
_is_running_at_zero_cost = true;
set_clock_is_unlimited(true);
}
if(
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
_tape.get_tape()->is_at_end()
)
{
_is_running_at_zero_cost = false;
set_clock_is_unlimited(false);
typer_.reset();
}
}
tape_->run_for_cycles(1);
if(c1540_) c1540_->run_for_cycles(1);
return 1;
}
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
{
set_nmi_line(_userPortVIA->get_interrupt_line());
set_irq_line(_keyboardVIA->get_interrupt_line());
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
set_nmi_line(user_port_via_->get_interrupt_line());
set_irq_line(keyboard_via_->get_interrupt_line());
}
#pragma mark - Setup
void Machine::set_region(Commodore::Vic20::Region region)
{
_region = region;
switch(region)
{
void Machine::set_region(Commodore::Vic20::Region region) {
region_ = region;
switch(region) {
case PAL:
set_clock_rate(1108404);
if(_mos6560)
{
_mos6560->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
_mos6560->set_clock_rate(1108404);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
mos6560_->set_clock_rate(1108404);
}
break;
case NTSC:
set_clock_rate(1022727);
if(_mos6560)
{
_mos6560->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
_mos6560->set_clock_rate(1022727);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
mos6560_->set_clock_rate(1022727);
}
break;
}
}
void Machine::setup_output(float aspect_ratio)
{
_mos6560.reset(new Vic6560());
_mos6560->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
set_region(_region);
void Machine::setup_output(float aspect_ratio) {
mos6560_.reset(new Vic6560());
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
set_region(region_);
memset(_mos6560->_videoMemoryMap, 0, sizeof(_mos6560->_videoMemoryMap));
write_to_map(_mos6560->_videoMemoryMap, _characterROM, 0x0000, sizeof(_characterROM));
write_to_map(_mos6560->_videoMemoryMap, _userBASICMemory, 0x2000, sizeof(_userBASICMemory));
write_to_map(_mos6560->_videoMemoryMap, _screenMemory, 0x3000, sizeof(_screenMemory));
_mos6560->_colorMemory = _colorMemory;
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
write_to_map(mos6560_->video_memory_map, character_rom_, 0x0000, sizeof(character_rom_));
write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_));
write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_));
mos6560_->colour_memory = colour_memory_;
}
void Machine::close_output()
{
_mos6560 = nullptr;
void Machine::close_output() {
mos6560_ = nullptr;
}
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
{
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
uint8_t *target = nullptr;
size_t max_length = 0x2000;
switch(slot)
{
case Kernel: target = _kernelROM; break;
case Characters: target = _characterROM; max_length = 0x1000; break;
case BASIC: target = _basicROM; break;
switch(slot) {
case Kernel: target = kernel_rom_; break;
case Characters: target = character_rom_; max_length = 0x1000; break;
case BASIC: target = basic_rom_; break;
case Drive:
_driveROM.reset(new uint8_t[length]);
memcpy(_driveROM.get(), data, length);
drive_rom_.reset(new uint8_t[length]);
memcpy(drive_rom_.get(), data, length);
install_disk_rom();
return;
}
if(target)
{
if(target) {
size_t length_to_copy = std::min(max_length, length);
memcpy(target, data, length_to_copy);
}
}
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
//{
// if(length > 2)
// {
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
// _rom_length = (uint16_t)(length - 2);
//
// // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism
// if(_rom_address == 0xa000)
// {
// _rom = new uint8_t[0x2000];
// memcpy(_rom, &data[2], length - 2);
// write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000);
// }
// else
// {
// set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name)));
// }
// }
//}
#pragma mar - Tape
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
if(target.tapes.size())
{
_tape.set_tape(target.tapes.front());
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
tape_->set_tape(target.tapes.front());
}
if(target.disks.size())
{
if(target.disks.size()) {
// construct the 1540
_c1540.reset(new ::Commodore::C1540::Machine);
c1540_.reset(new ::Commodore::C1540::Machine);
// attach it to the serial bus
_c1540->set_serial_bus(_serialBus);
c1540_->set_serial_bus(serial_bus_);
// hand it the disk
_c1540->set_disk(target.disks.front());
c1540_->set_disk(target.disks.front());
// install the ROM if it was previously set
install_disk_rom();
}
if(target.cartridges.size())
{
_rom_address = 0xa000;
if(target.cartridges.size()) {
rom_address_ = 0xa000;
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
_rom_length = (uint16_t)(rom_image.size());
rom_length_ = (uint16_t)(rom_image.size());
_rom = new uint8_t[0x2000];
memcpy(_rom, rom_image.data(), rom_image.size());
write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000);
rom_ = new uint8_t[0x2000];
memcpy(rom_, rom_image.data(), rom_image.size());
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
}
if(_should_automatically_load_media)
{
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
switch(target.vic20.memory_model)
{
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
case StaticAnalyser::Vic20MemoryModel::EightKB:
set_memory_size(ThreeKB);
break;
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
set_memory_size(ThirtyTwoKB);
break;
}
switch(target.vic20.memory_model) {
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
case StaticAnalyser::Vic20MemoryModel::EightKB:
set_memory_size(ThreeKB);
break;
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
set_memory_size(ThirtyTwoKB);
break;
}
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape)
{
_keyboardVIA->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
}
#pragma mark - Disc
void Machine::install_disk_rom()
{
if(_driveROM && _c1540)
{
_c1540->set_rom(_driveROM.get());
_c1540->run_for_cycles(2000000);
_driveROM.reset();
void Machine::install_disk_rom() {
if(drive_rom_ && c1540_) {
c1540_->set_rom(drive_rom_.get());
c1540_->run_for_cycles(2000000);
drive_rom_.reset();
}
}
#pragma mark - UserPortVIA
uint8_t UserPortVIA::get_port_input(Port port)
{
if(!port)
{
return _portA; // TODO: bit 6 should be high if there is no tape, low otherwise
uint8_t UserPortVIA::get_port_input(Port port) {
if(!port) {
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
}
return 0xff;
}
void UserPortVIA::set_control_line_output(Port port, Line line, bool value)
{
// if(port == Port::A && line == Line::Two) {
// printf("Tape motor %s\n", value ? "on" : "off");
// }
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
if(port == Port::A && line == Line::Two) {
tape_->set_motor_control(!value);
}
}
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value)
{
switch(line)
{
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
switch(line) {
default: break;
case ::Commodore::Serial::Line::Data: _portA = (_portA & ~0x02) | (value ? 0x02 : 0x00); break;
case ::Commodore::Serial::Line::Clock: _portA = (_portA & ~0x01) | (value ? 0x01 : 0x00); break;
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
}
}
void UserPortVIA::set_joystick_state(JoystickInput input, bool value)
{
if(input != JoystickInput::Right)
{
_portA = (_portA & ~input) | (value ? 0 : input);
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
if(input != JoystickInput::Right) {
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
}
}
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
{
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
// Line 7 of port A is inverted and output as serial ATN
if(!port)
{
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
if(!port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort)
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
}
}
UserPortVIA::UserPortVIA() : _portA(0xbf) {}
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
{
_serialPort = serialPort;
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
serial_port_ = serialPort;
}
void UserPortVIA::set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) {
tape_ = tape;
}
#pragma mark - KeyboardVIA
KeyboardVIA::KeyboardVIA() : _portB(0xff)
{
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
clear_all_keys();
}
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed)
{
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) {
if(isPressed)
_columns[key & 7] &= ~(key >> 3);
columns_[key & 7] &= ~(key >> 3);
else
_columns[key & 7] |= (key >> 3);
columns_[key & 7] |= (key >> 3);
}
void KeyboardVIA::clear_all_keys()
{
memset(_columns, 0xff, sizeof(_columns));
void KeyboardVIA::clear_all_keys() {
memset(columns_, 0xff, sizeof(columns_));
}
uint8_t KeyboardVIA::get_port_input(Port port)
{
if(!port)
{
uint8_t KeyboardVIA::get_port_input(Port port) {
if(!port) {
uint8_t result = 0xff;
for(int c = 0; c < 8; c++)
{
if(!(_activation_mask&(1 << c)))
result &= _columns[c];
for(int c = 0; c < 8; c++) {
if(!(activation_mask_&(1 << c)))
result &= columns_[c];
}
return result;
}
return _portB;
return port_b_;
}
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask)
{
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
if(port)
_activation_mask = (value & mask) | (~mask);
activation_mask_ = (value & mask) | (~mask);
}
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
{
if(line == Line::Two)
{
std::shared_ptr<::Commodore::Serial::Port> serialPort = _serialPort.lock();
if(serialPort)
{
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
if(line == Line::Two) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
if(port == Port::A)
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
@@ -483,28 +416,23 @@ void KeyboardVIA::set_control_line_output(Port port, Line line, bool value)
}
}
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value)
{
if(input == JoystickInput::Right)
{
_portB = (_portB & ~input) | (value ? 0 : input);
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
if(input == JoystickInput::Right) {
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
}
}
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort)
{
_serialPort = serialPort;
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
serial_port_ = serialPort;
}
#pragma mark - SerialPort
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level)
{
std::shared_ptr<UserPortVIA> userPortVIA = _userPortVIA.lock();
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
}
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA)
{
_userPortVIA = userPortVIA;
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
user_port_via_ = userPortVIA;
}

View File

@@ -87,10 +87,12 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
void set_port_output(Port port, uint8_t value, uint8_t mask);
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape);
private:
uint8_t _portA;
std::weak_ptr<::Commodore::Serial::Port> _serialPort;
uint8_t port_a_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
};
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
@@ -112,10 +114,10 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
private:
uint8_t _portB;
uint8_t _columns[8];
uint8_t _activation_mask;
std::weak_ptr<::Commodore::Serial::Port> _serialPort;
uint8_t port_b_;
uint8_t columns_[8];
uint8_t activation_mask_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
};
class SerialPort : public ::Commodore::Serial::Port {
@@ -124,19 +126,18 @@ class SerialPort : public ::Commodore::Serial::Port {
void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA);
private:
std::weak_ptr<UserPortVIA> _userPortVIA;
std::weak_ptr<UserPortVIA> user_port_via_;
};
class Vic6560: public MOS::MOS6560<Vic6560> {
public:
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data)
{
*pixel_data = _videoMemoryMap[address >> 10] ? _videoMemoryMap[address >> 10][address & 0x3ff] : 0xff; // TODO
*colour_data = _colorMemory[address & 0x03ff];
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
*colour_data = colour_memory[address & 0x03ff];
}
uint8_t *_videoMemoryMap[16];
uint8_t *_colorMemory;
uint8_t *video_memory_map[16];
uint8_t *colour_memory;
};
class Machine:
@@ -153,34 +154,29 @@ class Machine:
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void configure_as_target(const StaticAnalyser::Target &target);
// void set_prg(const char *file_name, size_t length, const uint8_t *data);
// void set_tape(std::shared_ptr<Storage::Tape::Tape> tape);
// void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
void set_key_state(uint16_t key, bool isPressed) { _keyboardVIA->set_key_state(key, isPressed); }
void clear_all_keys() { _keyboardVIA->clear_all_keys(); }
void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); }
void clear_all_keys() { keyboard_via_->clear_all_keys(); }
void set_joystick_state(JoystickInput input, bool isPressed) {
_userPortVIA->set_joystick_state(input, isPressed);
_keyboardVIA->set_joystick_state(input, isPressed);
user_port_via_->set_joystick_state(input, isPressed);
keyboard_via_->set_joystick_state(input, isPressed);
}
void set_memory_size(MemorySize size);
void set_region(Region region);
inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; }
inline void set_should_automatically_load_media(bool activate) { _should_automatically_load_media = activate; }
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
void synchronise() { _mos6560->synchronise(); }
void synchronise() { mos6560_->synchronise(); }
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _mos6560->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _mos6560->get_speaker(); }
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); }
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
// TODO: or 1108405 for PAL; see http://www.antimon.org/dl/c64/code/stable.txt
// to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522);
@@ -192,43 +188,39 @@ class Machine:
virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape);
private:
uint8_t _characterROM[0x1000];
uint8_t _basicROM[0x2000];
uint8_t _kernelROM[0x2000];
uint8_t _expansionRAM[0x8000];
uint8_t character_rom_[0x1000];
uint8_t basic_rom_[0x2000];
uint8_t kernel_rom_[0x2000];
uint8_t expansion_ram_[0x8000];
uint8_t *_rom;
uint16_t _rom_address, _rom_length;
uint8_t *rom_;
uint16_t rom_address_, rom_length_;
uint8_t _userBASICMemory[0x0400];
uint8_t _screenMemory[0x1000];
uint8_t _colorMemory[0x0400];
uint8_t _junkMemory[0x0400];
std::unique_ptr<uint8_t> _driveROM;
uint8_t user_basic_memory_[0x0400];
uint8_t screen_memory_[0x1000];
uint8_t colour_memory_[0x0400];
std::unique_ptr<uint8_t> drive_rom_;
uint8_t *_processorReadMemoryMap[64];
uint8_t *_processorWriteMemoryMap[64];
uint8_t *processor_read_memory_map_[64];
uint8_t *processor_write_memory_map_[64];
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length);
Region _region;
Region region_;
std::unique_ptr<Vic6560> _mos6560;
std::shared_ptr<UserPortVIA> _userPortVIA;
std::shared_ptr<KeyboardVIA> _keyboardVIA;
std::shared_ptr<SerialPort> _serialPort;
std::shared_ptr<::Commodore::Serial::Bus> _serialBus;
// std::shared_ptr<::Commodore::Serial::DebugPort> _debugPort;
std::unique_ptr<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_;
std::shared_ptr<KeyboardVIA> keyboard_via_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
// Tape
Storage::Tape::BinaryTapePlayer _tape;
bool _use_fast_tape_hack, _should_automatically_load_media;
bool _is_running_at_zero_cost;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_;
bool is_running_at_zero_cost_;
// Disk
std::shared_ptr<::Commodore::C1540::Machine> _c1540;
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
void install_disk_rom();
// Autoload string
};
}

View File

@@ -19,7 +19,7 @@ namespace ConfigurationTarget {
*/
class Machine {
public:
virtual void configure_as_target(const StaticAnalyser::Target &target) =0;
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,12 @@
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "Interrupts.hpp"
#include "Plus3.hpp"
#include "Speaker.hpp"
#include "Tape.hpp"
#include "Video.hpp"
#include <cstdint>
#include <vector>
@@ -35,15 +40,6 @@ enum ROMSlot: uint8_t {
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
};
enum Interrupt: uint8_t {
PowerOnReset = 0x02,
DisplayEnd = 0x04,
RealTimeClock = 0x08,
ReceiveDataFull = 0x10,
TransmitDataEmpty = 0x20,
HighToneDetect = 0x40
};
enum Key: uint16_t {
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
@@ -65,72 +61,6 @@ enum Key: uint16_t {
TerminateSequence = 0xffff, NotMapped = 0xfffe,
};
class Tape: public Storage::Tape::TapePlayer {
public:
Tape();
inline void run_for_cycles(unsigned int number_of_cycles);
inline uint8_t get_data_register();
inline void set_data_register(uint8_t value);
inline void set_counter(uint8_t value);
inline uint8_t get_interrupt_status() { return _interrupt_status; }
inline void clear_interrupts(uint8_t interrupts);
class Delegate {
public:
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
};
inline void set_delegate(Delegate *delegate) { _delegate = delegate; }
inline void set_is_running(bool is_running) { _is_running = is_running; }
inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; }
inline void set_is_in_input_mode(bool is_in_input_mode);
private:
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse();
struct {
int minimum_bits_until_full;
} _input;
struct {
unsigned int cycles_into_pulse;
unsigned int bits_remaining_until_empty;
} _output;
bool _is_running;
bool _is_enabled;
bool _is_in_input_mode;
inline void evaluate_interrupts();
uint16_t _data_register;
uint8_t _interrupt_status, _last_posted_interrupt_status;
Delegate *_delegate;
enum {
Long, Short, Unrecognised, Recognised
} _crossings[4];
};
class Speaker: public ::Outputs::Filter<Speaker> {
public:
void set_divider(uint8_t divider);
void set_is_enabled(bool is_enabled);
void get_samples(unsigned int number_of_samples, int16_t *target);
void skip_samples(unsigned int number_of_samples);
private:
unsigned int _counter;
unsigned int _divider;
bool _is_enabled;
};
/*!
@abstract Represents an Acorn Electron.
@@ -152,7 +82,7 @@ class Machine:
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; }
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target);
@@ -164,8 +94,8 @@ class Machine:
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return _crt; }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _speaker; }
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
// to satisfy Tape::Delegate
@@ -177,68 +107,48 @@ class Machine:
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
private:
inline void update_display();
inline void start_pixel_line();
inline void end_pixel_line();
inline void output_pixels(unsigned int number_of_cycles);
inline void queue_next_display_interrupt();
inline void update_audio();
inline void signal_interrupt(Interrupt interrupt);
inline void clear_interrupt(Interrupt interrupt);
inline void evaluate_interrupts();
// Things that directly constitute the memory map.
uint8_t _roms[16][16384];
bool _rom_write_masks[16];
uint8_t _os[16384], _ram[32768];
std::vector<uint8_t> _dfs, _adfs;
uint8_t roms_[16][16384];
bool rom_write_masks_[16];
uint8_t os_[16384], ram_[32768];
std::vector<uint8_t> dfs_, adfs_;
// Things affected by registers, explicitly or otherwise.
uint8_t _interrupt_status, _interrupt_control;
uint8_t _palette[16];
uint8_t _key_states[14];
ROMSlot _active_rom;
bool _keyboard_is_active, _basic_is_active;
uint8_t _screen_mode;
uint16_t _screenModeBaseAddress;
uint16_t _startScreenAddress;
// Paging
ROMSlot active_rom_;
bool keyboard_is_active_, basic_is_active_;
// Interrupt and keyboard state
uint8_t interrupt_status_, interrupt_control_;
uint8_t key_states_[14];
// Counters related to simultaneous subsystems
unsigned int _frameCycles, _displayOutputPosition;
unsigned int _audioOutputPosition, _audioOutputPositionError;
uint8_t _phase;
struct {
uint16_t forty1bpp[256];
uint8_t forty2bpp[256];
uint32_t eighty1bpp[256];
uint16_t eighty2bpp[256];
uint8_t eighty4bpp[256];
} _paletteTables;
// Display generation.
uint16_t _startLineAddress, _currentScreenAddress;
int _current_pixel_line, _current_pixel_column, _current_character_row;
uint8_t _last_pixel_byte;
bool _isBlankLine;
// CRT output
uint8_t *_current_output_target, *_initial_output_target;
unsigned int _current_output_divider;
unsigned int cycles_since_display_update_;
unsigned int cycles_since_audio_update_;
int cycles_until_display_interrupt_;
Interrupt next_display_interrupt_;
VideoOutput::Range video_access_range_;
// Tape
Tape _tape;
bool _use_fast_tape_hack;
bool _fast_load_is_in_data;
Tape tape_;
bool use_fast_tape_hack_;
bool fast_load_is_in_data_;
// Disk
std::unique_ptr<Plus3> _plus3;
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_;
int shift_restart_counter_;
// Outputs
std::shared_ptr<Outputs::CRT::CRT> _crt;
std::shared_ptr<Speaker> _speaker;
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<Speaker> speaker_;
bool speaker_is_enabled_;
};

View File

@@ -0,0 +1,27 @@
//
// Interrupts.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Interrupts_h
#define Interrupts_h
#include <cstdint>
namespace Electron {
enum Interrupt: uint8_t {
PowerOnReset = 0x02,
DisplayEnd = 0x04,
RealTimeClock = 0x08,
ReceiveDataFull = 0x10,
TransmitDataEmpty = 0x20,
HighToneDetect = 0x40
};
}
#endif /* Interrupts_h */

View File

@@ -10,26 +10,41 @@
using namespace Electron;
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive)
{
if(!_drives[drive]) _drives[drive].reset(new Storage::Disk::Drive);
_drives[drive]->set_disk(disk);
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
set_control_register(last_control_, 0xff);
}
void Plus3::set_control_register(uint8_t control)
{
// TODO:
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
}
void Plus3::set_control_register(uint8_t control) {
// bit 0 => enable or disable drive 1
// bit 1 => enable or disable drive 2
// bit 2 => side select
// bit 3 => single density select
switch(control&3)
{
case 0: set_drive(nullptr); break;
default: set_drive(_drives[0]); break;
case 2: set_drive(_drives[1]); break;
}
if(_drives[0]) _drives[0]->set_head((control & 0x04) ? 1 : 0);
if(_drives[1]) _drives[1]->set_head((control & 0x04) ? 1 : 0);
set_is_double_density(!(control & 0x08));
uint8_t changes = control ^ last_control_;
last_control_ = control;
set_control_register(control, changes);
}
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
if(changes&3) {
switch(control&3) {
case 0: selected_drive_ = -1; set_drive(nullptr); break;
default: selected_drive_ = 0; set_drive(drives_[0]); break;
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
}
}
if(changes & 0x04) {
invalidate_track();
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
}
if(changes & 0x08) set_is_double_density(!(control & 0x08));
}

View File

@@ -15,11 +15,16 @@ namespace Electron {
class Plus3 : public WD::WD1770 {
public:
Plus3();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void set_control_register(uint8_t control);
private:
std::shared_ptr<Storage::Disk::Drive> _drives[2];
void set_control_register(uint8_t control, uint8_t changes);
std::shared_ptr<Storage::Disk::Drive> drives_[2];
int selected_drive_;
uint8_t last_control_;
};
}

View File

@@ -0,0 +1,40 @@
//
// Speaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Speaker.hpp"
using namespace Electron;
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
if(is_enabled_) {
while(number_of_samples--) {
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2);
}
} else {
memset(target, 0, sizeof(int16_t) * number_of_samples);
}
}
void Speaker::skip_samples(unsigned int number_of_samples) {
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
}
void Speaker::set_divider(uint8_t divider) {
enqueue([=]() {
divider_ = divider * 32 / clock_rate_divider;
});
}
void Speaker::set_is_enabled(bool is_enabled) {
enqueue([=]() {
is_enabled_ = is_enabled;
counter_ = 0;
});
}

View File

@@ -0,0 +1,35 @@
//
// Speaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Electron_Speaker_hpp
#define Electron_Speaker_hpp
#include "../../Outputs/Speaker.hpp"
namespace Electron {
class Speaker: public ::Outputs::Filter<Speaker> {
public:
void set_divider(uint8_t divider);
void set_is_enabled(bool is_enabled);
void get_samples(unsigned int number_of_samples, int16_t *target);
void skip_samples(unsigned int number_of_samples);
static const unsigned int clock_rate_divider = 8;
private:
unsigned int counter_;
unsigned int divider_;
bool is_enabled_;
};
}
#endif /* Speaker_hpp */

111
Machines/Electron/Tape.cpp Normal file
View File

@@ -0,0 +1,111 @@
//
// Tape.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Tape.hpp"
using namespace Electron;
Tape::Tape() :
TapePlayer(2000000),
is_running_(false),
data_register_(0),
delegate_(nullptr),
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
last_posted_interrupt_status_(0),
interrupt_status_(0) {}
void Tape::push_tape_bit(uint16_t bit) {
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
if(!input_.minimum_bits_until_full) {
if((data_register_&0x3) == 0x1) {
interrupt_status_ |= Interrupt::ReceiveDataFull;
if(is_in_input_mode_) input_.minimum_bits_until_full = 9;
}
}
if(output_.bits_remaining_until_empty) output_.bits_remaining_until_empty--;
if(!output_.bits_remaining_until_empty) interrupt_status_ |= Interrupt::TransmitDataEmpty;
if(data_register_ == 0x3ff) interrupt_status_ |= Interrupt::HighToneDetect;
else interrupt_status_ &= ~Interrupt::HighToneDetect;
evaluate_interrupts();
}
void Tape::evaluate_interrupts() {
if(last_posted_interrupt_status_ != interrupt_status_) {
last_posted_interrupt_status_ = interrupt_status_;
if(delegate_) delegate_->tape_did_change_interrupt_status(this);
}
}
void Tape::clear_interrupts(uint8_t interrupts) {
interrupt_status_ &= ~interrupts;
evaluate_interrupts();
}
void Tape::set_is_in_input_mode(bool is_in_input_mode) {
is_in_input_mode_ = is_in_input_mode;
}
void Tape::set_counter(uint8_t value) {
output_.cycles_into_pulse = 0;
output_.bits_remaining_until_empty = 0;
}
void Tape::set_data_register(uint8_t value) {
data_register_ = (uint16_t)((value << 2) | 1);
output_.bits_remaining_until_empty = 9;
}
uint8_t Tape::get_data_register() {
return (uint8_t)(data_register_ >> 2);
}
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
crossings_[0] = crossings_[1];
crossings_[1] = crossings_[2];
crossings_[2] = crossings_[3];
crossings_[3] = Tape::Unrecognised;
if(pulse.type != Storage::Tape::Tape::Pulse::Zero) {
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short;
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long;
}
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) {
push_tape_bit(0);
crossings_[0] = crossings_[1] = Tape::Recognised;
} else {
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) {
push_tape_bit(1);
crossings_[0] = crossings_[1] =
crossings_[2] = crossings_[3] = Tape::Recognised;
}
}
}
void Tape::run_for_cycles(unsigned int number_of_cycles) {
if(is_enabled_) {
if(is_in_input_mode_) {
if(is_running_) {
TapePlayer::run_for_cycles((int)number_of_cycles);
}
} else {
output_.cycles_into_pulse += number_of_cycles;
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
push_tape_bit(1);
}
}
}
}

View File

@@ -0,0 +1,72 @@
//
// Tape.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Electron_Tape_h
#define Electron_Tape_h
#include "../../Storage/Tape/Tape.hpp"
#include "Interrupts.hpp"
#include <cstdint>
namespace Electron {
class Tape: public Storage::Tape::TapePlayer {
public:
Tape();
void run_for_cycles(unsigned int number_of_cycles);
uint8_t get_data_register();
void set_data_register(uint8_t value);
void set_counter(uint8_t value);
inline uint8_t get_interrupt_status() { return interrupt_status_; }
void clear_interrupts(uint8_t interrupts);
class Delegate {
public:
virtual void tape_did_change_interrupt_status(Tape *tape) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
inline void set_is_running(bool is_running) { is_running_ = is_running; }
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
void set_is_in_input_mode(bool is_in_input_mode);
private:
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse();
struct {
int minimum_bits_until_full;
} input_;
struct {
unsigned int cycles_into_pulse;
unsigned int bits_remaining_until_empty;
} output_;
bool is_running_;
bool is_enabled_;
bool is_in_input_mode_;
inline void evaluate_interrupts();
uint16_t data_register_;
uint8_t interrupt_status_, last_posted_interrupt_status_;
Delegate *delegate_;
enum {
Long, Short, Unrecognised, Recognised
} crossings_[4];
};
}
#endif /* Electron_Tape_h */

View File

@@ -8,18 +8,15 @@
#include "Electron.hpp"
int Electron::Machine::get_typer_delay()
{
int Electron::Machine::get_typer_delay() {
return get_is_resetting() ? 625*25*128 : 0; // wait one second if resetting
}
int Electron::Machine::get_typer_frequency()
{
int Electron::Machine::get_typer_frequency() {
return 625*128*2; // accept a new character every two frames
}
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character)
{
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}

453
Machines/Electron/Video.cpp Normal file
View File

@@ -0,0 +1,453 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
using namespace Electron;
#define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line)
#define graphics_column(v) ((((v) & 127) - first_graphics_cycle + 128) & 127)
namespace {
static const int cycles_per_line = 128;
static const int lines_per_frame = 625;
static const int cycles_per_frame = lines_per_frame * cycles_per_line;
static const int crt_cycles_multiplier = 8;
static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line;
static const int field_divider_line = 312; // i.e. the line, simultaneous with which, the first field's sync ends. So if
// the first line with pixels in field 1 is the 20th in the frame, the first line
// with pixels in field 2 will be 20+field_divider_line
static const int first_graphics_line = 31;
static const int first_graphics_cycle = 33;
static const int display_end_interrupt_line = 256;
static const int real_time_clock_interrupt_1 = 16704;
static const int real_time_clock_interrupt_2 = 56704;
static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line;
static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line;
}
#pragma mark - Lifecycle
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
current_pixel_line_(-1),
output_position_(0),
screen_mode_(6),
screen_map_pointer_(0),
cycles_into_draw_action_(0) {
memset(palette_, 0xf, sizeof(palette_));
setup_screen_map();
setup_base_address();
crt_.reset(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1));
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
"}");
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
}
#pragma mark - CRT getter
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_;
}
#pragma mark - Display update methods
void VideoOutput::start_pixel_line() {
current_pixel_line_ = (current_pixel_line_+1)&255;
if(!current_pixel_line_) {
start_line_address_ = start_screen_address_;
current_character_row_ = 0;
is_blank_line_ = false;
} else {
bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3);
is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249)));
if(!is_blank_line_) {
start_line_address_++;
if(current_character_row_ > 7) {
start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8;
current_character_row_ = 0;
}
}
}
current_screen_address_ = start_line_address_;
current_pixel_column_ = 0;
initial_output_target_ = current_output_target_ = nullptr;
}
void VideoOutput::end_pixel_line() {
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
current_character_row_++;
}
void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(!number_of_cycles) return;
if(is_blank_line_) {
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
} else {
unsigned int divider = 1;
switch(screen_mode_) {
case 0: case 3: divider = 2; break;
case 1: case 4: case 6: divider = 4; break;
case 2: case 5: divider = 8; break;
}
if(!initial_output_target_ || divider != current_output_divider_) {
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
current_output_divider_ = divider;
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
}
#define get_pixel() \
if(current_screen_address_&32768) {\
current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\
}\
last_pixel_byte_ = ram_[current_screen_address_];\
current_screen_address_ = current_screen_address_+8
switch(screen_mode_) {
case 0: case 3:
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
current_output_target_ += 4;
current_pixel_column_++;
}
} else current_output_target_ += 4*number_of_cycles;
break;
case 1:
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
current_output_target_ += 2;
current_pixel_column_++;
}
} else current_output_target_ += 2*number_of_cycles;
break;
case 2:
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
current_output_target_ += 1;
current_pixel_column_++;
}
} else current_output_target_ += number_of_cycles;
break;
case 4: case 6:
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 4;
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
number_of_cycles--;
current_pixel_column_++;
}
while(number_of_cycles > 1) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
last_pixel_byte_ <<= 4;
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
number_of_cycles -= 2;
current_pixel_column_+=2;
}
if(number_of_cycles) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
current_pixel_column_++;
}
} else current_output_target_ += 2 * number_of_cycles;
break;
case 5:
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 2;
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
number_of_cycles--;
current_pixel_column_++;
}
while(number_of_cycles > 1) {
get_pixel();
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
last_pixel_byte_ <<= 2;
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
number_of_cycles -= 2;
current_pixel_column_+=2;
}
if(number_of_cycles) {
get_pixel();
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
current_pixel_column_++;
}
} else current_output_target_ += number_of_cycles;
break;
}
#undef get_pixel
}
}
void VideoOutput::run_for_cycles(int number_of_cycles) {
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
number_of_cycles -= time_left_in_action;
cycles_into_draw_action_ += time_left_in_action;
if(cycles_into_draw_action_ == draw_action_length) {
switch(screen_map_[screen_map_pointer_].type) {
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Pixels: end_pixel_line(); break;
}
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
cycles_into_draw_action_ = 0;
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) start_pixel_line();
}
}
}
#pragma mark - Register hub
void VideoOutput::set_register(int address, uint8_t value) {
switch(address & 0xf) {
case 0x02:
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
if(!start_screen_address_) start_screen_address_ |= 0x8000;
break;
case 0x03:
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
if(!start_screen_address_) start_screen_address_ |= 0x8000;
break;
case 0x07: {
// update screen mode
uint8_t new_screen_mode = (value >> 3)&7;
if(new_screen_mode == 7) new_screen_mode = 4;
if(new_screen_mode != screen_mode_) {
screen_mode_ = new_screen_mode;
setup_base_address();
}
}
break;
case 0x08: case 0x09: case 0x0a: case 0x0b:
case 0x0c: case 0x0d: case 0x0e: case 0x0f: {
static const int registers[4][4] = {
{10, 8, 2, 0},
{14, 12, 6, 4},
{15, 13, 7, 5},
{11, 9, 3, 1},
};
const int index = (address >> 1)&3;
const uint8_t colour = ~value;
if(address&1) {
palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4);
palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4);
palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4);
palette_[registers[index][3]] = (palette_[registers[index][3]]&3) | ((colour << 2)&4);
palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2);
palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2);
} else {
palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1);
palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1);
palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1);
palette_[registers[index][3]] = (palette_[registers[index][3]]&6) | ((colour >> 4)&1);
palette_[registers[index][0]] = (palette_[registers[index][0]]&5) | ((colour >> 2)&2);
palette_[registers[index][1]] = (palette_[registers[index][1]]&5) | ((colour >> 1)&2);
}
// regenerate all palette tables for now
#define pack(a, b) (uint8_t)((a << 4) | (b))
for(int byte = 0; byte < 256; byte++) {
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
target = (uint8_t *)&palette_tables_.eighty2bpp[byte];
target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
target = (uint8_t *)&palette_tables_.eighty1bpp[byte];
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]);
target[3] = pack(palette_[(byte&0x02) << 2], palette_[(byte&0x01) << 3]);
palette_tables_.forty2bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
palette_tables_.eighty4bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)],
palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]);
}
#undef pack
}
break;
}
}
void VideoOutput::setup_base_address() {
switch(screen_mode_) {
case 0: case 1: case 2: screen_mode_base_address_ = 0x3000; break;
case 3: screen_mode_base_address_ = 0x4000; break;
case 4: case 5: screen_mode_base_address_ = 0x5800; break;
case 6: screen_mode_base_address_ = 0x6000; break;
}
}
#pragma mark - Interrupts
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
VideoOutput::Interrupt interrupt;
if(output_position_ < real_time_clock_interrupt_1) {
interrupt.cycles = real_time_clock_interrupt_1 - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
}
if(output_position_ < display_end_interrupt_1) {
interrupt.cycles = display_end_interrupt_1 - output_position_;
interrupt.interrupt = DisplayEnd;
return interrupt;
}
if(output_position_ < real_time_clock_interrupt_2) {
interrupt.cycles = real_time_clock_interrupt_2 - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
}
if(output_position_ < display_end_interrupt_2) {
interrupt.cycles = display_end_interrupt_2 - output_position_;
interrupt.interrupt = DisplayEnd;
return interrupt;
}
interrupt.cycles = real_time_clock_interrupt_1 + cycles_per_frame - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
}
#pragma mark - RAM timing and access information
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
unsigned int result = 0;
int position = output_position_ + from_time;
result += 1 + (position&1);
if(screen_mode_ < 4) {
const int current_column = graphics_column(position + (position&1));
int current_line = graphics_line(position);
if(current_column < 80 && current_line < 256) {
if(screen_mode_ == 3) {
int output_position_line = graphics_line(output_position_);
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
if(implied_row < 8)
result += (unsigned int)(80 - current_column);
}
else result += (unsigned int)(80 - current_column);
}
}
return result;
}
VideoOutput::Range VideoOutput::get_memory_access_range() {
// This can't be more specific than this without applying a lot more thought because of mixed modes:
// suppose a program runs half the screen in an 80-column mode then switches to 40 columns. Then the
// real end address will be at 128*80 + 128*40 after the original base, subject to wrapping that depends
// on where the overflow occurred. Assuming accesses may run from the lowest possible position through to
// the end of RAM is good enough for 95% of use cases however.
VideoOutput::Range range;
range.low_address = std::min(start_screen_address_, screen_mode_base_address_);
range.high_address = 0x8000;
return range;
}
#pragma mark - The screen map
void VideoOutput::setup_screen_map() {
/*
Odd field: Even field:
|--S--| -S-|
|--S--| |--S--|
|-S-B-| = 3 |--S--| = 2.5
|--B--| |--B--|
|--P--| |--P--|
|--B--| = 312 |--B--| = 312.5
|-B-
*/
for(int c = 0; c < 2; c++) {
if(c&1) {
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
} else {
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
}
for(int c = 0; c < first_graphics_line - 3; c++) emplace_blank_line();
for(int c = 0; c < 256; c++) emplace_pixel_line();
for(int c = 256 + first_graphics_line; c < 312; c++) emplace_blank_line();
if(c&1) emplace_blank_line();
}
}
void VideoOutput::emplace_blank_line() {
screen_map_.emplace_back(DrawAction::Sync, 9);
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
screen_map_.emplace_back(DrawAction::Blank, 128 - 24);
}
void VideoOutput::emplace_pixel_line() {
// output format is:
// 9 cycles: sync
// ... to 24 cycles: colour burst
// ... to first_graphics_cycle: blank
// ... for 80 cycles: pixels
// ... until end of line: blank
screen_map_.emplace_back(DrawAction::Sync, 9);
screen_map_.emplace_back(DrawAction::ColourBurst, 24 - 9);
screen_map_.emplace_back(DrawAction::Blank, first_graphics_cycle - 24);
screen_map_.emplace_back(DrawAction::Pixels, 80);
screen_map_.emplace_back(DrawAction::Blank, 48 - first_graphics_cycle);
}

127
Machines/Electron/Video.hpp Normal file
View File

@@ -0,0 +1,127 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Machines_Electron_Video_hpp
#define Machines_Electron_Video_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "Interrupts.hpp"
namespace Electron {
/*!
Implements the Electron's video subsystem plus appropriate signalling.
The Electron has an interlaced fully-bitmapped display with six different output modes,
running either at 40 or 80 columns. Memory is shared between video and CPU; when the video
is accessing it the CPU may not.
*/
class VideoOutput {
public:
/*!
Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied
should be to address 0 in the unexpanded Electron's memory map.
*/
VideoOutput(uint8_t *memory);
/// @returns the CRT to which output is being painted.
std::shared_ptr<Outputs::CRT::CRT> get_crt();
/// Produces the next @c number_of_cycles cycles of video output.
void run_for_cycles(int number_of_cycles);
/*!
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
*/
void set_register(int address, uint8_t value);
/*!
Describes an interrupt the video hardware will generate by its identity and scheduling time.
*/
struct Interrupt {
/// The interrupt that will be signalled.
Electron::Interrupt interrupt;
/// The number of cycles until it is signalled.
int cycles;
};
/*!
@returns the next interrupt that should be generated as a result of the video hardware.
The time until signalling returned is the number of cycles after the final one triggered
by the most recent call to @c run_for_cycles.
This result may be mutated by calls to @c set_register.
*/
Interrupt get_next_interrupt();
/*!
@returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time)
before the video circuits will allow the CPU to access RAM.
*/
unsigned int get_cycles_until_next_ram_availability(int from_time);
struct Range {
uint16_t low_address, high_address;
};
/*!
@returns the range of addresses that the video might read from.
*/
Range get_memory_access_range();
private:
inline void start_pixel_line();
inline void end_pixel_line();
inline void output_pixels(unsigned int number_of_cycles);
inline void setup_base_address();
int output_position_, unused_cycles_;
uint8_t palette_[16];
uint8_t screen_mode_;
uint16_t screen_mode_base_address_;
uint16_t start_screen_address_;
uint8_t *ram_;
struct {
uint16_t forty1bpp[256];
uint8_t forty2bpp[256];
uint32_t eighty1bpp[256];
uint16_t eighty2bpp[256];
uint8_t eighty4bpp[256];
} palette_tables_;
// Display generation.
uint16_t start_line_address_, current_screen_address_;
int current_pixel_line_, current_pixel_column_, current_character_row_;
uint8_t last_pixel_byte_;
bool is_blank_line_;
// CRT output
uint8_t *current_output_target_, *initial_output_target_;
unsigned int current_output_divider_;
std::shared_ptr<Outputs::CRT::CRT> crt_;
struct DrawAction {
enum Type {
Sync, ColourBurst, Blank, Pixels
} type;
int length;
DrawAction(Type type, int length) : type(type), length(length) {}
};
std::vector<DrawAction> screen_map_;
void setup_screen_map();
void emplace_blank_line();
void emplace_pixel_line();
size_t screen_map_pointer_;
int cycles_into_draw_action_;
};
}
#endif /* Video_hpp */

View File

@@ -10,18 +10,15 @@
#include <cstdlib>
void Memory::Fuzz(uint8_t *buffer, size_t size)
{
void Memory::Fuzz(uint8_t *buffer, size_t size) {
unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256;
unsigned int shift = 1, value = 1;
while(value < divider)
{
while(value < divider) {
value <<= 1;
shift++;
}
for(size_t c = 0; c < size; c++)
{
buffer[c] = (uint8_t)(rand() >> shift);
for(size_t c = 0; c < size; c++) {
buffer[c] = (uint8_t)(std::rand() >> shift);
}
}

117
Machines/Oric/Microdisc.cpp Normal file
View File

@@ -0,0 +1,117 @@
//
// Microdisc.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Microdisc.hpp"
using namespace Oric;
namespace {
// The number below, in cycles against an 8Mhz clock, was arrived at fairly unscientifically,
// by comparing the amount of time this emulator took to show a directory versus a video of
// a real Oric. It therefore assumes all other timing measurements were correct on the day
// of the test. More work to do, I think.
const int head_load_request_counter_target = 7653333;
}
Microdisc::Microdisc() :
irq_enable_(false),
delegate_(nullptr),
paging_flags_(BASICDisable),
head_load_request_counter_(-1),
WD1770(P1793),
last_control_(0) {
set_control_register(last_control_, 0xff);
}
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
}
void Microdisc::set_control_register(uint8_t control) {
uint8_t changes = last_control_ ^ control;
last_control_ = control;
set_control_register(control, changes);
}
void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b2: data separator clock rate select (1 = double) [TODO]
// b65: drive select
if((changes >> 5)&3) {
selected_drive_ = (control >> 5)&3;
set_drive(drives_[selected_drive_]);
}
// b4: side select
if(changes & 0x10) {
unsigned int head = (control & 0x10) ? 1 : 0;
for(int c = 0; c < 4; c++) {
if(drives_[c]) drives_[c]->set_head(head);
}
}
// b3: double density select (0 = double)
if(changes & 0x08) {
set_is_double_density(!(control & 0x08));
}
// b0: IRQ enable
if(changes & 0x01) {
bool had_irq = get_interrupt_request_line();
irq_enable_ = !!(control & 0x01);
bool has_irq = get_interrupt_request_line();
if(has_irq != had_irq && delegate_) {
delegate_->wd1770_did_change_output(this);
}
}
// b7: EPROM select (0 = select)
// b1: ROM disable (0 = disable)
if(changes & 0x82) {
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0);
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
}
}
bool Microdisc::get_interrupt_request_line() {
return irq_enable_ && WD1770::get_interrupt_request_line();
}
uint8_t Microdisc::get_interrupt_request_register() {
return 0x7f | (WD1770::get_interrupt_request_line() ? 0x00 : 0x80);
}
uint8_t Microdisc::get_data_request_register() {
return 0x7f | (get_data_request_line() ? 0x00 : 0x80);
}
void Microdisc::set_head_load_request(bool head_load) {
set_motor_on(head_load);
if(head_load) {
head_load_request_counter_ = 0;
} else {
head_load_request_counter_ = head_load_request_counter_target;
set_head_loaded(head_load);
}
}
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
if(head_load_request_counter_ < head_load_request_counter_target) {
head_load_request_counter_ += number_of_cycles;
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
}
WD::WD1770::run_for_cycles(number_of_cycles);
}
bool Microdisc::get_drive_is_ready() {
return true;
}

View File

@@ -0,0 +1,56 @@
//
// Microdisc.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Microdisc_hpp
#define Microdisc_hpp
#include "../../Components/1770/1770.hpp"
namespace Oric {
class Microdisc: public WD::WD1770 {
public:
Microdisc();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void set_control_register(uint8_t control);
uint8_t get_interrupt_request_register();
uint8_t get_data_request_register();
bool get_interrupt_request_line();
void run_for_cycles(unsigned int number_of_cycles);
enum PagingFlags {
BASICDisable = (1 << 0),
MicrodscDisable = (1 << 1)
};
class Delegate: public WD1770::Delegate {
public:
virtual void microdisc_did_change_paging_flags(Microdisc *microdisc) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
inline int get_paging_flags() { return paging_flags_; }
private:
void set_control_register(uint8_t control, uint8_t changes);
void set_head_load_request(bool head_load);
bool get_drive_is_ready();
std::shared_ptr<Storage::Disk::Drive> drives_[4];
int selected_drive_;
bool irq_enable_;
int paging_flags_;
int head_load_request_counter_;
Delegate *delegate_;
uint8_t last_control_;
};
}
#endif /* Microdisc_hpp */

View File

@@ -12,231 +12,278 @@
using namespace Oric;
Machine::Machine() :
_cycles_since_video_update(0),
_use_fast_tape_hack(false),
_typer_delay(2500000)
{
cycles_since_video_update_(0),
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false) {
set_clock_rate(1000000);
_via.set_interrupt_delegate(this);
_keyboard.reset(new Keyboard);
_via.keyboard = _keyboard;
via_.set_interrupt_delegate(this);
via_.keyboard = keyboard_;
clear_all_keys();
_via.tape->set_delegate(this);
Memory::Fuzz(_ram, sizeof(_ram));
via_.tape->set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
}
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
if(target.tapes.size())
{
_via.tape->set_tape(target.tapes.front());
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
via_.tape->set_tape(target.tapes.front());
}
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
int drive_index = 0;
for(auto disk : target.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}
if(target.oric.use_atmos_rom) {
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
tape_get_byte_address_ = 0xe6c9;
scan_keyboard_address_ = 0xf495;
tape_speed_address_ = 0x024d;
} else {
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
is_using_basic11_ = false;
tape_get_byte_address_ = 0xe630;
scan_keyboard_address_ = 0xf43c;
tape_speed_address_ = 0x67;
}
}
void Machine::set_rom(std::vector<uint8_t> data)
{
memcpy(_rom, data.data(), std::min(data.size(), sizeof(_rom)));
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
switch(rom) {
case BASIC11: basic11_rom_ = std::move(data); break;
case BASIC10: basic10_rom_ = std::move(data); break;
case Microdisc: microdisc_rom_ = std::move(data); break;
case Colour:
colour_rom_ = std::move(data);
if(video_output_) video_output_->set_colour_rom(colour_rom_);
break;
}
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{
if(address >= 0xc000)
{
if(isReadOperation(operation)) *value = _rom[address&16383];
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
if(address == 0xe6c9 && _use_fast_tape_hack && operation == CPU6502::BusOperation::ReadOpcode && _via.tape->has_tape() && !_via.tape->get_tape()->is_at_end())
{
uint8_t next_byte = _via.tape->get_next_byte(!_ram[0x024d]);
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
set_value_of_register(CPU6502::A, next_byte);
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
*value = 0x60; // i.e. RTS
}
}
else
{
if((address & 0xff00) == 0x0300)
{
if(isReadOperation(operation)) *value = _via.get_register(address);
else _via.set_register(address, *value);
}
else
{
} else {
if((address & 0xff00) == 0x0300) {
if(microdisc_is_enabled_ && address >= 0x0310) {
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
else microdisc_.set_register(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
} else {
if(isReadOperation(operation)) *value = via_.get_register(address);
else via_.set_register(address, *value);
}
} else {
if(isReadOperation(operation))
*value = _ram[address];
else
{
if(address >= 0x9800) { update_video(); _typer_delay = 0; }
_ram[address] = *value;
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
ram_[address] = *value;
}
}
}
if(_typer && operation == CPU6502::BusOperation::ReadOpcode && address == 0xF495)
{
if(!_typer->type_next_character())
{
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) {
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
// do anything until at least the second, regardless of machine
if(!keyboard_read_count_) keyboard_read_count_++;
else if(!typer_->type_next_character()) {
clear_all_keys();
_typer.reset();
typer_.reset();
}
}
_via.run_for_cycles(1);
_cycles_since_video_update++;
via_.run_for_cycles(1);
if(microdisc_is_enabled_) microdisc_.run_for_cycles(8);
cycles_since_video_update_++;
return 1;
}
void Machine::synchronise()
{
void Machine::synchronise() {
update_video();
_via.synchronise();
via_.synchronise();
}
void Machine::update_video()
{
_videoOutput->run_for_cycles(_cycles_since_video_update);
_cycles_since_video_update = 0;
void Machine::update_video() {
video_output_->run_for_cycles(cycles_since_video_update_);
cycles_since_video_update_ = 0;
}
void Machine::setup_output(float aspect_ratio)
{
_videoOutput.reset(new VideoOutput(_ram));
_via.ay8910.reset(new GI::AY38910());
_via.ay8910->set_clock_rate(1000000);
void Machine::setup_output(float aspect_ratio) {
via_.ay8910.reset(new GI::AY38910());
via_.ay8910->set_clock_rate(1000000);
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
}
void Machine::close_output()
{
_videoOutput.reset();
_via.ay8910.reset();
void Machine::close_output() {
video_output_.reset();
via_.ay8910.reset();
}
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
{
set_irq_line(_via.get_interrupt_line());
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
set_interrupt_line();
}
void Machine::set_key_state(uint16_t key, bool isPressed)
{
if(key == KeyNMI)
{
void Machine::set_key_state(uint16_t key, bool isPressed) {
if(key == KeyNMI) {
set_nmi_line(isPressed);
}
else
{
} else {
if(isPressed)
_keyboard->rows[key >> 8] |= (key & 0xff);
keyboard_->rows[key >> 8] |= (key & 0xff);
else
_keyboard->rows[key >> 8] &= ~(key & 0xff);
keyboard_->rows[key >> 8] &= ~(key & 0xff);
}
}
void Machine::clear_all_keys()
{
memset(_keyboard->rows, 0, sizeof(_keyboard->rows));
void Machine::clear_all_keys() {
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
}
void Machine::set_use_fast_tape_hack(bool activate)
{
_use_fast_tape_hack = activate;
void Machine::set_use_fast_tape_hack(bool activate) {
use_fast_tape_hack_ = activate;
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player)
{
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
video_output_->set_output_device(output_device);
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
// set CB1
_via.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
}
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt()
{
return _videoOutput->get_crt();
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
{
return _via.ay8910;
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return via_.ay8910;
}
void Machine::run_for_cycles(int number_of_cycles)
{
void Machine::run_for_cycles(int number_of_cycles) {
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
}
#pragma mark - The 6522
Machine::VIA::VIA() :
MOS::MOS6522<Machine::VIA>(),
_cycles_since_ay_update(0),
tape(new TapePlayer) {}
MOS::MOS6522<Machine::VIA>(),
cycles_since_ay_update_(0),
tape(new TapePlayer) {}
void Machine::VIA::set_control_line_output(Port port, Line line, bool value)
{
if(line)
{
if(port) _ay_bdir = value; else _ay_bc1 = value;
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
}
}
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask)
{
if(port)
{
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard->row = value;
tape->set_motor_control(value & 0x40);
}
else
{
} else {
ay8910->set_data_input(value);
}
}
uint8_t Machine::VIA::get_port_input(Port port)
{
if(port)
{
uint8_t Machine::VIA::get_port_input(Port port) {
if(port) {
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
}
else
{
} else {
return ay8910->get_data_output();
}
}
void Machine::VIA::synchronise()
{
ay8910->run_for_cycles(_cycles_since_ay_update);
_cycles_since_ay_update = 0;
void Machine::VIA::synchronise() {
ay8910->run_for_cycles(cycles_since_ay_update_);
ay8910->flush();
cycles_since_ay_update_ = 0;
}
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles)
{
_cycles_since_ay_update += number_of_cycles;
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
cycles_since_ay_update_ += number_of_cycles;
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
tape->run_for_cycles((int)number_of_cycles);
}
void Machine::VIA::update_ay()
{
ay8910->run_for_cycles(_cycles_since_ay_update);
_cycles_since_ay_update = 0;
ay8910->set_control_lines( (GI::AY38910::ControlLines)((_ay_bdir ? GI::AY38910::BCDIR : 0) | (_ay_bc1 ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
void Machine::VIA::update_ay() {
ay8910->run_for_cycles(cycles_since_ay_update_);
cycles_since_ay_update_ = 0;
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
#pragma mark - TapePlayer
Machine::TapePlayer::TapePlayer() :
Storage::Tape::BinaryTapePlayer(1000000)
{}
Storage::Tape::BinaryTapePlayer(1000000) {}
uint8_t Machine::TapePlayer::get_next_byte(bool fast)
{
return (uint8_t)_parser.get_next_byte(get_tape(), fast);
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
}
#pragma mark - Microdisc
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = 0xbfff;
paged_rom_ = rom_;
} else {
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
ram_top_ = 0xffff;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
set_interrupt_line();
}
void Machine::set_interrupt_line() {
set_irq_line(
via_.get_interrupt_line() ||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
}

View File

@@ -19,6 +19,7 @@
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "Video.hpp"
#include "Microdisc.hpp"
#include "../../Storage/Tape/Tape.hpp"
@@ -51,22 +52,28 @@ enum Key: uint16_t {
TerminateSequence = 0xffff, NotMapped = 0xfffe
};
enum ROM {
BASIC10, BASIC11, Microdisc, Colour
};
class Machine:
public CPU6502::Processor<Machine>,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate {
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate {
public:
Machine();
void set_rom(std::vector<uint8_t> data);
void set_rom(ROM rom, const std::vector<uint8_t> &data);
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
void set_use_fast_tape_hack(bool activate);
void set_output_device(Outputs::CRT::OutputDevice output_device);
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target);
@@ -91,14 +98,24 @@ class Machine:
// for Utility::TypeRecipient::Delegate
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc);
void wd1770_did_change_output(WD::WD1770 *wd1770);
private:
// RAM and ROM
uint8_t _ram[65536], _rom[16384];
int _cycles_since_video_update;
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536], rom_[16384];
int cycles_since_video_update_;
inline void update_video();
// ROM bookkeeping
bool is_using_basic11_;
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
int keyboard_read_count_;
// Outputs
std::unique_ptr<VideoOutput> _videoOutput;
std::unique_ptr<VideoOutput> video_output_;
// Keyboard
class Keyboard {
@@ -106,7 +123,7 @@ class Machine:
uint8_t row;
uint8_t rows[8];
};
int _typer_delay;
int typer_delay_;
// The tape
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
@@ -115,9 +132,9 @@ class Machine:
uint8_t get_next_byte(bool fast);
private:
Storage::Tape::Oric::Parser _parser;
Storage::Tape::Oric::Parser parser_;
};
bool _use_fast_tape_hack;
bool use_fast_tape_hack_;
// VIA (which owns the tape and the AY)
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
@@ -138,11 +155,19 @@ class Machine:
private:
void update_ay();
bool _ay_bdir, _ay_bc1;
unsigned int _cycles_since_ay_update;
bool ay_bdir_, ay_bc1_;
unsigned int cycles_since_ay_update_;
};
VIA _via;
std::shared_ptr<Keyboard> _keyboard;
VIA via_;
std::shared_ptr<Keyboard> keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_;
uint16_t ram_top_;
uint8_t *paged_rom_;
inline void set_interrupt_line();
};
}

View File

@@ -1,7 +1,6 @@
#include "Oric.hpp"
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character)
{
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
#define X {NotMapped}

View File

@@ -20,199 +20,207 @@ namespace {
}
VideoOutput::VideoOutput(uint8_t *memory) :
_ram(memory),
_frame_counter(0), _counter(0),
_is_graphics_mode(false),
_character_set_base_address(0xb400),
_phase(0),
_v_sync_start_position(PAL50VSyncStartPosition), _v_sync_end_position(PAL50VSyncEndPosition),
_counter_period(PAL50Period), _next_frame_is_sixty_hertz(false)
{
_crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1));
// TODO: this is a copy and paste from the Electron; factor out.
_crt->set_rgb_sampling_function(
ram_(memory),
frame_counter_(0), counter_(0),
is_graphics_mode_(false),
character_set_base_address_(0xb400),
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) {
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
"}");
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{"
"uint texValue = uint(dot(texture(sampler, coordinate).rg, uvec2(1, 256)));"
"uint iPhase = uint((phase + 3.141592654 + 0.39269908175) * 2.0 / 3.141592654) & 3u;"
"texValue = (texValue >> (4u*(3u - iPhase))) & 15u;"
"return (float(texValue) - 4.0) / 20.0;"
"}"
);
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
_crt->set_output_device(Outputs::CRT::Television);
_crt->set_visible_area(_crt->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
set_output_device(Outputs::CRT::Television);
crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt()
{
return _crt;
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
output_device_ = output_device;
crt_->set_output_device(output_device);
}
void VideoOutput::run_for_cycles(int number_of_cycles)
{
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
for(size_t c = 0; c < 8; c++) {
size_t index = (c << 2);
uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]);
rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0);
colour_forms_[c] = rom_value;
}
// check for big endianness and byte swap if required
uint16_t test_value = 0x0001;
if(*(uint8_t *)&test_value != 0x01) {
for(size_t c = 0; c < 8; c++) {
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
}
}
}
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_;
}
void VideoOutput::run_for_cycles(int number_of_cycles) {
// Vertical: 039: pixels; otherwise blank; 4853 sync, 5456 colour burst
// Horizontal: 0223: pixels; otherwise blank; 256259 sync
#define clamp(action) \
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
while(number_of_cycles)
{
int h_counter =_counter & 63;
while(number_of_cycles) {
int h_counter = counter_ & 63;
int cycles_run_for = 0;
if(_counter >= _v_sync_start_position && _counter < _v_sync_end_position)
{
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) {
// this is a sync line
cycles_run_for = _v_sync_end_position - _counter;
clamp(_crt->output_sync((unsigned int)(_v_sync_end_position - _v_sync_start_position) * 6));
}
else if(_counter < 224*64 && h_counter < 40)
{
cycles_run_for = v_sync_end_position_ - counter_;
clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6));
} else if(counter_ < 224*64 && h_counter < 40) {
// this is a pixel line
if(!h_counter)
{
_ink = 0xff;
_paper = 0x00;
_use_alternative_character_set = _use_double_height_characters = _blink_text = false;
if(!h_counter) {
ink_ = 0x7;
paper_ = 0x0;
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
set_character_set_base_address();
_phase += 64;
_pixel_target = _crt->allocate_write_area(120);
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
if(!_counter)
{
_phase += 128; // TODO: incorporate all the lines that were missed
_frame_counter++;
if(!counter_) {
frame_counter_++;
_v_sync_start_position = _next_frame_is_sixty_hertz ? PAL60VSyncStartPosition : PAL50VSyncStartPosition;
_v_sync_end_position = _next_frame_is_sixty_hertz ? PAL60VSyncEndPosition : PAL50VSyncEndPosition;
_counter_period = _next_frame_is_sixty_hertz ? PAL60Period : PAL50Period;
v_sync_start_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncStartPosition : PAL50VSyncStartPosition;
v_sync_end_position_ = next_frame_is_sixty_hertz_ ? PAL60VSyncEndPosition : PAL50VSyncEndPosition;
counter_period_ = next_frame_is_sixty_hertz_ ? PAL60Period : PAL50Period;
}
}
cycles_run_for = std::min(40 - h_counter, number_of_cycles);
int columns = cycles_run_for;
int pixel_base_address = 0xa000 + (_counter >> 6) * 40;
int character_base_address = 0xbb80 + (_counter >> 9) * 40;
uint8_t blink_mask = (_blink_text && (_frame_counter&32)) ? 0x00 : 0xff;
int pixel_base_address = 0xa000 + (counter_ >> 6) * 40;
int character_base_address = 0xbb80 + (counter_ >> 9) * 40;
uint8_t blink_mask = (blink_text_ && (frame_counter_&32)) ? 0x00 : 0xff;
while(columns--)
{
while(columns--) {
uint8_t pixels, control_byte;
if(_is_graphics_mode && _counter < 200*64)
{
control_byte = pixels = _ram[pixel_base_address + h_counter];
}
else
{
if(is_graphics_mode_ && counter_ < 200*64) {
control_byte = pixels = ram_[pixel_base_address + h_counter];
} else {
int address = character_base_address + h_counter;
control_byte = _ram[address];
int line = _use_double_height_characters ? ((_counter >> 7) & 7) : ((_counter >> 6) & 7);
pixels = _ram[_character_set_base_address + (control_byte&127) * 8 + line];
control_byte = ram_[address];
int line = use_double_height_characters_ ? ((counter_ >> 7) & 7) : ((counter_ >> 6) & 7);
pixels = ram_[character_set_base_address_ + (control_byte&127) * 8 + line];
}
uint8_t inverse_mask = (control_byte & 0x80) ? 0x77 : 0x00;
uint8_t inverse_mask = (control_byte & 0x80) ? 0x7 : 0x0;
pixels &= blink_mask;
if(control_byte & 0x60)
{
if(_pixel_target)
{
uint8_t colours[2] = {
(uint8_t)(_paper ^ inverse_mask),
(uint8_t)(_ink ^ inverse_mask),
};
_pixel_target[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0);
_pixel_target[1] = (colours[(pixels >> 2)&1] & 0x0f) | (colours[(pixels >> 3)&1] & 0xf0);
_pixel_target[2] = (colours[(pixels >> 0)&1] & 0x0f) | (colours[(pixels >> 1)&1] & 0xf0);
if(control_byte & 0x60) {
if(pixel_target_) {
uint16_t colours[2];
if(output_device_ == Outputs::CRT::Monitor) {
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
} else {
colours[0] = colour_forms_[paper_ ^ inverse_mask];
colours[1] = colour_forms_[ink_ ^ inverse_mask];
}
pixel_target_[0] = colours[(pixels >> 5)&1];
pixel_target_[1] = colours[(pixels >> 4)&1];
pixel_target_[2] = colours[(pixels >> 3)&1];
pixel_target_[3] = colours[(pixels >> 2)&1];
pixel_target_[4] = colours[(pixels >> 1)&1];
pixel_target_[5] = colours[(pixels >> 0)&1];
}
}
else
{
switch(control_byte & 0x1f)
{
case 0x00: _ink = 0x00; break;
case 0x01: _ink = 0x44; break;
case 0x02: _ink = 0x22; break;
case 0x03: _ink = 0x66; break;
case 0x04: _ink = 0x11; break;
case 0x05: _ink = 0x55; break;
case 0x06: _ink = 0x33; break;
case 0x07: _ink = 0x77; break;
} else {
switch(control_byte & 0x1f) {
case 0x00: ink_ = 0x0; break;
case 0x01: ink_ = 0x4; break;
case 0x02: ink_ = 0x2; break;
case 0x03: ink_ = 0x6; break;
case 0x04: ink_ = 0x1; break;
case 0x05: ink_ = 0x5; break;
case 0x06: ink_ = 0x3; break;
case 0x07: ink_ = 0x7; break;
case 0x08: case 0x09: case 0x0a: case 0x0b:
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
_use_alternative_character_set = (control_byte&1);
_use_double_height_characters = (control_byte&2);
_blink_text = (control_byte&4);
use_alternative_character_set_ = (control_byte&1);
use_double_height_characters_ = (control_byte&2);
blink_text_ = (control_byte&4);
set_character_set_base_address();
break;
case 0x10: _paper = 0x00; break;
case 0x11: _paper = 0x44; break;
case 0x12: _paper = 0x22; break;
case 0x13: _paper = 0x66; break;
case 0x14: _paper = 0x11; break;
case 0x15: _paper = 0x55; break;
case 0x16: _paper = 0x33; break;
case 0x17: _paper = 0x77; break;
case 0x10: paper_ = 0x0; break;
case 0x11: paper_ = 0x4; break;
case 0x12: paper_ = 0x2; break;
case 0x13: paper_ = 0x6; break;
case 0x14: paper_ = 0x1; break;
case 0x15: paper_ = 0x5; break;
case 0x16: paper_ = 0x3; break;
case 0x17: paper_ = 0x7; break;
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
_is_graphics_mode = (control_byte & 4);
_next_frame_is_sixty_hertz = !(control_byte & 2);
is_graphics_mode_ = (control_byte & 4);
next_frame_is_sixty_hertz_ = !(control_byte & 2);
break;
default: break;
}
if(_pixel_target) _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = (uint8_t)(_paper ^ inverse_mask);
if(pixel_target_) {
pixel_target_[0] = pixel_target_[1] =
pixel_target_[2] = pixel_target_[3] =
pixel_target_[4] = pixel_target_[5] =
(output_device_ == Outputs::CRT::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
}
}
if(_pixel_target) _pixel_target += 3;
if(pixel_target_) pixel_target_ += 6;
h_counter++;
}
if(h_counter == 40)
{
_crt->output_data(40 * 6, 2);
if(h_counter == 40) {
crt_->output_data(40 * 6, 1);
}
}
else
{
} else {
// this is a blank line (or the equivalent part of a pixel line)
if(h_counter < 48)
{
if(h_counter < 48) {
cycles_run_for = 48 - h_counter;
clamp(
int period = (_counter < 224*64) ? 8 : 48;
_crt->output_blank((unsigned int)period * 6);
int period = (counter_ < 224*64) ? 8 : 48;
crt_->output_blank((unsigned int)period * 6);
);
}
else if(h_counter < 54)
{
} else if(h_counter < 54) {
cycles_run_for = 54 - h_counter;
clamp(_crt->output_sync(6 * 6));
}
else if(h_counter < 56)
{
clamp(crt_->output_sync(6 * 6));
} else if(h_counter < 56) {
cycles_run_for = 56 - h_counter;
clamp(_crt->output_colour_burst(2 * 6, _phase, 128));
}
else
{
clamp(crt_->output_default_colour_burst(2 * 6));
} else {
cycles_run_for = 64 - h_counter;
clamp(_crt->output_blank(8 * 6));
clamp(crt_->output_blank(8 * 6));
}
}
_counter = (_counter + cycles_run_for)%_counter_period;
counter_ = (counter_ + cycles_run_for)%counter_period_;
number_of_cycles -= cycles_run_for;
}
}
void VideoOutput::set_character_set_base_address()
{
if(_is_graphics_mode) _character_set_base_address = _use_alternative_character_set ? 0x9c00 : 0x9800;
else _character_set_base_address = _use_alternative_character_set ? 0xb800 : 0xb400;
void VideoOutput::set_character_set_base_address() {
if(is_graphics_mode_) character_set_base_address_ = use_alternative_character_set_ ? 0x9c00 : 0x9800;
else character_set_base_address_ = use_alternative_character_set_ ? 0xb800 : 0xb400;
}

View File

@@ -18,31 +18,33 @@ class VideoOutput {
VideoOutput(uint8_t *memory);
std::shared_ptr<Outputs::CRT::CRT> get_crt();
void run_for_cycles(int number_of_cycles);
void set_colour_rom(const std::vector<uint8_t> &rom);
void set_output_device(Outputs::CRT::OutputDevice output_device);
private:
uint8_t *_ram;
std::shared_ptr<Outputs::CRT::CRT> _crt;
uint8_t *ram_;
std::shared_ptr<Outputs::CRT::CRT> crt_;
// Counters and limits
int _counter, _frame_counter;
int _v_sync_start_position, _v_sync_end_position, _counter_period;
int counter_, frame_counter_;
int v_sync_start_position_, v_sync_end_position_, counter_period_;
// Output target
uint8_t *_pixel_target;
// Output target and device
uint16_t *pixel_target_;
uint16_t colour_forms_[8];
Outputs::CRT::OutputDevice output_device_;
// Registers
uint8_t _ink, _paper;
uint8_t ink_, paper_;
int _character_set_base_address;
int character_set_base_address_;
inline void set_character_set_base_address();
bool _is_graphics_mode;
bool _next_frame_is_sixty_hertz;
bool _use_alternative_character_set;
bool _use_double_height_characters;
bool _blink_text;
uint8_t _phase;
bool is_graphics_mode_;
bool next_frame_is_sixty_hertz_;
bool use_alternative_character_set_;
bool use_double_height_characters_;
bool blink_text_;
};
}

View File

@@ -12,76 +12,61 @@
using namespace Utility;
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
_counter(-delay), _frequency(frequency), _string_pointer(0), _delegate(delegate), _phase(0)
{
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) {
size_t string_size = strlen(string) + 3;
_string = (char *)malloc(string_size);
snprintf(_string, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
string_ = (char *)malloc(string_size);
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
}
void Typer::update(int duration)
{
if(_string)
{
if(_counter < 0 && _counter + duration >= 0)
{
if(!type_next_character())
{
_delegate->typer_reset(this);
void Typer::update(int duration) {
if(string_) {
if(counter_ < 0 && counter_ + duration >= 0) {
if(!type_next_character()) {
delegate_->typer_reset(this);
}
}
_counter += duration;
while(_string && _counter > _frequency)
{
_counter -= _frequency;
if(!type_next_character())
{
_delegate->typer_reset(this);
counter_ += duration;
while(string_ && counter_ > frequency_) {
counter_ -= frequency_;
if(!type_next_character()) {
delegate_->typer_reset(this);
}
}
}
}
bool Typer::type_next_character()
{
if(_string == nullptr) return false;
bool Typer::type_next_character() {
if(string_ == nullptr) return false;
if(_delegate->typer_set_next_character(this, _string[_string_pointer], _phase))
{
_phase = 0;
if(!_string[_string_pointer])
{
free(_string);
_string = nullptr;
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) {
phase_ = 0;
if(!string_[string_pointer_]) {
free(string_);
string_ = nullptr;
return false;
}
_string_pointer++;
}
else
{
_phase++;
string_pointer_++;
} else {
phase_++;
}
return true;
}
Typer::~Typer()
{
free(_string);
Typer::~Typer() {
free(string_);
}
#pragma mark - Delegate
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase)
{
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) {
uint16_t *sequence = sequence_for_character(typer, character);
if(!sequence) return true;
if(!phase) clear_all_keys();
else
{
else {
set_key_state(sequence[phase - 1], true);
return sequence[phase] == Typer::Delegate::EndSequence;
}
@@ -89,7 +74,6 @@ bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char chara
return false;
}
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character)
{
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) {
return nullptr;
}

View File

@@ -35,31 +35,29 @@ class Typer {
const char EndString = 0x03; // i.e. ASCII end of text
private:
char *_string;
int _frequency;
int _counter;
int _phase;
Delegate *_delegate;
size_t _string_pointer;
char *string_;
int frequency_;
int counter_;
int phase_;
Delegate *delegate_;
size_t string_pointer_;
};
class TypeRecipient: public Typer::Delegate {
public:
void set_typer_for_string(const char *string)
{
_typer.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
void set_typer_for_string(const char *string) {
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
}
void typer_reset(Typer *typer)
{
void typer_reset(Typer *typer) {
clear_all_keys();
_typer.reset();
typer_.reset();
}
protected:
virtual int get_typer_delay() { return 0; }
virtual int get_typer_frequency() { return 0; }
std::unique_ptr<Typer> _typer;
std::unique_ptr<Typer> typer_;
};
}

View File

@@ -1,11 +0,0 @@
//
// CRC.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CRC.hpp"
using namespace NumberTheory;

View File

@@ -16,22 +16,27 @@ namespace NumberTheory {
class CRC16 {
public:
CRC16(uint16_t polynomial, uint16_t reset_value) :
reset_value_(reset_value), value_(reset_value), polynomial_(polynomial) {}
inline void reset() { value_ = reset_value_; }
inline void add(uint8_t value) {
// TODO: go table based
value_ ^= (uint16_t)value << 8;
for(int c = 0; c < 8; c++)
{
uint16_t exclusive_or = (value_&0x8000) ? polynomial_ : 0x0000;
value_ = (uint16_t)(value_ << 1) ^ exclusive_or;
reset_value_(reset_value), value_(reset_value) {
for(int c = 0; c < 256; c++) {
uint16_t shift_value = (uint16_t)(c << 8);
for(int b = 0; b < 8; b++) {
uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000;
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
}
xor_table[c] = (uint16_t)shift_value;
}
}
inline uint16_t get_value() { return value_; }
inline void reset() { value_ = reset_value_; }
inline void add(uint8_t byte) {
value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
}
inline uint16_t get_value() const { return value_; }
inline void set_value(uint16_t value) { value_ = value; }
private:
uint16_t reset_value_, polynomial_;
const uint16_t reset_value_;
uint16_t xor_table[256];
uint16_t value_;
};

View File

@@ -10,14 +10,17 @@
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */; };
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; };
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; };
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; };
@@ -26,8 +29,10 @@
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; };
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AF8681E513FC20027EE29 /* TIATests.mm */; };
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
@@ -39,6 +44,7 @@
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; };
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; };
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */; };
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; };
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; };
4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; };
@@ -47,15 +53,25 @@
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; };
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; };
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; };
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */; };
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */; };
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A12551DD55862007A2231 /* Disassembler6502.cpp */; };
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBB1DE31D1500AEC565 /* OricMFMDSK.cpp */; };
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; };
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; };
@@ -69,6 +85,8 @@
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */; };
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; };
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; };
@@ -343,6 +361,7 @@
4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; };
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; };
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
@@ -351,7 +370,7 @@
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */; };
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; };
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
@@ -374,19 +393,23 @@
4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */; };
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */; };
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; };
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; };
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -414,6 +437,10 @@
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; };
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; };
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = "<group>"; };
4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = "<group>"; };
@@ -421,11 +448,12 @@
4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = "<group>"; };
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
4B1D08051E0F7A1100763741 /* TimeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TimeTests.mm; sourceTree = "<group>"; };
4B1E85731D170228001EF87D /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; };
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
@@ -445,10 +473,12 @@
4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = "<group>"; };
4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; };
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; };
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
@@ -472,6 +502,8 @@
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = "<group>"; };
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
@@ -485,11 +517,22 @@
4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = "<group>"; };
4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = "<group>"; };
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = "<group>"; };
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = "<group>"; };
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = "<group>"; };
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
4B5A12551DD55862007A2231 /* Disassembler6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disassembler6502.cpp; path = ../../StaticAnalyser/Disassembler/Disassembler6502.cpp; sourceTree = "<group>"; };
4B5A12561DD55862007A2231 /* Disassembler6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disassembler6502.hpp; path = ../../StaticAnalyser/Disassembler/Disassembler6502.hpp; sourceTree = "<group>"; };
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; };
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
4B5FADBB1DE31D1500AEC565 /* OricMFMDSK.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricMFMDSK.cpp; sourceTree = "<group>"; };
4B5FADBC1DE31D1500AEC565 /* OricMFMDSK.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricMFMDSK.hpp; sourceTree = "<group>"; };
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = "<group>"; };
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Microdisc.hpp; path = Oric/Microdisc.hpp; sourceTree = "<group>"; };
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
@@ -501,6 +544,11 @@
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = "<group>"; };
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; };
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
@@ -521,6 +569,8 @@
4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; };
4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+DataResource.m"; sourceTree = "<group>"; };
4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronOptionsPanel.swift; sourceTree = "<group>"; };
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = "<group>"; };
4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; };
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = "<group>"; };
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
@@ -805,6 +855,7 @@
4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; };
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
@@ -825,8 +876,8 @@
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; };
4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTInputBufferBuilder.cpp; sourceTree = "<group>"; };
4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTInputBufferBuilder.hpp; sourceTree = "<group>"; };
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = "<group>"; };
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.hpp; sourceTree = "<group>"; };
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = "<group>"; };
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = "<group>"; };
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = "<group>"; };
@@ -868,12 +919,37 @@
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = "<group>"; };
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
4BEA52611DF339D7007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = Electron/Speaker.cpp; sourceTree = "<group>"; };
4BEA52621DF339D7007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = Electron/Speaker.hpp; sourceTree = "<group>"; };
4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = "<group>"; };
4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeActivisionStack.hpp; sourceTree = "<group>"; };
4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari16k.hpp; sourceTree = "<group>"; };
4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari32k.hpp; sourceTree = "<group>"; };
4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeAtari8k.hpp; sourceTree = "<group>"; };
4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCBSRAMPlus.hpp; sourceTree = "<group>"; };
4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeCommaVid.hpp; sourceTree = "<group>"; };
4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMegaBoy.hpp; sourceTree = "<group>"; };
4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeMNetwork.hpp; sourceTree = "<group>"; };
4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeParkerBros.hpp; sourceTree = "<group>"; };
4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeTigervision.hpp; sourceTree = "<group>"; };
4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartridgeUnpaged.hpp; sourceTree = "<group>"; };
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CartridgePitfall2.hpp; sourceTree = "<group>"; };
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
@@ -885,13 +961,11 @@
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = "<group>"; };
4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = "<group>"; };
4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -948,6 +1022,7 @@
4B1414631B588A1100E04248 /* Test Binaries */ = {
isa = PBXGroup;
children = (
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
@@ -964,6 +1039,18 @@
path = 6532;
sourceTree = "<group>";
};
4B1EDB411E39A0AC009D6819 /* Icons */ = {
isa = PBXGroup;
children = (
4B2C45411E3C3896002A2389 /* cartridge.png */,
4B79E4411E3AF38600141F11 /* cassette.png */,
4B79E4421E3AF38600141F11 /* floppy35.png */,
4B79E4431E3AF38600141F11 /* floppy525.png */,
4B1EDB431E39A0AC009D6819 /* chip.png */,
);
path = Icons;
sourceTree = "<group>";
};
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
isa = PBXGroup;
children = (
@@ -1024,8 +1111,15 @@
isa = PBXGroup;
children = (
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
4BE7C9161E3D397100A5496D /* TIA.cpp */,
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */,
4BEA52671DF34909007E74F2 /* PIA.hpp */,
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
4BE7C9171E3D397100A5496D /* TIA.hpp */,
4BEAC0801E7E0DF800EE56B2 /* Cartridges */,
);
path = Atari2600;
sourceTree = "<group>";
@@ -1034,10 +1128,17 @@
isa = PBXGroup;
children = (
4B2E2D9B1C3A070400138695 /* Electron.cpp */,
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */,
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
4BEA52611DF339D7007E74F2 /* Speaker.cpp */,
4BEA525D1DF33323007E74F2 /* Tape.cpp */,
4BC8A62B1DCE60E000DAC693 /* Typer.cpp */,
4B7913CA1DFCD80E00175A82 /* Video.cpp */,
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */,
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
4BEA52621DF339D7007E74F2 /* Speaker.hpp */,
4BEA525F1DF333D8007E74F2 /* Tape.hpp */,
4B7913CB1DFCD80E00175A82 /* Video.hpp */,
);
name = Electron;
sourceTree = "<group>";
@@ -1046,7 +1147,6 @@
isa = PBXGroup;
children = (
4B0CCC411C62D0B3001CAC5F /* CRT */,
4B2409531C45AB05004DA684 /* Speaker.cpp */,
4B2409541C45AB05004DA684 /* Speaker.hpp */,
);
name = Outputs;
@@ -1145,6 +1245,15 @@
path = Views;
sourceTree = "<group>";
};
4B5A12581DD55873007A2231 /* Disassembler */ = {
isa = PBXGroup;
children = (
4B5A12551DD55862007A2231 /* Disassembler6502.cpp */,
4B5A12561DD55862007A2231 /* Disassembler6502.hpp */,
);
name = Disassembler;
sourceTree = "<group>";
};
4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */ = {
isa = PBXGroup;
children = (
@@ -1172,6 +1281,8 @@
4B8805F81DCFF6CD003085B1 /* Data */,
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */,
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */,
);
name = Storage;
path = ../../Storage;
@@ -1242,11 +1353,15 @@
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
4BB697CF1D4BA44900248BDF /* Encodings */,
4BAB62B21D327F7E00DF5BA0 /* Formats */,
@@ -1257,14 +1372,16 @@
4BAB62B21D327F7E00DF5BA0 /* Formats */ = {
isa = PBXGroup;
children = (
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
4B4C836E1D4F623200CD541F /* D64.cpp */,
4B4C836F1D4F623200CD541F /* D64.hpp */,
4BF829611D8F536B001BAE39 /* SSD.cpp */,
4BF829621D8F536B001BAE39 /* SSD.hpp */,
4BD69F921D98760000243FE1 /* AcornADF.cpp */,
4BD69F931D98760000243FE1 /* AcornADF.hpp */,
4B4C836E1D4F623200CD541F /* D64.cpp */,
4B4C836F1D4F623200CD541F /* D64.hpp */,
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
4B5FADBB1DE31D1500AEC565 /* OricMFMDSK.cpp */,
4B5FADBC1DE31D1500AEC565 /* OricMFMDSK.hpp */,
4BF829611D8F536B001BAE39 /* SSD.cpp */,
4BF829621D8F536B001BAE39 /* SSD.hpp */,
);
path = Formats;
sourceTree = "<group>";
@@ -1545,7 +1662,6 @@
isa = PBXGroup;
children = (
4BB697C61D4B558F00248BDF /* Factors.hpp */,
4BF8295E1D8F3C87001BAE39 /* CRC.cpp */,
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
);
name = NumberTheory;
@@ -1618,6 +1734,14 @@
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
isa = PBXGroup;
children = (
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
@@ -1675,16 +1799,18 @@
4BBF99071C8FBA6F0075DAFB /* Internals */ = {
isa = PBXGroup;
children = (
4BC3B74C1CD194CC00F86E85 /* Shaders */,
4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */,
4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */,
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */,
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */,
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */,
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */,
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */,
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */,
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */,
4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */,
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */,
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */,
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */,
4BC3B74C1CD194CC00F86E85 /* Shaders */,
);
path = Internals;
sourceTree = "<group>";
@@ -1692,12 +1818,12 @@
4BC3B74C1CD194CC00F86E85 /* Shaders */ = {
isa = PBXGroup;
children = (
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */,
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */,
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */,
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */,
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */,
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */,
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */,
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */,
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */,
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */,
);
path = Shaders;
sourceTree = "<group>";
@@ -1759,6 +1885,8 @@
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
isa = PBXGroup;
children = (
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */,
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */,
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
4BC8A6291DCE4F2700DAC693 /* Typer.cpp */,
@@ -1784,7 +1912,6 @@
children = (
4BF829641D8F732B001BAE39 /* Disk.cpp */,
4BF829651D8F732B001BAE39 /* Disk.hpp */,
4BF829671D8F7361001BAE39 /* File.cpp */,
4BF829681D8F7361001BAE39 /* File.hpp */,
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
@@ -1815,11 +1942,32 @@
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
isa = PBXGroup;
children = (
4B1EDB411E39A0AC009D6819 /* Icons */,
4BC9DF441D044FCA00F44158 /* ROMImages */,
);
path = Resources;
sourceTree = "<group>";
};
4BEAC0801E7E0DF800EE56B2 /* Cartridges */ = {
isa = PBXGroup;
children = (
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */,
4BEAC0821E7E0DF800EE56B2 /* CartridgeActivisionStack.hpp */,
4BEAC0831E7E0DF800EE56B2 /* CartridgeAtari16k.hpp */,
4BEAC0841E7E0DF800EE56B2 /* CartridgeAtari32k.hpp */,
4BEAC0851E7E0DF800EE56B2 /* CartridgeAtari8k.hpp */,
4BEAC0861E7E0DF800EE56B2 /* CartridgeCBSRAMPlus.hpp */,
4BEAC0871E7E0DF800EE56B2 /* CartridgeCommaVid.hpp */,
4BEAC0881E7E0DF800EE56B2 /* CartridgeMegaBoy.hpp */,
4BEAC0891E7E0DF800EE56B2 /* CartridgeMNetwork.hpp */,
4BEAC08A1E7E0DF800EE56B2 /* CartridgeParkerBros.hpp */,
4BEAC08B1E7E0DF800EE56B2 /* CartridgeTigervision.hpp */,
4BEAC08C1E7E0DF800EE56B2 /* CartridgeUnpaged.hpp */,
4BEAC08E1E7E110500EE56B2 /* CartridgePitfall2.hpp */,
);
path = Cartridges;
sourceTree = "<group>";
};
4BEE0A691D72496600532C7B /* Cartridge */ = {
isa = PBXGroup;
children = (
@@ -1851,6 +1999,7 @@
4BA799961D8B65730045123D /* Atari */,
4BC830D21D6E7C6D0000A26F /* Commodore */,
4BCF1FAC1DADD41F0039D2E7 /* Oric */,
4B5A12581DD55873007A2231 /* Disassembler */,
);
name = StaticAnalyser;
sourceTree = "<group>";
@@ -1967,13 +2116,18 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2104,6 +2258,7 @@
4BB299661B587D8400A49093 /* inszx in Resources */,
4BB299101B587D8400A49093 /* asoz in Resources */,
4BB2998B1B587D8400A49093 /* lseiy in Resources */,
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */,
4BB2997D1B587D8400A49093 /* ldxay in Resources */,
4BB299D71B587D8400A49093 /* staax in Resources */,
4BB2990C1B587D8400A49093 /* asoax in Resources */,
@@ -2272,14 +2427,15 @@
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */,
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */,
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,
4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
@@ -2295,24 +2451,29 @@
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */,
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
4BF829691D8F7361001BAE39 /* File.cpp in Sources */,
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */,
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
@@ -2337,11 +2498,14 @@
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */,
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
4B4C83701D4F623200CD541F /* D64.cpp in Sources */,
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */,
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */,
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */,
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
@@ -2363,18 +2527,26 @@
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */,
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */,
4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */,
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
BuildableName = "Clock Signal.app"
BlueprintName = "Clock Signal"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73EB11B587A5100552FC2"
BuildableName = "Clock SignalTests.xctest"
BlueprintName = "Clock SignalTests"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73EBC1B587A5100552FC2"
BuildableName = "Clock SignalUITests.xctest"
BlueprintName = "Clock SignalUITests"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
BuildableName = "Clock Signal.app"
BlueprintName = "Clock Signal"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "NO">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
BuildableName = "Clock Signal.app"
BlueprintName = "Clock Signal"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
BuildableName = "Clock Signal.app"
BlueprintName = "Clock Signal"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -12,32 +12,48 @@
#define AudioQueueBufferMaxLength 8192
#define NumberOfStoredAudioQueueBuffer 16
static NSLock *CSAudioQueueDeallocLock;
/*!
Holds a weak reference to a CSAudioQueue. Used to work around an apparent AudioQueue bug.
See -[CSAudioQueue dealloc].
*/
@interface CSWeakAudioQueuePointer: NSObject
@property(nonatomic, weak) CSAudioQueue *queue;
@end
@implementation CSWeakAudioQueuePointer
@end
@implementation CSAudioQueue
{
AudioQueueRef _audioQueue;
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
NSLock *_storedBuffersLock;
CSWeakAudioQueuePointer *_weakPointer;
}
#pragma mark - AudioQueue callbacks
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
/*!
@returns @c YES if the queue is running dry; @c NO otherwise.
*/
- (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
{
[self.delegate audioQueueIsRunningDry:self];
@synchronized(self)
[_storedBuffersLock lock];
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
{
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
{
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
{
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
_storedBuffers[c] = buffer;
return;
}
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
_storedBuffers[c] = buffer;
[_storedBuffersLock unlock];
return YES;
}
}
[_storedBuffersLock unlock];
AudioQueueFreeBuffer(_audioQueue, buffer);
return YES;
}
static void audioOutputCallback(
@@ -45,7 +61,17 @@ static void audioOutputCallback(
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
{
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
// Pull the delegate call for audio queue running dry outside of the locked region, to allow non-deadlocking
// lifecycle -dealloc events to result from it.
if([CSAudioQueueDeallocLock tryLock])
{
CSAudioQueue *queue = ((__bridge CSWeakAudioQueuePointer *)inUserData).queue;
BOOL isRunningDry = NO;
isRunningDry = [queue audioQueue:inAQ didCallbackWithBuffer:inBuffer];
id<CSAudioQueueDelegate> delegate = queue.delegate;
[CSAudioQueueDeallocLock unlock];
if(isRunningDry) [delegate audioQueueIsRunningDry:queue];
}
}
#pragma mark - Standard object lifecycle
@@ -56,6 +82,12 @@ static void audioOutputCallback(
if(self)
{
if(!CSAudioQueueDeallocLock)
{
CSAudioQueueDeallocLock = [[NSLock alloc] init];
}
_storedBuffersLock = [[NSLock alloc] init];
_samplingRate = samplingRate;
// determine preferred buffer sizes
@@ -80,11 +112,13 @@ static void audioOutputCallback(
outputDescription.mReserved = 0;
// create an audio output queue along those lines
// create an audio output queue along those lines; see -dealloc re: the CSWeakAudioQueuePointer
_weakPointer = [[CSWeakAudioQueuePointer alloc] init];
_weakPointer.queue = self;
if(!AudioQueueNewOutput(
&outputDescription,
audioOutputCallback,
(__bridge void *)(self),
(__bridge void *)(_weakPointer),
NULL,
kCFRunLoopCommonModes,
0,
@@ -104,7 +138,31 @@ static void audioOutputCallback(
- (void)dealloc
{
if(_audioQueue) AudioQueueDispose(_audioQueue, NO);
[CSAudioQueueDeallocLock lock];
if(_audioQueue)
{
AudioQueueDispose(_audioQueue, true);
_audioQueue = NULL;
}
[CSAudioQueueDeallocLock unlock];
// Yuck. Horrid hack happening here. At least under macOS v10.12, I am frequently seeing calls to
// my registered audio callback (audioOutputCallback in this case) that occur **after** the call
// to AudioQueueDispose above, even though the second parameter there asks for a synchronous shutdown.
// So this appears to be a bug on Apple's side.
//
// Since the audio callback receives a void * pointer that identifies the class it should branch into,
// it's therefore unsafe to pass 'self'. Instead I pass a CSWeakAudioQueuePointer which points to the actual
// queue. The lifetime of that class is the lifetime of this instance plus 1 second, as effected by the
// artificial dispatch_after below it serves only to keep pointerSaviour alive for an extra second.
//
// Why a second? That's definitely quite a lot longer than any amount of audio that may be queued. So
// probably safe. As and where Apple's audio queue works properly, CSAudioQueueDeallocLock should provide
// absolute safety; elsewhere the CSWeakAudioQueuePointer provides probabilistic.
CSWeakAudioQueuePointer *pointerSaviour = _weakPointer;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[pointerSaviour hash];
});
}
#pragma mark - Audio enqueuer
@@ -113,28 +171,28 @@ static void audioOutputCallback(
{
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
@synchronized(self)
[_storedBuffersLock lock];
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
{
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
{
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
{
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
_storedBuffers[c] = NULL;
return;
}
AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL);
_storedBuffers[c] = NULL;
[_storedBuffersLock unlock];
return;
}
AudioQueueBufferRef newBuffer;
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
}
[_storedBuffersLock unlock];
AudioQueueBufferRef newBuffer;
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer);
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
}
#pragma mark - Sampling Rate getters

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
@@ -14,14 +15,14 @@
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="Vic20OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="134"/>
<rect key="contentRect" x="83" y="102" width="200" height="112"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="7Pv-WL-2Rq">
<rect key="frame" x="0.0" y="0.0" width="200" height="134"/>
<rect key="frame" x="0.0" y="0.0" width="200" height="112"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
<rect key="frame" x="18" y="98" width="164" height="18"/>
<rect key="frame" x="18" y="76" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -30,35 +31,8 @@
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="lbt-Wo-6fc">
<rect key="frame" x="18" y="78" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Load Automatically" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="jTj-uV-at1">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setShouldLoadAutomatically:" target="ota-g7-hOL" id="T3i-gO-T1C"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0NP-x1-qH2">
<rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="K81-0X-C4f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="diI-80-lCf">
<items>
<menuItem title="5 kb" id="ze7-6B-ois"/>
<menuItem title="8 kb" id="6C7-Iv-Wvl"/>
<menuItem title="32 kb" id="DOo-f6-OeZ"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setMemorySize:" target="ota-g7-hOL" id="lep-Qi-00V"/>
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MlB-rE-TXV" userLabel="Country Selector">
<rect key="frame" x="18" y="48" width="165" height="26"/>
<rect key="frame" x="18" y="46" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="UIu-uz-pTu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -76,18 +50,32 @@
<action selector="setCountry:" target="ota-g7-hOL" id="YIc-QB-R1S"/>
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0NP-x1-qH2">
<rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="K81-0X-C4f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="diI-80-lCf">
<items>
<menuItem title="5 kb" id="ze7-6B-ois"/>
<menuItem title="8 kb" id="6C7-Iv-Wvl"/>
<menuItem title="32 kb" id="DOo-f6-OeZ"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setMemorySize:" target="ota-g7-hOL" id="lep-Qi-00V"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="8" id="0kc-u0-05p"/>
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
<constraint firstItem="0NP-x1-qH2" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="7EF-L9-lIu"/>
<constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="lbt-Wo-6fc" secondAttribute="bottom" constant="8" id="DIc-Sm-VlA"/>
<constraint firstAttribute="bottom" secondItem="0NP-x1-qH2" secondAttribute="bottom" constant="20" id="Dtd-kf-4oU"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
<constraint firstItem="0NP-x1-qH2" firstAttribute="top" secondItem="MlB-rE-TXV" secondAttribute="bottom" constant="10" id="NbW-5e-wGB"/>
<constraint firstItem="lbt-Wo-6fc" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="cID-bi-rVP"/>
<constraint firstItem="lbt-Wo-6fc" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="ciY-E8-07P"/>
<constraint firstItem="0NP-x1-qH2" firstAttribute="top" secondItem="MlB-rE-TXV" secondAttribute="bottom" constant="8" id="NbW-5e-wGB"/>
<constraint firstAttribute="trailing" secondItem="0NP-x1-qH2" secondAttribute="trailing" constant="20" id="ero-D6-tJj"/>
<constraint firstAttribute="trailing" secondItem="lbt-Wo-6fc" secondAttribute="trailing" constant="20" id="gMU-gX-3Sg"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
<constraint firstItem="MlB-rE-TXV" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="qb4-Lp-ZMc"/>
<constraint firstAttribute="trailing" secondItem="MlB-rE-TXV" secondAttribute="trailing" constant="20" id="v18-62-uee"/>
@@ -96,10 +84,9 @@
<connections>
<outlet property="countryButton" destination="MlB-rE-TXV" id="Duc-AC-ZRO"/>
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
<outlet property="loadAutomaticallyButton" destination="lbt-Wo-6fc" id="dv0-u8-BTc"/>
<outlet property="memorySizeButton" destination="0NP-x1-qH2" id="qYy-3f-o94"/>
</connections>
<point key="canvasLocation" x="-2" y="32"/>
<point key="canvasLocation" x="-2" y="21"/>
</window>
</objects>
</document>

View File

@@ -31,24 +31,14 @@ class MachineDocument:
return NSSize(width: 4.0, height: 3.0)
}
@IBOutlet weak var openGLView: CSOpenGLView! {
didSet {
openGLView.delegate = self
openGLView.responderDelegate = self
}
}
@IBOutlet weak var openGLView: CSOpenGLView!
@IBOutlet var optionsPanel: MachinePanel!
@IBAction func showOptions(_ sender: AnyObject!) {
optionsPanel?.setIsVisible(true)
}
fileprivate var audioQueue: CSAudioQueue! = nil
fileprivate lazy var bestEffortUpdater: CSBestEffortUpdater = {
let updater = CSBestEffortUpdater()
updater.delegate = self
return updater
}()
fileprivate var bestEffortUpdater: CSBestEffortUpdater!
override var windowNibName: String? {
return "MachineDocument"
@@ -64,12 +54,22 @@ class MachineDocument:
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
})
setupClockRate()
self.machine.delegate = self
self.bestEffortUpdater = CSBestEffortUpdater()
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
// hence the full setup of the best-effort updater prior to setting self as a delegate
self.openGLView.delegate = self
self.openGLView.responderDelegate = self
setupClockRate()
self.optionsPanel?.establishStoredOptions()
// bring OpenGL view-holding window on top of the options panel
self.openGLView.window!.makeKeyAndOrderFront(self)
// start accepting best effort updates
self.bestEffortUpdater.delegate = self
}
func machineDidChangeClockRate(_ machine: CSMachine!) {

View File

@@ -13,18 +13,6 @@ class Vic20OptionsPanel: MachinePanel {
}
}
// MARK: automatic loading tick box
@IBOutlet var loadAutomaticallyButton: NSButton?
var autoloadingUserDefaultsKey: String {
get { return prefixedUserDefaultsKey("autoload") }
}
@IBAction func setShouldLoadAutomatically(_ sender: NSButton!) {
let loadAutomatically = sender.state == NSOnState
vic20.shouldLoadAutomatically = loadAutomatically
UserDefaults.standard.set(loadAutomatically, forKey: self.autoloadingUserDefaultsKey)
}
// MARK: country selector
@IBOutlet var countryButton: NSPopUpButton?
var countryUserDefaultsKey: String {
@@ -85,28 +73,21 @@ class Vic20OptionsPanel: MachinePanel {
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.register(defaults: [
self.autoloadingUserDefaultsKey: true,
self.memorySizeUserDefaultsKey: 5,
self.countryUserDefaultsKey: 1
])
let loadAutomatically = standardUserDefaults.bool(forKey: self.autoloadingUserDefaultsKey)
vic20.shouldLoadAutomatically = loadAutomatically
self.loadAutomaticallyButton?.state = loadAutomatically ? NSOnState : NSOffState
if !loadAutomatically {
let memorySize = standardUserDefaults.integer(forKey: self.memorySizeUserDefaultsKey)
var indexToSelect: Int?
switch memorySize {
case 32: indexToSelect = 2
case 8: indexToSelect = 1
default: indexToSelect = 0
}
if let indexToSelect = indexToSelect {
self.memorySizeButton?.selectItem(at: indexToSelect)
setMemorySize(indexToSelect)
}
}
// let memorySize = standardUserDefaults.integer(forKey: self.memorySizeUserDefaultsKey)
// var indexToSelect: Int?
// switch memorySize {
// case 32: indexToSelect = 2
// case 8: indexToSelect = 1
// default: indexToSelect = 0
// }
// if let indexToSelect = indexToSelect {
// self.memorySizeButton?.selectItem(at: indexToSelect)
// setMemorySize(indexToSelect)
// }
// TODO: this should be part of the configuration
let country = standardUserDefaults.integer(forKey: self.countryUserDefaultsKey)

View File

@@ -13,7 +13,7 @@
<string>bin</string>
</array>
<key>CFBundleTypeIconFile</key>
<string></string>
<string>cartridge</string>
<key>CFBundleTypeName</key>
<string>Atari 2600 Cartridge</string>
<key>CFBundleTypeOSTypes</key>
@@ -27,29 +27,15 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>uef</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array/>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>rom</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>chip</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC ROM Image</string>
<string>ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
@@ -65,6 +51,8 @@
<string>uef</string>
<string>uef.gz</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC UEF Image</string>
<key>CFBundleTypeRole</key>
@@ -79,6 +67,8 @@
<array>
<string>prg</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Commodore Program</string>
<key>CFBundleTypeRole</key>
@@ -93,6 +83,8 @@
<array>
<string>tap</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
@@ -107,6 +99,8 @@
<array>
<string>g64</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Commodore Disk</string>
<key>CFBundleTypeRole</key>
@@ -121,6 +115,8 @@
<array>
<string>d64</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Commodore 1540/1 Disk</string>
<key>CFBundleTypeRole</key>
@@ -139,6 +135,8 @@
<string>adl</string>
<string>adm</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC Disk Image</string>
<key>CFBundleTypeRole</key>
@@ -146,6 +144,20 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dsk</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@@ -11,42 +11,8 @@
#include "Atari2600.hpp"
#import "CSMachine+Subclassing.h"
@interface CSAtari2600 ()
- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs;
@end
struct CRTDelegate: public Outputs::CRT::Delegate {
__weak CSAtari2600 *atari2600;
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
[atari2600 crt:crt didEndBatchOfFrames:number_of_frames withUnexpectedVerticalSyncs:number_of_unexpected_vertical_syncs];
}
};
@implementation CSAtari2600 {
Atari2600::Machine _atari2600;
CRTDelegate _crtDelegate;
int _frameCount;
int _hitCount;
BOOL _didDecideRegion;
int _batchesReceived;
}
- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs {
if(!_didDecideRegion)
{
_batchesReceived++;
if(_batchesReceived == 2)
{
_didDecideRegion = YES;
if(numberOfUnexpectedSyncs >= numberOfFrames >> 1)
{
[self.view performWithGLContext:^{
_atari2600.switch_region();
}];
}
}
}
}
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
@@ -78,8 +44,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate {
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
@synchronized(self) {
[super setupOutputWithAspectRatio:aspectRatio];
_atari2600.get_crt()->set_delegate(&_crtDelegate);
_crtDelegate.atari2600 = self;
}
}

View File

@@ -25,8 +25,15 @@
self = [super init];
if(self)
{
NSData *rom = [self rom:@"basic11"]; // test108j
if(rom) _oric.set_rom(rom.stdVector8);
NSData *basic10 = [self rom:@"basic10"];
NSData *basic11 = [self rom:@"basic11"];
NSData *colour = [self rom:@"colour"];
NSData *microdisc = [self rom:@"microdisc"];
if(basic10) _oric.set_rom(Oric::BASIC10, basic10.stdVector8);
if(basic11) _oric.set_rom(Oric::BASIC11, basic11.stdVector8);
if(colour) _oric.set_rom(Oric::Colour, colour.stdVector8);
if(microdisc) _oric.set_rom(Oric::Microdisc, microdisc.stdVector8);
}
return self;
}
@@ -145,7 +152,7 @@
- (void)setUseCompositeOutput:(BOOL)useCompositeOutput {
@synchronized(self) {
_useCompositeOutput = useCompositeOutput;
_oric.get_crt()->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
_oric.set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
}
}

View File

@@ -29,7 +29,6 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
@interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading>
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL shouldLoadAutomatically;
@property (nonatomic, assign) CSVic20Country country;
@property (nonatomic, assign) CSVic20MemorySize memorySize;

View File

@@ -178,13 +178,6 @@ using namespace Commodore::Vic20;
}
}
- (void)setShouldLoadAutomatically:(BOOL)shouldLoadAutomatically {
_shouldLoadAutomatically = shouldLoadAutomatically;
@synchronized(self) {
_vic20.set_should_automatically_load_media(shouldLoadAutomatically ? true : false);
}
}
- (void)setCountry:(CSVic20Country)country {
_country = country;
NSString *charactersROM, *kernelROM;

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -8,10 +8,12 @@
#import "CSBestEffortUpdater.h"
#include <stdatomic.h>
@implementation CSBestEffortUpdater
{
// these are inherently handled only by thread-safe constructions
uint32_t _updateIsOngoing;
atomic_flag _updateIsOngoing;
dispatch_queue_t _serialDispatchQueue;
// these are permitted for modification on _serialDispatchQueue only
@@ -25,16 +27,18 @@
if(self = [super init])
{
_serialDispatchQueue = dispatch_queue_create("Best Effort Updater", DISPATCH_QUEUE_SERIAL);
// This is a workaround for assigning the correct initial value within Objective-C's form.
atomic_flag initialFlagValue = ATOMIC_FLAG_INIT;
_updateIsOngoing = initialFlagValue;
}
return self;
}
- (void)update
{
const uint32_t processingMask = 0x01;
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing))
if(!atomic_flag_test_and_set(&_updateIsOngoing))
{
dispatch_async(_serialDispatchQueue, ^{
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
@@ -52,7 +56,7 @@
}
_previousTimeInterval = timeInterval;
_hasSkipped = NO;
OSAtomicTestAndClear(processingMask, &_updateIsOngoing);
atomic_flag_clear(&_updateIsOngoing);
});
}
else

View File

@@ -53,7 +53,7 @@
*/
@interface CSOpenGLView : NSOpenGLView
@property (nonatomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
/*!

View File

@@ -21,14 +21,25 @@ class MOS6532Tests: XCTestCase {
with6532 {
// set a count of 128 at single-clock intervals
$0.setValue(128, forRegister:0x14)
XCTAssertEqual($0.value(forRegister: 4), 128)
// run for one clock and the count should now be 127
// run for one more clock and the count should now be 127
$0.run(forCycles: 1)
XCTAssert($0.value(forRegister: 4) == 127, "A single tick should decrease the counter once")
XCTAssertEqual($0.value(forRegister: 4), 127)
// run for a further 200 clock counts; timer should reach -73 = 183
$0.run(forCycles: 200)
XCTAssert($0.value(forRegister: 4) == 183, "Timer should underflow and keep counting")
// run for 127 clocks and the timer should be zero, but the timer flag will not yet be set
$0.run(forCycles: 127)
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0)
XCTAssertEqual($0.value(forRegister: 4), 0)
// after one more cycle the counter should be 255 and the timer flag will now be set
$0.run(forCycles: 1)
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80)
XCTAssertEqual($0.value(forRegister: 4), 255)
// run for a further 55 clock counts; timer should reach -200
$0.run(forCycles: 55)
XCTAssertEqual($0.value(forRegister: 4), 200)
}
}
@@ -37,26 +48,40 @@ class MOS6532Tests: XCTestCase {
with6532 {
// set a count of 28 at eight-clock intervals
$0.setValue(28, forRegister:0x15)
XCTAssertEqual($0.value(forRegister: 4), 28)
// run for seven clock and the count should still be 28
$0.run(forCycles: 7)
XCTAssert($0.value(forRegister: 4) == 28, "The timer should remain unchanged for seven clocks")
// run for a further clock and the count should now be 27
// one further cycle and the timer should hit 27
$0.run(forCycles: 1)
XCTAssert($0.value(forRegister: 4) == 27, "The timer should have decremented once after 8 cycles")
XCTAssertEqual($0.value(forRegister: 4), 27)
// run for a further 7 + 27*8 + 5 = 228 clock counts; timer should reach -5 = 0xfb
$0.run(forCycles: 228)
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should underflow and start counting at single-clock pace")
// run for seven clock and the count should still be 27
$0.run(forCycles: 7)
XCTAssertEqual($0.value(forRegister: 4), 27)
// run for a further clock and the count should now be 26
$0.run(forCycles: 1)
XCTAssertEqual($0.value(forRegister: 4), 26)
// run for another 26 * 8 = 208 cycles and the count should hit zero without setting the timer flag, and
// stay there for seven more cycles
$0.run(forCycles: 208)
for _ in 0 ..< 8 {
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0)
XCTAssertEqual($0.value(forRegister: 4), 0)
$0.run(forCycles: 1)
}
// run six more, and the timer should reach 249, with the interrupt flag set
$0.run(forCycles: 6)
XCTAssertEqual($0.value(forRegister: 5) & 0x80, 0x80)
XCTAssertEqual($0.value(forRegister: 4), 249)
// timer should now resume dividing by eight
$0.run(forCycles: 7)
XCTAssert($0.value(forRegister: 4) == 0xfb, "Timer should remain unchanged for seven cycles")
XCTAssertEqual($0.value(forRegister: 4), 249)
// timer should now resume dividing by eight
$0.run(forCycles: 1)
XCTAssert($0.value(forRegister: 4) == 0xfa, "Timer should decrement after eighth cycle")
XCTAssertEqual($0.value(forRegister: 4), 248)
}
}
@@ -64,8 +89,6 @@ class MOS6532Tests: XCTestCase {
with6532 {
// set a count of 1 at single-clock intervals
$0.setValue(1, forRegister:0x1c)
// run for one clock and the count should now be zero
$0.run(forCycles: 1)
// interrupt shouldn't be signalled yet, bit should not be set

View File

@@ -0,0 +1,140 @@
//
// ArrayBuilderTests.m
// Clock Signal
//
// Created by Thomas Harte on 19/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "ArrayBuilder.hpp"
static NSData *inputData, *outputData;
static void setData(bool is_input, uint8_t *data, size_t size)
{
NSData *dataObject = [NSData dataWithBytes:data length:size];
if(is_input) inputData = dataObject; else outputData = dataObject;
}
@interface ArrayBuilderTests : XCTestCase
@end
@implementation ArrayBuilderTests
+ (void)setUp
{
inputData = nil;
outputData = nil;
}
- (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize
{
XCTAssert(inputData != nil, @"Should have received some input data");
XCTAssert(outputData != nil, @"Should have received some output data");
XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length);
XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length);
if(inputData.length == inputSize && outputData.length == outputSize)
{
uint8_t *input = (uint8_t *)inputData.bytes;
uint8_t *output = (uint8_t *)outputData.bytes;
for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]);
for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]);
}
}
- (std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)>)emptyFlushFunction
{
return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {};
}
- (void)testSingleWriteSingleFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
uint8_t *input = arrayBuilder.get_input_storage(5);
uint8_t *output = arrayBuilder.get_output_storage(3);
for(int c = 0; c < 5; c++) input[c] = c;
for(int c = 0; c < 3; c++) output[c] = c + 0x80;
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
[self assertMonotonicForInputSize:5 outputSize:3];
}
- (void)testDoubleWriteSingleFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
uint8_t *input;
uint8_t *output;
input = arrayBuilder.get_input_storage(2);
output = arrayBuilder.get_output_storage(2);
for(int c = 0; c < 2; c++) input[c] = c;
for(int c = 0; c < 2; c++) output[c] = c + 0x80;
input = arrayBuilder.get_input_storage(2);
output = arrayBuilder.get_output_storage(2);
for(int c = 0; c < 2; c++) input[c] = c+2;
for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80;
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
[self assertMonotonicForInputSize:4 outputSize:4];
}
- (void)testSubmitWithoutFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
arrayBuilder.get_input_storage(5);
arrayBuilder.get_input_storage(8);
arrayBuilder.get_output_storage(6);
arrayBuilder.get_input_storage(12);
arrayBuilder.get_output_storage(3);
arrayBuilder.submit();
XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
}
- (void)testSubmitContinuity
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
arrayBuilder.get_input_storage(5);
arrayBuilder.get_output_storage(5);
arrayBuilder.flush(self.emptyFlushFunction);
uint8_t *input = arrayBuilder.get_input_storage(5);
uint8_t *output = arrayBuilder.get_output_storage(5);
arrayBuilder.submit();
for(int c = 0; c < 5; c++) input[c] = c;
for(int c = 0; c < 5; c++) output[c] = c + 0x80;
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
[self assertMonotonicForInputSize:5 outputSize:5];
}
@end

View File

@@ -0,0 +1,3 @@
This folder is intended to contain commercial Atari ROM images; these are used to unit test the Atari static analyser.
Those tested are (i) everything presently available on AtariAge, and (ii) a selection of things from Pouët.

View File

@@ -0,0 +1,614 @@
//
// AtariStaticAnalyserTests.m
// Clock Signal
//
// Created by Thomas Harte on 11/03/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#import <CommonCrypto/CommonDigest.h>
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
@interface AtariROMRecord : NSObject
@property(nonatomic, readonly) StaticAnalyser::Atari2600PagingModel pagingModel;
@property(nonatomic, readonly) BOOL usesSuperchip;
+ (instancetype)recordWithPagingModel:(StaticAnalyser::Atari2600PagingModel)pagingModel usesSuperchip:(BOOL)usesSuperchip;
@end
@implementation AtariROMRecord
+ (instancetype)recordWithPagingModel:(StaticAnalyser::Atari2600PagingModel)pagingModel usesSuperchip:(BOOL)usesSuperchip
{
AtariROMRecord *record = [[AtariROMRecord alloc] init];
record->_pagingModel = pagingModel;
record->_usesSuperchip = usesSuperchip;
return record;
}
@end
#define Record(sha, model, uses) sha : [AtariROMRecord recordWithPagingModel:StaticAnalyser::Atari2600PagingModel::model usesSuperchip:uses],
static NSDictionary<NSString *, AtariROMRecord *> *romRecordsBySHA1 = @{
Record(@"58dbcbdffbe80be97746e94a0a75614e64458fdc", None, NO) // 4kraVCS
Record(@"9967a76efb68017f793188f691159f04e6bb4447", None, NO) // 'X'Mission
Record(@"21d983f2f52b84c22ecae84b0943678ae2c31c10", None, NO) // 3d Tic-Tac-Toe
Record(@"d7c62df8300a68b21ce672cfaa4d0f2f4b3d0ce1", Atari16k, NO) // Acid Drop
Record(@"924ca836aa08eeffc141d487ac6b9b761b2f8ed5", None, NO) // Action Force
Record(@"e07e48d463d30321239a8acc00c490f27f1f7422", None, NO) // Adventure
Record(@"03a495c7bfa0671e24aa4d9460d232731f68cb43", None, NO) // Adventures of Tron
Record(@"6e420544bf91f603639188824a2b570738bb7e02", None, NO) // Adventures On GX12
Record(@"3b02e7dacb418c44d0d3dc77d60a9663b90b0fbc", None, NO) // Air Raid
Record(@"29f5c73d1fe806a4284547274dd73f9972a7ed70", None, NO) // Air Raiders
Record(@"af5b9f33ccb7778b42957da4f20f2bc000992366", None, NO) // Air-Sea Battle
Record(@"0376c242819b785310b8af43c03b1d1156bd5f02", None, NO) // Airlock
Record(@"fb870ec3d51468fa4cf40e0efae9617e60c1c91c", None, NO) // AKA Space Adventure
Record(@"01d99bf307262825db58631e8002dd008a42cb1e", None, NO) // Alien
Record(@"a1f660827ce291f19719a5672f2c5d277d903b03", Atari8k, NO) // Alpha Beam with Ernie
Record(@"b89a5ac6593e83fbebee1fe7d4cec81a7032c544", None, NO) // Amidar
Record(@"ac58ac94ceab78725a1182cc7b907376c011b0c8", None, NO) // Angriff der Luftflotten
Record(@"7d132ab776ff755b86bf4f204165aa54e9e1f1cf", Atari8k, NO) // Aquaventure
Record(@"9b6a54969240baf64928118741c3affee148d721", None, NO) // Armor Ambush
Record(@"8c249e9eaa83fc6be16039f05ec304efdf987beb", Atari8k, NO) // Artillery Duel
Record(@"0c03eba97df5178eec5d4d0aea4a6fe2f961c88f", None, NO) // Assault
Record(@"1a094f92e46a8127d9c29889b5389865561c0a6f", Atari8k, NO) // Asterix (NTSC)
Record(@"f14408429a911854ec76a191ad64231cc2ed7d11", Atari8k, NO) // Asterix (PAL)
Record(@"8f4a00cb4ab6a6f809be0e055d97e8fe17f19e7d", None, NO) // Asteroid Fire
Record(@"8423f99092b454aed89f89f5d7da658caf7af016", Atari8k, NO) // Asteroids
Record(@"b850bd72d18906d9684e1c7251cb699588cbcf64", None, NO) // Astroblast
Record(@"d1563c24208766cf8d28de7af995021a9f89d7e1", None, NO) // Atari Video Cube
Record(@"f4e838de9159c149ac080ab85e4f830d5b299963", None, NO) // Atlantis II
Record(@"c6b1dcdb2f024ab682316db45763bacc6949c33c", None, NO) // Atlantis
Record(@"75e7efa861f7e7d8e367c09bf7c0cc351b472f03", None, NO) // Bachelor Party
Record(@"b88ca823aaa10a7a4a3d325023881b2de969c156", None, NO) // Bachelorette Party
Record(@"9b1da7fbd0bf6fcadf1b60c11eeb31b6a61a03c3", None, NO) // Backgammon
Record(@"80d4020575b14e130f28146bf45921e001f9f649", None, NO) // Bank Heist
Record(@"372663097b419ced64f44ef743fe8d0af4317f46", None, NO) // Barnstorming
Record(@"d0bdd609ebc6e69fb351ba469ff322406bcbab50", None, NO) // Base Attack
Record(@"6c56fad688b2e9bb783f8a5a2360c80ad2338e47", None, NO) // Basic Programming
Record(@"bffe99454cb055552e5d612f0dba25470137328d", None, NO) // Basketball
Record(@"e4134a3b4a065c856802bc935c12fa7e9868110a", Atari8k, NO) // Battlezone
Record(@"47619edb352f7f955f811cbb03a00746c8e099b1", Atari8k, NO) // Beamrider
Record(@"fad0c97331a525a4aeba67987552ba324629a7a0", None, NO) // Beany Bopper
Record(@"e2c29d0a73a4575028b62dca745476a17f07c8f0", None, NO) // Beat 'Em & Eat 'Em
Record(@"c3afd7909b72b49ca7d4485465b622d5e55f8913", Atari8k, NO) // Berenstain Bears
Record(@"fcad0e5130de24f06b98fb86a7c3214841ca42e2", None, NO) // Bermuda Triangle
Record(@"08bcbc8954473e8f0242b881315b0af4466998ae", None, NO) // Berzerk
Record(@"5e4517db83c061926130ab65975e3b83d9401cc9", Atari8k, NO) // Big Bird's Egg Catch
Record(@"512e4d047f1f813bc805c8d2a5f7cbdb34b9ea46", None, NO) // bin00016
Record(@"f6a41507b8cf890ab7c59bb1424f0500534385ce", Atari8k, NO) // Bionic Breakthrough
Record(@"edfd905a34870196f8acb2a9cd41f79f4326f88d", None, NO) // Blackjack
Record(@"0fadef01ce28192880f745b23a5fbb64c5a96efe", Atari8k, NO) // Blueprint
Record(@"ff25ed062dcc430448b358d2ac745787410e1169", Atari16k, NO) // BMX Air Master
Record(@"50e26688fdd3eadcfa83240616267a8f60216c25", None, NO) // Bobby is Going Home
Record(@"282cad17482f5f87805065d1a62e49e662d5b4bb", None, NO) // Bogey Blaster
Record(@"d106bb41a38ed222dead608d839e8a3f0d0ecc18", None, NO) // Boing!
Record(@"cf6ce244b3edaad7ad5e9ca5f01668135c2f93d0", None, NO) // Bowling
Record(@"14b9cd91188c7fb0d4566442d639870f8d6f174d", None, NO) // Boxing
Record(@"238915cafd26f69bc8a3b9aa7d880dde59f6f12d", None, NO) // Brain Games
Record(@"8d473b87b70e26890268e6c417c0bb7f01e402eb", None, NO) // Breakout
Record(@"2873eb6effd35003d13e2f8f997b76dbc85d0f64", None, NO) // Bridge
Record(@"a65dea2d9790f3eb308c048a01566e35e8c24549", Atari8k, NO) // Buck Rogers — Planet of Zoom
Record(@"9c0e13af336a986c271fe828fafdca250afba647", Atari8k, NO) // Bugs Bunny
Record(@"67387d0d3d48a44800c44860bf15339a81f41aa9", None, NO) // Bugs
Record(@"1819ef408c1216c83dcfeceec28d13f6ea5ca477", MNetwork, NO) // Bump 'n' Jump
Record(@"6c199782c79686dc0cbce6d5fe805f276a86a3f5", None, NO) // Bumper Bash
Record(@"49e01b8048ae344cb65838f6b1c1de0e1f416f29", MNetwork, NO) // BurgerTime
Record(@"b233c37aa5164a54e2e7cc3dc621b331ddc6e55b", None, NO) // Burning Desire
Record(@"3f1f17cf620f462355009f5302cddffa730fa2fa", None, NO) // Cakewalk
Record(@"609c20365c3a71ce45cb277c66ec3ce6b2c50980", Atari16k, NO) // California Games
Record(@"b89443a0029e765c2716774fe2582be37650115c", None, NO) // Canyon Bomber
Record(@"e1acf7a845b56e4b3d18192a75a81c7afa6f341a", None, NO) // Carnival
Record(@"54ed2864f58ef3768579ec96cca445ee62078521", None, NO) // cart
Record(@"08598101e38756916613f37581ef1b61c719016f", None, NO) // Casino
Record(@"e979de719cecab2115affd9c0552c6c596b1999a", None, NO) // Cat Trax
Record(@"6adf70e0b7b5dab74cf4778f56000de7605e8713", None, NO) // Cathouse Blues
Record(@"0b5914bc1526a9beaf54d7fd11408175cd8fcc72", Atari8k, NO) // Centipede
Record(@"b2b1bd165b3c10cde5316ed0f9f05a509aac828d", None, NO) // Challenge (Zellers)
Record(@"ac9b0c62ba0ca7a975d08fabbbc7c7448ecdf18d", None, NO) // Challenge of… Nexar
Record(@"e81b5e49cfbb283edba2c8f21f31a8148d8645a1", None, NO) // Challenge
Record(@"872b2f9aa7edbcbb2368de0db3696c90998ff016", None, NO) // Chase the Chuckwagon
Record(@"39b5bb27a6c4cb6532bd9d4cc520415c59dac653", None, NO) // Checkers
Record(@"0b1bb76769ae3f8b4936f0f95f4941d276791bde", None, NO) // China Syndrome
Record(@"51a53bbfdbcc22925515ae0af79df434df6ee68a", None, NO) // Chopper Command
Record(@"8a91ecdbd8bf9d412da051c3422abb004eab8603", None, NO) // Circus
Record(@"3f56d1a376702b64b3992b2d5652a3842c56ffad", None, NO) // Coco Nuts
Record(@"137bd3d3f36e2549c6e1cc3a60f2a7574f767775", None, NO) // Codebreaker
Record(@"53c324ae736afa92a83d619b04e4fe72182281a6", None, NO) // Color Bar Generator
Record(@"66014de1f8e9f39483ee3f97ca0d97d026ffc3bb", Atari8k, NO) // Combat Two
Record(@"ce7580059e8b41cb4a1e734c9b35ce3774bf777a", None, NO) // Combat
Record(@"8dad05085657e95e567f47836502be515b42f66b", None, NO) // Commando Raid
Record(@"68a7cb3ff847cd987a551f3dd9cda5f90ce0a3bf", Atari16k, NO) // Commando
Record(@"dbc0c0451dee44425810e04df8f1d26d1c2d3993", None, NO) // Computer Chess
Record(@"5512a0ed4306edc007a78bb52dbcf492adf798ec", None, NO) // Confrontation
Record(@"3a77db43b6583e8689435f0f14aa04b9e57bdded", Atari8k, NO) // Congo Bongo
Record(@"f4a62ba0ff59803c5f40d59eeed1e126fe37979b", Atari8k, NO) // Cookie Monster Munch
Record(@"187983fd14d37498437d0ef8f3fbd05675feb6ae", None, NO) // Cosmic Ark
Record(@"3717c97bbb0f547e4389db8fc954d1bad992444c", None, NO) // Cosmic Commuter
Record(@"8b9dfef6c6757a6a59e01d783b751e4ab9541d9e", None, NO) // Cosmic Corridor
Record(@"22ff281b1e698e8a5d7a6f6173c86c46d3cd8561", None, NO) // Cosmic Creeps
Record(@"c01354760f2ca8d6e4d01b230f31611973c6ae2d", None, NO) // Cosmic Swarm
Record(@"6ee0a26af4643ff250198dfc1c2b7c6568b4f207", None, NO) // Crackpots
Record(@"73d68f32d1fb73883ceb183d5150bff5f1065de4", None, NO) // Crash Dive
Record(@"70e723aa67d68f8549d9bd8f96d8b1262cbdac3c", Atari8k, NO) // Crazy Climber
Record(@"dd385886fdd20727c060bad6c92541938661e2b4", None, NO) // Crazy Valet
Record(@"b1b3d8d6afe94b73a43c36668cc756c5b6fdc1c3", None, NO) // Cross Force
Record(@"5da3d089ccda960ce244adb855975877c670e615", Atari16k, NO) // Crossbow
Record(@"a57062f44e7ac793d4c39d1350521dc5bc2a665f", None, NO) // Crypts of Chaos
Record(@"2e4ee5ee040b08be1fe568602d1859664e607efb", Atari16k, YES) // Crystal Castles
Record(@"0dd72a3461b4167f2d68c93511ed4985d97e6adc", None, NO) // Cubicolor
Record(@"4b3d02b59e17520b4d60236568d5cb50a4e6aeb3", None, NO) // Custer's Revenge
Record(@"07e94a7d357e859dcff77180981713ce8119324e", None, NO) // Dancing Plate
Record(@"0ae2fc87f87a5cc199c3b9a17444bf3c2f6a829b", None, NO) // Dark Cavern
Record(@"fbb4814973fcb4e101521515e04daa6424c45f5c", Atari16k, YES) // Dark Chambers
Record(@"c5af53c4b64c3db552d4717b8583d6fe8d3e7952", Atari8k, NO) // Dark Mage
Record(@"68de291d5e9cbebfed72d2f9039e60581b6dbdc5", None, NO) // Deadly Duck
Record(@"2ad9db4b5aec2da36ecc3178599b02619c3c462e", ParkerBros, NO) // Death Star Battle
Record(@"5f710a1148740760b4ebcc42861a1f9c3384799e", None, NO) // Death Trap
Record(@"717656f561823edaa69240471c3106963f5c307e", ActivisionStack, NO)// Decathlon
Record(@"d7b506b84f28e1b917a2978753d5a40eb197537a", Atari8k, YES) // Defender 2
Record(@"79facc1bf70e642685057999f5c2b8e94b102439", None, NO) // Defender
Record(@"5aae618292a728b55ad7f00242d870736b5356d3", None, NO) // Demolition Herby
Record(@"a580d886c191a069f6b9036c3f879e83e09500c2", None, NO) // Demon Attack
Record(@"b45582de81c48b04c2bb758d69021e8088c70ce7", None, NO) // Demons to Diamonds
Record(@"ccea2d5095441d7e1b1468e3879a6ab556dc8b7a", Atari16k, YES) // Desert Falcon
Record(@"538108ca9821265e23f06fa7672965631bdf8175", None, NO) // Diagnostic Test Cartridge 2.6
Record(@"81022ef30e682183d51b18bff145ce425c6f924e", None, NO) // Dice Puzzle
Record(@"79e746524520da546249149c33614fc23a4f2a51", Atari16k, YES) // Dig Dug
Record(@"7485cf55201ef98ded201aec73c4141f9f74f863", None, NO) // Dishaster
Record(@"157117df23cb5229386d06bbdb3af20a208722e0", None, NO) // Dodge 'Em
Record(@"e3985d759f8a8f4705f543ce7eb5e93bf63722b5", None, NO) // Dolphin
Record(@"4606c0751f560200aede6598ec9c8e6249a105f5", Atari8k, NO) // Donald Duck's Speedboat
Record(@"359e662da02bf0a2184472e25d05bc597b6c497a", None, NO) // Donkey Kong (1983) (CBS Electronics) (PAL) [!]
Record(@"98f98ac0728c68de66afda6500cafbdffe8ab50a", Atari8k, NO) // Donkey Kong Junior
Record(@"6e6e37ec8d66aea1c13ed444863e3db91497aa35", None, NO) // Donkey Kong
Record(@"251e02ac583d84eb43f1451d55b62c7c70e9644f", Atari16k, NO) // Double Dragon
Record(@"8e2ea320b23994dc87abe69d61249489f3a0fccc", Atari16k, NO) // Double Dunk
Record(@"b446381fe480156077b0b3c51747d156e5dde89f", None, NO) // Dragon Treasures
Record(@"944c52de85464070a946813b050518977750e939", None, NO) // Dragster
Record(@"9caf114c9582d5e0c14396b13d2bd1a89cad90b1", None, NO) // Dukes of Hazzard (1980)
Record(@"c061d753435dcb7275a8764f4ad003b05fa100ed", Atari16k, NO) // Dukes of Hazzard (1983)
Record(@"d16eba13ab1313f375e86b488181567f846f1dc4", Atari8k, NO) // Dumbo's Flying Circus
Record(@"9e34f9ca51573c92918720f8a259b9449a0cd65e", Atari8k, NO) // E.T. — The Extra-Terrestrial
Record(@"68cbfadf097ae2d1e838f315c7cc7b70bbf2ccc8", None, NO) // Eggomania
Record(@"bab872ee41695cefe41d88e4932132eca6c4e69c", Atari8k, YES) // Elevator Action
Record(@"475fc2b23c0ee273388539a4eeafa34f8f8d3fd8", None, NO) // Eli's Ladder
Record(@"3983e109fc0b38c0b559a09a001f3e5f2bb1dc2a", Atari8k, NO) // Elk Attack
Record(@"205af4051ea39fb5a038a8545c78bff91df321b7", None, NO) // Encounter at L-5
Record(@"82e9b2dd6d99f15381506a76ef958a1773a7ba21", None, NO) // Enduro
Record(@"7905aee90a6dd64d9538e0b8e772f833ba9feb83", None, NO) // Entombed
Record(@"27d925d482553deff23f0889b3051091977d6920", Tigervision, NO) // Espial
Record(@"c17801c0190ade27f438e2aa98dde81b3ae66267", None, NO) // Exocet
Record(@"6297dd336a6343f98cd142d1d3d76ce84770a488", None, NO) // Fantastic Voyage
Record(@"a5614c751f29118ddb3dec9794612b98a0f00b98", None, NO) // Fast Eddie
Record(@"c62a70645939480b184e3b2e378ec4bcbd484bc7", None, NO) // Fast Food
Record(@"d0bb58ea1fc37e929e5f7cdead037bb14a166451", Atari32k, YES) // Fatal Run
Record(@"686427cc47b69980d292d04597270347942773ff", Atari8k, NO) // Fathom
Record(@"684275b22f2bac7d577cf48cf42fa14fa6f69678", Atari16k, NO) // Fighter Pilot
Record(@"ba9a8ccfeb552dd756c660ea843a39619d3c77e9", None, NO) // Final Approach
Record(@"f76cc14afd7aef367c5a5defbd84f3bbb2f98ba3", None, NO) // Fire Fighter
Record(@"df5420eb0f71e681e7222ede8e211a7601e7a327", None, NO) // Fire Fly
Record(@"531e995aef6cd47b0efea72ae3e56aeee449d798", None, NO) // Fishing Derby
Record(@"ac05f05f3365f5e348e1e618410065a1c2a88ee4", None, NO) // Flag Capture
Record(@"c6fe4ce24bc1ebd538258d98cfe829963323acca", None, NO) // Football
Record(@"c6023bf73818c78b2e477a9c6dac411cdbf9c0aa", None, NO) // Frankenstein's Monster
Record(@"91cc7e5cd6c0d4a6f42ed66353b7ee7bb972fa3f", None, NO) // Freeway
Record(@"de6fc1b51d41b34dcda92f579b2aa4df8eccf586", Atari8k, NO) // Frog Pond
Record(@"f344d5a8dc895c5a2ae0288f3c6cb66650e49167", None, NO) // Frogflys
Record(@"6b9e591cc53844795725fc66c564f0364d1fbe40", ParkerBros, NO) // Frogger II
Record(@"e859b935a36494f3c4b4bf5547392600fb9c96f0", None, NO) // Frogger
Record(@"cf32bfcd7f2c3b7d2a6ad2f298aea2dfad8242e7", Atari8k, NO) // Front Line
Record(@"b9e60437e7691d5ef9002cfc7d15ae95f1c03a12", None, NO) // Frostbite
Record(@"5cc4010eb2858afe8ce77f53a89d37c7584e15b4", None, NO) // Fun with Numbers
Record(@"9cfb6288a5c2dae63ee6f5e9325200ccd21a3055", None, NO) // G.I. Joe — Cobra Strike
Record(@"8e708e0067d3302327900fa322aeb8e2df2022d7", Atari8k, NO) // Galaxian Enhanced Graphics
Record(@"b081b327ac32d951c36cb4b3ff812be95685d52f", Atari8k, NO) // Galaxian
Record(@"8cf49d43bd62308df788cfacbfcd80e9226c7590", None, NO) // Gangster Alley
Record(@"bc0d1edc251d8d4db3d5234ec83dee171642a547", Atari16k, NO) // Garfield
Record(@"0da1f2de5a9b5a6604ccdb0f30b9da4e5f799b40", None, NO) // Gas Hog
Record(@"73adae38d86d50360b1a247244df05892e33da46", None, NO) // Gauntlet
Record(@"3b1fb93342c7f014a28dddf6f16895d11ac7d6f0", None, NO) // General Re-Treat
Record(@"4b533776dcd9d538f9206ad1e28b30116d08df1e", Atari8k, NO) // Ghost Manor
Record(@"1bcf03e1129015a46ad7028e0e74253653944e86", Atari16k, NO) // Ghostbusters II (alternate)
Record(@"e032876305647a95b622e5c4971f7096ef72acdb", Atari16k, NO) // Ghostbusters II
Record(@"5ed0b2cb346d20720e3c526da331551aa16a23a4", Atari8k, NO) // Ghostbusters
Record(@"b64ed2d5a2f8fdac4ff0ce56939ba72e343fec33", None, NO) // Gigolo
Record(@"3a3d7206afee36786026d6287fe956c2ebc80ea7", None, NO) // Glacier Patrol
Record(@"7bca0f7a0f992782e4e4c90772bac976ca963a6d", None, NO) // Glib
Record(@"f78c478aacf6536522e8d37a3888a975e1a383cd", None, NO) // Go Go Home Monster (2)
Record(@"4c41379f0dd9880384fcbb46bad9fbaaf109a477", None, NO) // Go Go Home Monster
Record(@"a25d52770408314dec6f41aaf5f9f0a2a3e2c18f", None, NO) // Golf
Record(@"97fb489ba4ce0f8a306563063563617321352cfb", None, NO) // Gopher
Record(@"35f8341c73c7e6e896cb065977427b3f98ae9f08", None, NO) // Gorf
Record(@"24fab817728216582b6d95558c361ace66abf96f", None, NO) // Grand Prix
Record(@"a372d4dd3d95b3866553cae2336e4565e00cc25b", Atari8k, NO) // Gravitar
Record(@"7a027329309e018b0d51adcb6ae13c9d13e54f4a", Atari8k, NO) // Gremlins
Record(@"c90acaee066f97efc6a520deb7fa3e5760a471fa", Atari8k, NO) // Grover's Music Maker
Record(@"7d30ff565ad7b2a3143d049c5b39e4a6ac3f9cd5", None, NO) // Guardian
Record(@"45f3f98735798e19427a9100a9000d97917b932f", None, NO) // Gunfight (NTSC)
Record(@"7ac6356224cc718ee5731d1ce14aea6fb2335439", None, NO) // Gunfight (PAL)
Record(@"4bd87ba8b3b6d7850e3ea41b4d494c3b12659f27", ParkerBros, NO) // Gyruss
Record(@"282f94817401e3725c622b73a0c05685ce761783", Atari8k, NO) // H.E.R.O.
Record(@"4c72cec151f219866bf870fa7ac749a19ca501c9", None, NO) // Halloween
Record(@"561bccf508e162bc70c42d85c170cf0d1d4691a3", None, NO) // Hangman
Record(@"d6db71da02ae194140bf812be34d6e8a6785d138", None, NO) // Harbor Escape
Record(@"1476c869619075b551b20f2c7f95b11e0d16aec1", None, NO) // Haunted House
Record(@"8196209ef7048c5494dbdc932adbf1c7abf79f4e", Atari8k, NO) // Holey Moley
Record(@"f362d2b3a50e5ae3c2b412b6c08ecdcfee47a688", None, NO) // Home Run
Record(@"d4b0b2aa379893356c72414ee0065a3a91cf9f97", None, NO) // Human Cannonball
Record(@"a6e42c63138a2fd527cdbe9b7e60f5feabdd55c8", None, NO) // Hunt & Score — Memory Match
Record(@"f7e782214b5f9227e34c00f590be50534f1fda91", None, NO) // I Want my Mommy
Record(@"21de0f034e5dad03fa91eb7ae6cc081c142be35c", None, NO) // Ice Hockey
Record(@"e5b9c3a3638bd42f96a26b651463da96a9432315", Atari16k, NO) // Ikari Warriors
Record(@"620ab88d63cdd3f8ce67deac00a22257c7205c8b", None, NO) // Indy 500
Record(@"922cd171ef132bf6c5bed00ad01410ada4b20729", None, NO) // Infiltrate
Record(@"19fc37f2a24e31a59a17f9cbf3cc03416a8bab9a", None, NO) // Invaders
Record(@"2bbc124cead9aa49b364268735dad8cb1eb6594f", ParkerBros, NO) // James Bond 007
Record(@"ea5c827052886908c0deaa0a03d6f8da8e4f298d", None, NO) // Jammed Demo
Record(@"af4d6867a8bc4818fc6bb701a765a3c907feb628", None, NO) // Jaw Breaker
Record(@"36b9edc7150311203f375c1be10d0510efde6476", None, NO) // Jedi Arena
Record(@"0d94c1caacb862d9e0b4c2dda121cd4d74a1cced", None, NO) // John K Harvey's Equalizer
Record(@"928eaa424b36d98078f9251d67fb13a8fddfafbd", None, NO) // Journey Escape
Record(@"cb94dc316cba282a0036871db2417257e960786b", Atari8k, NO) // Joust
Record(@"cd2cf245d6e924ff2100cc93d20223c4a231e160", Atari16k, YES) // Jr. Pac-Man
Record(@"9a0ee845d9928d4db003b07b927bb2c1f628e725", None, NO) // Jungle Fever
Record(@"83a32a2d686355438c915540cfe0bb13b76c1113", Atari8k, NO) // Jungle Hunt
Record(@"ce8ac88b799c282567495ce509402a5a4c2c4d82", None, NO) // Kabobber
Record(@"40d4df4f8e4a69a299ae7678c17e72bedeb70105", None, NO) // Kaboom!
Record(@"a82aaeef44ad88de605c50d23fb4f6cec73f3ab4", None, NO) // Kamikaze Saucers
Record(@"01fd30311e028944eafb6d14bb001035f816ced7", Atari8k, NO) // Kangaroo
Record(@"c0db7d295e2ce5e00e00b8a83075b1103688ea15", None, NO) // Karate
Record(@"3eefc193dec3b242bcfd43f5a4d9f023e55378a4", None, NO) // Keystone Kapers
Record(@"839e13ffedcbed22d51a24c001900c3474a078f2", None, NO) // King Kong
Record(@"3162259c6dbfbb57a2ea41d849155702151ee39b", Atari16k, YES) // Klax
Record(@"759597d1d779cfdfd7aa30fd28a59acc58ca2533", None, NO) // Knight on the Town
Record(@"2f550743e237f6dc8c75c389a01b02e9a396fdad", None, NO) // Kool-Aid Man
Record(@"4bdf1cf73316bdb0002606facf11b6ddcb287207", Atari8k, NO) // Krull
Record(@"1637b6b9cd1a918339ec054cf95b924e7ce4789a", Atari8k, NO) // Kung Fu Superkicks
Record(@"3b93a34ba2a6b7db387ea588c48d939eee5d71a1", Atari8k, NO) // Kung-Fu Master
Record(@"6d59dfea26b7a06545a817f03f62a59be8993587", None, NO) // Lady in Wading
Record(@"ea8ecc2f6818e1c9479f55c0a3356edcf7a4d657", None, NO) // Laser Blast
Record(@"cdf55b73b4322428a001e545019eaa591d3479cf", None, NO) // Laser Gates
Record(@"afab795719386a776b5fb2165fc84f4858e16e05", None, NO) // Laser Volley
Record(@"fe208ad775cbf9523e7a99632b9f10f2c9c7aa87", None, NO) // Lochjaw
Record(@"fc3d75d46d917457aa1701bf47844817d0ba96c3", None, NO) // Lock 'n' Chase
Record(@"f92b0b83db3cd840d16ee2726011f5f0144103d5", None, NO) // London Blitz
Record(@"ef02fdb94ac092247bfcd5f556e01a68c06a4832", ParkerBros, NO) // Lord of the Rings
Record(@"e8492fe9d62750df682358fe59a4d4272655eb96", None, NO) // Lost Luggage
Record(@"dcd96913a1c840c8b57848986242eeb928bfd2ff", None, NO) // M*A*S*H
Record(@"d6e2b7765a9d30f91c9b2b8d0adf61ec5dc2b30a", None, NO) // M.A.D.
Record(@"4c66b84ab0d25e46729bbcf23f985d59ca8520ad", CommaVid, NO) // MagiCard
Record(@"cdc7e65d965a7a00adda1e8bedfbe6200e349497", None, NO) // Malagai
Record(@"ee8f9bf7cdb55f25f4d99e1a23f4c90106fadc39", None, NO) // Mangia
Record(@"249a11bb4872a24f22dff1027ff256c1408140c2", None, NO) // Marauder
Record(@"dd9e94ca96c75a212f1414aa511fd99ecdadaf44", None, NO) // Marine Wars
Record(@"49425ff154b92ca048abb4ce5e8d485c24935035", Atari8k, NO) // Mario Bros.
Record(@"fbe7a78764407743b43a91136903ede65306f4e7", None, NO) // Master Builder
Record(@"6db8fa65755db86438ada3d90f4c39cc288dcf84", MNetwork, NO) // Masters of the Universe
Record(@"18fac606400c08a0469aebd9b071ae3aec2a3cf2", None, NO) // Math Gran Prix
Record(@"aba25089d87cd6fee8d206b880baa5d938aae255", None, NO) // Maze Craze
Record(@"0ae118373c7bda97da2f8d9c113e1e09ea7e49e1", None, NO) // Mega Force
Record(@"46977baf0e1ee6124b524258879c46f80d624fae", MegaBoy, NO) // MegaBoy
Record(@"9c5748b38661dbadcbc9cd1ec6a6b0c550b0e3da", None, NO) // MegaMania
Record(@"debb1572eadb20beb0e4cd2df8396def8eb02098", None, NO) // Meltdown
Record(@"7fcf95459ea597a332bf5b6f56c8f891307b45b4", Atari16k, NO) // Midnight Magic
Record(@"0616f0dde6d697816dda92ed9e5a4c3d77a39408", Atari16k, YES) // Millipede
Record(@"5edbf8a24fcba9763983befe20e2311f61b986d4", Tigervision, NO) // Miner 2049er Volume 2
Record(@"0e56b48e88f69d405eabf544e57663bd180b3b1e", Tigervision, NO) // Miner 2049er
Record(@"34773998d7740e1e8c206b3b22a19e282ca132e1", None, NO) // Mines of Minos
Record(@"be24b42e3744a81fb217c86c4ed5ce51bff28e65", None, NO) // Miniature Golf
Record(@"f721d1f750e19b9e1788eed5e3872923ab46a91d", Atari8k, NO) // Miss Piggy's Wedding
Record(@"faa06bb0643dbf556b13591c31917d277a83110b", None, NO) // Missile Command
Record(@"224e7a310afdb91c6915743e72b7b53b38eb5754", None, NO) // Missile Control
Record(@"999dc390a7a3f7be7c88022506c70bd4208b26d8", None, NO) // Mission 3,000 AD
Record(@"93520821ce406a7aa6cc30472f76bca543805fd4", None, NO) // Mission Survive
Record(@"0b74a90a22a7a16f9c2131fabd76b7742de0473e", None, NO) // Mogul Maniac
Record(@"81a4d56820b1e00130e368a3532c409929aff5fb", Atari8k, NO) // Monstercise
Record(@"7dfeb1a8ec863c1e0f297113a1cc4185c215e81c", ParkerBros, NO) // Montezuma's Revenge
Record(@"dce778f397a325113f035722b7769492645d69eb", Atari8k, NO) // Moon Patrol
Record(@"05ab04dc30eae31b98ebf6f43fec6793a53e0a23", Atari8k, NO) // Moonsweeper
Record(@"c4d495d42ea5bd354af04e1f2b68cce0fb43175d", Atari8k, NO) // Motocross Racer
Record(@"ece97bda734faffcf847a8bcdfa474789c377d8d", Atari16k, NO) // MotoRodeo
Record(@"ef4112e86d6a3e8f7b8e482d294a5917f162b38c", CBSRamPlus, NO) // Mountain King
Record(@"cf6347dedcfec213c28dd92111ec6f41e74b6f64", None, NO) // Mouse Trap
Record(@"e4c912199779bba25f1b9950007f14dca3d19c84", Atari8k, NO) // Mr Do!
Record(@"330c2c67399e07c40f4101f9e18670fef070475e", ParkerBros, NO) // Mr. Do!'s Castle
Record(@"62b933cdd8844bb1816ce57889203954fe782603", Atari8k, NO) // Ms. Pac-Man
Record(@"b2df23b1bf6df9d253ad0705592d3fce352a837b", Atari8k, NO) // My Golf
Record(@"2b4a0535ca83b963906eb0a5d60ce0e21f07905d", None, NO) // Name This Game
Record(@"372771aeb4e2fb2cd1dead5497e3821e4236d5fc", None, NO) // Night Driver
Record(@"26e1309bc848cf5880b831d7566488ec5b3db58c", None, NO) // Night Stalker (2)
Record(@"281ff7e55c27656522b144b84cba08eb148e2f0a", None, NO) // Night Stalker
Record(@"e2e8750b8856dd44d914c43a7d277188cc148e5c", None, NO) // No Escape!
Record(@"771a2a87b3b457c0b83f556ce00d1e9c54caeabc", None, NO) // Nothing
Record(@"be3f4beeb322cddc7223d6d77e17302aa811e43a", Atari8k, NO) // Obelix
Record(@"1f8d06b99db94b0aa8ca320c7cb212639ac9591f", None, NO) // Ocean City
Record(@"3dcfe93399044148561586056288c6f8e5c96e2b", Atari16k, YES) // Off the Wall
Record(@"7ad74a7c36318f1304f5dc454401cf257fa60d7a", None, NO) // Off Your Rocker
Record(@"ea674cf2c90d407b8f8b96eac692690b602b73f9", None, NO) // Oink!
Record(@"7bd1cbddefcf3bd24da570be015234d0c444a7e5", None, NO) // Okie Dokie
Record(@"dcaab259e7617c7ac7d349893451896a9ca0e292", CBSRamPlus, NO) // Omega Race
Record(@"cd968c2a983f9adf4d8d8d56823923b31c33980f", None, NO) // Open Sesame
Record(@"7905709fcc85cbcfc28ca2ed543ffa737a5483ae", Atari8k, NO) // Oscar's Trash Race
Record(@"cbecf1a32d9366a3dd4ad643916cd59cdc820a8b", None, NO) // Othello
Record(@"344d6942723513c376a7a844779804e10f357b85", None, NO) // Out of Control
Record(@"f8eeaaf4635ac39b4bdf7ded1348bce46313ef9f", None, NO) // Outlaw
Record(@"412e8a2438379878ee4de5c6bf4e5a9ee2707c8b", None, NO) // Oystron
Record(@"923c969d31cef450075932436f03f1404e1cab0e", None, NO) // Pac-Kong
Record(@"0940fea7f04cdb6d4b90c5ad1a7e344e68f6dbb1", None, NO) // Pac-Man
Record(@"b529c1664ed1abc8f5f962a1fed65c0e4440219c", None, NO) // Peek-A-Boo
Record(@"832283530f5dee332f29cf8c4854dd554f2030a0", None, NO) // Pele's Soccer
Record(@"461c2ea3e4d24f86ec02215c1f4743d250796c11", Atari8k, NO) // Pengo (prototype)
Record(@"89b991a7a251f78f422bcdf9cf7d4475fdf33e97", Atari8k, NO) // Pengo
Record(@"c5317035e73f60e959c123d89600c81b7c45701f", None, NO) // Pepsi Invaders
Record(@"19c3ad034466c0433501a415a996ed7155d6063a", Atari16k, NO) // Pete Rose Baseball
Record(@"959aca4b44269b1e5ac58791fc3c7c461a6a4a17", None, NO) // Phantom Tank
Record(@"b299df2792c5cca73118925dff85695b73a16228", None, NO) // Philly Flasher
Record(@"010d51e3f522ba60f021d56819437d7c85897cdd", Atari8k, NO) // Phoenix
Record(@"a5917537cf1093aa350903d85d9e271e8a11d2cf", Atari16k, NO) // Pick 'n' Pile
Record(@"483fc907471c5c358fb3e624097861a2fc9c1e45", None, NO) // Picnic
Record(@"57774193081acea010bd935a0449bc8f53157128", None, NO) // Piece o' Cake
Record(@"d08b30ca2e5e351cac3bd3fb760b87a1a30aa300", Atari8k, NO) // Pigs in Space
Record(@"920cfbd517764ad3fa6a7425c031bd72dc7d927c", Pitfall2, NO) // Pitfall II
Record(@"d0ec08b88d032627701ad72337524d91b26c656b", None, NO) // Pitfall! (PAL)
Record(@"8d525480445d48cc48460dc666ebad78c8fb7b73", None, NO) // Pitfall! (NTSC)
Record(@"dcca30e4ae58c85a070f0c6cfaa4d27be2970d61", None, NO) // Planet of the Apes
Record(@"ccfcbf52815a441158977292b719f7c5ed80c515", None, NO) // Planet Patrol
Record(@"103398dd35ebd39450c5cac760fa332aac3f9458", None, NO) // Plaque Attack
Record(@"2410931a8a18b915993b6982fbabab0f437967a4", Tigervision, NO) // Polaris
Record(@"9d334da07352a9399cbbd9b41c6923232d0cdcd3", Atari8k, NO) // Pole Position
Record(@"c0af0188028cd899c49ba18f52bd1678e573bff2", None, NO) // Polo
Record(@"954d2980ea8f8d9a76921612c378889f24c35639", None, NO) // Pompeii
Record(@"b7a002025c24ab2ec4a03f62212db7b96c0e5ffd", None, NO) // Pooyan
Record(@"1772a22df3e9a1f3842387ac63eeddff7f04b01c", ParkerBros, NO) // Popeye
Record(@"70afc2cc870be546dc976fa0c6811f7e01ebc471", Atari8k, NO) // Porky's
Record(@"8b001373be485060f88182e9a7afcf55b4d07a57", Atari8k, NO) // Pressure Cooker
Record(@"1ea6bea907a6b5607c76f222730f812a99cd1015", Atari8k, NO) // Private Eye
Record(@"feb6bd37e5d722bd080433587972b980afff5fa5", None, NO) // Pumruckl I
Record(@"a61be3702437b5d16e19c0d2cd92393515d42f23", ParkerBros, NO) // Q-Bert's Qubes
Record(@"f3ef9787b4287a32e4d9ac7b9c3358edc16315b2", None, NO) // Q-Bert
Record(@"1e634a8733cbc50462d363562b80013343d2fac3", Atari8k, NO) // Quadrun
Record(@"d83c740d2968343e6401828d62f58be6aea8e858", Atari8k, NO) // Quest for Quintana Roo
Record(@"33a47f79610c4525802c9881f67ad1f3f8c1b55d", None, NO) // Quick Step!
Record(@"7bf945ea667e683ec24a4ed779e88bbe55dc4b26", Atari8k, NO) // Rabbit Transit
Record(@"4af6008152f1d38626d84016a7ef753406b48b46", None, NO) // Racquetball
Record(@"33f016c941fab01e1e2d0d7ba7930e3bcd8feaa3", Atari16k, YES) // Radar Lock
Record(@"5f1f2b5b407b0624b59409e02060a3a9e8eed8fc", None, NO) // Radar
Record(@"a79f6e0f4fd76878e5c3ba6b52d17e88acdbe9f6", None, NO) // Raft Rider
Record(@"7ae70783969709318e56f189cf03da92320a6aba", Atari8k, NO) // Raiders of the Lost Ark
Record(@"fd0a69c06eb3f7c9328951c890644f93c4bad6ad", None, NO) // Ram It
Record(@"7bb7df255829d5fbbee0d944915e50f89a5e7075", Atari16k, NO) // Rampage!
Record(@"5adf9b530321472380ebceb2539de2ffbb0310bc", None, NO) // Reactor
Record(@"ace97b89b8b6ab947434dbfd263951c6c0b349ac", Atari8k, NO) // RealSports Baseball
Record(@"bc2e6bdaa950bc06be040899dfeb9ad0938f4e98", Atari8k, NO) // RealSports Basketball
Record(@"22dedbfce6cc9055a6c4caec013ca80200e51971", Atari16k, NO) // RealSports Boxing
Record(@"200d04c1e7f41a5a3730287ed0c3f9293628f195", Atari8k, NO) // RealSports Football
Record(@"e3d964d918b7f2c420776acd3370ec1ee62744ea", Atari8k, NO) // RealSports Soccer
Record(@"702c1c7d985d0d22f935265bd284d1ed50df2527", Atari8k, NO) // RealSports Tennis
Record(@"2025e1d868595fad36e5d9e7384ffd24c206208d", None, NO) // RealSports Volleyball
Record(@"94e94810bf6c72eee49157f9218c3c170b65c836", None, NO) // Rescue Terra I
Record(@"f8a9dd46f9bad232f74d1ee2671ccb26ea1b3029", None, NO) // Revenge of the Beefsteak Tomatoes
Record(@"acb2430b4e6c72ce13f321d9d3a38986dc4768ef", None, NO) // Riddle of the Sphinx
Record(@"6715493dce54b22362741229078815b3360988ae", Tigervision, NO) // River Patrol
Record(@"40329780402f8247f294fe884ffc56cc3da0c62d", None, NO) // River Raid copy
Record(@"a08c3eae3368334c937a5e03329782e95f7b57c7", Atari16k, NO) // River Raid II
Record(@"325a2374800b2cb78ab7ff9e4017759865109d7d", None, NO) // River Raid
Record(@"7f9c2321c9f22cf2cdbcf1b3f0e563a1c53f68ca", Atari8k, NO) // Robin Hood
Record(@"f45dfcd6db0dae5458e1c0ae8eeaa75b553cdfec", Atari16k, NO) // Road Runner
Record(@"21a3ee57cb622f410ffd51986ab80acadb8d44b7", ActivisionStack, NO)// Robot Tank
Record(@"0abf0a292d4a24df5a5ebe19a9729f3a8f883c8b", Atari8k, NO) // Roc 'n Rope
Record(@"fd243c480e769b20b7bf3e74bcd86e4ac99dab19", None, NO) // Room of Doom
Record(@"85752ac6eb7045a9083425cd166609882a1c2c58", Atari8k, NO) // Saboteur
Record(@"ecd8ef49ae23ddd3e10ec60839b95c8e7764ea27", Atari16k, YES) // Save Mary!
Record(@"e566d7b1f4eb6c2b110eb4fc676eb0ce9e90fe1e", None, NO) // SCIScide 1.32
Record(@"8e9b320d8966315a8b07f1babc0ba2662f761102", None, NO) // SCSIcide 1.30
Record(@"e8f5ae861ca1f410c6a9af116a96ed65d9a3abb2", None, NO) // Scuba Diver
Record(@"e5558ae30acc1fa5b4ffe782ae480622586a32ca", None, NO) // Sea Hunt
Record(@"4ec6982b2da25b29840428fd993a391e63f53730", None, NO) // Seahawk
Record(@"7324a1ebc695a477c8884718ffcad27732a98ab0", None, NO) // Seaquest
Record(@"1914f57ab0a6f221f2ad344b244a3cdd7b1d991a", None, NO) // Secret Agent
Record(@"af11f1666d345267196a1c35223727e2ef93483a", Atari16k, YES) // Secret Quest
Record(@"6518078b3786ac26f75067f0646aef4e83f2db15", None, NO) // Self_Portrait_by_Tjoppen
Record(@"fcf5f8a7d6e59a339c2002e3d4084d87deb670fe", Atari16k, NO) // Sentinel
Record(@"6e91759756c34f40a2c26936df6c0ca1a3850e80", None, NO) // Shark Attack
Record(@"cfb4b41e318c7cd0070e75e412f67c973e124d8e", None, NO) // Shootin' Gallery
Record(@"6e6daa34878d3e331c630359c7125a4ffba1b22d", Atari16k, YES) // Shooting Arcade
Record(@"08952192ea6bf0ef94373520a7e855f58bae6179", None, NO) // Shuttle Orbiter
Record(@"242fc23def80da96da22c2c7238d48635489abb0", Atari8k, NO) // Sinistar
Record(@"e9fa52f8e7f747cd9685ddb18bdeed2f66255324", Atari8k, NO) // Sir Lancelot
Record(@"a26fe0b5a43fe8116ab0ae6656d6b11644d871ec", Atari8k, NO) // Skate Boardin'
Record(@"5ea6d2eb27c76e85f477ba6c799deb7c416ebbc3", None, NO) // Skeet Shoot
Record(@"6581846f983b50cffb75d1c1b902238ba7dd4e92", None, NO) // Skiing
Record(@"4dde18d4abc139562fdd7a9d2fd49a1f00a9e64a", None, NO) // Sky Diver
Record(@"105f722dcf9a89b683c10ddd7f684c5966c8e1db", None, NO) // Sky Jinks
Record(@"fc5f1e30db3b2469c9701dadfa95f3268fd1e4cb", Atari8k, NO) // Sky Patrol
Record(@"ef0a7ecfe8f3b5d1e67a736552a0cdc472803be9", None, NO) // Sky Skipper
Record(@"7239d1c64f3dfc2a1613be325cce13803dd2baa5", None, NO) // Slot Machine
Record(@"a2b13017d759346174e3d8dd53b6347222d3b85d", None, NO) // Slot Racers
Record(@"530c7883fed4c5b9d78e35d48770b56e328999a3", Atari8k, NO) // Smurfs: Rescue in Gargamel's Castle
Record(@"c0ae3965fcfab0294f770af0af57d7d1adc17750", Atari8k, NO) // Smurfs Save the Day
Record(@"e7bf450cf3a3f40de9d24d89968a4bc89b53cb18", None, NO) // Snail Against Squirrel
Record(@"843e3c2fc71af2db3f2ae98eb350fde26334cfd1", None, NO) // Sneak 'n Peak
Record(@"972bc0a77e76f3e4e1270ec1c2fc395e9826bc07", Atari8k, NO) // Snoopy and the Red Baron
Record(@"9d725002e94b04e29d8cbce3c71d3bb2a84352fa", None, NO) // Soccer
Record(@"09ea74f14db8d21ea785d0c8209ed670e4ce88be", Atari8k, NO) // Solar Fox
Record(@"ec65ef9e47239a7d15db9bca7e625b166e8ac242", None, NO) // Solar Storm
Record(@"33b16fbc95c2cdc52d84d98ca471f10dae3f9dbf", Atari16k, NO) // Solaris
Record(@"ae3009e921f23254bb71f67c8cb2d7d6de2845a5", Atari8k, NO) // Sorceror's Apprentice
Record(@"70e912379060d834aa9fb2baa2e6a438f3b5d3b6", None, NO) // Sorceror
Record(@"560563613bc309a532d611f11a1cf2b9af1e2f16", None, NO) // Space Attack
Record(@"26c6c47e9b654e81f47875c5fcb4e6212125f329", None, NO) // Space Canyon
Record(@"b757b883ee114054c650027f3b9a8f15548cbf32", None, NO) // Space Cavern
Record(@"31d9668fe5812c3d2e076987ca327ac6b2e280bf", None, NO) // Space Invaders
Record(@"5bdd8af54020fa43065750bd4239a497695d403b", None, NO) // Space Jockey
Record(@"bcec5a66f8dff1a751769626b0fce305fab44ca2", Atari8k, NO) // Space Shuttle
Record(@"ce4432bb48921a3565d996b80b65fdf73bbfc39b", None, NO) // Space Tunnel
Record(@"23510ba617431097668eaf104aa1e36233173093", None, NO) // Space War
Record(@"b356294e35827bf81add95fee5453b0ca0f497ad", None, NO) // Spacechase
Record(@"983b1aff97ab1243e283ba62d3a6a75ad186d225", None, NO) // SpaceMaster X-7
Record(@"06820ad3c957913847f9849d920bc8725f535f11", None, NO) // Spider Fighter
Record(@"5d6f918bba4bd046e85b707da3b7d643cc2e1f1f", None, NO) // Spider Maze
Record(@"60af23a860b33e1a85081b8de779d2ddfe36b19a", None, NO) // Spider Monster
Record(@"912c5f5571ac59a6782da412183cdd6277345816", None, NO) // Spider-Man
Record(@"904118b0c1be484782ec2a60a24436059608b36d", None, NO) // Spiderdroid
Record(@"205241a12778829981e9281d9c6fa137f11e1376", Atari8k, NO) // Spike's Peak
Record(@"165de0ebca628eb1e9f564390c9eedfe289c7a1d", None, NO) // Spitfire Attack
Record(@"6da0aa8aa40cd9c78dc014deb9074529688d91d0", Tigervision, NO) // Springer
Record(@"c0e29b86fc1cc41a1c8afa37572c3c5698ae70b2", Atari16k, YES) // Sprint Master
Record(@"1d0acf064d06a026a04b6028285db78c834e9854", Atari8k, NO) // Spy Hunter
Record(@"033148faebc97d4ed3a86c97fe0cdee21bd261f7", None, NO) // Squeeze Box
Record(@"11a9dd44787f011ec540159248377cb27fb8f7bb", None, NO) // Squoosh
Record(@"46aabde3074acded8890a2efa5586d6b8bd76b5d", None, NO) // Sssnake
Record(@"277184c4e61ced14393049a21a304e941d05993f", None, NO) // Stampede
Record(@"1b95e07437ddc1523d7ec21c460273e91dbf36c7", None, NO) // Star Fox
Record(@"3359aa7a6a5fa25beaa3ae5868d0034d52de9882", None, NO) // Star Gunner
Record(@"e10cce1a438c82bd499e1eb31a3f07d7254198f5", Atari8k, NO) // Star Raiders
Record(@"878e78ed46e29c44949d0904a2198826e412ed81", None, NO) // Star Ship
Record(@"de05d1ca8ad1e7a85df3faf25b1aa90b159afded", None, NO) // Star Strike
Record(@"61a3ebbffa0bfb761295c66e189b62915f4818d9", Atari8k, NO) // Star Trek — Strategic Operations Simulator
Record(@"ccc5b829c4aa71acb7976e741fdbf59c8ef9eb55", None, NO) // Star Voyager
Record(@"c9d201935bbe6373793241ba9c03cc02f1df31c9", ParkerBros, NO) // Star Wars — Ewok Adventure
Record(@"8823fe3d8e3aeadc6b61ca51914e3b15aa13801c", ParkerBros, NO) // Star Wars — The Arcade Game
Record(@"ad5b2c9df558ab23ad2954fe49ed5b37a06009bf", None, NO) // Star Wars — The Empire Strikes Back
Record(@"4f87be0ef16a1d0389226d1fbda9b4c16b06e13e", Atari8k, YES) // Stargate
Record(@"814876ed270114912363e4718a84123dee213b6f", None, NO) // StarMaster
Record(@"e56ef1c0313d6d04e25446c4e34f9bb7eda8efac", None, NO) // Steeplechase copy
Record(@"bd7f0005fa018f13ed7e942c83c1751fb746a317", None, NO) // Steeplechase
Record(@"de2c146fa7a701d6c37728f5415563ce923a3e5d", None, NO) // Stella_Lives!
Record(@"7d97d014c22a2ed3a5bc4b310f5a7be1b1d3520f", None, NO) // Stellar Track
Record(@"9d6decda6e8ab263f7380ff662c814b8cb8caf34", None, NO) // Strategy X
Record(@"7ca8f9cd74cfa505c493757ff37bf127ff467bb4", None, NO) // Strawberry Shortcake — Musical Match-Ups
Record(@"bffb3d41916c83398624151eb00aa2a3acd23ab8", None, NO) // Street Racer
Record(@"2cfe280fdbb6b5c8cda8a4620df12a5154e123be", None, NO) // Stronghold
Record(@"2e19d7e16cf17682b043baaa30e345e6fa4540e5", None, NO) // Stunt Cycle
Record(@"3aec7ea8af72bbe105b9d2903a92f5ad2b37bddb", None, NO) // Stunt Man
Record(@"ccd75f0141b917656ef2b86c068fba3238d18a0c", None, NO) // Sub-Scan
Record(@"b22ba7cbde60a21ecbbe3953cc4a5c0bf007cc26", None, NO) // Submarine Commander
Record(@"2abc6bbcab27985f19e42915530fd556b6b1ae23", Atari8k, NO) // Subterrenea
Record(@"65f4a708e6af565f1f75d0fbdc8942cb149cf299", Atari16k, NO) // Summer Games
Record(@"b066a60ea1df1db0a55271c7608b0e19e4d18a1e", Atari16k, NO) // Super Baseball
Record(@"e380e243c671e954e86aa1a3a0bfeb36d5e0c3e2", None, NO) // Super Breakout
Record(@"dfce4d6436f91d8d385f8b01f0d8e3488400407b", None, NO) // Super Challenge Baseball
Record(@"5c1338ec76828cfa4a85b5bd8db1c00c8095c330", None, NO) // Super Challenge Football
Record(@"bac0a0256509f8fd1feea93d74ba4c7d82c1edc6", ParkerBros, NO) // Super Cobra
Record(@"eaca6b474fd552ab4aaf75526618828165a91934", Atari16k, YES) // Super Football
Record(@"b9dee027c8d7dd2a46be111ab0b8363c1becc081", None, NO) // Superman
Record(@"cf84e21ada55730d689cfac7d26e2295317222bc", Atari8k, NO) // Surf's Up
Record(@"e754c8985ca7f5780c23a856656099b710e89919", None, NO) // Surfer's Paradise
Record(@"b7988373b81992d08056560d15d3e32d9d3888bc", None, NO) // Surround
Record(@"6c993b4c70cfed390f1f436fdbaa1f81495be18e", None, NO) // Survival Run
Record(@"e2b3b43cadf2f2c91c1ec615651ff9b1e295d065", None, NO) // sv2k12
Record(@"0d59545b22e15019a33de16999a57dae1f998283", None, NO) // Swordfight
Record(@"05db3d09fa3dac80c70aae2e39f1ad7c31c62f02", Atari8k, NO) // SwordQuest — EarthWorld
Record(@"5c3cf976edbea5ded66634a284787f965616d97e", Atari8k, NO) // SwordQuest — FireWorld
Record(@"569fcb67ca1674b48e2f3a2e7af7077a374402de", Atari8k, NO) // SwordQuest — WaterWorld
Record(@"55e98fe14b07460734781a6aa2f4f1646830c0af", None, NO) // Tac-Scan
Record(@"13a9d86cbde32a1478ef0c7ef412427b13bd6222", None, NO) // Tanks But No Tanks
Record(@"ee8bc1710a67c33e9f95bb05cc3d8f841093fde2", None, NO) // Tapeworm
Record(@"e986e1818e747beb9b33ce4dff1cdc6b55bdb620", Atari8k, NO) // Tapper
Record(@"bae73700ba6532e9e6415b6471d115bdb7805464", None, NO) // Task Force
Record(@"7aaf6be610ba6ea1205bdd5ed60838ccb8280d57", Atari8k, NO) // Tax Avoiders
Record(@"476f0c565f54accecafd72c63b0464f469ed20ea", Atari8k, NO) // Taz
Record(@"7efc0ebe334dde84e25fa020ecde4fddcbea9e8f", Atari8k, NO) // Telepathy
Record(@"bf4d570c1c738a4d6d00237e25c62e9c3225f98f", Atari8k, NO) // Tempest
Record(@"3d30e7ed617168d852923def2000c9c0a8b728c6", None, NO) // Tennis
Record(@"53413577afe7def1d390e3892c45822405513c07", Atari8k, NO) // The A-Team
Record(@"1f834923eac271bf04c18621ac2aada68d426917", None, NO) // The Earth Dies Screaming
Record(@"5a641caa2ab3c7c0cd5deb027acbc58efccf8d6a", None, NO) // The Music Machine
Record(@"717122a4184bc8db41e65ab7c369c40b21c048d9", None, NO) // The Texas Chainsaw Massacre
Record(@"49bebad3e2eb210591be709a6ec7e4f0864265ab", None, NO) // This Planet Sucks
Record(@"9a52fa88bd7455044f00548e9615452131d1c711", None, NO) // Threshold
Record(@"09608cfaa7c6e9638f12a1cff9dd5036c9effa43", Atari16k, NO) // Thrust
Record(@"3cc8bcc0ff5164303433f469aa4da2eb256d1ad0", None, NO) // Thunderground
Record(@"53ee70d4b35ee3df3ffb95fa360bddb4f2f56ab2", ActivisionStack, NO)// Thwocker
Record(@"387358514964d0b6b55f9431576a59b55869f7ab", Atari8k, NO) // Time Pilot
Record(@"979d9b0b0f32b40c0a0568be65a0bc5ef36ca6d0", Atari8k, NO) // Title Match Pro Wrestling
Record(@"fcd339065a012c9fe47efbec62969cbc32f3fbf0", Atari8k, NO) // Tomarc the Barbarian
Record(@"d82ac7237df54cc8688e3074b58433a7dd6b7d11", ParkerBros, NO) // Tooth Protectors
Record(@"b344b3e042447afbb3e40292dc4ca063d5d1110d", None, NO) // Towering Inferno
Record(@"005a6a53f5a856f0bdbca519af1ef236aaa1494d", Atari16k, NO) // Track and Field
Record(@"9a9917d82362c77b4d396f56966219fc248edf47", None, NO) // Treasure Below
Record(@"86c563db11db9afbffbd73c55e9fae9b2f69be4f", None, NO) // Trick Shot
Record(@"0ec58a3a5a27d1b82a5f9aabab02f9a8387b6956", None, NO) // TRON — Deadly Discs
Record(@"fc1a0b58765a7dcbd8e33562e1074ddd9e0ac624", CBSRamPlus, NO) // Tunnel Runner
Record(@"1162fe46977f01b4d25efab813e0d05ec90aeadc", None, NO) // Turmoil
Record(@"a4d6bac854a70d2c55946932f1511cc62db7d4aa", ParkerBros, NO) // Tutankham
Record(@"bd0ca4884c85db2323f5a4be5266aabb99d84542", None, NO) // TVNoise.bin
Record(@"286106fb973530bc3e2af13240f28c4bcb37e642", None, NO) // Universal Chaos
Record(@"6bde671a50330af154ed15e73fdba3fa55f23d87", Atari8k, NO) // Up 'n Down
Record(@"01475d037cb7a2a892be09d67083102fa9159216", Atari8k, NO) // Vanguard
Record(@"dce98883e813d77e03a5de975d4c52bfb34e7f77", None, NO) // Vault Assault
Record(@"17626ae7bfd10bcde14903040baee0923ecf41dd", None, NO) // Venture II
Record(@"0305dfc99bf192e53452a1e0408ccc148940afcd", None, NO) // Venture
Record(@"babae88a832b76d8c5af6ea63b8f10a0da5bb992", None, NO) // Video Checkers
Record(@"043ef523e4fcb9fc2fc2fda21f15671bf8620fc3", None, NO) // Video Chess
Record(@"1554b146d076b64776bf49136cea01f60eeba4c1", None, NO) // Video Jogger
Record(@"3b18db73933747851eba9a0ffa3c12b9f602a95c", CommaVid, NO) // Video Life
Record(@"1ffe89d79d55adabc0916b95cc37e18619ef7830", None, NO) // Video Olympics
Record(@"2c16c1a6374c8e22275d152d93dd31ffba26271f", None, NO) // Video Pinball
Record(@"dec2a3e9b366dce2b63dc1c13662d3f22420a22e", None, NO) // Video Reflex
Record(@"a345a5696f1d63a879e7bb7e3a74c825e97ef7c3", None, NO) // Video Simon
Record(@"2ebd0f43ee76833f75759ac1bbb45a8e0c3b86e9", None, NO) // Vulture Attack
Record(@"73072295721453065d62d9136343b81310a4d225", None, NO) // Wabbit
Record(@"482bd349222b8c702e125c27fd516e73af13967b", None, NO) // Wall Ball
Record(@"6de42bbc4766b26301e291ba00f7f7a9ac748639", None, NO) // Wall Break
Record(@"2d7563d337cbc0cdf4fc14f69853ab6757697788", None, NO) // Warlords
Record(@"232a370519a7fcce121e15f850d0d3671909f8b8", None, NO) // Warplock
Record(@"e325c5c0501ff527f06e6518526f9eefed309e89", None, NO) // Waring Worms
Record(@"472215fcb46cec905576d539efc8043488efc4ed", None, NO) // Westward Ho
Record(@"16df34446af2e6035ca871a00e1e8a008cfb8df4", Atari8k, NO) // Wing War
Record(@"6850d329e8ab403bdae38850665a2eff91278e92", Atari16k, NO) // Winter Games
Record(@"326e5e63a54ec6a0231fd38e62e352004d4719fe", None, NO) // Wizard of Wor
Record(@"e4b0f68abff3273cdd2b973639d607ae4a700adc", None, NO) // Wizard
Record(@"806c5a8a7b042a1a3ada1b6f29451a3446f93da3", None, NO) // Word Zapper
Record(@"36a1e73eb5aa5c3cd0b01af5117d19b8c36071e4", None, NO) // Worm War 1
Record(@"1c5d151e86c0a0bbdf3b33ef153888c6be78c36b", None, NO) // X-Man
Record(@"160b6e36437ad6acbc2686fbde1002e2fa88c5fb", Atari16k, NO) // Xenophobe
Record(@"73133b81196e5cbc1cec99eefc1223ddb8f4ca83", Atari8k, NO) // Xevious
Record(@"6a1e0142c6886a6589a58e029e5aec6b72f7d27f", None, NO) // Yahtzee
Record(@"e2cd8996c1cf929e29130690024d1ec23d3b0bde", None, NO) // Yars' Revenge
Record(@"58c2f6abc5599cd35c0e722f24bcc128ac8f9a30", Atari8k, NO) // Zaxxon
};
#undef Record
@interface AtariStaticAnalyserTests : XCTestCase
@end
@implementation AtariStaticAnalyserTests
- (void)testROMsOfSize:(NSInteger)size
{
NSString *basePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"Atari ROMs"];
for(NSString *testFile in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:basePath error:nil])
{
NSString *fullPath = [basePath stringByAppendingPathComponent:testFile];
// get a SHA1 for the file
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
if(size > 0 && [fileData length] != (NSUInteger)size) continue;
uint8_t sha1Bytes[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([fileData bytes], (CC_LONG)[fileData length], sha1Bytes);
NSMutableString *sha1 = [[NSMutableString alloc] init];
for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]];
// get an analysis of the file
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([fullPath UTF8String]);
// grab the ROM record
AtariROMRecord *romRecord = romRecordsBySHA1[sha1];
if(!romRecord) continue;
// assert equality
XCTAssert(targets.front().atari.paging_model == romRecord.pagingModel, @"%@; should be %d, is %d", testFile, romRecord.pagingModel, targets.front().atari.paging_model);
XCTAssert(targets.front().atari.uses_superchip == romRecord.usesSuperchip, @"%@; should be %@", testFile, romRecord.usesSuperchip ? @"true" : @"false");
}
}
- (void)testAtariROMs { [self testROMsOfSize:-1]; } // This will duplicate all tests below, but also catch anything that isn't 2, 4, 8, 12, 16 or 32kb in size.
- (void)test2kROMs { [self testROMsOfSize:2048]; }
- (void)test4kROMs { [self testROMsOfSize:4096]; }
- (void)test8kROMs { [self testROMsOfSize:8192]; }
- (void)test12kROMs { [self testROMsOfSize:12288]; }
- (void)test16kROMs { [self testROMsOfSize:16384]; }
- (void)test32kROMs { [self testROMsOfSize:32768]; }
@end

View File

@@ -0,0 +1,72 @@
//
// CRCTests.m
// Clock Signal
//
// Created by Thomas Harte on 27/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "CRC.hpp"
@interface CRCTests : XCTestCase
@end
@implementation CRCTests
- (NumberTheory::CRC16)mfmCRCGenerator
{
return NumberTheory::CRC16(0x1021, 0xffff);
}
- (uint16_t)crcOfData:(uint8_t *)data length:(size_t)length generator:(NumberTheory::CRC16 &)generator
{
generator.reset();
for(size_t c = 0; c < length; c++)
generator.add(data[c]);
return generator.get_value();
}
- (void)testIDMark
{
uint8_t IDMark[] =
{
0xa1, 0xa1, 0xa1, 0xfe, 0x00, 0x00, 0x01, 0x01
};
uint16_t crc = 0xfa0c;
NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator;
uint16_t computedCRC = [self crcOfData:IDMark length:sizeof(IDMark) generator:crcGenerator];
XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC);
}
- (void)testData
{
uint8_t sectorData[] =
{
0xa1, 0xa1, 0xa1, 0xfb, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x53, 0x45, 0x44, 0x4f,
0x52, 0x49, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x53, 0x45, 0x44, 0x4f, 0x52, 0x49, 0x43, 0x20, 0x56, 0x31, 0x2e, 0x30,
0x30, 0x36, 0x20, 0x30, 0x31, 0x2f, 0x30, 0x31, 0x2f, 0x38, 0x36, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20
};
uint16_t crc = 0x4de7;
NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator;
uint16_t computedCRC = [self crcOfData:sectorData length:sizeof(sectorData) generator:crcGenerator];
XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC);
}
@end

View File

@@ -0,0 +1,220 @@
//
// PCMPatchedTrackTests.m
// Clock Signal
//
// Created by Thomas Harte on 17/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "PCMTrack.hpp"
#include "PCMPatchedTrack.hpp"
@interface PCMPatchedTrackTests : XCTestCase
@end
@implementation PCMPatchedTrackTests
#pragma mark - Prebuilt tracks
- (std::shared_ptr<Storage::Disk::Track>)togglingTrack
{
Storage::Disk::PCMSegment segment;
segment.data = { 0xff, 0xff, 0xff, 0xff };
segment.number_of_bits = 32;
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(segment));
}
- (std::shared_ptr<Storage::Disk::Track>)patchableTogglingTrack
{
std::shared_ptr<Storage::Disk::Track> track = self.togglingTrack;
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMPatchedTrack(track));
}
- (std::shared_ptr<Storage::Disk::Track>)fourSegmentPatchedTrack
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
Storage::Disk::PCMPatchedTrack *patchable = static_cast<Storage::Disk::PCMPatchedTrack *>(patchableTrack.get());
for(int c = 0; c < 4; c++)
{
Storage::Disk::PCMSegment segment;
segment.data = {0xff};
segment.number_of_bits = 8;
segment.length_of_a_bit.length = 1;
segment.length_of_a_bit.clock_rate = 32;
patchable->add_segment(Storage::Time(c, 4), segment);
}
return patchableTrack;
}
#pragma mark -
- (std::vector<Storage::Disk::Track::Event>)eventsFromTrack:(std::shared_ptr<Storage::Disk::Track>)track
{
std::vector<Storage::Disk::Track::Event> events;
while(1)
{
events.push_back(track->get_next_event());
if(events.back().type == Storage::Disk::Track::Event::IndexHole) break;
}
return events;
}
- (Storage::Time)timeForEvents:(const std::vector<Storage::Disk::Track::Event> &)events
{
Storage::Time result(0);
for(auto event : events)
{
result += event.length;
}
return result;
}
- (void)patchTrack:(std::shared_ptr<Storage::Disk::Track>)track withSegment:(Storage::Disk::PCMSegment)segment atTime:(Storage::Time)time
{
Storage::Disk::PCMPatchedTrack *patchable = static_cast<Storage::Disk::PCMPatchedTrack *>(track.get());
patchable->add_segment(time, segment);
}
#pragma mark - Repeating Asserts
- (void)assertOneThirtyTwosForTrack:(std::shared_ptr<Storage::Disk::Track>)track
{
// Confirm that there are now flux transitions (just the first five will do)
// located 1/32nd of a rotation apart.
for(int c = 0; c < 5; c++)
{
Storage::Disk::Track::Event event = track->get_next_event();
XCTAssert(
event.length == (c ? Storage::Time(1, 32) : Storage::Time(1, 64)),
@"flux transitions should be 1/32nd of a track apart");
}
}
- (void)assertEvents:(const std::vector<Storage::Disk::Track::Event> &)events hasEntries:(size_t)numberOfEntries withEntry:(size_t)entry ofLength:(Storage::Time)time
{
XCTAssert(events.size() == numberOfEntries, @"Should be %zu total events", numberOfEntries);
XCTAssert(events[entry].length == time, @"Event %zu should have been %d/%d long, was %d/%d", entry, time.length, time.clock_rate, events[entry].length.length, events[entry].length.clock_rate);
Storage::Time eventTime = [self timeForEvents:events];
XCTAssert(eventTime == Storage::Time(1), @"Total track length should be 1; was %d/%d", eventTime.length, eventTime.clock_rate);
}
#pragma mark - Unpatched tracks
- (void)testUnpatchedRawTrack
{
[self assertOneThirtyTwosForTrack:self.togglingTrack];
}
- (void)testUnpatchedTrack
{
[self assertOneThirtyTwosForTrack:self.patchableTogglingTrack];
}
#pragma mark - Insertions affecting one existing segment
- (void)testSingleSplice
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 1, {0xff}) atTime:Storage::Time(3, 128)];
std::vector<Storage::Disk::Track::Event> events = [self eventsFromTrack:patchableTrack];
Storage::Time total_length = [self timeForEvents:events];
XCTAssert(events.size() == 33, @"Should still be 33 total events");
XCTAssert(events[0].length == Storage::Time(1, 64), @"First event should be after 1/64 as usual");
XCTAssert(events[1].length == Storage::Time(3, 128), @"Second event should be 3/128 later"); // ... as it was inserted at 3/128 and runs at the same rate as the main data, so first inserted event is at 3/128+1/64-1/64
XCTAssert(events[2].length == Storage::Time(5, 128), @"Should still be 33 total events"); // 1/64 = 2/128 to exit the patch, plus 3/128 to get to the next event, having spliced in 1/128 ahead of the normal clock
XCTAssert(total_length == Storage::Time(1), @"Total track length should still be 1");
}
- (void)testLeftReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(0)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:0 ofLength:Storage::Time(33, 64)];
}
- (void)testRightReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(1, 2)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:16 ofLength:Storage::Time(33, 64)];
}
#pragma mark - Insertions affecting three existing segments
- (void)testMultiSegmentTrack
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:33 withEntry:4 ofLength:Storage::Time(1, 32)];
}
- (void)testMultiTrimBothSideReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 16), 8, {0x00}) atTime:Storage::Time(1, 8)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:17 withEntry:4 ofLength:Storage::Time(17, 32)];
}
- (void)testMultiTrimRightReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 8)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:4 ofLength:Storage::Time(13, 32)];
}
- (void)testMultiTrimLeftReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 4)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:8 ofLength:Storage::Time(13, 32)];
}
#pragma mark - Insertions affecting two existing segments
- (void)testTwoSegmentOverlap
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(1, 32), 8, {0x00}) atTime:Storage::Time(1, 8)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:25 withEntry:4 ofLength:Storage::Time(9, 32)];
}
- (void)testTwoSegmentRightReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(1, 8)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:4 ofLength:Storage::Time(13, 32)];
}
- (void)testTwoSegmentLeftReplace
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.fourSegmentPatchedTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(3, 8), 1, {0x00}) atTime:Storage::Time(0)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:21 withEntry:0 ofLength:Storage::Time(25, 64)];
}
#pragma mark - Wrapping segment
- (void)testWrappingSegment
{
std::shared_ptr<Storage::Disk::Track> patchableTrack = self.patchableTogglingTrack;
[self patchTrack:patchableTrack withSegment:Storage::Disk::PCMSegment(Storage::Time(5, 2), 1, {0x00}) atTime:Storage::Time(0)];
[self assertEvents:[self eventsFromTrack:patchableTrack] hasEntries:1 withEntry:0 ofLength:Storage::Time(1, 1)];
}
@end

View File

@@ -0,0 +1,113 @@
//
// PCMSegmentEventSourceTests.m
// Clock Signal
//
// Created by Thomas Harte on 17/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "PCMSegment.hpp"
@interface PCMSegmentEventSourceTests : XCTestCase
@end
@implementation PCMSegmentEventSourceTests
- (Storage::Disk::PCMSegmentEventSource)segmentSource
{
Storage::Disk::PCMSegment alternatingFFs;
alternatingFFs.data = {0xff, 0x00, 0xff, 0x00};
alternatingFFs.length_of_a_bit.length = 1;
alternatingFFs.length_of_a_bit.clock_rate = 10;
alternatingFFs.number_of_bits = 32;
return Storage::Disk::PCMSegmentEventSource(alternatingFFs);
}
- (void)testCentring
{
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
[self assertFirstTwoEventLengthsForSource:segmentSource];
}
- (void)assertFirstTwoEventLengthsForSource:(Storage::Disk::PCMSegmentEventSource &)segmentSource
{
Storage::Disk::Track::Event first_event = segmentSource.get_next_event();
Storage::Disk::Track::Event second_event = segmentSource.get_next_event();
first_event.length.simplify();
second_event.length.simplify();
XCTAssertTrue(first_event.length.length == 1 && first_event.length.clock_rate == 20, @"First event should occur half a bit's length in");
XCTAssertTrue(second_event.length.length == 1 && second_event.length.clock_rate == 10, @"Second event should occur a whole bit's length after the first");
}
- (void)testLongerGap
{
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
// skip first eight flux transitions
for(int c = 0; c < 8; c++) segmentSource.get_next_event();
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
next_event.length.simplify();
XCTAssertTrue(next_event.length.length == 9 && next_event.length.clock_rate == 10, @"Zero byte should give a nine bit length event gap");
}
- (void)testTermination
{
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
Storage::Time total_time;
for(int c = 0; c < 16; c++) total_time += segmentSource.get_next_event().length;
Storage::Disk::Track::Event final_event = segmentSource.get_next_event();
total_time += final_event.length;
total_time.simplify();
XCTAssertTrue(final_event.type == Storage::Disk::Track::Event::IndexHole, @"Segment should end with an index hole");
XCTAssertTrue(total_time.length == 16 && total_time.clock_rate == 5, @"Should have taken 32 bit lengths to finish the segment");
}
- (void)testReset
{
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
for(int c = 0; c < 8; c++) segmentSource.get_next_event();
segmentSource.reset();
[self assertFirstTwoEventLengthsForSource:segmentSource];
}
- (void)testSeekToSecondBit
{
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
Storage::Time target_time(1, 10);
Storage::Time found_time = segmentSource.seek_to(target_time);
found_time.simplify();
XCTAssertTrue(found_time.length == 1 && found_time.clock_rate == 20, @"A request to seek to 1/10th should have seeked to 1/20th");
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
next_event.length.simplify();
XCTAssertTrue(next_event.length.length == 1 && next_event.length.clock_rate == 10, @"Next event should be 1/10th later");
}
- (void)testSeekBeyondFinalBit
{
Storage::Disk::PCMSegmentEventSource segmentSource = self.segmentSource;
Storage::Time target_time(24, 10);
Storage::Time found_time = segmentSource.seek_to(target_time);
found_time.simplify();
XCTAssertTrue(found_time.length == 47 && found_time.clock_rate == 20, @"A request to seek to 24/10ths should have seeked to 47/20ths");
Storage::Disk::Track::Event next_event = segmentSource.get_next_event();
next_event.length.simplify();
XCTAssertTrue(next_event.length.length == 17 && next_event.length.clock_rate == 20, @"Next event should be 17/20ths later");
XCTAssertTrue(next_event.type == Storage::Disk::Track::Event::IndexHole, @"End should have been reached");
}
@end

View File

@@ -0,0 +1,54 @@
//
// PCMTrackTests.m
// Clock Signal
//
// Created by Thomas Harte on 18/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "PCMTrack.hpp"
@interface PCMTrackTests : XCTestCase
@end
@implementation PCMTrackTests
- (Storage::Disk::PCMTrack)multiSpeedTrack
{
Storage::Disk::PCMSegment quickSegment, slowSegment;
quickSegment.data = {0xff};
quickSegment.number_of_bits = 8;
quickSegment.length_of_a_bit.length = 1;
quickSegment.length_of_a_bit.clock_rate = 100;
slowSegment.data = {0xff};
slowSegment.number_of_bits = 8;
slowSegment.length_of_a_bit.length = 1;
slowSegment.length_of_a_bit.clock_rate = 3;
return Storage::Disk::PCMTrack({quickSegment, slowSegment});
}
- (void)testMultispeedTrack
{
Storage::Disk::PCMTrack track = self.multiSpeedTrack;
std::vector<Storage::Disk::Track::Event> events;
Storage::Time total_length;
do {
events.push_back(track.get_next_event());
total_length += events.back().length;
} while(events.back().type != Storage::Disk::Track::Event::IndexHole);
XCTAssert(events.size() == 17, "Should have received 17 events; got %lu", events.size());
total_length.simplify();
XCTAssert(total_length.length == 1 && total_length.clock_rate == 1, "Events should have summed to a total time of 1; instead got %u/%u", total_length.length, total_length.clock_rate);
Storage::Time transition_length = events[0].length + events.back().length;
XCTAssert(events[8].length == transition_length, "Time taken in transition between speed zones should be half of a bit length in the first part plus half of a bit length in the second");
}
@end

View File

@@ -0,0 +1,119 @@
//
// TIATests.m
// Clock Signal
//
// Created by Thomas Harte on 12/02/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "TIA.hpp"
static uint8_t *line;
static void receive_line(uint8_t *next_line)
{
line = next_line;
}
@interface TIATests : XCTestCase
@end
@implementation TIATests {
std::unique_ptr<Atari2600::TIA> _tia;
}
- (void)setUp
{
[super setUp];
std::function<void(uint8_t *)> function = receive_line;
_tia.reset(new Atari2600::TIA(function));
line = nullptr;
_tia->set_playfield(0, 0x00);
_tia->set_playfield(1, 0x00);
_tia->set_playfield(2, 0x00);
_tia->set_player_graphic(0, 0x00);
_tia->set_player_graphic(1, 0x00);
_tia->set_ball_enable(false);
_tia->set_missile_enable(0, false);
_tia->set_missile_enable(1, false);
}
- (void)testReflectedPlayfield
{
// set reflected, bit pattern 1000
_tia->set_playfield_control_and_ball_size(1);
_tia->set_playfield(0, 0x10);
_tia->set_playfield(1, 0xf0);
_tia->set_playfield(2, 0x0e);
_tia->run_for_cycles(228);
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
uint8_t expected_line[] = {
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1
};
XCTAssert(!memcmp(expected_line, line, sizeof(expected_line)));
}
- (void)testRepeatedPlayfield
{
// set reflected, bit pattern 1000
_tia->set_playfield_control_and_ball_size(0);
_tia->set_playfield(0, 0x10);
_tia->set_playfield(1, 0xf0);
_tia->set_playfield(2, 0x0e);
_tia->run_for_cycles(228);
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
uint8_t expected_line[] = {
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
XCTAssert(!memcmp(expected_line, line, sizeof(expected_line)));
}
- (void)testSinglePlayer
{
// set a player graphic, reset position so that it'll appear from column 1
_tia->set_player_graphic(0, 0xff);
_tia->set_player_position(0);
_tia->run_for_cycles(228);
uint8_t first_expected_line[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line)));
line = nullptr;
_tia->run_for_cycles(228);
uint8_t second_expected_line[] = {
0, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
XCTAssert(line != nullptr, @"228 cycles should have ended the line");
XCTAssert(!memcmp(second_expected_line, line, sizeof(second_expected_line)));
}
@end

View File

@@ -0,0 +1,44 @@
//
// TimeTests.m
// Clock Signal
//
// Created by Thomas Harte on 24/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "Storage.hpp"
@interface TimeTests : XCTestCase
@end
@implementation TimeTests
- (void)testHalf
{
Storage::Time half(0.5f);
XCTAssert(half == Storage::Time(1, 2), @"0.5 should be converted to 1/2");
}
- (void)testTwenty
{
Storage::Time twenty(20.0f);
XCTAssert(twenty == Storage::Time(20, 1), @"20.0 should be converted to 20/1");
}
- (void)testTooSmallFloat
{
float original = 1.0f / powf(2.0f, 25.0f);
Storage::Time time(original);
XCTAssert(time == Storage::Time(0), @"Numbers too small to be represented should be 0");
}
- (void)testTooBigFloat
{
float original = powf(2.0f, 48.0f);
Storage::Time time(original);
XCTAssert(time == Storage::Time::max(), @"Numbers too big to be represented should saturate");
}
@end

View File

@@ -14,9 +14,8 @@
using namespace Outputs::CRT;
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
{
_openGL_output_builder->set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate) {
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
const unsigned int syncCapacityLineChargeThreshold = 2;
const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234
@@ -28,71 +27,78 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
// a TV picture tube or camera tube to the starting point of a line or field. It is about 7 µs
// for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV."
_time_multiplier = IntermediateBufferWidth / cycles_per_line;
time_multiplier_ = IntermediateBufferWidth / cycles_per_line;
phase_denominator_ = cycles_per_line * colour_cycle_denominator * time_multiplier_;
phase_numerator_ = 0;
colour_cycle_numerator_ = colour_cycle_numerator;
phase_alternates_ = should_alternate;
is_alernate_line_ &= phase_alternates_;
cycles_per_line_ = cycles_per_line;
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
// store fundamental display configuration properties
_height_of_display = height_of_display;
_cycles_per_line = cycles_per_line * _time_multiplier;
// generate timing values implied by the given arbuments
_sync_capacitor_charge_threshold = (int)(syncCapacityLineChargeThreshold * _cycles_per_line);
// generate timing values implied by the given arguments
sync_capacitor_charge_threshold_ = ((int)(syncCapacityLineChargeThreshold * cycles_per_line) * 3) / 4;
// create the two flywheels
_horizontal_flywheel.reset(new Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6, _cycles_per_line >> 6));
_vertical_flywheel.reset(new Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line, (_cycles_per_line * height_of_display) >> 3));
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6));
vertical_flywheel_.reset(new Flywheel(multiplied_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * multiplied_cycles_per_line, (multiplied_cycles_per_line * height_of_display) >> 3));
// figure out the divisor necessary to get the horizontal flywheel into a 16-bit range
unsigned int real_clock_scan_period = (_cycles_per_line * height_of_display) / (_time_multiplier * _common_output_divisor);
_vertical_flywheel_output_divider = (uint16_t)(ceilf(real_clock_scan_period / 65536.0f) * (_time_multiplier * _common_output_divisor));
unsigned int real_clock_scan_period = (multiplied_cycles_per_line * height_of_display) / (time_multiplier_ * common_output_divisor_);
vertical_flywheel_output_divider_ = (uint16_t)(ceilf(real_clock_scan_period / 65536.0f) * (time_multiplier_ * common_output_divisor_));
_openGL_output_builder->set_timing(cycles_per_line, _cycles_per_line, _height_of_display, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period(), _vertical_flywheel_output_divider);
openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_);
}
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType)
{
switch(displayType)
{
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
switch(displayType) {
case DisplayType::PAL50:
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500); // i.e. 283.7516
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, true); // i.e. 283.7516
break;
case DisplayType::NTSC60:
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2);
set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 455, 2, false); // i.e. 227.5
break;
}
}
CRT::CRT(unsigned int common_output_divisor) :
_sync_capacitor_charge_level(0),
_is_receiving_sync(false),
_sync_period(0),
_common_output_divisor(common_output_divisor),
_is_writing_composite_run(false),
_delegate(nullptr),
_frames_since_last_delegate_call(0) {}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) : CRT(common_output_divisor)
{
_openGL_output_builder.reset(new OpenGLOutputBuilder(buffer_depth));
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator);
void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_first_sample) {
if(type == DiscreteFourSamplesPerCycle) {
colour_burst_phase_adjustment_ = (uint8_t)(offset_of_first_sample * 256.0f) & 63;
} else {
colour_burst_phase_adjustment_ = 0xff;
}
}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : CRT(common_output_divisor)
{
_openGL_output_builder.reset(new OpenGLOutputBuilder(buffer_depth));
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
sync_capacitor_charge_level_(0),
is_receiving_sync_(false),
sync_period_(0),
common_output_divisor_(common_output_divisor),
is_writing_composite_run_(false),
delegate_(nullptr),
frames_since_last_delegate_call_(0),
openGL_output_builder_(buffer_depth),
is_alernate_line_(false) {}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth) :
CRT(common_output_divisor, buffer_depth) {
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, should_alternate);
}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
CRT(common_output_divisor, buffer_depth) {
set_new_display_type(cycles_per_line, displayType);
}
#pragma mark - Sync loop
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
return _vertical_flywheel->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
}
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
return _horizontal_flywheel->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
}
#define output_x1() (*(uint16_t *)&next_run[OutputVertexOffsetOfHorizontal + 0])
@@ -108,11 +114,10 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
#define source_output_position_x2() (*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2])
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2]
#define source_phase_time() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1]
void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y)
{
number_of_cycles *= _time_multiplier;
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type) {
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
number_of_cycles *= time_multiplier_;
bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data));
@@ -125,51 +130,35 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
// get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so
// set it to false for the next run through this loop (if any)
unsigned int next_run_length = std::min(time_until_vertical_sync_event, time_until_horizontal_sync_event);
phase_numerator_ += next_run_length * colour_cycle_numerator_;
phase_numerator_ %= phase_denominator_;
hsync_requested = false;
vsync_requested = false;
bool is_output_segment = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace());
bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
uint8_t *next_run = nullptr;
if(is_output_segment && !_openGL_output_builder->composite_output_buffer_is_full())
{
next_run = _openGL_output_builder->get_next_source_run();
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) {
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
}
if(next_run)
{
source_input_position_x1() = tex_x;
source_input_position_y() = tex_y;
source_output_position_x1() = (uint16_t)_horizontal_flywheel->get_current_output_position();
source_output_position_y() = _openGL_output_builder->get_composite_output_y();
source_phase() = _colour_burst_phase;
source_amplitude() = _colour_burst_amplitude;
source_phase_time() = (uint8_t)_colour_burst_time; // assumption: burst was within the first 1/16 of the line
if(next_run) {
// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
source_phase() = colour_burst_phase_;
source_amplitude() = colour_burst_amplitude_;
}
// decrement the number of cycles left to run for and increment the
// horizontal counter appropriately
number_of_cycles -= next_run_length;
// either charge or deplete the vertical retrace capacitor (making sure it stops at 0)
if(vsync_charging)
_sync_capacitor_charge_level += next_run_length;
else
_sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0);
// react to the incoming event...
_horizontal_flywheel->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
_vertical_flywheel->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
if(next_run)
{
// if this is a data run then advance the buffer pointer
if(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider);
source_input_position_x2() = tex_x;
source_output_position_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position();
_openGL_output_builder->complete_source_run();
if(next_run) {
source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
// if this is horizontal retrace then advance the output line counter and bookend an output run
@@ -177,53 +166,64 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event;
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event;
bool needs_endpoint =
(honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) ||
(honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace());
(honoured_event == Flywheel::SyncEvent::StartRetrace && is_writing_composite_run_) ||
(honoured_event == Flywheel::SyncEvent::EndRetrace && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
if(needs_endpoint)
{
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) is_alernate_line_ ^= phase_alternates_;
if(needs_endpoint) {
if(
_openGL_output_builder->composite_output_run_has_room_for_vertex() &&
!_openGL_output_builder->composite_output_buffer_is_full())
{
if(!_is_writing_composite_run)
{
_output_run.x1 = (uint16_t)_horizontal_flywheel->get_current_output_position();
_output_run.y = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider);
_output_run.tex_y = _openGL_output_builder->get_composite_output_y();
!openGL_output_builder_.array_builder.is_full() &&
!openGL_output_builder_.composite_output_buffer_is_full()) {
if(!is_writing_composite_run_) {
output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position();
output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
} else {
// Get and write all those previously unwritten output ys
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
// Construct the output run
uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
if(next_run) {
output_x1() = output_run_.x1;
output_position_y() = output_run_.y;
output_tex_y() = output_y;
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
openGL_output_builder_.array_builder.flush(
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
openGL_output_builder_.texture_builder.flush(
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) {
for(size_t run = 0; run < number_of_write_areas; run++) {
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0] = write_areas[run].x;
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y;
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfEnds + 0] = write_areas[run].x + write_areas[run].length;
}
});
for(size_t position = 0; position < input_size; position += SourceVertexSize) {
(*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y;
}
});
colour_burst_amplitude_ = 0;
}
else
{
uint8_t *next_run = _openGL_output_builder->get_next_output_run();
output_x1() = _output_run.x1;
output_position_y() = _output_run.y;
output_tex_y() = _output_run.tex_y;
output_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position();
_openGL_output_builder->complete_output_run();
}
_is_writing_composite_run ^= true;
is_writing_composite_run_ ^= true;
}
}
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
{
_openGL_output_builder->increment_composite_output_y();
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) {
openGL_output_builder_.increment_composite_output_y();
}
// if this is vertical retrace then adcance a field
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace)
{
if(_delegate)
{
_frames_since_last_delegate_call++;
if(_frames_since_last_delegate_call == 20)
{
// Yuck: to deal with the permitted ability of the delegate to make CRT changes that require the lock to be
// asserted during the delegate call, temporarily release the lock. TODO: find a less blunt instrument.
_openGL_output_builder->unlock_output();
_delegate->crt_did_end_batch_of_frames(this, _frames_since_last_delegate_call, _vertical_flywheel->get_and_reset_number_of_surprises());
_openGL_output_builder->lock_output();
_frames_since_last_delegate_call = 0;
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) {
if(delegate_) {
frames_since_last_delegate_call_++;
if(frames_since_last_delegate_call_ == 20) {
output_lock.unlock();
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
output_lock.lock();
frames_since_last_delegate_call_ = 0;
}
}
}
@@ -247,89 +247,78 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
#pragma mark - stream feeding methods
void CRT::output_scan(const Scan *const scan)
{
void CRT::output_scan(const Scan *const scan) {
const bool this_is_sync = (scan->type == Scan::Type::Sync);
const bool is_trailing_edge = (_is_receiving_sync && !this_is_sync);
const bool is_leading_edge = (!_is_receiving_sync && this_is_sync);
_is_receiving_sync = this_is_sync;
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
is_receiving_sync_ = this_is_sync;
// Accumulate: (i) a total of the amount of time in sync; and (ii) the amount of time since sync.
if(this_is_sync) { cycles_of_sync_ += scan->number_of_cycles; cycles_since_sync_ = 0; }
else cycles_since_sync_ += scan->number_of_cycles;
bool vsync_requested = false;
// If it has been at least half a line since sync ended, then it is safe to decide whether what ended
// was vertical sync.
if(cycles_since_sync_ > (cycles_per_line_ >> 1)) {
// If it was vertical sync, set that flag. If it wasn't, clear the summed amount of sync to avoid
// a mistaken vertical sync due to an aggregate of horizontals.
vsync_requested = (cycles_of_sync_ > sync_capacitor_charge_threshold_);
if(vsync_requested || cycles_of_sync_ < (cycles_per_line_ >> 2))
cycles_of_sync_ = 0;
}
// This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not
// recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek
// the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal
// flywheel. I'm currently unclear whether this is an accurate solution to this problem.
const bool hsync_requested = is_leading_edge && !_vertical_flywheel->is_near_expected_sync();
const bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold);
const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync();
// simplified colour burst logic: if it's within the back porch we'll take it
if(scan->type == Scan::Type::ColourBurst)
{
if(_horizontal_flywheel->get_current_time() < (_horizontal_flywheel->get_standard_period() * 12) >> 6)
{
_colour_burst_time = (uint16_t)_horizontal_flywheel->get_current_time();
_colour_burst_phase = scan->phase;
_colour_burst_amplitude = scan->amplitude;
if(scan->type == Scan::Type::ColourBurst) {
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
colour_burst_phase_ = (position_phase + scan->phase) & 255;
colour_burst_amplitude_ = scan->amplitude;
if(colour_burst_phase_adjustment_ != 0xff)
colour_burst_phase_ = (colour_burst_phase_ & ~63) + colour_burst_phase_adjustment_;
}
}
// TODO: inspect raw data for potential colour burst if required
_sync_period = _is_receiving_sync ? (_sync_period + scan->number_of_cycles) : 0;
advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type, scan->tex_x, scan->tex_y);
sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0;
advance_cycles(scan->number_of_cycles, hsync_requested, vsync_requested, scan->type);
}
/*
These all merely channel into advance_cycles, supplying appropriate arguments
*/
void CRT::output_sync(unsigned int number_of_cycles)
{
_openGL_output_builder->lock_output();
void CRT::output_sync(unsigned int number_of_cycles) {
Scan scan{
.type = Scan::Type::Sync,
.number_of_cycles = number_of_cycles
};
output_scan(&scan);
_openGL_output_builder->unlock_output();
}
void CRT::output_blank(unsigned int number_of_cycles)
{
_openGL_output_builder->lock_output();
void CRT::output_blank(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::Blank,
.number_of_cycles = number_of_cycles
};
output_scan(&scan);
_openGL_output_builder->unlock_output();
}
void CRT::output_level(unsigned int number_of_cycles)
{
_openGL_output_builder->lock_output();
if(!_openGL_output_builder->input_buffer_is_full())
{
Scan scan {
.type = Scan::Type::Level,
.number_of_cycles = number_of_cycles,
.tex_x = _openGL_output_builder->get_last_write_x_posititon(),
.tex_y = _openGL_output_builder->get_last_write_y_posititon()
};
output_scan(&scan);
}
else
{
Scan scan {
.type = Scan::Type::Blank,
.number_of_cycles = number_of_cycles
};
output_scan(&scan);
}
_openGL_output_builder->unlock_output();
void CRT::output_level(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::Level,
.number_of_cycles = number_of_cycles,
};
output_scan(&scan);
}
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude)
{
_openGL_output_builder->lock_output();
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) {
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
@@ -337,44 +326,37 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
.amplitude = amplitude
};
output_scan(&scan);
_openGL_output_builder->unlock_output();
}
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider)
{
_openGL_output_builder->lock_output();
if(!_openGL_output_builder->input_buffer_is_full())
{
_openGL_output_builder->reduce_previous_allocation_to(number_of_cycles / source_divider);
Scan scan {
.type = Scan::Type::Data,
.number_of_cycles = number_of_cycles,
.tex_x = _openGL_output_builder->get_last_write_x_posititon(),
.tex_y = _openGL_output_builder->get_last_write_y_posititon(),
.source_divider = source_divider
};
output_scan(&scan);
}
else
{
Scan scan {
.type = Scan::Type::Blank,
.number_of_cycles = number_of_cycles
};
output_scan(&scan);
}
_openGL_output_builder->unlock_output();
void CRT::output_default_colour_burst(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
.phase = (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
.amplitude = 32
};
output_scan(&scan);
}
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio)
{
first_cycle_after_sync *= _time_multiplier;
number_of_cycles *= _time_multiplier;
number_of_lines++;
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) {
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
Scan scan {
.type = Scan::Type::Data,
.number_of_cycles = number_of_cycles,
};
output_scan(&scan);
}
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) {
first_cycle_after_sync *= time_multiplier_;
number_of_cycles *= time_multiplier_;
first_line_after_sync -= 2;
number_of_lines += 4;
// determine prima facie x extent
unsigned int horizontal_period = _horizontal_flywheel->get_standard_period();
unsigned int horizontal_scan_period = _horizontal_flywheel->get_scan_period();
unsigned int horizontal_period = horizontal_flywheel_->get_standard_period();
unsigned int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
unsigned int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
// make sure that the requested range is visible
@@ -385,8 +367,8 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
float width = (float)number_of_cycles / (float)horizontal_scan_period;
// determine prima facie y extent
unsigned int vertical_period = _vertical_flywheel->get_standard_period();
unsigned int vertical_scan_period = _vertical_flywheel->get_scan_period();
unsigned int vertical_period = vertical_flywheel_->get_standard_period();
unsigned int vertical_scan_period = vertical_flywheel_->get_scan_period();
unsigned int vertical_retrace_period = vertical_period - vertical_scan_period;
// make sure that the requested range is visible
@@ -401,13 +383,10 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
// adjust to ensure aspect ratio is correct
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);
float ideal_width = height * adjusted_aspect_ratio;
if(ideal_width > width)
{
if(ideal_width > width) {
start_x -= (ideal_width - width) * 0.5f;
width = ideal_width;
}
else
{
} else {
float ideal_height = width / adjusted_aspect_ratio;
start_y -= (ideal_height - height) * 0.5f;
height = ideal_height;

View File

@@ -14,6 +14,8 @@
#include "CRTTypes.hpp"
#include "Internals/Flywheel.hpp"
#include "Internals/CRTOpenGL.hpp"
#include "Internals/ArrayBuilder.hpp"
#include "Internals/TextureBuilder.hpp"
namespace Outputs {
namespace CRT {
@@ -27,38 +29,29 @@ class Delegate {
class CRT {
private:
CRT(unsigned int common_output_divisor);
CRT(unsigned int common_output_divisor, unsigned int buffer_depth);
// the incoming clock lengths will be multiplied by something to give at least 1000
// sample points per line
unsigned int _time_multiplier;
const unsigned int _common_output_divisor;
// fundamental creator-specified properties
unsigned int _cycles_per_line;
unsigned int _height_of_display;
unsigned int time_multiplier_;
const unsigned int common_output_divisor_;
// the two flywheels regulating scanning
std::unique_ptr<Flywheel> _horizontal_flywheel, _vertical_flywheel;
uint16_t _vertical_flywheel_output_divider;
std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_;
uint16_t vertical_flywheel_output_divider_;
// elements of sync separation
bool _is_receiving_sync; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
int _sync_capacitor_charge_level; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
int _sync_capacitor_charge_threshold; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
unsigned int _sync_period;
bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
int sync_capacitor_charge_level_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
unsigned int sync_period_;
// each call to output_* generates a scan. A two-slot queue for scans allows edge extensions.
struct Scan {
enum Type {
Sync, Level, Data, Blank, ColourBurst
} type;
unsigned int number_of_cycles;
union {
struct {
unsigned int source_divider;
uint16_t tex_x, tex_y;
};
struct {
uint8_t phase, amplitude;
};
@@ -66,29 +59,44 @@ class CRT {
};
void output_scan(const Scan *scan);
uint8_t _colour_burst_phase, _colour_burst_amplitude;
uint16_t _colour_burst_time;
bool _is_writing_composite_run;
uint8_t colour_burst_phase_, colour_burst_amplitude_, colour_burst_phase_adjustment_;
bool is_writing_composite_run_;
unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_;
bool is_alernate_line_, phase_alternates_;
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y);
void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type);
// the inner entry point that determines whether and when the next sync event will occur within
// the current output window
Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced);
Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced);
// OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here.
std::unique_ptr<OpenGLOutputBuilder> _openGL_output_builder;
// OpenGL state
OpenGLOutputBuilder openGL_output_builder_;
// temporary storage used during the construction of output runs
struct {
uint16_t x1, y, tex_y;
} _output_run;
uint16_t x1, y;
} output_run_;
// The delegate
Delegate *_delegate;
unsigned int _frames_since_last_delegate_call;
// the delegate
Delegate *delegate_;
unsigned int frames_since_last_delegate_call_;
// queued tasks for the OpenGL queue; performed before the next draw
std::mutex function_mutex_;
std::vector<std::function<void(void)>> enqueued_openGL_functions_;
inline void enqueue_openGL_function(const std::function<void(void)> &function) {
std::lock_guard<std::mutex> function_guard(function_mutex_);
enqueued_openGL_functions_.push_back(function);
}
// sync counter, for determining vertical sync
unsigned int cycles_of_sync_;
unsigned int cycles_since_sync_;
unsigned int cycles_per_line_;
public:
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
@@ -97,7 +105,7 @@ class CRT {
@param cycles_per_line The clock rate at which this CRT will be driven, specified as the number
of cycles expected to take up one whole scanline of the display.
@param common_output_divisor The greatest a priori common divisor of all cycle counts that will be
supplied to @c output_sync, @c output_data, etc; supply 1 if no greater divisor is known. For many
machines output will run at a fixed multiple of the clock rate; knowing this divisor can improve
@@ -119,7 +127,7 @@ class CRT {
@see @c set_rgb_sampling_function , @c set_composite_sampling_function
*/
CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth);
CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate, unsigned int buffer_depth);
/*! Constructs the CRT with the specified clock rate, with the display height and colour
subcarrier frequency dictated by a standard display type and with the requested number of
@@ -132,7 +140,7 @@ class CRT {
/*! Resets the CRT with new timing information. The CRT then continues as though the new timing had
been provided at construction. */
void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator);
void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, bool should_alternate);
/*! Resets the CRT with new timing information derived from a new display type. The CRT then continues
as though the new timing had been provided at construction. */
@@ -181,6 +189,12 @@ class CRT {
*/
void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude);
/*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude.
@param number_of_cycles The length of the colour burst;
*/
void output_default_colour_burst(unsigned int number_of_cycles);
/*! Attempts to allocate the given number of output samples for writing.
The beginning of the most recently allocated area is used as the start
@@ -192,17 +206,24 @@ class CRT {
@param required_length The number of samples to allocate.
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
*/
inline uint8_t *allocate_write_area(size_t required_length)
{
return _openGL_output_builder->allocate_write_area(required_length);
inline uint8_t *allocate_write_area(size_t required_length) {
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
return openGL_output_builder_.texture_builder.allocate_write_area(required_length);
}
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call.
*/
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
_openGL_output_builder->draw_frame(output_width, output_height, only_if_dirty);
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
{
std::lock_guard<std::mutex> function_guard(function_mutex_);
for(std::function<void(void)> function : enqueued_openGL_functions_)
{
function();
}
enqueued_openGL_functions_.clear();
}
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
}
/*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than
@@ -212,9 +233,10 @@ class CRT {
currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If
@c false then the references are simply marked as invalid.
*/
inline void set_openGL_context_will_change(bool should_delete_resources)
{
_openGL_output_builder->set_openGL_context_will_change(should_delete_resources);
inline void set_openGL_context_will_change(bool should_delete_resources) {
enqueue_openGL_function([should_delete_resources, this] {
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
});
}
/*! Sets a function that will map from whatever data the machine provided to a composite signal.
@@ -224,11 +246,32 @@ class CRT {
that evaluates to the composite signal level as a function of a source buffer, sampling location, colour
carrier phase and amplitude.
*/
inline void set_composite_sampling_function(const char *shader)
{
_openGL_output_builder->set_composite_sampling_function(shader);
inline void set_composite_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_composite_sampling_function(shader);
});
}
enum CompositeSourceType {
/// The composite function provides continuous output.
Continuous,
/// The composite function provides discrete output with four unique values per colour cycle.
DiscreteFourSamplesPerCycle
};
/*! Provides information about the type of output the composite sampling function provides — discrete or continuous.
This is necessary because the CRT implementation samples discretely and therefore can use fewer intermediate
samples if it can exactly duplicate the sampling rate and placement of the composite sampling function.
A continuous function is assumed by default.
@param type The type of output provided by the function supplied to `set_composite_sampling_function`.
@param offset_of_first_sample The relative position within a full cycle of the colour subcarrier at which the
first sample falls. E.g. 0.125 means "at 1/8th of the way through the complete cycle".
*/
void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f);
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
If the output mode is composite then a default mapping from RGB to the display's composite
@@ -242,26 +285,28 @@ class CRT {
* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and
* `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking.
*/
inline void set_rgb_sampling_function(const char *shader)
{
_openGL_output_builder->set_rgb_sampling_function(shader);
inline void set_rgb_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_rgb_sampling_function(shader);
});
}
inline void set_output_device(OutputDevice output_device)
{
_openGL_output_builder->set_output_device(output_device);
inline void set_output_device(OutputDevice output_device) {
enqueue_openGL_function([output_device, this] {
openGL_output_builder_.set_output_device(output_device);
});
}
inline void set_visible_area(Rect visible_area)
{
_openGL_output_builder->set_visible_area(visible_area);
inline void set_visible_area(Rect visible_area) {
enqueue_openGL_function([visible_area, this] {
openGL_output_builder_.set_visible_area(visible_area);
});
}
Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio);
inline void set_delegate(Delegate *delegate)
{
_delegate = delegate;
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
};

View File

@@ -0,0 +1,145 @@
//
// ArrayBuilder.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "ArrayBuilder.hpp"
using namespace Outputs::CRT;
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
output_(output_size, nullptr),
input_(input_size, nullptr) {}
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
output_(output_size, submission_function),
input_(input_size, submission_function) {}
bool ArrayBuilder::is_full() {
bool was_full;
was_full = is_full_;
return was_full;
}
uint8_t *ArrayBuilder::get_input_storage(size_t size) {
return get_storage(size, input_);
}
uint8_t *ArrayBuilder::get_output_storage(size_t size) {
return get_storage(size, output_);
}
void ArrayBuilder::flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &function) {
if(!is_full_) {
size_t input_size = 0, output_size = 0;
uint8_t *input = input_.get_unflushed(input_size);
uint8_t *output = output_.get_unflushed(output_size);
function(input, input_size, output, output_size);
input_.flush();
output_.flush();
}
}
void ArrayBuilder::bind_input() {
input_.bind();
}
void ArrayBuilder::bind_output() {
output_.bind();
}
ArrayBuilder::Submission ArrayBuilder::submit() {
ArrayBuilder::Submission submission;
submission.input_size = input_.submit(true);
submission.output_size = output_.submit(false);
if(is_full_) {
is_full_ = false;
input_.reset();
output_.reset();
}
return submission;
}
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
is_full(false),
submission_function_(submission_function),
allocated_data(0), flushed_data(0), submitted_data(0) {
if(!submission_function_) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW);
}
data.resize(size);
}
ArrayBuilder::Buffer::~Buffer() {
if(!submission_function_)
glDeleteBuffers(1, &buffer);
}
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) {
uint8_t *pointer = buffer.get_storage(size);
if(!pointer) is_full_ = true;
return pointer;
}
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) {
if(is_full || allocated_data + size > data.size()) {
is_full = true;
return nullptr;
}
uint8_t *pointer = &data[allocated_data];
allocated_data += size;
return pointer;
}
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) {
if(is_full) {
return nullptr;
}
size = allocated_data - flushed_data;
return &data[flushed_data];
}
void ArrayBuilder::Buffer::flush() {
if(submitted_data) {
memmove(data.data(), &data[submitted_data], allocated_data - submitted_data);
allocated_data -= submitted_data;
flushed_data -= submitted_data;
submitted_data = 0;
}
flushed_data = allocated_data;
}
size_t ArrayBuilder::Buffer::submit(bool is_input) {
size_t length = flushed_data;
if(submission_function_) {
submission_function_(is_input, data.data(), length);
} else {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
memcpy(destination, data.data(), length);
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length);
glUnmapBuffer(GL_ARRAY_BUFFER);
}
submitted_data = flushed_data;
return length;
}
void ArrayBuilder::Buffer::bind() {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
}
void ArrayBuilder::Buffer::reset() {
is_full = false;
allocated_data = 0;
flushed_data = 0;
submitted_data = 0;
}

View File

@@ -0,0 +1,101 @@
//
// ArrayBuilder.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef ArrayBuilder_hpp
#define ArrayBuilder_hpp
#include <functional>
#include <memory>
#include <vector>
#include "OpenGL.hpp"
namespace Outputs {
namespace CRT {
/*!
Owns two array buffers, an 'input' and an 'output' and vends pointers to allow an owner to write provisional data into those
plus a flush function to lock provisional data into place. Also supplies a submit method to transfer all currently locked
data to the GPU and bind_input/output methods to bind the internal buffers.
It is safe for one thread to communicate via the get_*_storage and flush inputs asynchronously from another that is making
use of the bind and submit outputs.
*/
class ArrayBuilder {
public:
/// Creates an instance of ArrayBuilder with @c output_size bytes of storage for the output buffer and
/// @c input_size bytes of storage for the input buffer.
ArrayBuilder(size_t input_size, size_t output_size);
/// Creates an instance of ArrayBuilder with @c output_size bytes of storage for the output buffer and
/// @c input_size bytes of storage for the input buffer that, rather than using OpenGL, will submit data
/// to the @c submission_function. [Teleological: this is provided as a testing hook.]
ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function);
/// Attempts to add @c size bytes to the input set.
/// @returns a pointer to the allocated area if allocation was possible; @c nullptr otherwise.
uint8_t *get_input_storage(size_t size);
/// Attempts to add @c size bytes to the output set.
/// @returns a pointer to the allocated area if allocation was possible; @c nullptr otherwise.
uint8_t *get_output_storage(size_t size);
/// @returns @c true if either of the input or output storage areas is currently exhausted; @c false otherwise.
bool is_full();
/// If neither input nor output was exhausted since the last flush, atomically commits both input and output
/// up to the currently allocated size for use upon the next @c submit, giving the supplied function a
/// chance to perform last-minute processing. Otherwise acts as a no-op.
void flush(const std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)> &);
/// Binds the input array to GL_ARRAY_BUFFER.
void bind_input();
/// Binds the output array to GL_ARRAY_BUFFER.
void bind_output();
struct Submission {
size_t input_size, output_size;
};
/// Submits all flushed input and output data to the corresponding arrays.
/// @returns A @c Submission record, indicating how much data of each type was submitted.
Submission submit();
private:
class Buffer {
public:
Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function);
~Buffer();
uint8_t *get_storage(size_t size);
uint8_t *get_unflushed(size_t &size);
void flush();
size_t submit(bool is_input);
void bind();
void reset();
private:
bool is_full;
GLuint buffer;
std::function<void(bool is_input, uint8_t *, size_t)> submission_function_;
std::vector<uint8_t> data;
size_t allocated_data;
size_t flushed_data;
size_t submitted_data;
} output_, input_;
uint8_t *get_storage(size_t size, Buffer &buffer);
bool is_full_;
};
}
}
#endif /* ArrayBuilder_hpp */

View File

@@ -37,7 +37,7 @@ const GLsizei InputBufferBuilderHeight = 512;
// This is the size of the intermediate buffers used during composite to RGB conversion
const GLsizei IntermediateBufferWidth = 2048;
const GLsizei IntermediateBufferHeight = 1024;
const GLsizei IntermediateBufferHeight = 512;
// Some internal buffer sizes
const GLsizeiptr OutputVertexBufferDataSize = OutputVertexSize * IntermediateBufferHeight; // i.e. the maximum number of scans of output that can be created between draws

View File

@@ -1,110 +0,0 @@
//
// CRTInputBufferBuilder.cpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CRTInputBufferBuilder.hpp"
#include "CRTOpenGL.hpp"
#include <string.h>
using namespace Outputs::CRT;
CRTInputBufferBuilder::CRTInputBufferBuilder(size_t bytes_per_pixel) :
_bytes_per_pixel(bytes_per_pixel),
_next_write_x_position(0),
_next_write_y_position(0),
_image(new uint8_t[bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight])
{}
void CRTInputBufferBuilder::allocate_write_area(size_t required_length)
{
if(_next_write_y_position != InputBufferBuilderHeight)
{
_last_allocation_amount = required_length;
if(_next_write_x_position + required_length + 2 > InputBufferBuilderWidth)
{
_next_write_x_position = 0;
_next_write_y_position++;
if(_next_write_y_position == InputBufferBuilderHeight)
return;
}
_write_x_position = _next_write_x_position + 1;
_write_y_position = _next_write_y_position;
_write_target_pointer = (_write_y_position * InputBufferBuilderWidth) + _write_x_position;
_next_write_x_position += required_length + 2;
}
}
bool CRTInputBufferBuilder::is_full()
{
return (_next_write_y_position == InputBufferBuilderHeight);
}
void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length)
{
if(_next_write_y_position == InputBufferBuilderHeight) return;
uint8_t *const image_pointer = _image.get();
// correct if the writing cursor was reset while a client was writing
if(_next_write_x_position == 0 && _next_write_y_position == 0)
{
memmove(&image_pointer[_bytes_per_pixel], &image_pointer[_write_target_pointer * _bytes_per_pixel], actual_length * _bytes_per_pixel);
_write_target_pointer = 1;
_last_allocation_amount = actual_length;
_next_write_x_position = (uint16_t)(actual_length + 2);
_write_x_position = 1;
_write_y_position = 0;
}
// book end the allocation with duplicates of the first and last pixel, to protect
// against rounding errors when this run is drawn
memcpy( &image_pointer[(_write_target_pointer - 1) * _bytes_per_pixel],
&image_pointer[_write_target_pointer * _bytes_per_pixel],
_bytes_per_pixel);
memcpy( &image_pointer[(_write_target_pointer + actual_length) * _bytes_per_pixel],
&image_pointer[(_write_target_pointer + actual_length - 1) * _bytes_per_pixel],
_bytes_per_pixel);
// return any allocated length that wasn't actually used to the available pool
_next_write_x_position -= (_last_allocation_amount - actual_length);
}
uint8_t *CRTInputBufferBuilder::get_image_pointer()
{
return _image.get();
}
uint16_t CRTInputBufferBuilder::get_and_finalise_current_line()
{
uint16_t result = _write_y_position + (_next_write_x_position ? 1 : 0);
_next_write_x_position = _next_write_y_position = 0;
return result;
}
uint8_t *CRTInputBufferBuilder::get_write_target()
{
return (_next_write_y_position == InputBufferBuilderHeight) ? nullptr : &_image.get()[_write_target_pointer * _bytes_per_pixel];
}
uint16_t CRTInputBufferBuilder::get_last_write_x_position()
{
return _write_x_position;
}
uint16_t CRTInputBufferBuilder::get_last_write_y_position()
{
return _write_y_position;
}
size_t CRTInputBufferBuilder::get_bytes_per_pixel()
{
return _bytes_per_pixel;
}

View File

@@ -1,62 +0,0 @@
//
// CRTInputBufferBuilder.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef CRTInputBufferBuilder_hpp
#define CRTInputBufferBuilder_hpp
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
#include "CRTConstants.hpp"
#include "OpenGL.hpp"
#include <memory>
namespace Outputs {
namespace CRT {
struct CRTInputBufferBuilder {
CRTInputBufferBuilder(size_t bytes_per_pixel);
void allocate_write_area(size_t required_length);
void reduce_previous_allocation_to(size_t actual_length);
uint16_t get_and_finalise_current_line();
uint8_t *get_image_pointer();
uint8_t *get_write_target();
uint16_t get_last_write_x_position();
uint16_t get_last_write_y_position();
size_t get_bytes_per_pixel();
bool is_full();
private:
// where pixel data will be put to the next time a write is requested
uint16_t _next_write_x_position, _next_write_y_position;
// the most recent position returned for pixel data writing
uint16_t _write_x_position, _write_y_position;
// details of the most recent allocation
size_t _write_target_pointer;
size_t _last_allocation_amount;
// the buffer size
size_t _bytes_per_pixel;
// the buffer
std::unique_ptr<uint8_t> _image;
};
}
}
#endif /* CRTInputBufferBuilder_hpp */

View File

@@ -6,179 +6,80 @@
//
#include "CRT.hpp"
#include <stdlib.h>
#include <math.h>
#include <cstdlib>
#include <cmath>
#include "CRTOpenGL.hpp"
#include "../../../SignalProcessing/FIRFilter.hpp"
#include "Shaders/OutputShader.hpp"
static const GLint internalFormatForDepth(size_t depth)
{
switch(depth)
{
default: return GL_FALSE;
case 1: return GL_R8UI;
case 2: return GL_RG8UI;
case 3: return GL_RGB8UI;
case 4: return GL_RGBA8UI;
}
}
static const GLenum formatForDepth(size_t depth)
{
switch(depth)
{
default: return GL_FALSE;
case 1: return GL_RED_INTEGER;
case 2: return GL_RG_INTEGER;
case 3: return GL_RGB_INTEGER;
case 4: return GL_RGBA_INTEGER;
}
}
struct Range {
GLsizei location, length;
};
static int getCircularRanges(GLsizei *start_pointer, GLsizei *end_pointer, GLsizei buffer_length, GLsizei granularity, GLsizei offset, Range *ranges)
{
GLsizei start = *start_pointer;
GLsizei end = *end_pointer;
*end_pointer %= buffer_length;
*start_pointer = *end_pointer;
start += offset;
end += offset;
start -= start%granularity;
end -= end%granularity;
GLsizei length = end - start;
if(!length) return 0;
if(length >= buffer_length)
{
ranges[0].location = 0;
ranges[0].length = buffer_length;
return 1;
}
else
{
ranges[0].location = start % buffer_length;
if(ranges[0].location + length <= buffer_length)
{
ranges[0].length = length;
return 1;
}
else
{
ranges[0].length = buffer_length - ranges[0].location;
ranges[1].location = 0;
ranges[1].length = length - ranges[0].length;
return 2;
}
}
}
static GLsizei submitArrayData(GLuint buffer, uint8_t *source, GLsizei *length_pointer)
{
GLsizei length = *length_pointer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
uint8_t *data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
memcpy(data, source, (size_t)length);
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, length);
glUnmapBuffer(GL_ARRAY_BUFFER);
*length_pointer = 0;
return length;
}
using namespace Outputs::CRT;
namespace {
static const GLenum composite_texture_unit = GL_TEXTURE0;
static const GLenum separated_texture_unit = GL_TEXTURE1;
static const GLenum filtered_y_texture_unit = GL_TEXTURE2;
static const GLenum filtered_texture_unit = GL_TEXTURE3;
static const GLenum source_data_texture_unit = GL_TEXTURE4;
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5;
static const GLenum source_data_texture_unit = GL_TEXTURE0;
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
static const GLenum composite_texture_unit = GL_TEXTURE2;
static const GLenum separated_texture_unit = GL_TEXTURE3;
static const GLenum filtered_texture_unit = GL_TEXTURE4;
static const GLenum work_texture_unit = GL_TEXTURE2;
}
OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) :
_output_mutex(new std::mutex),
_draw_mutex(new std::mutex),
_visible_area(Rect(0, 0, 1, 1)),
_composite_src_output_y(0),
_cleared_composite_output_y(0),
_composite_shader(nullptr),
_rgb_shader(nullptr),
_output_buffer_data(new uint8_t[OutputVertexBufferDataSize]),
_source_buffer_data(new uint8_t[SourceVertexBufferDataSize]),
_output_buffer_data_pointer(0),
_source_buffer_data_pointer(0),
_last_output_width(0),
_last_output_height(0),
_fence(nullptr)
{
_buffer_builder.reset(new CRTInputBufferBuilder(buffer_depth));
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
visible_area_(Rect(0, 0, 1, 1)),
composite_src_output_y_(0),
last_output_width_(0),
last_output_height_(0),
fence_(nullptr),
texture_builder(bytes_per_pixel, source_data_texture_unit),
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) {
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
// Create intermediate textures and bind to slots 0, 1 and 2
compositeTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit));
separatedTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit));
filteredYTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit));
filteredTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit));
// create the surce texture
glGenTextures(1, &textureName);
glActiveTexture(source_data_texture_unit);
glBindTexture(GL_TEXTURE_2D, textureName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(_buffer_builder->get_bytes_per_pixel()), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_buffer_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, nullptr);
// create the output vertex array
glGenVertexArrays(1, &output_vertex_array);
// create a buffer for output vertex attributes
glGenBuffers(1, &output_array_buffer);
glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer);
glBufferData(GL_ARRAY_BUFFER, OutputVertexBufferDataSize, NULL, GL_STREAM_DRAW);
glGenVertexArrays(1, &output_vertex_array_);
// create the source vertex array
glGenVertexArrays(1, &source_vertex_array);
glGenVertexArrays(1, &source_vertex_array_);
// create a buffer for source vertex attributes
glGenBuffers(1, &source_array_buffer);
glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer);
glBufferData(GL_ARRAY_BUFFER, SourceVertexBufferDataSize, NULL, GL_STREAM_DRAW);
bool supports_texture_barrier = false;
#ifdef GL_NV_texture_barrier
GLint number_of_extensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
for(GLuint c = 0; c < (GLuint)number_of_extensions; c++) {
const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c);
if(!strcmp(extension_name, "GL_NV_texture_barrier")) {
supports_texture_barrier = true;
}
}
#endif
// if(supports_texture_barrier) {
// work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit));
// } else {
composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST));
separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST));
filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR));
// }
}
OpenGLOutputBuilder::~OpenGLOutputBuilder()
{
glDeleteTextures(1, &textureName);
glDeleteBuffers(1, &output_array_buffer);
glDeleteBuffers(1, &source_array_buffer);
glDeleteVertexArrays(1, &output_vertex_array);
free(_composite_shader);
free(_rgb_shader);
OpenGLOutputBuilder::~OpenGLOutputBuilder() {
glDeleteVertexArrays(1, &output_vertex_array_);
}
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
bool OpenGLOutputBuilder::get_is_television_output() {
return output_device_ == Television || !rgb_input_shader_program_;
}
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
// lock down any other draw_frames
_draw_mutex->lock();
draw_mutex_.lock();
// establish essentials
if(!output_shader_program)
{
if(!output_shader_program_) {
prepare_composite_input_shaders();
prepare_rgb_input_shaders();
prepare_source_vertex_array();
@@ -190,298 +91,265 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
set_colour_space_uniforms();
}
if(_fence != nullptr)
{
if(fence_ != nullptr) {
// if the GPU is still busy, don't wait; we'll catch it next time
if(glClientWaitSync(_fence, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED)
{
_draw_mutex->unlock();
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) {
draw_mutex_.unlock();
return;
}
glDeleteSync(_fence);
glDeleteSync(fence_);
}
// make sure there's a target to draw to
if(!framebuffer || framebuffer->get_height() != output_height || framebuffer->get_width() != output_width)
{
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit));
if(framebuffer)
{
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) {
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR));
if(framebuffer_) {
new_framebuffer->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(pixel_accumulation_texture_unit);
framebuffer->bind_texture();
framebuffer->draw((float)output_width / (float)output_height);
framebuffer_->bind_texture();
framebuffer_->draw((float)output_width / (float)output_height);
new_framebuffer->bind_texture();
}
framebuffer = std::move(new_framebuffer);
framebuffer_ = std::move(new_framebuffer);
}
// lock out the machine emulation until data is copied
_output_mutex->lock();
output_mutex_.lock();
// release the mapping, giving up on trying to draw if data has been lost
GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer_data.get(), &_output_buffer_data_pointer);
ArrayBuilder::Submission array_submission = array_builder.submit();
// bind and flush the source array buffer
GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer_data.get(), &_source_buffer_data_pointer);
// upload new source pixels, if any
glActiveTexture(source_data_texture_unit);
texture_builder.submit();
// determine how many lines are newly reclaimed; they'll need to be cleared
Range clearing_zones[2];
// the clearing zones for the composite output Y are calculated with a fixed offset of '1' which has the effect of clearing
// one ahead of the expected drawing area this frame; that's because the current _composite_src_output_y may or may not have been
// written to during the last update, so we want it to have been cleared during the last update.
int number_of_clearing_zones = getCircularRanges(&_cleared_composite_output_y, &_composite_src_output_y, IntermediateBufferHeight, 1, 1, clearing_zones);
uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line();
// upload new source pixels
if(completed_texture_y)
{
glActiveTexture(source_data_texture_unit);
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, 0,
InputBufferBuilderWidth, completed_texture_y,
formatForDepth(_buffer_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE,
_buffer_builder->get_image_pointer());
}
// buffer usage restart from 0 for the next time around
composite_src_output_y_ = 0;
// data having been grabbed, allow the machine to continue
_output_mutex->unlock();
output_mutex_.unlock();
struct RenderStage {
OpenGL::TextureTarget *const target;
OpenGL::Shader *const shader;
OpenGL::TextureTarget *const target;
float clear_colour[3];
};
RenderStage composite_render_stages[] =
{
{compositeTexture.get(), composite_input_shader_program.get(), {0.0, 0.0, 0.0}},
{separatedTexture.get(), composite_separation_filter_program.get(), {0.0, 0.5, 0.5}},
{filteredYTexture.get(), composite_y_filter_shader_program.get(), {0.0, 0.5, 0.5}},
{filteredTexture.get(), composite_chrominance_filter_shader_program.get(), {0.0, 0.0, 0.0}},
// for composite video, go through four steps to get to something that can be painted to the output
RenderStage composite_render_stages[] = {
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr}
};
RenderStage rgb_render_stages[] =
{
{compositeTexture.get(), rgb_input_shader_program.get(), {0.0, 0.0, 0.0}},
{filteredTexture.get(), rgb_filter_shader_program.get(), {0.0, 0.0, 0.0}},
// for RGB video, there's only two steps
RenderStage rgb_render_stages[] = {
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr}
};
RenderStage *active_pipeline = (_output_device == Television || !rgb_input_shader_program) ? composite_render_stages : rgb_render_stages;
RenderStage *active_pipeline = get_is_television_output() ? composite_render_stages : rgb_render_stages;
// for television, update intermediate buffers and then draw; for a monitor, just draw
if(submitted_source_data)
{
if(array_submission.input_size || array_submission.output_size) {
// all drawing will be from the source vertex array and without blending
glBindVertexArray(source_vertex_array);
glBindVertexArray(source_vertex_array_);
glDisable(GL_BLEND);
while(active_pipeline->target)
{
// switch to the initial texture
active_pipeline->target->bind_framebuffer();
#ifdef GL_NV_texture_barrier
if(work_texture_) {
work_texture_->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
}
#endif
while(active_pipeline->shader) {
// switch to the framebuffer and shader associated with this stage
active_pipeline->shader->bind();
// clear as desired
if(number_of_clearing_zones)
{
glEnable(GL_SCISSOR_TEST);
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0);
for(int c = 0; c < number_of_clearing_zones; c++)
{
glScissor(0, clearing_zones[c].location, IntermediateBufferWidth, clearing_zones[c].length);
if(!work_texture_) {
active_pipeline->target->bind_framebuffer();
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
// those portions for which no input was provided
// if(!active_pipeline[1].shader) {
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
// }
}
// draw as desired
glDrawArraysInstanced(GL_LINES, 0, 2, submitted_source_data / SourceVertexSize);
// draw
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.input_size / SourceVertexSize);
active_pipeline++;
#ifdef GL_NV_texture_barrier
glTextureBarrierNV();
#endif
}
}
// transfer to framebuffer
framebuffer->bind_framebuffer();
// prepare to transfer to framebuffer
framebuffer_->bind_framebuffer();
if(submitted_output_data)
{
// draw from the output array buffer, with blending
glBindVertexArray(output_vertex_array_);
glEnable(GL_BLEND);
// Ensure we're back on the output framebuffer, drawing from the output array buffer
glBindVertexArray(output_vertex_array);
// update uniforms (implicitly binding the shader)
if(_last_output_width != output_width || _last_output_height != output_height)
{
output_shader_program->set_output_size(output_width, output_height, _visible_area);
_last_output_width = output_width;
_last_output_height = output_height;
// update uniforms, then bind the target
if(last_output_width_ != output_width || last_output_height_ != output_height) {
output_shader_program_->set_output_size(output_width, output_height, visible_area_);
last_output_width_ = output_width;
last_output_height_ = output_height;
}
output_shader_program->bind();
output_shader_program_->bind();
// draw
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, submitted_output_data / OutputVertexSize);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize);
}
#ifdef GL_NV_texture_barrier
glTextureBarrierNV();
#endif
// copy framebuffer to the intended place
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(pixel_accumulation_texture_unit);
framebuffer->bind_texture();
framebuffer->draw((float)output_width / (float)output_height);
// glViewport(0, 0, (GLsizei)output_width / 4, (GLsizei)output_height / 4);
// compositeTexture->bind_texture();
// compositeTexture->draw((float)output_width / (float)output_height);
framebuffer_->bind_texture();
framebuffer_->draw((float)output_width / (float)output_height);
_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
_draw_mutex->unlock();
fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
draw_mutex_.unlock();
}
void OpenGLOutputBuilder::reset_all_OpenGL_state()
{
composite_input_shader_program = nullptr;
composite_separation_filter_program = nullptr;
composite_y_filter_shader_program = nullptr;
composite_chrominance_filter_shader_program = nullptr;
rgb_input_shader_program = nullptr;
rgb_filter_shader_program = nullptr;
output_shader_program = nullptr;
framebuffer = nullptr;
_last_output_width = _last_output_height = 0;
void OpenGLOutputBuilder::reset_all_OpenGL_state() {
composite_input_shader_program_ = nullptr;
composite_separation_filter_program_ = nullptr;
composite_chrominance_filter_shader_program_ = nullptr;
rgb_input_shader_program_ = nullptr;
rgb_filter_shader_program_ = nullptr;
output_shader_program_ = nullptr;
framebuffer_ = nullptr;
last_output_width_ = last_output_height_ = 0;
}
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources)
{
_output_mutex->lock();
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) {
output_mutex_.lock();
reset_all_OpenGL_state();
_output_mutex->unlock();
output_mutex_.unlock();
}
void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader)
{
_output_mutex->lock();
_composite_shader = strdup(shader);
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
composite_shader_ = shader;
reset_all_OpenGL_state();
_output_mutex->unlock();
}
void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader)
{
_output_mutex->lock();
_rgb_shader = strdup(shader);
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
rgb_shader_ = shader;
reset_all_OpenGL_state();
_output_mutex->unlock();
}
#pragma mark - Program compilation
void OpenGLOutputBuilder::prepare_composite_input_shaders()
{
composite_input_shader_program = OpenGL::IntermediateShader::make_source_conversion_shader(_composite_shader, _rgb_shader);
composite_input_shader_program->set_source_texture_unit(source_data_texture_unit);
composite_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
void OpenGLOutputBuilder::prepare_composite_input_shaders() {
composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_);
composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_separation_filter_program = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
composite_separation_filter_program->set_source_texture_unit(composite_texture_unit);
composite_separation_filter_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
composite_separation_filter_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : composite_texture_unit);
composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_y_filter_shader_program = OpenGL::IntermediateShader::make_luma_filter_shader();
composite_y_filter_shader_program->set_source_texture_unit(separated_texture_unit);
composite_y_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader();
composite_chrominance_filter_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : separated_texture_unit);
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_chrominance_filter_shader_program = OpenGL::IntermediateShader::make_chroma_filter_shader();
composite_chrominance_filter_shader_program->set_source_texture_unit(filtered_y_texture_unit);
composite_chrominance_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
}
void OpenGLOutputBuilder::prepare_rgb_input_shaders()
{
if(_rgb_shader)
{
rgb_input_shader_program = OpenGL::IntermediateShader::make_rgb_source_shader(_rgb_shader);
rgb_input_shader_program->set_source_texture_unit(source_data_texture_unit);
rgb_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
rgb_filter_shader_program = OpenGL::IntermediateShader::make_rgb_filter_shader();
rgb_filter_shader_program->set_source_texture_unit(composite_texture_unit);
rgb_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
if(work_texture_) {
composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f);
composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f);
} else {
composite_input_shader_program_->set_is_double_height(false);
composite_separation_filter_program_->set_is_double_height(false);
composite_chrominance_filter_shader_program_->set_is_double_height(false);
}
}
void OpenGLOutputBuilder::prepare_source_vertex_array()
{
if(composite_input_shader_program)
{
glBindVertexArray(source_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer);
void OpenGLOutputBuilder::prepare_rgb_input_shaders() {
if(rgb_shader_.size()) {
rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_);
rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_input_shader_program->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1);
composite_input_shader_program->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1);
composite_input_shader_program->enable_vertex_attribute_with_pointer("ends", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfEnds, 1);
composite_input_shader_program->enable_vertex_attribute_with_pointer("phaseTimeAndAmplitude", 3, GL_UNSIGNED_BYTE, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfPhaseTimeAndAmplitude, 1);
rgb_filter_shader_program_ = OpenGL::IntermediateShader::make_rgb_filter_shader();
rgb_filter_shader_program_->set_source_texture_unit(composite_texture_unit);
rgb_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
}
}
void OpenGLOutputBuilder::prepare_output_shader()
{
output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
output_shader_program->set_source_texture_unit(filtered_texture_unit);
void OpenGLOutputBuilder::prepare_source_vertex_array() {
if(composite_input_shader_program_) {
glBindVertexArray(source_vertex_array_);
array_builder.bind_input();
composite_input_shader_program_->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("ends", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfEnds, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("phaseTimeAndAmplitude", 3, GL_UNSIGNED_BYTE, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfPhaseTimeAndAmplitude, 1);
}
}
void OpenGLOutputBuilder::prepare_output_vertex_array()
{
if(output_shader_program)
{
glBindVertexArray(output_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer);
output_shader_program->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
output_shader_program->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1);
void OpenGLOutputBuilder::prepare_output_shader() {
output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
output_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : filtered_texture_unit);
// output_shader_program_->set_source_texture_unit(composite_texture_unit);
output_shader_program_->set_origin_is_double_height(!!work_texture_);
}
void OpenGLOutputBuilder::prepare_output_vertex_array() {
if(output_shader_program_) {
glBindVertexArray(output_vertex_array_);
array_builder.bind_output();
output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
output_shader_program_->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1);
}
}
#pragma mark - Public Configuration
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
{
if(_output_device != output_device)
{
_output_device = output_device;
_composite_src_output_y = 0;
_last_output_width = 0;
_last_output_height = 0;
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) {
if(output_device_ != output_device) {
output_device_ = output_device;
composite_src_output_y_ = 0;
last_output_width_ = 0;
last_output_height_ = 0;
set_output_shader_width();
}
}
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
{
_output_mutex->lock();
_input_frequency = input_frequency;
_cycles_per_line = cycles_per_line;
_height_of_display = height_of_display;
_horizontal_scan_period = horizontal_scan_period;
_vertical_scan_period = vertical_scan_period;
_vertical_period_divider = vertical_period_divider;
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
input_frequency_ = input_frequency;
cycles_per_line_ = cycles_per_line;
height_of_display_ = height_of_display;
horizontal_scan_period_ = horizontal_scan_period;
vertical_scan_period_ = vertical_scan_period;
vertical_period_divider_ = vertical_period_divider;
set_timing_uniforms();
_output_mutex->unlock();
}
#pragma mark - Internal Configuration
void OpenGLOutputBuilder::set_colour_space_uniforms()
{
void OpenGLOutputBuilder::set_colour_space_uniforms() {
GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
@@ -490,8 +358,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
GLfloat *fromRGB, *toRGB;
switch(_colour_space)
{
switch(colour_space_) {
case ColourSpace::YIQ:
fromRGB = rgbToYIQ;
toRGB = yiqToRGB;
@@ -503,31 +370,49 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
break;
}
if(composite_input_shader_program) composite_input_shader_program->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_separation_filter_program_) composite_separation_filter_program_->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
}
void OpenGLOutputBuilder::set_timing_uniforms()
{
OpenGL::IntermediateShader *intermediate_shaders[] = {
composite_input_shader_program.get(),
composite_separation_filter_program.get(),
composite_y_filter_shader_program.get(),
composite_chrominance_filter_shader_program.get()
};
bool extends = false;
float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line);
for(int c = 0; c < 3; c++)
{
if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends);
extends = true;
float OpenGLOutputBuilder::get_composite_output_width() const {
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
}
void OpenGLOutputBuilder::set_output_shader_width() {
if(output_shader_program_) {
const float width = get_is_television_output() ? get_composite_output_width() : 1.0f;
output_shader_program_->set_input_width_scaler(width);
}
}
void OpenGLOutputBuilder::set_timing_uniforms() {
const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
const float output_width = get_composite_output_width();
const float sample_cycles_per_line = cycles_per_line_ / output_width;
if(composite_separation_filter_program_) {
composite_separation_filter_program_->set_width_scalers(output_width, output_width);
composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency);
composite_separation_filter_program_->set_extension(6.0f);
}
if(composite_chrominance_filter_shader_program_) {
composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width);
composite_chrominance_filter_shader_program_->set_extension(5.0f);
}
if(rgb_filter_shader_program_) {
rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f);
rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, (float)input_frequency_ * 0.5f);
}
if(output_shader_program_) {
set_output_shader_width();
output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
}
if(composite_input_shader_program_) {
composite_input_shader_program_->set_width_scalers(1.0f, output_width);
composite_input_shader_program_->set_extension(0.0f);
}
if(rgb_input_shader_program_) {
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
}
if(output_shader_program) output_shader_program->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _vertical_period_divider);
float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator;
if(composite_separation_filter_program) composite_separation_filter_program->set_separation_frequency(_cycles_per_line, colour_subcarrier_frequency);
if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.66f);
if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f);
if(rgb_filter_shader_program) rgb_filter_shader_program->set_filter_coefficients(_cycles_per_line, (float)_input_frequency * 0.5f);
}

Some files were not shown because too many files have changed in this diff Show More