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

Compare commits

...

155 Commits

Author SHA1 Message Date
Thomas Harte
506b4da6c3 Merge pull request #408 from TomHarte/MixerBalance
Enhances the CompoundSource so that constituents can have different volumes.
2018-04-07 14:32:47 -04:00
Thomas Harte
10f637d2cf Enhances the CompoundSource so that constituents can have different volumes. 2018-04-07 14:30:02 -04:00
Thomas Harte
0bab7c88f0 Merge pull request #407 from TomHarte/NameImplications
Allows the Vic-20 analyser to act on 'NTSC' in a filename.
2018-04-06 20:10:56 -04:00
Thomas Harte
78c612ca17 Adds a missing import, removes a redundant conversion. 2018-04-06 20:07:10 -04:00
Thomas Harte
e1c4035812 Switches away from C strings and allows Vic-20 region inference from filenames. 2018-04-06 17:42:24 -04:00
Thomas Harte
eb6d6c8033 Merge pull request #406 from TomHarte/NewFixes
Tweaks the 'new machine' dialogue for ZX memory size
2018-04-05 22:02:10 -04:00
Thomas Harte
7bf88565ce Resizes to fit all options. 2018-04-05 21:59:19 -04:00
Thomas Harte
ee10155296 Adds advice and withdraws the ZX 64kb option. 2018-04-05 21:57:26 -04:00
Thomas Harte
cc49140f6f Merge pull request #405 from TomHarte/VicFraming
Introduces different clipping zones for NTSC and PAL output.
2018-04-05 21:26:07 -04:00
Thomas Harte
3e846f89a1 Introduces different clipping zones for NTSC and PAL output. 2018-04-05 21:25:19 -04:00
Thomas Harte
5782cab2a0 Minor whitespace fix. 2018-04-05 21:15:25 -04:00
Thomas Harte
8c511e2b76 Merge pull request #404 from TomHarte/ProperShaderSetup
Ensures the SVideo shader gets all proper `enable_vertex_attribute_with_pointer`s.
2018-04-05 21:13:26 -04:00
Thomas Harte
ec72fb3baf Ensures the SVideo shader gets all proper enable_vertex_attribute_with_pointers. 2018-04-05 21:12:28 -04:00
Thomas Harte
bab1440f5c Merge pull request #403 from TomHarte/VicRange
Causes the 6560 to obey `set_sample_volume_range`.
2018-04-05 21:06:09 -04:00
Thomas Harte
60c1da6a66 Causes the 6560 to obey set_sample_volume_range.
Thereby resolves a clipping issue.
2018-04-05 21:04:46 -04:00
Thomas Harte
a849b3f2e4 Merge pull request #402 from TomHarte/AudioCutoff
Ensures artificial audio frequency limits are honoured.
2018-04-05 19:05:48 -04:00
Thomas Harte
dbe3c5c3f8 Ensures artificial frequency limits are honoured. 2018-04-05 18:40:07 -04:00
Thomas Harte
60cf6b3cfd Merge pull request #401 from TomHarte/VideoQuirks
Corrects composite output of the ZX80/81 and the Oric
2018-04-04 19:23:45 -04:00
Thomas Harte
5044aac337 Sizes up default window size better to fit machine selector. 2018-04-04 19:18:22 -04:00
Thomas Harte
36e0cb29c0 Ensures proper propagation of video choice through the Oric. 2018-04-04 19:14:42 -04:00
Thomas Harte
c0b4dd65da Mades the expected video signal usage explicit. 2018-04-04 19:01:18 -04:00
Thomas Harte
d061ea232b Ensures no attempt to compile an SVideo shader without appropriate source. 2018-04-04 19:01:01 -04:00
Thomas Harte
49feca4ddf Merge pull request #400 from TomHarte/NewCrash
Introduces a rudimentary 'new' dialogue for the Mac
2018-04-03 23:24:00 -04:00
Thomas Harte
46b1c57bf4 Enables the titlebar, inexplicably allowing the sheet to obtain focus. 2018-04-03 23:22:26 -04:00
Thomas Harte
eaf1482182 Reverts the once-again-unused document controller. 2018-04-03 23:11:19 -04:00
Thomas Harte
d3418550eb Attempts explicitly to disable promise of saving. 2018-04-03 23:06:48 -04:00
Thomas Harte
3ffa9e2751 Ensures complete machine picker state is preserved. 2018-04-03 23:01:12 -04:00
Thomas Harte
c697dd78f0 Ensures a new machine starts as first responder. 2018-04-03 22:22:39 -04:00
Thomas Harte
7dac791290 Causes the machine picker to show as a sheet.
Albeit with some user experience issues lingering.
2018-04-03 18:47:07 -04:00
Thomas Harte
cde2faeda6 Makes an unsuccessful attempt to show the new machine dialogue as a sheet.
Also corrects the 'open' case versus recent changes.
2018-04-02 23:31:36 -04:00
Thomas Harte
69f520428d Makes a first, ugly attempt at a 'new machine' dialogue for the Mac.
Which has implied getting much more specific about MSX disk drive attachment, and has prompted an excuse to offer the ZX80 with the ZX81 ROM.
2018-04-02 22:42:41 -04:00
Thomas Harte
80c84ddd75 Merge pull request #398 from TomHarte/SVideoOption
Exposes S-Video as a user-selectable option
2018-04-01 13:30:41 -04:00
Thomas Harte
fca8a58b36 Exposes S-Video option in the Mac UI. 2018-04-01 13:29:04 -04:00
Thomas Harte
33084899d0 Provides s-video as a command-line option. 2018-03-31 22:14:34 -04:00
Thomas Harte
7b381a8b6b Merge pull request #397 from TomHarte/Vic20FastTape
Improves Vic-20 fast tape ownership and simplifies memory logic.
2018-03-31 21:05:22 -04:00
Thomas Harte
9c75689a8d Increased verbosity. 2018-03-31 20:58:16 -04:00
Thomas Harte
0ee40e8556 Reintroduces 90% crop for VIC output. 2018-03-31 20:57:45 -04:00
Thomas Harte
8b45377b89 Simplifies storage underlying Vic memory.
In the hope of avoiding non-obvious bugs.
2018-03-31 18:54:40 -04:00
Thomas Harte
f6fb368d88 Allows the fast-tape mechanism to take ownership of tape handling.
Any successful fast tape interaction will now permanently pause the tape until a failed interaction occurs. This may or may not be a good idea.
2018-03-30 21:22:52 -04:00
Thomas Harte
183a5379de Merge pull request #396 from TomHarte/SVideo
Adds support for s-video.
2018-03-30 18:25:28 -04:00
Thomas Harte
912791d3d4 Causes the s-video path correctly to function. 2018-03-30 18:24:18 -04:00
Thomas Harte
163a61dd63 Corrects SVideo-as-composite output; the Atari and Vic-20 now both supply svideo. 2018-03-30 13:16:18 -04:00
Thomas Harte
207d462dbf Attempts to provide an implementation of SVideo support. 2018-03-30 12:41:20 -04:00
Thomas Harte
33281b9d89 Introduces S-Video as a video signal type at the interface level. 2018-03-30 10:25:41 -04:00
Thomas Harte
389979923e Performs update to and satisfaction of Xcode 9.3's preferred warnings. 2018-03-30 10:25:01 -04:00
Thomas Harte
067174965e Merge pull request #395 from TomHarte/TEDEsqueColours
Introduces Vic luminances sourced from the TED manual.
2018-03-30 09:39:02 -04:00
Thomas Harte
286259c83b Adds missing 6560 update hooks. 2018-03-29 20:49:36 -04:00
Thomas Harte
e1aa3e5a7f Imports chrominances from the TED documentation. They seem to apply to the VIC-I also. 2018-03-29 20:04:37 -04:00
Thomas Harte
78e1c2851a Merge pull request #393 from TomHarte/Vic20Faster
Introduces some minor Vic-20 optimisations.
2018-03-27 22:04:40 -04:00
Thomas Harte
0869213c55 Cuts detritus. 2018-03-27 22:00:13 -04:00
Thomas Harte
f3fe16215a Reintroduces options for the Vic-20, now tape loading speed only. 2018-03-27 21:55:43 -04:00
Thomas Harte
ec353ce663 Makes minor Vic-20 optimisations.
Specifically: the 6560 is updated only upon writes (more nuance can arrive), and tape sleeps are observed.
2018-03-27 21:52:52 -04:00
Thomas Harte
b7ff5ef9dd Merge pull request #392 from TomHarte/VicPalette
Tweaks VIC palette, especially PAL.
2018-03-26 21:25:12 -04:00
Thomas Harte
3b26e0a7c5 Tweaks NTSC colour generation. 2018-03-26 21:22:06 -04:00
Thomas Harte
6d464557a0 Reintroduces a warm-up run for the C1540.
That simulates the normal real-life scenario of switching the drive on slightly before the computer, and causes it to function correctly from immediate fast typing on an American Vic.

Also switches a couple of casts within the C1540 to functional style.
2018-03-26 21:06:07 -04:00
Thomas Harte
a776bec46a Tweaks PAL colours for the 6560 to be closer to screenshots found online. 2018-03-26 19:02:16 -04:00
Thomas Harte
a2da51c30b Commutes Vic-20 machine configuration options to its Target. 2018-03-26 19:01:57 -04:00
Thomas Harte
8067bf548a Merge pull request #390 from TomHarte/VicOptions
Ensures the Vic-20 doesn't show the ZX80/81 options panel on macOS.
2018-03-25 16:07:13 -04:00
Thomas Harte
62b0645ed0 Ensures the Vic-20 doesn't show the ZX80/81 options panel on macOS. 2018-03-25 16:04:44 -04:00
Thomas Harte
39a94874ae Merge pull request #389 from TomHarte/VicAnalysis
Strips back Vic-20 static analysis to the bare minimum.
2018-03-25 13:42:59 -04:00
Thomas Harte
e15d6717a1 Strips back Vic-20 static analysis to the bare minimum.
Also corrects an unsafe assumption in fast loading.
2018-03-25 13:37:33 -04:00
Thomas Harte
37ef46e7bb Merge branch 'SDLTravis' of github.com:TomHarte/CLK into SDLTravis 2018-03-23 21:52:27 -04:00
Thomas Harte
70c09b3031 Attempted to draft a travis.yml for SDL. 2018-03-23 21:51:15 -04:00
Thomas Harte
9378fbb0df Attempted to draft a travis.yml for SDL. 2018-03-23 21:40:46 -04:00
Thomas Harte
2118b9c0cd Merge pull request #385 from TomHarte/OricHFE
Corrects nullptr references in the CPC static analyser.
2018-03-23 18:40:13 -04:00
Thomas Harte
d0c53de250 Corrects nullptr references in the CPC static analyser. 2018-03-23 18:39:37 -04:00
Thomas Harte
d98507eab0 Merge pull request #384 from TomHarte/PlentifulIcons
Fills out the application icon set.
2018-03-23 18:33:02 -04:00
Thomas Harte
760c75103e Fills out the application icon set. 2018-03-23 18:29:18 -04:00
Thomas Harte
4407fd2f1f Merge pull request #383 from TomHarte/D64Crash
Ensures the rom fetcher is properly provided to the C1540.
2018-03-23 18:22:37 -04:00
Thomas Harte
7fcd243be0 Ensures the rom fetcher is properly recorded for potential provision to the C1540. 2018-03-23 18:20:17 -04:00
Thomas Harte
3165e9d82e Merge pull request #382 from TomHarte/Headers
Introduces missing #includes.
2018-03-23 18:08:55 -04:00
Thomas Harte
6656a08c60 Introduces missing #includes. 2018-03-23 18:05:51 -04:00
Thomas Harte
76661c0b51 Merge pull request #375 from TomHarte/UndefinedBehaviour
Resolves various pieces of undefined behaviour.
2018-03-22 22:01:19 -04:00
Thomas Harte
3bb496f9ae Enforces a maximum sector size to avoid impossible sizes.
Such as 128 * 2^255.
2018-03-22 22:00:26 -04:00
Thomas Harte
45be1c19df Resolves undefined behaviour of a signed shift left. 2018-03-22 21:59:39 -04:00
Thomas Harte
a301964bd0 Ensures all audio queues are fully merged before machine destruction.
Thereby avoids a race condition.
2018-03-22 21:59:19 -04:00
Thomas Harte
eea6858121 Resolves undefined behaviour from uninitialised limited-range values. 2018-03-22 21:58:42 -04:00
Thomas Harte
2a320fdf56 Merge pull request #374 from TomHarte/HFEFixup
Corrects two errors in all-machine HFE offering.
2018-03-22 20:24:24 -04:00
Thomas Harte
4695296ef2 Corrects bit mask for offering HFE around. 2018-03-22 20:23:39 -04:00
Thomas Harte
0fdbbeca1d Ensures the Commodore parser properly rejects non-GCR disks. 2018-03-22 20:23:21 -04:00
Thomas Harte
34cc39ad65 Merge pull request #373 from TomHarte/SpeakerCritical
Moves all LowpassSpeaker delegate calls outside of critical sections.
2018-03-22 20:07:20 -04:00
Thomas Harte
3d0c832a21 Moves all LowpassSpeaker delegate calls outside of critical sections. 2018-03-22 19:01:20 -04:00
Thomas Harte
1acdab9448 Expanded potential HFE targets to everything other than the MSX.
The MSX does not yet perform any sanity checks on disks. That's TODO.
2018-03-22 18:55:52 -04:00
Thomas Harte
93e85c5c4a The CPC now accepts disks only if it can make sense of them. 2018-03-22 18:52:43 -04:00
Thomas Harte
ab98189d25 Merge pull request #372 from TomHarte/MultiJoystick
Implements multimachine joystick support.
2018-03-22 11:09:38 -04:00
Thomas Harte
cd0fb7624b Pulls delegate messages out of the critical sections. 2018-03-22 11:08:07 -04:00
Thomas Harte
bae38497bb Implements multitarget joysticks. 2018-03-22 11:07:52 -04:00
Thomas Harte
29921bfa8d Merge pull request #371 from TomHarte/NanosecondMachines
Devolves time -> clock rate mapping to machines.
2018-03-22 10:08:58 -04:00
Thomas Harte
2712702461 Makes get_clock_rate protected. It's now an implementation detail. 2018-03-22 10:01:18 -04:00
Thomas Harte
a3fa9440d1 Renames method better to communicate purpose. 2018-03-22 09:49:36 -04:00
Thomas Harte
6419b0e619 Reintroduces CSMachineDelegate, allowing the Mac port to switch output audio rate dynamically. 2018-03-22 09:48:19 -04:00
Thomas Harte
58e5b6e3f1 Updates SDL kiosk mode to the death of CRTMachineDelegate. 2018-03-22 09:23:27 -04:00
Thomas Harte
682c3d8079 Adds new hook for watching audio output rate changes. 2018-03-22 09:23:01 -04:00
Thomas Harte
da3d65c18f Devolves time to cycle conversion to machines.
Thereby avoids a whole bunch of complicated machinations that would otherwise have been required of the multimachine.
2018-03-21 22:18:13 -04:00
Thomas Harte
ece3a05504 Merge pull request #370 from TomHarte/OricDiskDetection
Causes the Oric properly to evaluate disks offered to it.
2018-03-21 20:51:12 -04:00
Thomas Harte
927697b0f0 Causes the Oric properly to evaluate disks offered to it. 2018-03-21 20:48:21 -04:00
Thomas Harte
74dfc80b0f Merge pull request #369 from TomHarte/AnalyserUnion
Encapsulates per-platform analyser result fields.
2018-03-09 16:13:17 -05:00
Thomas Harte
a7f229bc4b Adds missing files. 2018-03-09 16:10:17 -05:00
Thomas Harte
89bec2919f Encapsulates machine configuration properties for all remaining platforms. 2018-03-09 16:07:29 -05:00
Thomas Harte
78eaecb29e Provides the proper framework for encapsulation of analyser target specifics.
... while making them a safe container for objects too. Uses the ZX80/81 as the pilot platform.
2018-03-09 15:36:11 -05:00
Thomas Harte
d410aea856 Merge pull request #368 from TomHarte/DiamondInheritance
Eliminates diamond inheritance of KeyboardMachine::Machine by typers.
2018-03-09 15:19:54 -05:00
Thomas Harte
6b1eef572b Eliminates diamond inheritance of KeyboardMachine::Machine by typers.
Specifically by pulling the key action stuff into a purely abstract class [/interface]. Takes the opportunity to unpublish a bunch of machine details.
2018-03-09 15:19:02 -05:00
Thomas Harte
719f5d79c2 Merge pull request #367 from TomHarte/DynamicVolume
Introduces formal setting of the output volume to `SampleSource`.
2018-03-09 14:10:55 -05:00
Thomas Harte
48737a32a7 Introduces formal setting of the output volume to SampleSource.
Previously every output device was making its own decision. Which is increasingly less sustainable due to the CompoundSource.
2018-03-09 13:23:18 -05:00
Thomas Harte
53f05efb2d Merge pull request #366 from TomHarte/MoreMemptr
Improves Z80 memptr behaviour.
2018-03-09 10:05:57 -05:00
Thomas Harte
0e73ba4b3e Introduces proper 5/3 SCF/CCF behaviour for the Z80.
While also `const`ing a bunch of things.
2018-03-09 09:47:00 -05:00
Thomas Harte
f0f9d5a6af Corrects memptr leakage via BIT, and ld (de/bc/nn), A behaviour. 2018-03-08 20:30:22 -05:00
Thomas Harte
03501df9e5 Merge pull request #365 from TomHarte/CartridgeDetermination
Works towards eliminating the special cases for Atari 2600 ROM handling.
2018-03-08 18:40:58 -05:00
Thomas Harte
dd6f85d4db Merge pull request #364 from TomHarte/TimingUpfront
Ensures the Coleco & MSX account for instruction lengths prior to outward accesses.
2018-03-07 17:29:32 -05:00
Thomas Harte
1804ea6849 Ensures the ColecoVision and MSX account for instruction lengths in advance when timing secondary components. 2018-03-07 17:00:18 -05:00
Thomas Harte
c8657e08f4 Merge remote-tracking branch 'origin/master' into CartridgeDetermination 2018-03-07 16:42:16 -05:00
Thomas Harte
a942e1319b Merge pull request #363 from TomHarte/ZonX
Introduces ZonX emulation and corrects a minor ColecoVision AY timing issue.
2018-03-07 16:23:51 -05:00
Thomas Harte
9e0a56b4f0 Withdraws the 2600 from .rom consideration.
Will return when it is performing more sanity checks; for the time being I don't want it constantly forcing multimachines.
2018-03-07 16:21:17 -05:00
Thomas Harte
9abc020818 Corrects potential ColecoVision SGM AY timing issues. 2018-03-07 16:16:58 -05:00
Thomas Harte
2dade8d353 Introduces ZonX emulation for the ZX81. 2018-03-07 16:16:29 -05:00
Thomas Harte
1100dc6993 Opens up .bin and .rom to all cartridge platforms, and adds a confidence estimate to the Atari 2600. 2018-03-07 14:26:07 -05:00
Thomas Harte
f212b18511 Declares a confidence for the ColecoVision equal to the probability that the special bytes are wrong. 2018-03-07 14:25:25 -05:00
Thomas Harte
a6ca69550f Standardises machines that aren't making a real guess on reporting a confidence of 0.5. 2018-03-07 14:24:52 -05:00
Thomas Harte
2452641844 Introduces a fast workaround to avert a MultiMachine where it would instantly end. 2018-03-06 19:08:02 -05:00
Thomas Harte
c82af4b814 Introduces get_confidence for the ColecoVision.
Based almost entirely on joypad accesses for now.
2018-03-06 19:06:35 -05:00
Thomas Harte
fdef914137 Corrects test target regression. 2018-03-06 18:32:21 -05:00
Thomas Harte
dfcc502a88 Merge pull request #360 from TomHarte/SDLJoystick
Introduces keyboard-as-joystick fallback for the SDL target.
2018-03-04 17:28:05 -05:00
Thomas Harte
1c6faaae88 Introduces keyboard-as-joystick fallback for the SDL target. 2018-03-04 17:26:32 -05:00
Thomas Harte
35c8a0dd8c Merge pull request #359 from TomHarte/MentionColecoVision
Adds the ColecoVision to the declared list of machines.
2018-03-03 19:05:05 -05:00
Thomas Harte
38feedaf6a Adds the ColecoVision. 2018-03-03 19:03:54 -05:00
Thomas Harte
0a2f908af4 Merge pull request #358 from TomHarte/TMSPhase
Picks a phase for the TMS empirically.
2018-03-03 13:56:10 -05:00
Thomas Harte
705d53cc21 Picks a phase for the TMS empirically. 2018-03-03 13:53:00 -05:00
Thomas Harte
35b18d58af Merge pull request #357 from TomHarte/SuperGameModule
Adds Super Game Module support for the ColecoVision.
2018-03-03 13:14:48 -05:00
Thomas Harte
3c5a8d9ff3 Adds Super Game Module support for the ColecoVision. 2018-03-03 13:08:33 -05:00
Thomas Harte
7ca02be578 Merge pull request #356 from TomHarte/Multicolour
Implements multicolour mode on the TMS.
2018-03-02 23:10:31 -05:00
Thomas Harte
ea13c7dd32 Implements multicolour mode on the TMS. 2018-03-02 23:08:01 -05:00
Thomas Harte
fdfd72a42c Merge pull request #355 from TomHarte/MegaCart
Adds MegaCart support for the ColecoVision.
2018-03-02 19:21:26 -05:00
Thomas Harte
da97bf95c0 Loosens ColecoVision cartridge size test to allow for slightly broken images. 2018-03-02 19:20:37 -05:00
Thomas Harte
bdfc36427c Implements MegaCart support. 2018-03-02 18:40:01 -05:00
Thomas Harte
74dfe56d2b Expands documentation of NMI setting.
Given that it was previously incorrect, explains logic behind request_status_ and last_request_status_ setting. Also takes the opportunity to ensure that NMI is 'sampled' at the same time as IRQ; whether the next thing should be the NMI routine now occurs one cycle before the end of any instruction. That's an assumption for now. Testing to come.
2018-03-02 11:10:02 -05:00
Thomas Harte
6cce9aa54e Merge pull request #353 from TomHarte/ColecoVision
Adds provisional emulation of the ColecoVision
2018-03-01 22:33:34 -05:00
Thomas Harte
ba68b7247b Adds latest files to SConstruct. 2018-03-01 22:19:50 -05:00
Thomas Harte
b02e4fbbf6 Corrects NMI receipt to be genuinely edge triggered.
Previously a caller that signalled NMI set multiple times would trigger multiple NMIs.
2018-03-01 22:04:56 -05:00
Thomas Harte
59b4c7314d Merge branch 'master' into ColecoVision 2018-03-01 22:01:26 -05:00
Thomas Harte
d328589bd0 Merge pull request #354 from TomHarte/MSXTiming
Corrects a counting error in the MSX.
2018-03-01 22:00:53 -05:00
Thomas Harte
b05d2b26bf Corrects a counting error in the MSX. 2018-03-01 21:59:51 -05:00
Thomas Harte
86239469e7 Allows SN76489 consumers to apply an additional divider that reduces computation. 2018-03-01 18:51:05 -05:00
Thomas Harte
7890506b16 Gives the SN76489 its proper dividers and personalities. 2018-02-28 22:36:03 -05:00
Thomas Harte
83f73c3f02 Installs additional safeguards against unsafe deconstruction. 2018-02-28 22:15:22 -05:00
Thomas Harte
87760297fc Fixes underpumping of SN76489.
Audio works now. Though I still need properly to confirm who owns dividers in practice. I think probably all division should be within the SN.
2018-02-27 22:59:29 -05:00
Thomas Harte
5b854d51e7 Corrects out-of-bounds access. 2018-02-27 22:45:45 -05:00
Thomas Harte
d4df101ab6 Makes a first attempt at implementing the SN76489. 2018-02-27 22:25:12 -05:00
Thomas Harte
0ad2676640 Adds a class for the SN76489 and wires it into the ColecoVision. 2018-02-26 22:04:34 -05:00
Thomas Harte
a074ee2071 Possibly fixes ColecoVision input mapping.
Also provides symbolic input from the Mac.
2018-02-25 22:47:47 -05:00
Thomas Harte
204d5cc964 Extends JoystickMachine protocol to cover ColecoVision use case.
Also thereby implements input on the ColecoVision, in theory at least. No input is being fed though, so...
2018-02-25 19:08:50 -05:00
Thomas Harte
23d15a4d6c The ColecoVision now accepts and loads cartridges. 2018-02-24 18:26:44 -05:00
Thomas Harte
23c47e21de Proceeds the ColecoVision to booting. 2018-02-24 18:14:38 -05:00
Thomas Harte
5530b96446 Wired up a class and analyser for a ColecoVision. 2018-02-23 22:47:15 -05:00
Thomas Harte
99d28a172b Merge pull request #352 from TomHarte/TZXCompletion
Makes an attempt at implementing all missing TZX 1.20 blocks.
2018-02-22 21:37:46 -05:00
Thomas Harte
d83178f29d Makes an attempt at implementing all missing TZX 1.20 blocks.
Towards that aim, simplifies CSW handling so that even regular RLE compression uses a static grab of file contents.
2018-02-22 21:28:12 -05:00
180 changed files with 4177 additions and 1697 deletions

View File

@@ -1,5 +1,13 @@
language: objective-c
osx_image: xcode8.2
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
xcode_scheme: Clock Signal
xcode_sdk: macosx10.12
# language: objective-c
# osx_image: xcode8.2
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
# xcode_scheme: Clock Signal
# xcode_sdk: macosx10.12
language: cpp
before_install:
- sudo apt-get install libsdl2-dev
script: cd OSBindings/SDL && scons
compiler:
- clang
- gcc

View File

@@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}
void MultiCRTMachine::run_for(const Cycles cycles) {
void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([=](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(cycles);
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->multi_crt_did_run_machines();
}
double MultiCRTMachine::get_clock_rate() {
// TODO: something smarter than this? Not all clock rates will necessarily be the same.
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_rate() : 0.0;
}
bool MultiCRTMachine::get_clock_is_unlimited() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_is_unlimited() : false;
}
void MultiCRTMachine::did_change_machine_order() {
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}
void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) {
// TODO:
}
void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) {
// TODO: consider passing along.
}
void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) {
// TODO: consider passing along.
}

View File

@@ -29,7 +29,7 @@ namespace Dynamic {
acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes.
*/
class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate {
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
@@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D
void close_output() override;
Outputs::CRT::CRT *get_crt() override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(const Cycles cycles) override;
double get_clock_rate() override;
bool get_clock_is_unlimited() override;
void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
void run_for(Time::Seconds duration) override;
private:
// CRTMachine::Machine::Delegate
void machine_did_change_clock_rate(Machine *machine) override;
void machine_did_change_clock_is_unlimited(Machine *machine) override;
void run_for(const Cycles cycles) override {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;

View File

@@ -17,7 +17,7 @@ MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique
}
}
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target &target) {
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
}
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {

View File

@@ -29,7 +29,7 @@ struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
void configure_as_target(const Analyser::Static::Target &target) override;
void configure_as_target(const Analyser::Static::Target *target) override;
bool insert_media(const Analyser::Static::Media &media) override;
private:

View File

@@ -8,13 +8,69 @@
#include "MultiJoystickMachine.hpp"
#include <algorithm>
using namespace Analyser::Dynamic;
namespace {
class MultiJoystick: public Inputs::Joystick {
public:
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
joysticks_.push_back(joysticks[index].get());
}
}
}
std::vector<DigitalInput> get_inputs() override {
std::vector<DigitalInput> inputs;
for(const auto &joystick: joysticks_) {
std::vector<DigitalInput> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
}
}
}
return inputs;
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
for(const auto &joystick: joysticks_) {
joystick->set_digital_input(digital_input, is_active);
}
}
void reset_all_inputs() override {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
}
private:
std::vector<Inputs::Joystick *> joysticks_;
};
}
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::size_t total_joysticks = 0;
std::vector<JoystickMachine::Machine *> joystick_machines;
for(const auto &machine: machines) {
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
if(joystick_machine) machines_.push_back(joystick_machine);
if(joystick_machine) {
joystick_machines.push_back(joystick_machine);
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
}
}
for(std::size_t index = 0; index < total_joysticks; ++index) {
joysticks_.emplace_back(new MultiJoystick(joystick_machines, index));
}
}
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {

View File

@@ -31,7 +31,6 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
private:
std::vector<JoystickMachine::Machine *> machines_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};

View File

@@ -48,13 +48,29 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(delegate_ && speaker == front_speaker_) {
delegate_->speaker_did_complete_samples(this, buffer);
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_complete_samples(this, buffer);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_change_input_clock(this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
}
}

View File

@@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
void set_new_front_machine(::Machine::DynamicMachine *machine);
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum);
void set_output_rate(float cycles_per_second, int buffer_size);
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate);
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_output_rate(float cycles_per_second, int buffer_size) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer);
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
void speaker_did_change_input_clock(Speaker *speaker) override;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;

View File

@@ -62,8 +62,15 @@ Configurable::Device *MultiMachine::configurable_device() {
}
}
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
return
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
#ifdef DEBUG
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
printf("%0.2f ", crt->get_confidence());
@@ -71,6 +78,7 @@ void MultiMachine::multi_crt_did_run_machines() {
printf("; ");
}
printf("\n");
#endif
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),
@@ -84,10 +92,7 @@ void MultiMachine::multi_crt_did_run_machines() {
crt_machine_.did_change_machine_order();
}
if(
(machines_.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence())
) {
if(would_collapse(machines_)) {
pick_first();
}
}

View File

@@ -40,6 +40,14 @@ namespace Dynamic {
*/
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
requesting this class as a proxy.
@returns @c true if the multimachine would discard all but the first machine in this list;
@c false otherwise.
*/
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
ConfigurationTarget::Machine *configuration_target() override;

View File

@@ -14,6 +14,7 @@ namespace Analyser {
enum class Machine {
AmstradCPC,
Atari2600,
ColecoVision,
Electron,
MSX,
Oric,

View File

@@ -10,6 +10,7 @@
#include "Disk.hpp"
#include "Tape.hpp"
#include "Target.hpp"
using namespace Analyser::Static::Acorn;
@@ -56,13 +57,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges;
}
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Electron;
target->confidence = 1.0; // TODO: a proper estimation
target->acorn.has_dfs = false;
target->acorn.has_adfs = false;
target->acorn.should_shift_restart = false;
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;
target->has_adfs = false;
target->should_shift_restart = false;
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
@@ -109,17 +110,18 @@ void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::un
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
target->media.disks = media.disks;
target->acorn.has_dfs = !!dfs_catalogue;
target->acorn.has_adfs = !!adfs_catalogue;
target->has_dfs = !!dfs_catalogue;
target->has_adfs = !!adfs_catalogue;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None)
target->acorn.should_shift_restart = true;
target->should_shift_restart = true;
else
target->loading_command = "*CAT\n";
}
}
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) {
destination.push_back(std::move(target));
}
}

View File

@@ -0,0 +1,28 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Acorn_Target_h
#define Analyser_Static_Acorn_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target {
bool has_adfs = false;
bool has_dfs = false;
bool should_shift_restart = false;
};
}
}
}
#endif /* Analyser_Static_Acorn_Target_h */

View File

@@ -11,6 +11,8 @@
#include <algorithm>
#include <cstring>
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
@@ -58,7 +60,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
static void InspectCatalogue(
const Storage::Disk::CPM::Catalogue &catalogue,
const std::unique_ptr<Analyser::Static::Target> &target) {
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
std::vector<const Storage::Disk::CPM::File *> candidate_files;
candidate_files.reserve(catalogue.files.size());
@@ -153,10 +155,10 @@ static void InspectCatalogue(
target->loading_command = "cat\n";
}
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) {
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr && !boot_sector->samples.empty()) {
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced.
bool matched = true;
@@ -177,24 +179,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
return false;
}
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::AmstradCPC;
target->confidence = 1.0;
target->media.disks = media.disks;
target->media.tapes = media.tapes;
target->media.cartridges = media.cartridges;
target->confidence = 0.5;
target->amstradcpc.model = AmstradCPCModel::CPC6128;
target->model = Target::Model::CPC6128;
if(!media.tapes.empty()) {
// TODO: which of these are actually potentially CPC tapes?
target->media.tapes = media.tapes;
if(!target->media.tapes.empty()) {
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck!
target->loading_command = "|tape\nrun\"\n1234567890";
}
if(!target->media.disks.empty()) {
if(!media.disks.empty()) {
Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
@@ -203,26 +205,40 @@ void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<st
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0;
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format);
if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
} else {
if(!CheckBootSector(target->media.disks.front(), target)) {
Storage::Disk::CPM::ParameterBlock system_format;
system_format.sectors_per_track = 9;
system_format.tracks = 40;
system_format.block_size = 1024;
system_format.first_sector = 0x41;
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
Storage::Disk::CPM::ParameterBlock system_format;
system_format.sectors_per_track = 9;
system_format.tracks = 40;
system_format.block_size = 1024;
system_format.first_sector = 0x41;
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format);
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
}
for(const auto &disk: media.disks) {
// Check for an ordinary catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
target->media.disks.push_back(disk);
continue;
}
// Failing that check for a boot sector.
if(CheckBootSector(disk, target)) {
target->media.disks.push_back(disk);
continue;
}
// Failing that check for a system catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
target->media.disks.push_back(disk);
continue;
}
}
}
destination.push_back(std::move(target));
// If any media survived, add the target.
if(!target->media.empty())
destination.push_back(std::move(target));
}

View File

@@ -0,0 +1,33 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AmstradCPC_Target_h
#define Analyser_Static_AmstradCPC_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public ::Analyser::Static::Target {
enum class Model {
CPC464,
CPC664,
CPC6128
};
Model model = Model::CPC464;
};
}
}
}
#endif /* Analyser_Static_AmstradCPC_Target_h */

View File

@@ -8,11 +8,13 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari;
static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
@@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, cons
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid;
if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
@@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
) {
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack;
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
return;
}
// make an assumption that this is the Atari paging model
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k;
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons
tigervision_access_count += masked_address == 0x3f;
}
if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k;
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, con
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
}
if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork;
if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
target.atari.paging_model =
target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy;
Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
}
static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
@@ -138,16 +140,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2;
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
break;
case 12288:
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus;
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k;
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
@@ -159,8 +161,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus &&
target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) {
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
@@ -168,24 +170,24 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
break;
}
}
target.atari.uses_superchip = has_superchip;
target.uses_superchip = has_superchip;
}
// check for a Tigervision or Tigervision-esque scheme
if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) {
if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
}
}
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
// TODO: sanity checking; is this image really for an Atari 2600.
std::unique_ptr<Target> target(new Target);
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
// TODO: sanity checking; is this image really for an Atari 2600?
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
target->machine = Machine::Atari2600;
target->confidence = 1.0;
target->confidence = 0.5;
target->media.cartridges = media.cartridges;
target->atari.paging_model = Atari2600PagingModel::None;
target->atari.uses_superchip = false;
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
target->uses_superchip = false;
// try to figure out the paging scheme
if(!media.cartridges.empty()) {

View File

@@ -0,0 +1,43 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Atari_Target_h
#define Analyser_Static_Atari_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Atari {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {
None,
CommaVid,
Atari8k,
Atari16k,
Atari32k,
ActivisionStack,
ParkerBros,
Tigervision,
CBSRamPlus,
MNetwork,
MegaBoy,
Pitfall2
};
// TODO: shouldn't these be properties of the cartridge?
PagingModel paging_model = PagingModel::None;
bool uses_superchip = false;
};
}
}
}
#endif /* Analyser_Static_Atari_Target_h */

View File

@@ -0,0 +1,62 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 8, 12, 16, 24 or 32 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
const std::size_t data_size = segment.data.size();
const std::size_t overflow = data_size&8191;
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
if(data_size < 8192) continue;
// the two bytes that will be first must be 0xaa and 0x55, either way around
auto *start = &segment.data[0];
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
start = &segment.data[segment.data.size() - 16384];
}
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
if(start[0] == start[1]) continue;
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
if(!overflow) {
coleco_cartridges.push_back(cartridge);
} else {
// Size down to a multiple of 8kb and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
}
}
return coleco_cartridges;
}
void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::ColecoVision;
target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty())
destination.push_back(std::move(target));
}

View File

@@ -0,0 +1,25 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Coleco {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -71,19 +71,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
bit_count_++;
}
unsigned int proceed_to_next_block() {
unsigned int proceed_to_next_block(int max_index_count) {
// find GCR lead-in
proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff;
// find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < 2) {
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
run_for(Cycles(1));
}
// continue for a further nine bits
bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < 2) {
while(bit_count_ < 9 && index_count_ < max_index_count) {
run_for(Cycles(1));
}
@@ -97,8 +97,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
void proceed_to_shift_value(unsigned int shift_value) {
index_count_ = 0;
while(shift_register_ != shift_value && index_count_ < 2) {
const int max_index_count = index_count_ + 2;
while(shift_register_ != shift_value && index_count_ < max_index_count) {
run_for(Cycles(1));
}
}
@@ -124,13 +124,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::shared_ptr<Sector> get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;
const int max_index_count = index_count_ + 2;
while(index_count_ < 2) {
while(index_count_ < max_index_count) {
// look for a sector header
while(1) {
if(proceed_to_next_block() == 0x08) break;
if(index_count_ >= 2) return nullptr;
if(proceed_to_next_block(max_index_count) == 0x08) break;
if(index_count_ >= max_index_count) return nullptr;
}
// get sector details, skip if this looks malformed
@@ -144,8 +144,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
// look for the following data
while(1) {
if(proceed_to_next_block() == 0x07) break;
if(index_count_ >= 2) return nullptr;
if(proceed_to_next_block(max_index_count) == 0x07) break;
if(index_count_ >= max_index_count) return nullptr;
}
checksum = 0;

View File

@@ -11,8 +11,10 @@
#include "Disk.hpp"
#include "File.hpp"
#include "Tape.hpp"
#include "Target.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include <algorithm>
#include <sstream>
using namespace Analyser::Static::Commodore;
@@ -38,10 +40,10 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return vic20_cartridges;
}
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 1.0; // TODO: a proper estimation
target->confidence = 0.5; // TODO: a proper estimation
int device = 0;
std::vector<File> files;
@@ -73,7 +75,7 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
}
if(!files.empty()) {
target->vic20.memory_model = Vic20MemoryModel::Unexpanded;
target->memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
@@ -86,19 +88,22 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
// make a first guess based on loading address
switch(files.front().starting_address) {
default:
printf("Starting address %04x?\n", files.front().starting_address);
case 0x1001:
default: break;
target->memory_model = Target::MemoryModel::Unexpanded;
break;
case 0x1201:
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
target->vic20.memory_model = Vic20MemoryModel::EightKB;
target->memory_model = Target::MemoryModel::EightKB;
break;
}
// General approach: increase memory size conservatively such that the largest file found will fit.
for(File &file : files) {
std::size_t file_size = file.data.size();
// for(File &file : files) {
// std::size_t file_size = file.data.size();
// bool is_basic = file.is_basic();
/*if(is_basic)
@@ -124,18 +129,26 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
// A 32kb expanded Vic has memory in the entire low 32kb.
uint16_t starting_address = file.starting_address;
// uint16_t starting_address = file.starting_address;
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
if(starting_address + file_size > 0x2000)
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
// if(starting_address + file_size > 0x2000)
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
// }
}
// }
}
if(!target->media.empty())
if(!target->media.empty()) {
// Inspect filename for a region hint.
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Analyser::Static::Commodore::Target::Region::American;
}
destination.push_back(std::move(target));
}
}

View File

@@ -10,12 +10,13 @@
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Commodore {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name);
}
}

View File

@@ -0,0 +1,42 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Region {
American,
Danish,
Japanese,
European,
Swedish
};
MemoryModel memory_model = MemoryModel::Unexpanded;
Region region = Region::European;
bool has_c1540 = false;
};
}
}
}
#endif /* Analyser_Static_Commodore_Target_h */

View File

@@ -10,6 +10,8 @@
#include "Cartridge.hpp"
#include "Tape.hpp"
#include "Target.hpp"
#include "../Disassembler/Z80.hpp"
#include "../Disassembler/AddressMapper.hpp"
@@ -32,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
output_segments.emplace_back(start_address, segment.data);
}
std::unique_ptr<Analyser::Static::Target> target(new Analyser::Static::Target);
std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
target->machine = Analyser::Machine::MSX;
target->confidence = confidence;
@@ -259,9 +261,9 @@ static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFr
return targets;
}
void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
// Append targets for any cartridges that look correct.
std::vector<std::unique_ptr<Target>> cartridge_targets = CartridgeTargetsFrom(media.cartridges);
auto cartridge_targets = CartridgeTargetsFrom(media.cartridges);
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
// Consider building a target for disks and/or tapes.
@@ -283,10 +285,11 @@ void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::uniq
// Blindly accept disks for now.
target->media.disks = media.disks;
target->has_disk_drive = !media.disks.empty();
if(!target->media.empty()) {
target->machine = Machine::MSX;
target->confidence = 1.0;
target->confidence = 0.5;
destination.push_back(std::move(target));
}
}

View File

@@ -0,0 +1,26 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_MSX_Target_h
#define Analyser_Static_MSX_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace MSX {
struct Target: public ::Analyser::Static::Target {
bool has_disk_drive = false;
};
}
}
}
#endif /* Analyser_Static_MSX_Target_h */

View File

@@ -9,9 +9,15 @@
#include "StaticAnalyser.hpp"
#include "Tape.hpp"
#include "Target.hpp"
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include <cstring>
using namespace Analyser::Static::Oric;
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
@@ -73,10 +79,31 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
return Score(disassembly, rom_functions, variable_locations);
}
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
if(!sector) return false;
if(sector->samples.empty()) return false;
const std::vector<uint8_t> &first_sample = sector->samples[0];
if(first_sample.size() != 256) return false;
const uint8_t signature[] = {
0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0,
0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00,
0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6,
0x12, 0x00
};
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Oric;
target->confidence = 1.0;
target->confidence = 0.5;
int basic10_votes = 0;
int basic11_votes = 0;
@@ -102,17 +129,22 @@ void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::uni
}
}
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
if(!media.disks.empty()) {
target->oric.has_microdisc = true;
target->media.disks = media.disks;
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
for(const auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
target->has_microdrive = true;
target->media.disks.push_back(disk);
}
}
} else {
target->oric.has_microdisc = false;
target->has_microdrive = false;
}
// TODO: really this should add two targets if not all votes agree
target->oric.use_atmos_rom = basic11_votes >= basic10_votes;
if(target->oric.has_microdisc) target->oric.use_atmos_rom = true;
target->use_atmos_rom = basic11_votes >= basic10_votes;
if(target->has_microdrive) target->use_atmos_rom = true;
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
destination.push_back(std::move(target));

View File

@@ -0,0 +1,25 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public ::Analyser::Static::Target {
bool use_atmos_rom = false;
bool has_microdrive = false;
};
}
}
}
#endif /* Analyser_Static_Oric_Target_h */

View File

@@ -16,6 +16,7 @@
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
@@ -51,21 +52,16 @@
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) {
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
const char *mixed_case_extension = strrchr(file_name, '.');
char *lowercase_extension = nullptr;
if(mixed_case_extension) {
lowercase_extension = strdup(mixed_case_extension+1);
char *parser = lowercase_extension;
while(*parser) {
*parser = (char)tolower(*parser);
parser++;
}
}
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
Media result;
#define Insert(list, class, platforms) \
list.emplace_back(new Storage::class(file_name));\
potential_platforms |= platforms;\
@@ -77,70 +73,72 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
Insert(list, class, platforms) \
} catch(...) {}
#define Format(extension, list, class, platforms) \
if(!std::strcmp(lowercase_extension, extension)) { \
#define Format(ext, list, class, platforms) \
if(extension == ext) { \
TryInsert(list, class, platforms) \
}
if(lowercase_extension) {
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format("hfe", result.disks, Disk::DiskImageHolder<Storage::Disk::HFE>, TargetPlatform::AmstradCPC) // HFE (TODO: plus other target platforms)
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format( "hfe",
result.disks,
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
if(!std::strcmp(lowercase_extension, "prg")) {
// try instantiating as a ROM; failing that accept as a tape
// PRG
if(extension == "prg") {
// try instantiating as a ROM; failing that accept as a tape
try {
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
} catch(...) {
try {
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
} catch(...) {
try {
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) {}
}
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) {}
}
}
Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn | TargetPlatform::MSX) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
#undef Format
#undef Insert
#undef TryInsert
// Filter potential platforms as per file preferences, if any.
free(lowercase_extension);
}
return result;
}
Media Analyser::Static::GetMedia(const char *file_name) {
Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetPlatform::IntType throwaway;
return GetMediaAndPlatforms(file_name, throwaway);
}
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *file_name) {
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) {
std::vector<std::unique_ptr<Target>> targets;
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
@@ -153,7 +151,8 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *fi
if(potential_platforms & TargetPlatform::Acorn) Acorn::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets, file_name);
if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);

View File

@@ -21,39 +21,6 @@
namespace Analyser {
namespace Static {
enum class Vic20MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Atari2600PagingModel {
None,
CommaVid,
Atari8k,
Atari16k,
Atari32k,
ActivisionStack,
ParkerBros,
Tigervision,
CBSRamPlus,
MNetwork,
MegaBoy,
Pitfall2
};
enum class ZX8081MemoryModel {
Unexpanded,
SixteenKB,
SixtyFourKB
};
enum class AmstradCPCModel {
CPC464,
CPC664,
CPC6128
};
/*!
A list of disks, tapes and cartridges.
*/
@@ -72,45 +39,13 @@ struct Media {
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/
struct Target {
virtual ~Target() {}
Machine machine;
Media media;
float confidence;
std::string loading_command;
// TODO: this is too C-like a solution; make Target a base class and
// turn the following into information held by more specific subclasses.
union {
struct {
bool has_adfs;
bool has_dfs;
bool should_shift_restart;
} acorn;
struct {
Atari2600PagingModel paging_model;
bool uses_superchip;
} atari;
struct {
bool use_atmos_rom;
bool has_microdisc;
} oric;
struct {
Vic20MemoryModel memory_model;
bool has_c1540;
} vic20;
struct {
ZX8081MemoryModel memory_model;
bool isZX81;
} zx8081;
struct {
AmstradCPCModel model;
} amstradcpc;
};
};
/*!
@@ -118,12 +53,12 @@ struct Target {
@returns The list of potential targets, sorted from most to least probable.
*/
std::vector<std::unique_ptr<Target>> GetTargets(const char *file_name);
std::vector<std::unique_ptr<Target>> GetTargets(const std::string &file_name);
/*!
Inspects the supplied file and determines the media included.
*/
Media GetMedia(const char *file_name);
Media GetMedia(const std::string &file_name);
}
}

View File

@@ -11,6 +11,7 @@
#include <string>
#include <vector>
#include "Target.hpp"
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
@@ -27,41 +28,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
return files;
}
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) {
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) {
if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset();
if(!files.empty()) {
std::unique_ptr<Target> target(new Target);
Target *target = new Target;
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
target->machine = Machine::ZX8081;
// Guess the machine type from the file only if it isn't already known.
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
default:
target->zx8081.isZX81 = files.front().isZX81;
target->is_ZX81 = files.front().isZX81;
break;
case TargetPlatform::ZX80: target->zx8081.isZX81 = false; break;
case TargetPlatform::ZX81: target->zx8081.isZX81 = true; break;
case TargetPlatform::ZX80: target->is_ZX81 = false; break;
case TargetPlatform::ZX81: target->is_ZX81 = true; break;
}
/*if(files.front().data.size() > 16384) {
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
} else*/ if(files.front().data.size() > 1024) {
target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
target->memory_model = Target::MemoryModel::SixteenKB;
} else {
target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
target->memory_model = Target::MemoryModel::Unexpanded;
}
target->media.tapes = media.tapes;
// TODO: how to run software once loaded? Might require a BASIC detokeniser.
if(target->zx8081.isZX81) {
if(target->is_ZX81) {
target->loading_command = "J\"\"\n";
} else {
target->loading_command = "W\n";
}
destination.push_back(std::move(target));
}
}
}

View File

@@ -0,0 +1,34 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
Unexpanded,
SixteenKB,
SixtyFourKB
};
MemoryModel memory_model = MemoryModel::Unexpanded;
bool is_ZX81 = false;
bool ZX80_uses_ZX81_ROM = false;
};
}
}
}
#endif /* Analyser_Static_ZX8081_Target_h */

View File

@@ -0,0 +1,18 @@
//
// TimeTypes.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef TimeTypes_h
#define TimeTypes_h
namespace Time {
typedef double Seconds;
}
#endif /* TimeTypes_h */

View File

@@ -18,7 +18,7 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.defer([=]() {
volume_ = volume;
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
});
}
@@ -114,12 +114,12 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
// this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff?
target[c] = (
target[c] = static_cast<int16_t>(
(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;
) * volume_ + (volume_ >> 4);
}
}
@@ -132,6 +132,10 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
}
}
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = static_cast<int16_t>(range / 64);
}
#undef shift
#undef increment
#undef update

View File

@@ -25,8 +25,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value);
// For ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -34,7 +36,8 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
uint8_t volume_ = 0;
int16_t volume_ = 0;
int16_t range_multiplier_ = 1;
};
/*!
@@ -52,20 +55,28 @@ template <class T> class MOS6560 {
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = cos(phase + phaseOffset);"
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
"return vec2(yc.x, chroma);"
"}");
// default to s-video output
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
// default to NTSC
set_output_mode(OutputMode::NTSC);
}
~MOS6560() {
audio_queue_.flush();
}
void set_clock_rate(double clock_rate) {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
}
@@ -87,28 +98,28 @@ template <class T> class MOS6560 {
void set_output_mode(OutputMode output_mode) {
output_mode_ = output_mode;
// Lumunances are encoded trivially: on a 0255 scale.
// Luminances 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
0, 255, 60, 189,
80, 144, 40, 227,
90, 161, 207, 227,
200, 196, 160, 196
};
// 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,
255, 255, 37, 101,
19, 86, 123, 59,
46, 53, 37, 101,
19, 86, 123, 59,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 8, 72,
32, 88, 48, 112,
0, 0, 8, 72,
32, 88, 48, 112,
255, 255, 7, 71,
25, 86, 48, 112,
0, 119, 7, 71,
25, 86, 48, 112,
};
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
@@ -134,16 +145,15 @@ template <class T> class MOS6560 {
}
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
// switch(output_mode) {
// case OutputMode::PAL:
// 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));
// break;
// }
switch(output_mode) {
case OutputMode::PAL:
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
for(int c = 0; c < 16; c++) {
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);

View File

@@ -72,6 +72,15 @@ struct ReverseTable {
}
} reverse_table;
// Bits are reversed in the internal mode value; they're stored
// in the order M1 M2 M3. Hence the definitions below.
enum ScreenMode {
Text = 4,
MultiColour = 2,
ColouredText = 0,
Graphics = 1
};
}
TMS9918Base::TMS9918Base() :
@@ -87,9 +96,15 @@ TMS9918::TMS9918(Personality p) {
"{"
"return texture(sampler, coordinate).rgb / vec3(255.0);"
"}");
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
crt_->set_input_gamma(2.8f);
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
// intended to produce the correct relationship between the hard edges between pixels and
// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
// colour burst generator because I've yet to find any.
crt_->set_immediate_default_phase(0.85f);
}
Outputs::CRT::CRT *TMS9918::get_crt() {
@@ -293,7 +308,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
int row_base = pattern_name_address_;
int pattern_base = pattern_generator_table_address_;
int colour_base = colour_table_address_;
if(screen_mode_ == 1) {
if(screen_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
}
@@ -304,7 +320,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
const int pattern_names_end = (end - 27 + 3) >> 2;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Colours are collected ever fourth window starting from window 29.
// Colours are collected every fourth window starting from window 29.
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
const int colours_end = (end - 29 + 3) >> 2;
if(screen_mode_ != 1) {
@@ -320,8 +336,11 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Patterns are collected ever fourth window starting from window 30.
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
const int pattern_buffer_end = (end - 30 + 3) >> 2;
// Multicolour mode uss a different function of row to pick bytes
const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7);
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)];
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row];
}
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
@@ -421,7 +440,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
case LineMode::Character: {
// If this is the start of the visible area, seed sprite shifter positions.
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) {
if(output_column_ == first_pixel_column_) {
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
@@ -435,36 +454,46 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
// Paint the background tiles.
const int shift = (output_column_ - first_pixel_column_) & 7;
int byte_column = (output_column_ - first_pixel_column_) >> 3;
const int pixels_left = pixels_end - output_column_;
int length = std::min(pixels_left, 8 - shift);
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
uint8_t colour = colour_buffer_[byte_column];
uint32_t colours[2] = {
palette[(colour & 15) ? (colour & 15) : background_colour_],
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
};
int background_pixels_left = pixels_left;
while(true) {
background_pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
if(screen_mode_ == ScreenMode::MultiColour) {
int pixel_location = output_column_ - first_pixel_column_;
for(int c = 0; c < pixels_left; ++c) {
pixel_target_[c] = palette[
(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15
];
}
pixel_target_ += length;
pixel_target_ += pixels_left;
} else {
const int shift = (output_column_ - first_pixel_column_) & 7;
int byte_column = (output_column_ - first_pixel_column_) >> 3;
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
int length = std::min(pixels_left, 8 - shift);
pattern = reverse_table.map[pattern_buffer_[byte_column]];
colour = colour_buffer_[byte_column];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
uint8_t colour = colour_buffer_[byte_column];
uint32_t colours[2] = {
palette[(colour & 15) ? (colour & 15) : background_colour_],
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
};
int background_pixels_left = pixels_left;
while(true) {
background_pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
pattern = reverse_table.map[pattern_buffer_[byte_column]];
colour = colour_buffer_[byte_column];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
}
}
// Paint sprites and check for collisions.
@@ -554,7 +583,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
screen_mode_ = next_screen_mode_;
blank_screen_ = next_blank_screen_;
switch(screen_mode_) {
case 2:
case ScreenMode::Text:
line_mode_ = LineMode::Text;
first_pixel_column_ = 69;
first_right_border_column_ = 309;
@@ -608,7 +637,7 @@ void TMS9918::set_register(int address, uint8_t value) {
case 1:
next_blank_screen_ = !(low_write_ & 0x40);
generate_interrupts_ = !!(low_write_ & 0x20);
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3);
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2);
sprites_16x16_ = !!(low_write_ & 0x02);
sprites_magnified_ = !!(low_write_ & 0x01);

View File

@@ -56,13 +56,18 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_
}
}
set_sample_volume_range(0);
}
void AY38910::set_sample_volume_range(std::int16_t range) {
// set up volume lookup table
float max_volume = 8192;
float root_two = sqrtf(2.0f);
const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
const float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++) {
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
}
volumes_[0] = 0;
evaluate_output_volume();
}
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
@@ -156,6 +161,11 @@ void AY38910::evaluate_output_volume() {
);
}
bool AY38910::is_zero_level() {
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
}
// MARK: - Register manipulation
void AY38910::select_register(uint8_t r) {

View File

@@ -85,6 +85,8 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;

View File

@@ -15,19 +15,19 @@ using namespace Konami;
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
task_queue_(task_queue) {}
bool SCC::is_silent() {
bool SCC::is_zero_level() {
return !(channel_enable_ & 0x1f);
}
void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
if(is_silent()) {
if(is_zero_level()) {
std::memset(target, 0, sizeof(std::int16_t) * number_of_samples);
return;
}
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
target[c] = transient_output_level_;
master_divider_++;
c++;
}
@@ -44,7 +44,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
target[c] = output_volume_;
target[c] = transient_output_level_;
c++;
master_divider_++;
}
@@ -86,18 +86,24 @@ void SCC::write(uint16_t address, uint8_t value) {
}
void SCC::evaluate_output_volume() {
output_volume_ =
transient_output_level_ =
static_cast<int16_t>(
(
((
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
)
) * master_volume_) / (255*15*5)
// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5.
);
}
void SCC::set_sample_volume_range(std::int16_t range) {
master_volume_ = range;
evaluate_output_volume();
}
uint8_t SCC::read(uint16_t address) {
address &= 0xff;
if(address < 0x80) {
@@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) {
}
return 0xff;
}

View File

@@ -27,10 +27,11 @@ class SCC: public ::Outputs::Speaker::SampleSource {
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
/// As per ::SampleSource; provides a broadphase test for silence.
bool is_silent();
bool is_zero_level();
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
/// Writes to the SCC.
void write(uint16_t address, uint8_t value);
@@ -43,7 +44,8 @@ class SCC: public ::Outputs::Speaker::SampleSource {
// State from here on down is accessed ony from the audio thread.
int master_divider_ = 0;
int16_t output_volume_ = 0;
std::int16_t master_volume_ = 0;
int16_t transient_output_level_ = 0;
struct Channel {
int period = 0;

View File

@@ -0,0 +1,161 @@
//
// SN76489.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "SN76489.hpp"
#include <cassert>
#include <cmath>
using namespace TI;
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
set_sample_volume_range(0);
switch(personality) {
case Personality::SN76494:
master_divider_period_ = 2;
shifter_is_16bit_ = false;
break;
case Personality::SN76489:
master_divider_period_ = 16;
shifter_is_16bit_ = false;
break;
case Personality::SMS:
master_divider_period_ = 16;
shifter_is_16bit_ = true;
break;
}
assert((master_divider_period_ % additional_divider) == 0);
assert(additional_divider < master_divider_period_);
master_divider_period_ /= additional_divider;
}
void SN76489::set_sample_volume_range(std::int16_t range) {
// Build a volume table.
double multiplier = pow(10.0, -0.1);
double volume = static_cast<float>(range) / 4.0f; // As there are four channels.
for(int c = 0; c < 16; ++c) {
volumes_[c] = (int)round(volume);
volume *= multiplier;
}
volumes_[15] = 0;
evaluate_output_volume();
}
void SN76489::set_register(uint8_t value) {
task_queue_.defer([value, this] () {
if(value & 0x80) {
active_register_ = value;
}
const int channel = (active_register_ >> 5)&3;
if(active_register_ & 0x10) {
// latch for volume
channels_[channel].volume = value & 0xf;
evaluate_output_volume();
} else {
// latch for tone/data
if(channel < 3) {
if(value & 0x80) {
channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
} else {
channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
}
} else {
// writes to the noise register always reset the shifter
noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000;
if(value & 4) {
noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15;
} else {
noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
}
channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
// Special case: if these bits are both set, the noise channel should track channel 2,
// which is marked with a divider of 0xffff.
if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
}
}
});
}
bool SN76489::is_zero_level() {
return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
}
void SN76489::evaluate_output_volume() {
output_volume_ = static_cast<int16_t>(
channels_[0].level * volumes_[channels_[0].volume] +
channels_[1].level * volumes_[channels_[1].volume] +
channels_[2].level * volumes_[channels_[2].volume] +
channels_[3].level * volumes_[channels_[3].volume]
);
}
void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::size_t c = 0;
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
}
while(c < number_of_samples) {
bool did_flip = false;
#define step_channel(x, s) \
if(channels_[x].counter) channels_[x].counter--;\
else {\
channels_[x].level ^= 1;\
channels_[x].counter = channels_[x].divider;\
s;\
}
step_channel(0, /**/);
step_channel(1, /**/);
step_channel(2, did_flip = true);
#undef step_channel
if(channels_[3].divider != 0xffff) {
if(channels_[3].counter) channels_[3].counter--;
else {
did_flip = true;
channels_[3].counter = channels_[3].divider;
}
}
if(did_flip) {
channels_[3].level = noise_shifter_ & 1;
int new_bit = channels_[3].level;
switch(noise_mode_) {
default: break;
case Noise15:
new_bit ^= (noise_shifter_ >> 1);
break;
case Noise16:
new_bit ^= (noise_shifter_ >> 3);
break;
}
noise_shifter_ >>= 1;
noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14);
}
evaluate_output_volume();
for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) {
target[c] = output_volume_;
c++;
master_divider_++;
}
}
master_divider_ &= (master_divider_period_ - 1);
}

View File

@@ -0,0 +1,68 @@
//
// SN76489.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef SN76489_hpp
#define SN76489_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace TI {
class SN76489: public Outputs::Speaker::SampleSource {
public:
enum class Personality {
SN76489,
SN76494,
SMS
};
/// Creates a new SN76489.
SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
/// Writes a new value to the SN76489.
void set_register(uint8_t value);
// As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private:
int master_divider_ = 0;
int master_divider_period_ = 16;
int16_t output_volume_ = 0;
void evaluate_output_volume();
int volumes_[16];
Concurrency::DeferringAsyncTaskQueue &task_queue_;
struct ToneChannel {
// Programmatically-set state; updated by the processor.
uint16_t divider = 0;
uint8_t volume = 0xf;
// Active state; self-evolving as a function of time.
uint16_t counter = 0;
int level = 0;
} channels_[4];
enum {
Periodic15,
Periodic16,
Noise15,
Noise16
} noise_mode_ = Periodic15;
uint16_t noise_shifter_ = 0;
int active_register_ = 0;
bool shifter_is_16bit_ = false;
};
}
#endif /* SN76489_hpp */

View File

@@ -45,6 +45,7 @@ AsyncTaskQueue::AsyncTaskQueue()
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
flush();
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
#else
@@ -80,6 +81,11 @@ void AsyncTaskQueue::flush() {
#endif
}
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
perform();
flush();
}
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
if(!deferred_tasks_) {
deferred_tasks_.reset(new std::list<std::function<void(void)>>);

View File

@@ -30,7 +30,7 @@ namespace Concurrency {
class AsyncTaskQueue {
public:
AsyncTaskQueue();
~AsyncTaskQueue();
virtual ~AsyncTaskQueue();
/*!
Adds @c function to the queue.
@@ -69,6 +69,8 @@ class AsyncTaskQueue {
*/
class DeferringAsyncTaskQueue: public AsyncTaskQueue {
public:
~DeferringAsyncTaskQueue();
/*!
Adds a function to the deferral list.

View File

@@ -17,6 +17,11 @@ BestEffortUpdater::BestEffortUpdater() {
update_is_ongoing_.clear();
}
BestEffortUpdater::~BestEffortUpdater() {
// Don't allow further deconstruction until the task queue is stopped.
flush();
}
void BestEffortUpdater::update() {
// Perform an update only if one is not currently ongoing.
if(!update_is_ongoing_.test_and_set()) {
@@ -31,13 +36,11 @@ void BestEffortUpdater::update() {
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(duration > 0) {
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
error_ = fmod(cycles, 1.0);
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(integer_duration > 0) {
if(delegate_) {
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
const double duration = static_cast<double>(integer_duration) / 1e9;
delegate_->update(this, duration, has_skipped_);
}
has_skipped_ = false;
}
@@ -65,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) {
});
}
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
async_task_queue_.enqueue([this, clock_rate]() {
this->clock_rate_ = clock_rate;
});
}

View File

@@ -13,6 +13,7 @@
#include <chrono>
#include "AsyncTaskQueue.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
namespace Concurrency {
@@ -26,18 +27,16 @@ namespace Concurrency {
class BestEffortUpdater {
public:
BestEffortUpdater();
~BestEffortUpdater();
/// A delegate receives timing cues.
struct Delegate {
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
};
/// Sets the current delegate.
void set_delegate(Delegate *);
/// Sets the clock rate of the delegate.
void set_clock_rate(double clock_rate);
/*!
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
The call is asynchronous; this method will return immediately.
@@ -53,11 +52,9 @@ class BestEffortUpdater {
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
bool has_previous_time_point_ = false;
double error_ = 0.0;
bool has_skipped_ = false;
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
};
}

View File

@@ -33,7 +33,13 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std:
std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
std::vector<std::unique_ptr<Configurable::Option>> options;
if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
if(mask & DisplayRGBComposite) options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"}));
if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) {
std::vector<std::string> display_options;
if(mask & DisplayComposite) display_options.emplace_back("composite");
if(mask & DisplaySVideo) display_options.emplace_back("svideo");
if(mask & DisplayRGB) display_options.emplace_back("rgb");
options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
}
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
return options;
}
@@ -48,7 +54,14 @@ void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &s
}
void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) {
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite"));
std::string string_selection;
switch(selection) {
default:
case Display::RGB: string_selection = "rgb"; break;
case Display::SVideo: string_selection = "svideo"; break;
case Display::Composite: string_selection = "composite"; break;
}
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
}
// MARK: - Selection parsers
@@ -67,6 +80,10 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
result = Configurable::Display::RGB;
return true;
}
if(display->value == "svideo") {
result = Configurable::Display::SVideo;
return true;
}
if(display->value == "composite") {
result = Configurable::Display::Composite;
return true;

View File

@@ -14,13 +14,16 @@
namespace Configurable {
enum StandardOptions {
DisplayRGBComposite = (1 << 0),
QuickLoadTape = (1 << 1),
AutomaticTapeMotorControl = (1 << 2)
DisplayRGB = (1 << 0),
DisplaySVideo = (1 << 1),
DisplayComposite = (1 << 2),
QuickLoadTape = (1 << 3),
AutomaticTapeMotorControl = (1 << 4)
};
enum class Display {
RGB,
SVideo,
Composite
};

View File

@@ -21,18 +21,45 @@ class Joystick {
public:
virtual ~Joystick() {}
enum class DigitalInput {
Up, Down, Left, Right, Fire
struct DigitalInput {
enum Type {
Up, Down, Left, Right, Fire,
Key
} type;
union {
struct {
int index;
} control;
struct {
wchar_t symbol;
} key;
} info;
DigitalInput(Type type, int index = 0) : type(type) {
info.control.index = index;
}
DigitalInput(wchar_t symbol) : type(Key) {
info.key.symbol = symbol;
}
bool operator == (const DigitalInput &rhs) {
if(rhs.type != type) return false;
if(rhs.type == Key) {
return rhs.info.key.symbol == info.key.symbol;
} else {
return rhs.info.control.index == info.control.index;
}
}
};
virtual std::vector<DigitalInput> get_inputs() = 0;
// Host interface.
virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0;
virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0;
virtual void reset_all_inputs() {
set_digital_input(DigitalInput::Up, false);
set_digital_input(DigitalInput::Down, false);
set_digital_input(DigitalInput::Left, false);
set_digital_input(DigitalInput::Right, false);
set_digital_input(DigitalInput::Fire, false);
for(const auto &input: get_inputs()) {
set_digital_input(input, false);
}
}
};

View File

@@ -20,11 +20,17 @@
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
#include <cstdint>
#include <vector>
@@ -120,6 +126,10 @@ class AYDeferrer {
speaker_.set_input_rate(1000000);
}
~AYDeferrer() {
audio_queue_.flush();
}
/// Adds @c half_cycles half cycles to the amount of time that has passed.
inline void run_for(HalfCycles half_cycles) {
cycles_since_update_ += half_cycles;
@@ -307,7 +317,7 @@ class CRTCBusHandler {
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}");
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
}
/// Destructs the CRT.
@@ -674,6 +684,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
*/
class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public Sleeper::SleepObserver,
@@ -867,19 +880,21 @@ class ConcreteMachine:
}
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
void configure_as_target(const Analyser::Static::Target &target) override final {
switch(target.amstradcpc.model) {
case Analyser::Static::AmstradCPCModel::CPC464:
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target);
switch(cpc_target->model) {
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
rom_model_ = ROMType::OS464;
has_128k_ = false;
has_fdc_ = false;
break;
case Analyser::Static::AmstradCPCModel::CPC664:
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
rom_model_ = ROMType::OS664;
has_128k_ = false;
has_fdc_ = true;
break;
case Analyser::Static::AmstradCPCModel::CPC6128:
case Analyser::Static::AmstradCPC::Target::Model::CPC6128:
rom_model_ = ROMType::OS6128;
has_128k_ = true;
has_fdc_ = true;
@@ -901,11 +916,11 @@ class ConcreteMachine:
read_pointers_[3] = roms_[upper_rom_].data();
// Type whatever is required.
if(target.loading_command.length()) {
type_string(target.loading_command);
if(target->loading_command.length()) {
type_string(target->loading_command);
}
insert_media(target.media);
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {

View File

@@ -9,19 +9,12 @@
#ifndef AmstradCPC_hpp
#define AmstradCPC_hpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
namespace AmstradCPC {
/*!
Models an Amstrad CPC.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
class Machine {
public:
virtual ~Machine();

View File

@@ -11,6 +11,12 @@
#include <algorithm>
#include <cstdio>
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../../Analyser/Static/Atari/Target.hpp"
#include "Cartridges/Atari8k.hpp"
#include "Cartridges/Atari16k.hpp"
#include "Cartridges/Atari32k.hpp"
@@ -36,8 +42,18 @@ class Joystick: public Inputs::Joystick {
Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) :
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
void set_digital_input(DigitalInput digital_input, bool is_active) {
switch(digital_input) {
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire)
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
switch(digital_input.type) {
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
@@ -50,6 +66,8 @@ class Joystick: public Inputs::Joystick {
else
bus_->tia_input_value_[fire_tia_input_] |= 0x80;
break;
default: break;
}
}
@@ -60,11 +78,12 @@ class Joystick: public Inputs::Joystick {
class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine,
public Outputs::CRT::Delegate {
public:
ConcreteMachine() :
frame_record_pointer_(0),
is_ntsc_(true) {
ConcreteMachine() {
set_clock_rate(NTSC_clock_rate);
}
@@ -72,35 +91,38 @@ class ConcreteMachine:
close_output();
}
void configure_as_target(const Analyser::Static::Target &target) override {
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
switch(target.atari.paging_model) {
case Analyser::Static::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case Analyser::Static::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case Analyser::Static::Atari2600PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case Analyser::Static::Atari2600PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case Analyser::Static::Atari2600PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case Analyser::Static::Atari2600PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case Analyser::Static::Atari2600PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
void configure_as_target(const Analyser::Static::Target *target) override {
auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target);
const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data;
case Analyser::Static::Atari2600PagingModel::Atari8k:
if(target.atari.uses_superchip) {
using PagingModel = Analyser::Static::Atari::Target::PagingModel;
switch(atari_target->paging_model) {
case PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case PagingModel::Atari8k:
if(atari_target->uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
}
break;
case Analyser::Static::Atari2600PagingModel::Atari16k:
if(target.atari.uses_superchip) {
case PagingModel::Atari16k:
if(atari_target->uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
}
break;
case Analyser::Static::Atari2600PagingModel::Atari32k:
if(target.atari.uses_superchip) {
case PagingModel::Atari32k:
if(atari_target->uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
@@ -167,6 +189,7 @@ class ConcreteMachine:
void run_for(const Cycles cycles) override {
bus_->run_for(cycles);
bus_->apply_confidence(confidence_counter_);
}
// to satisfy Outputs::CRT::Delegate
@@ -207,6 +230,10 @@ class ConcreteMachine:
}
}
float get_confidence() override {
return confidence_counter_.get_confidence();
}
private:
// the bus
std::unique_ptr<Bus> bus_;
@@ -218,9 +245,12 @@ class ConcreteMachine:
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
} frame_records_[4];
unsigned int frame_record_pointer_;
bool is_ntsc_;
unsigned int frame_record_pointer_ = 0;
bool is_ntsc_ = true;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
// a confidence counter
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
};
}

View File

@@ -9,10 +9,6 @@
#ifndef Atari2600_cpp
#define Atari2600_cpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
@@ -20,10 +16,7 @@ namespace Atari2600 {
/*!
Models an Atari 2600.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine {
class Machine {
public:
virtual ~Machine();

View File

@@ -14,6 +14,7 @@
#include "TIA.hpp"
#include "TIASound.hpp"
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@@ -23,11 +24,14 @@ class Bus {
public:
Bus() :
tia_sound_(audio_queue_),
speaker_(tia_sound_),
tia_input_value_{0xff, 0xff},
cycles_since_speaker_update_(0) {}
speaker_(tia_sound_) {}
virtual ~Bus() {
audio_queue_.flush();
}
virtual void run_for(const Cycles cycles) = 0;
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
virtual void set_reset_line(bool state) = 0;
// the RIOT, TIA and speaker
@@ -39,7 +43,7 @@ class Bus {
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
// joystick state
uint8_t tia_input_value_[2];
uint8_t tia_input_value_[2] = {0xff, 0xff};
protected:
// speaker backlog accumlation counter

View File

@@ -39,7 +39,23 @@ template<class T> class Cartridge:
// consider doing something less fragile.
}
void run_for(const Cycles cycles) { m6502_.run_for(cycles); }
void run_for(const Cycles cycles) {
// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
// title. Random memory accesses are likely to trigger random counter resets.
horizontal_counter_resets_ = 0;
cycle_count_ = cycles;
m6502_.run_for(cycles);
}
/*!
Adjusts @c confidence_counter according to the results of the most recent run_for.
*/
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) {
if(cycle_count_.as_int() < 200) return;
if(horizontal_counter_resets_ > 10)
confidence_counter.add_miss();
}
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
// to satisfy CPU::MOS6502::Processor
@@ -108,7 +124,11 @@ template<class T> class Cartridge:
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
case 0x03:
update_video();
tia_->reset_horizontal_counter();
horizontal_counter_resets_++;
break;
// TODO: audio will now be out of synchronisation — fix
case 0x04:
@@ -189,6 +209,9 @@ template<class T> class Cartridge:
private:
T bus_extender_;
int horizontal_counter_resets_ = 0;
Cycles cycle_count_;
};
}

View File

@@ -25,7 +25,7 @@ namespace {
TIA::TIA(bool create_crt) {
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::OutputDevice::Television);
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
set_output_mode(OutputMode::NTSC);
}
@@ -123,20 +123,20 @@ 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)"
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{"
"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);"
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset));"
"}");
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)"
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"
@@ -145,10 +145,12 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
"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);"
"return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));"
"}");
display_type = Outputs::CRT::DisplayType::PAL50;
}
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// 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

View File

@@ -119,7 +119,11 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta
break;
}
target[c] += volume_[channel] * 1024 * level;
target[c] += (volume_[channel] * per_channel_volume_ * level) >> 4;
}
}
}
void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) {
per_channel_volume_ = range / 2;
}

View File

@@ -26,7 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource {
void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control);
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target);
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -41,6 +43,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
int output_state_[2];
int divider_counter_[2];
int16_t per_channel_volume_ = 0;
};
}

View File

@@ -12,8 +12,13 @@
#include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
#include "ROMMachine.hpp"
#include "../Configurable/StandardOptions.hpp"
#include <cmath>
namespace CRTMachine {
/*!
@@ -41,45 +46,58 @@ class Machine: public ROMMachine::Machine {
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
/// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; }
virtual void print_type() {}
// TODO: sever the clock-rate stuff.
virtual double get_clock_rate() {
return clock_rate_;
/// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) {
const double cycles = (duration * clock_rate_) + clock_conversion_error_;
clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(static_cast<int>(cycles)));
}
virtual bool get_clock_is_unlimited() {
return clock_is_unlimited_;
}
class Delegate {
public:
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected:
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
void set_clock_rate(double clock_rate) {
if(clock_rate_ != clock_rate) {
clock_rate_ = clock_rate;
if(delegate_) delegate_->machine_did_change_clock_rate(this);
}
clock_rate_ = clock_rate;
}
void set_clock_is_unlimited(bool clock_is_unlimited) {
if(clock_is_unlimited != clock_is_unlimited_) {
clock_is_unlimited_ = clock_is_unlimited;
if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this);
double get_clock_rate() {
return clock_rate_;
}
/*!
Maps from Configurable::Display to Outputs::CRT::VideoSignal and calls
@c set_video_signal with the result.
*/
void set_video_signal_configurable(Configurable::Display type) {
Outputs::CRT::VideoSignal signal;
switch(type) {
default:
case Configurable::Display::RGB:
signal = Outputs::CRT::VideoSignal::RGB;
break;
case Configurable::Display::SVideo:
signal = Outputs::CRT::VideoSignal::SVideo;
break;
case Configurable::Display::Composite:
signal = Outputs::CRT::VideoSignal::Composite;
break;
}
set_video_signal(signal);
}
/*!
Forwards the video signal to the CRT returned by get_crt().
*/
virtual void set_video_signal(Outputs::CRT::VideoSignal video_signal) {
get_crt()->set_video_signal(video_signal);
}
private:
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
bool clock_is_unlimited_ = false;
double clock_conversion_error_ = 0.0;
};
}

View File

@@ -0,0 +1,405 @@
//
// ColecoVision.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ColecoVision.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/9918/9918.hpp"
#include "../../Components/AY38910/AY38910.hpp" // For the Super Game Module.
#include "../../Components/SN76489/SN76489.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
namespace {
const int sn76489_divider = 2;
}
namespace Coleco {
namespace Vision {
class Joystick: public Inputs::Joystick {
public:
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire, 0),
DigitalInput(DigitalInput::Fire, 1),
DigitalInput('0'), DigitalInput('1'), DigitalInput('2'),
DigitalInput('3'), DigitalInput('4'), DigitalInput('5'),
DigitalInput('6'), DigitalInput('7'), DigitalInput('8'),
DigitalInput('9'), DigitalInput('*'), DigitalInput('#'),
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
switch(digital_input.type) {
default: return;
case DigitalInput::Key:
if(!is_active) keypad_ |= 0xf;
else {
uint8_t mask = 0xf;
switch(digital_input.info.key.symbol) {
case '8': mask = 0x1; break;
case '4': mask = 0x2; break;
case '5': mask = 0x3; break;
case '7': mask = 0x5; break;
case '#': mask = 0x6; break;
case '2': mask = 0x7; break;
case '*': mask = 0x9; break;
case '0': mask = 0xa; break;
case '9': mask = 0xb; break;
case '3': mask = 0xc; break;
case '1': mask = 0xd; break;
case '6': mask = 0xe; break;
default: break;
}
keypad_ = (keypad_ & 0xf0) | mask;
}
break;
case DigitalInput::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case DigitalInput::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case DigitalInput::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case DigitalInput::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
case DigitalInput::Fire:
switch(digital_input.info.control.index) {
default: break;
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break;
}
break;
}
}
uint8_t get_direction_input() {
return direction_;
}
uint8_t get_keypad_input() {
return keypad_;
}
private:
uint8_t direction_ = 0xff;
uint8_t keypad_ = 0xff;
};
class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine {
public:
ConcreteMachine() :
z80_(*this),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
ay_(audio_queue_),
mixer_(sn76489_, ay_),
speaker_(mixer_) {
speaker_.set_input_rate(3579545.0f / static_cast<float>(sn76489_divider));
set_clock_rate(3579545);
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
~ConcreteMachine() {
audio_queue_.flush();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
void setup_output(float aspect_ratio) override {
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
}
void close_output() override {
vdp_.reset();
}
Outputs::CRT::CRT *get_crt() override {
return vdp_->get_crt();
}
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
}
void run_for(const Cycles cycles) override {
z80_.run_for(cycles);
}
void configure_as_target(const Analyser::Static::Target *target) override {
// Insert the media.
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
if(cartridge_.size() >= 32768)
cartridge_address_limit_ = 0xffff;
else
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
if(cartridge_.size() > 32768) {
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
cartridge_pages_[1] = cartridge_.data();
is_megacart_ = true;
} else {
cartridge_pages_[0] = cartridge_.data();
cartridge_pages_[1] = cartridge_.data() + 16384;
is_megacart_ = false;
}
}
return true;
}
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
"ColecoVision",
{ "coleco.rom" });
if(!roms[0]) return false;
bios_ = *roms[0];
bios_.resize(8192);
return true;
}
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
time_since_vdp_update_ += cycle.length;
time_since_sn76489_update_ += cycle.length;
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(!address) pc_zero_accesses_++;
case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
if(super_game_module_.replace_bios) {
*cycle.value = super_game_module_.ram[address];
} else {
*cycle.value = bios_[address];
}
} else if(super_game_module_.replace_ram && address < 0x8000) {
*cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
} else {
*cycle.value = 0xff;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(super_game_module_.replace_bios && address < 0x2000) {
super_game_module_.ram[address] = *cycle.value;
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
super_game_module_.ram[address] = *cycle.value;
} else if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
} else if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
break;
case CPU::Z80::PartialMachineCycle::Input:
switch((address >> 5) & 7) {
case 5:
update_video();
*cycle.value = vdp_->get_register(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7: {
const std::size_t joystick_id = (address&2) >> 1;
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
if(joysticks_in_keypad_mode_) {
*cycle.value = joystick->get_keypad_input();
} else {
*cycle.value = joystick->get_direction_input();
}
// Hitting exactly the recommended joypad input port is an indicator that
// this really is a ColecoVision game. The BIOS won't do this when just waiting
// to start a game (unlike accessing the VDP and SN).
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
} break;
default:
switch(address&0xff) {
default: *cycle.value = 0xff; break;
case 0x52:
// Read AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
}
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int eighth = (address >> 5) & 7;
switch(eighth) {
case 4: case 6:
joysticks_in_keypad_mode_ = eighth == 4;
break;
case 5:
update_video();
vdp_->set_register(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7:
update_audio();
sn76489_.set_register(*cycle.value);
break;
default:
// Catch Super Game Module accesses; it decodes more thoroughly.
switch(address&0xff) {
default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x51:
// Set AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
default: break;
}
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= cycle.length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_);
}
}
return HalfCycles(0);
}
void flush() {
update_video();
update_audio();
audio_queue_.perform();
}
float get_confidence() override {
if(pc_zero_accesses_ > 1) return 0.0f;
return confidence_counter_.get_confidence();
}
private:
inline void page_megacart(uint16_t address) {
const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size();
cartridge_pages_[1] = &cartridge_[selected_start];
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
}
inline void update_video() {
vdp_->run_for(time_since_vdp_update_.flush());
}
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
std::unique_ptr<TI::TMS9918> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910>> speaker_;
std::vector<uint8_t> bios_;
std::vector<uint8_t> cartridge_;
uint8_t *cartridge_pages_[2];
uint8_t ram_[1024];
bool is_megacart_ = false;
uint16_t cartridge_address_limit_ = 0;
struct {
bool replace_bios = false;
bool replace_ram = false;
uint8_t ram[32768];
} super_game_module_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool joysticks_in_keypad_mode_ = false;
HalfCycles time_since_vdp_update_;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
int pc_zero_accesses_ = 0;
};
}
}
using namespace Coleco::Vision;
Machine *Machine::ColecoVision() {
return new ConcreteMachine;
}
Machine::~Machine() {}

View File

@@ -0,0 +1,24 @@
//
// ColecoVision.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ColecoVision_hpp
#define ColecoVision_hpp
namespace Coleco {
namespace Vision {
class Machine {
public:
virtual ~Machine();
static Machine *ColecoVision();
};
}
}
#endif /* ColecoVision_hpp */

View File

@@ -165,14 +165,15 @@ void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint
attention_acknowledge_level_ = !(value&0x10);
data_level_output_ = (value&0x02);
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!(value&0x08));
// printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Clock), value ? "high" : "low");
serialPort->set_output(::Commodore::Serial::Line::Clock, static_cast<::Commodore::Serial::LineLevel>(!(value&0x08)));
update_data_line();
}
}
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
// printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low");
// printf("[C1540] %s input is %s\n", StringForLine(line), value ? "high" : "low");
switch(line) {
default: break;
@@ -194,9 +195,10 @@ void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::P
void SerialPortVIA::update_data_line() {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Data), (!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)) ? "high" : "low");
// "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_)));
static_cast<::Commodore::Serial::LineLevel>(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
}
}

View File

@@ -10,6 +10,11 @@
#include "Keyboard.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
@@ -26,6 +31,8 @@
#include "../../../Configurable/StandardOptions.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
#include <algorithm>
#include <cstdint>
@@ -40,7 +47,9 @@ enum ROMSlot {
};
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(Configurable::QuickLoadTape);
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape)
);
}
enum JoystickInput {
@@ -252,9 +261,19 @@ class Joystick: public Inputs::Joystick {
user_port_via_port_handler_(user_port_via_port_handler),
keyboard_via_port_handler_(keyboard_via_port_handler) {}
void set_digital_input(DigitalInput digital_input, bool is_active) override {
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire)
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
JoystickInput mapped_input;
switch (digital_input) {
switch(digital_input.type) {
default: return;
case DigitalInput::Up: mapped_input = Up; break;
case DigitalInput::Down: mapped_input = Down; break;
@@ -273,11 +292,17 @@ class Joystick: public Inputs::Joystick {
};
class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Machine {
public Machine,
public Sleeper::SleepObserver {
public:
ConcreteMachine() :
m6502_(*this),
@@ -303,17 +328,16 @@ class ConcreteMachine:
user_port_via_port_handler_->set_interrupt_delegate(this);
keyboard_via_port_handler_->set_interrupt_delegate(this);
tape_->set_delegate(this);
tape_->set_sleep_observer(this);
// install a joystick
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
}
~ConcreteMachine() {
delete[] rom_;
}
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
rom_fetcher_ = roms_with_names;
auto roms = roms_with_names(
"Vic20",
{
@@ -343,24 +367,14 @@ class ConcreteMachine:
return true;
}
void configure_as_target(const Analyser::Static::Target &target) override final {
if(target.loading_command.length()) {
type_string(target.loading_command);
void configure_as_target(const Analyser::Static::Target *target) override final {
commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target);
if(target->loading_command.length()) {
type_string(target->loading_command);
}
switch(target.vic20.memory_model) {
case Analyser::Static::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
case Analyser::Static::Vic20MemoryModel::EightKB:
set_memory_size(ThreeKB);
break;
case Analyser::Static::Vic20MemoryModel::ThirtyTwoKB:
set_memory_size(ThirtyTwoKB);
break;
}
if(target.media.disks.size()) {
if(target->media.disks.size()) {
// construct the 1540
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
@@ -369,9 +383,12 @@ class ConcreteMachine:
// give it a means to obtain its ROM
c1540_->set_rom_fetcher(rom_fetcher_);
// give it a little warm up
c1540_->run_for(Cycles(2000000));
}
insert_media(target.media);
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
@@ -388,9 +405,8 @@ class ConcreteMachine:
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
rom_length_ = static_cast<uint16_t>(rom_image.size());
rom_ = new uint8_t[0x2000];
std::memcpy(rom_, rom_image.data(), rom_image.size());
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
rom_ = rom_image;
rom_.resize(0x2000);
}
set_use_fast_tape();
@@ -413,16 +429,6 @@ class ConcreteMachine:
return joysticks_;
}
void set_memory_size(MemorySize size) override final {
memory_size_ = size;
needs_configuration_ = true;
}
void set_region(Region region) override final {
region_ = region;
needs_configuration_ = true;
}
void set_ntsc_6560() {
set_clock_rate(1022727);
if(mos6560_) {
@@ -439,9 +445,9 @@ class ConcreteMachine:
}
}
void configure_memory() {
void set_memory_map(Analyser::Static::Commodore::Target::MemoryModel memory_model, Analyser::Static::Commodore::Target::Region region) {
// Determine PAL/NTSC
if(region_ == American || region_ == Japanese) {
if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) {
// NTSC
set_ntsc_6560();
} else {
@@ -449,57 +455,73 @@ class ConcreteMachine:
set_pal_6560();
}
// Initialise the memory maps as all pointing to nothing
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
switch(memory_size_) {
default: break;
case ThreeKB:
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000);
#define set_ram(baseaddr, length) \
write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \
write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length);
// Add 6502-visible RAM as requested
switch(memory_model) {
case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded:
// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000.
set_ram(0x0000, 0x0400);
set_ram(0x1000, 0x1000);
break;
case ThirtyTwoKB:
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000);
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000);
case Analyser::Static::Commodore::Target::MemoryModel::EightKB:
// An 8kb Vic-20 fills in the gap between the two blocks of RAM on an unexpanded machine.
set_ram(0x0000, 0x2000);
break;
case Analyser::Static::Commodore::Target::MemoryModel::ThirtyTwoKB:
// A 32kb Vic-20 fills the entire lower 32kb with RAM.
set_ram(0x0000, 0x8000);
break;
}
// install the system ROMs and VIC-visible memory
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_));
#undef set_ram
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_));
// all expansions also have colour RAM visible at 0x9400.
write_to_map(processor_read_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
write_to_map(processor_write_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
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_;
// also push memory resources into the 6560 video memory map; the 6560 has only a
// 14-bit address bus and the top bit is invested and used as bit 15 for the main
// memory bus.
for(int addr = 0; addr < 0x4000; addr += 0x400) {
int source_address = (addr & 0x1fff) | (((addr & 0x2000) << 2) ^ 0x8000);
if(processor_read_memory_map_[source_address >> 10]) {
write_to_map(mos6560_->video_memory_map, &ram_[source_address], static_cast<uint16_t>(addr), 0x400);
}
}
mos6560_->colour_memory = colour_ram_;
// install the BASIC ROM
write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size()));
// install the system ROM
ROM character_rom;
ROM kernel_rom;
switch(region_) {
switch(region) {
default:
character_rom = CharactersEnglish;
kernel_rom = KernelPAL;
break;
case American:
case Analyser::Static::Commodore::Target::Region::American:
character_rom = CharactersEnglish;
kernel_rom = KernelNTSC;
break;
case Danish:
case Analyser::Static::Commodore::Target::Region::Danish:
character_rom = CharactersDanish;
kernel_rom = KernelDanish;
break;
case Japanese:
case Analyser::Static::Commodore::Target::Region::Japanese:
character_rom = CharactersJapanese;
kernel_rom = KernelJapanese;
break;
case Swedish:
case Analyser::Static::Commodore::Target::Region::Swedish:
character_rom = CharactersSwedish;
kernel_rom = KernelSwedish;
break;
@@ -510,30 +532,30 @@ class ConcreteMachine:
write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size()));
// install the inserted ROM if there is one
if(rom_) {
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
if(!rom_.empty()) {
write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_);
}
}
// to satisfy CPU::MOS6502::Processor
forceinline Cycles perform_bus_operation(CPU::MOS6502::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));
cycles_since_mos6560_update_++;
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
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&0xff00) == 0x9000) {
update_video();
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;
// This combined with the stuff below constitutes the fast tape hack. Performed here: if the
// 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.
// Consider applying the fast tape hack.
if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
if(address == 0xf7b2) {
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
@@ -541,58 +563,78 @@ class ConcreteMachine:
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 = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8);
const uint64_t tape_position = tape_->get_tape()->get_offset();
if(header) {
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
// serialise to wherever b2:b3 points
const uint16_t tape_buffer_pointer = static_cast<uint16_t>(ram_[0xb2]) | static_cast<uint16_t>(ram_[0xb3] << 8);
header->serialise(&ram_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
hold_tape_ = true;
printf("Found header\n");
} else {
// no header found, so store end-of-tape
user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape
// no header found, so pretend this hack never interceded
tape_->get_tape()->set_offset(tape_position);
hold_tape_ = false;
printf("Didn't find header\n");
}
// clear status and the verify flag
user_basic_memory_[0x90] = 0;
user_basic_memory_[0x93] = 0;
ram_[0x90] = 0;
ram_[0x93] = 0;
*value = 0x0c; // i.e. NOP abs
*value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
} else if(address == 0xf90b) {
uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::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 = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
const uint64_t tape_position = tape_->get_tape()->get_offset();
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
if(data) {
uint16_t start_address, end_address;
start_address = static_cast<uint16_t>(ram_[0xc1] | (ram_[0xc2] << 8));
end_address = static_cast<uint16_t>(ram_[0xae] | (ram_[0xaf] << 8));
// perform a via-processor_write_memory_map_ memcpy
uint8_t *data_ptr = data->data.data();
std::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--;
// perform a via-processor_write_memory_map_ memcpy
uint8_t *data_ptr = data->data.data();
std::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
ram_[0x90] |= 0x40;
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
// ensure that the PC leaps to 0xfccf
m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
hold_tape_ = true;
printf("Found data\n");
} else {
tape_->get_tape()->set_offset(tape_position);
hold_tape_ = false;
printf("Didn't find data\n");
}
// set tape status, carry and flag
user_basic_memory_[0x90] |= 0x40;
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
// ensure that the PC leaps to 0xfccf
m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
}
}
}
} else {
uint8_t *ram = processor_write_memory_map_[address >> 10];
if(ram) ram[address & 0x3ff] = *value;
if(ram) {
update_video();
ram[address & 0x3ff] = *value;
}
if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
if((address&0xff00) == 0x9000) {
update_video();
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);
}
@@ -606,21 +648,18 @@ class ConcreteMachine:
typer_.reset();
}
}
tape_->run_for(Cycles(1));
if(!tape_is_sleeping_ && !hold_tape_) tape_->run_for(Cycles(1));
if(c1540_) c1540_->run_for(Cycles(1));
return Cycles(1);
}
forceinline void flush() {
void flush() {
update_video();
mos6560_->flush();
}
void run_for(const Cycles cycles) override final {
if(needs_configuration_) {
needs_configuration_ = false;
configure_memory();
}
m6502_.run_for(cycles);
}
@@ -628,7 +667,7 @@ class ConcreteMachine:
mos6560_.reset(new Vic6560());
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
set_pal_6560();
set_memory_map(commodore_target_.memory_model, commodore_target_.region);
}
void close_output() override final {
@@ -672,21 +711,38 @@ class ConcreteMachine:
allow_fast_tape_hack_ = quickload;
set_use_fast_tape();
}
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::Composite);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_display_selection(selection_set, Configurable::Display::SVideo);
return selection_set;
}
void set_component_is_sleeping(void *component, bool is_sleeping) override {
tape_is_sleeping_ = is_sleeping;
set_use_fast_tape();
}
private:
void update_video() {
mos6560_->run_for(cycles_since_mos6560_update_.flush());
}
Analyser::Static::Commodore::Target commodore_target_;
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
std::vector<uint8_t> roms_[9];
@@ -694,14 +750,11 @@ class ConcreteMachine:
std::vector<uint8_t> character_rom_;
std::vector<uint8_t> basic_rom_;
std::vector<uint8_t> kernel_rom_;
uint8_t expansion_ram_[0x8000];
uint8_t *rom_ = nullptr;
std::vector<uint8_t> rom_;
uint16_t rom_address_, rom_length_;
uint8_t user_basic_memory_[0x0400];
uint8_t screen_memory_[0x1000];
uint8_t colour_memory_[0x0400];
uint8_t ram_[0x8000];
uint8_t colour_ram_[0x0400];
std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_;
@@ -717,13 +770,10 @@ class ConcreteMachine:
}
}
Region region_ = European;
MemorySize memory_size_ = MemorySize::Default;
bool needs_configuration_ = true;
Commodore::Vic20::KeyboardMapper keyboard_mapper_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Cycles cycles_since_mos6560_update_;
std::unique_ptr<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
@@ -736,10 +786,11 @@ class ConcreteMachine:
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_ = false;
bool hold_tape_ = false;
bool allow_fast_tape_hack_ = false;
bool is_running_at_zero_cost_ = false;
bool tape_is_sleeping_ = true;
void set_use_fast_tape() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_->has_tape();
use_fast_tape_hack_ = !tape_is_sleeping_ && allow_fast_tape_hack_ && tape_->has_tape();
}
// Disk

View File

@@ -10,48 +10,19 @@
#define Vic20_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
namespace Commodore {
namespace Vic20 {
enum MemorySize {
Default,
ThreeKB,
ThirtyTwoKB
};
enum Region {
American,
Danish,
Japanese,
European,
Swedish
};
/// @returns The options available for a Vic-20.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine,
public Configurable::Device {
class Machine {
public:
virtual ~Machine();
/// Creates and returns a Vic-20.
static Machine *Vic20();
/// Sets the memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 0;
};
}

View File

@@ -23,7 +23,7 @@ namespace ConfigurationTarget {
class Machine {
public:
/// Instructs the machine to configure itself as described by @c target and insert the included media.
virtual void configure_as_target(const Analyser::Static::Target &target) = 0;
virtual void configure_as_target(const Analyser::Static::Target *target) = 0;
/*!
Requests that the machine insert @c media as a modification to current state

View File

@@ -8,6 +8,10 @@
#include "Electron.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/StandardOptions.hpp"
@@ -16,6 +20,7 @@
#include "../../Storage/Tape/Tape.hpp"
#include "../Utility/Typer.hpp"
#include "../../Analyser/Static/Acorn/Target.hpp"
#include "Interrupts.hpp"
#include "Keyboard.hpp"
@@ -28,12 +33,16 @@ namespace Electron {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape)
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape)
);
}
class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public Tape::Delegate,
public Utility::TypeRecipient {
@@ -52,6 +61,10 @@ class ConcreteMachine:
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
}
~ConcreteMachine() {
audio_queue_.flush();
}
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final {
uint8_t *target = nullptr;
switch(slot) {
@@ -115,28 +128,30 @@ class ConcreteMachine:
if(is_holding_shift_) set_key_state(KeyShift, true);
}
void configure_as_target(const Analyser::Static::Target &target) override final {
if(target.loading_command.length()) {
type_string(target.loading_command);
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target);
if(target->loading_command.length()) {
type_string(target->loading_command);
}
if(target.acorn.should_shift_restart) {
if(acorn_target->should_shift_restart) {
shift_restart_counter_ = 1000000;
}
if(target.acorn.has_dfs || target.acorn.has_adfs) {
if(acorn_target->has_dfs || acorn_target->has_adfs) {
plus3_.reset(new Plus3);
if(target.acorn.has_dfs) {
if(acorn_target->has_dfs) {
set_rom(ROMSlot0, dfs_, true);
}
if(target.acorn.has_adfs) {
if(acorn_target->has_adfs) {
set_rom(ROMSlot4, adfs1_, true);
set_rom(ROMSlot5, adfs2_, true);
}
}
insert_media(target.media);
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
@@ -439,7 +454,7 @@ class ConcreteMachine:
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
set_video_signal_configurable(display);
}
}

View File

@@ -10,9 +10,6 @@
#define Electron_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
@@ -42,11 +39,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options();
@discussion An instance of Electron::Machine represents the current state of an
Acorn Electron.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
class Machine {
public:
virtual ~Machine();

View File

@@ -15,10 +15,14 @@ using namespace Electron;
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void SoundGenerator::set_sample_volume_range(std::int16_t range) {
volume_ = static_cast<unsigned int>(range / 2);
}
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
if(is_enabled_) {
while(number_of_samples--) {
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
*target = static_cast<int16_t>((counter_ / (divider_+1)) * volume_);
target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2);
}

View File

@@ -22,16 +22,19 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
void set_is_enabled(bool is_enabled);
static const unsigned int clock_rate_divider = 8;
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
static const unsigned int clock_rate_divider = 8;
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counter_ = 0;
unsigned int divider_ = 0;
bool is_enabled_ = false;
unsigned int volume_ = 0;
};
}

View File

@@ -16,21 +16,30 @@
namespace KeyboardMachine {
class Machine: public Inputs::Keyboard::Delegate {
/*!
Covers just the actions necessary to communicate keyboard state, as a purely abstract class.
*/
struct KeyActions {
/*!
Indicates that the key @c key has been either pressed or released, according to
the state of @c isPressed.
*/
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
/*!
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() = 0;
};
/*!
Describes the full functionality of being an emulated machine with a keyboard: not just being
able to receive key actions, but being able to vend a generic keyboard and a keyboard mapper.
*/
class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
public:
Machine();
/*!
Indicates that the key @c key has been either pressed or released, according to
the state of @c isPressed.
*/
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
/*!
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() = 0;
/*!
Causes the machine to attempt to type the supplied string.

View File

@@ -40,11 +40,13 @@
#include "../../Configurable/StandardOptions.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Analyser/Static/MSX/Target.hpp"
namespace MSX {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape)
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape)
);
}
@@ -62,13 +64,17 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
}
}
void set_sample_volume_range(std::int16_t range) {
volume_ = range;
}
void skip_samples(const std::size_t number_of_samples) {}
void set_output(bool enabled) {
if(is_enabled_ == enabled) return;
is_enabled_ = enabled;
audio_queue_.defer([=] {
level_ = enabled ? 4096 : 0;
level_ = enabled ? volume_ : 0;
});
}
@@ -78,7 +84,7 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
private:
bool is_enabled_ = false;
int16_t level_ = 0;
int16_t level_ = 0, volume_ = 0;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
};
@@ -138,6 +144,13 @@ class ConcreteMachine:
ay_.set_port_handler(&ay_port_handler_);
speaker_.set_input_rate(3579545.0f / 2.0f);
tape_player_.set_sleep_observer(this);
// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC.
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
}
~ConcreteMachine() {
audio_queue_.flush();
}
void setup_output(float aspect_ratio) override {
@@ -174,20 +187,22 @@ class ConcreteMachine:
}
}
void configure_as_target(const Analyser::Static::Target &target) override {
void configure_as_target(const Analyser::Static::Target *target) override {
auto *const msx_target = dynamic_cast<const Analyser::Static::MSX::Target *>(target);
// Add a disk cartridge if any disks were supplied.
if(!target.media.disks.empty()) {
if(msx_target->has_disk_drive) {
map(2, 0, 0x4000, 0x2000);
unmap(2, 0x6000, 0x2000);
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
}
// Insert the media.
insert_media(target.media);
insert_media(target->media);
// Type whatever has been requested.
if(target.loading_command.length()) {
type_string(target.loading_command);
if(target->loading_command.length()) {
type_string(target->loading_command);
}
}
@@ -282,12 +297,16 @@ class ConcreteMachine:
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= cycle.length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_interrupt_line(true, time_until_interrupt_);
}
}
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
// but otherwise runs without pause.
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
const HalfCycles total_length = addition + cycle.length;
time_since_vdp_update_ += total_length;
time_since_ay_update_ += total_length;
memory_slots_[0].cycles_since_update += total_length;
memory_slots_[1].cycles_since_update += total_length;
memory_slots_[2].cycles_since_update += total_length;
memory_slots_[3].cycles_since_update += total_length;
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
@@ -466,15 +485,12 @@ class ConcreteMachine:
if(!tape_player_is_sleeping_)
tape_player_.run_for(cycle.length.as_int());
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
// but otherwise runs without pause.
HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
time_since_vdp_update_ += cycle.length + addition;
time_since_ay_update_ += cycle.length + addition;
memory_slots_[0].cycles_since_update += cycle.length + addition;
memory_slots_[1].cycles_since_update += cycle.length + addition;
memory_slots_[2].cycles_since_update += cycle.length + addition;
memory_slots_[3].cycles_since_update += cycle.length + addition;
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= total_length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_interrupt_line(true, time_until_interrupt_);
}
}
return addition;
}
@@ -553,7 +569,7 @@ class ConcreteMachine:
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
set_video_signal_configurable(display);
}
}

View File

@@ -12,6 +12,10 @@
#include "Microdisc.hpp"
#include "Video.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
@@ -26,6 +30,8 @@
#include "../../Configurable/StandardOptions.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Static/Oric/Target.hpp"
#include <cstdint>
#include <memory>
#include <vector>
@@ -38,7 +44,7 @@ enum ROM {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape)
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape)
);
}
@@ -184,6 +190,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
};
class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
@@ -205,6 +215,10 @@ class ConcreteMachine:
Memory::Fuzz(ram_, sizeof(ram_));
}
~ConcreteMachine() {
audio_queue_.flush();
}
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
@@ -249,23 +263,21 @@ class ConcreteMachine:
use_fast_tape_hack_ = activate;
}
void set_output_device(Outputs::CRT::OutputDevice output_device) {
video_output_->set_output_device(output_device);
}
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const Analyser::Static::Target &target) override final {
if(target.oric.has_microdisc) {
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target);
if(oric_target->has_microdrive) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
if(target.loading_command.length()) {
type_string(target.loading_command);
if(target->loading_command.length()) {
type_string(target->loading_command);
}
if(target.oric.use_atmos_rom) {
if(oric_target->use_atmos_rom) {
std::memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
@@ -281,7 +293,7 @@ class ConcreteMachine:
tape_speed_address_ = 0x67;
}
insert_media(target.media);
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
@@ -376,7 +388,7 @@ class ConcreteMachine:
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
set_output_device(Outputs::CRT::OutputDevice::Monitor);
set_video_signal(Outputs::CRT::VideoSignal::RGB);
}
void close_output() override final {
@@ -449,10 +461,14 @@ class ConcreteMachine:
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
set_video_signal_configurable(display);
}
}
void set_video_signal(Outputs::CRT::VideoSignal video_signal) override {
video_output_->set_video_signal(video_signal);
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);

View File

@@ -10,9 +10,6 @@
#define Oric_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
namespace Oric {
@@ -22,11 +19,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options();
/*!
Models an Oric 1/Atmos with or without a Microdisc.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
class Machine {
public:
virtual ~Machine();

View File

@@ -41,13 +41,13 @@ VideoOutput::VideoOutput(uint8_t *memory) :
);
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
set_output_device(Outputs::CRT::OutputDevice::Television);
set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
output_device_ = output_device;
crt_->set_output_device(output_device);
void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) {
video_signal_ = video_signal;
crt_->set_video_signal(video_signal);
}
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
@@ -129,7 +129,7 @@ void VideoOutput::run_for(const Cycles cycles) {
if(control_byte & 0x60) {
if(pixel_target_) {
uint16_t colours[2];
if(output_device_ == Outputs::CRT::OutputDevice::Monitor) {
if(video_signal_ == Outputs::CRT::VideoSignal::RGB) {
colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask);
colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask);
} else {
@@ -183,7 +183,7 @@ void VideoOutput::run_for(const Cycles cycles) {
pixel_target_[0] = pixel_target_[1] =
pixel_target_[2] = pixel_target_[3] =
pixel_target_[4] = pixel_target_[5] =
(output_device_ == Outputs::CRT::OutputDevice::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
(video_signal_ == Outputs::CRT::VideoSignal::RGB) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
}
}
if(pixel_target_) pixel_target_ += 6;

View File

@@ -20,7 +20,7 @@ class VideoOutput {
Outputs::CRT::CRT *get_crt();
void run_for(const Cycles cycles);
void set_colour_rom(const std::vector<uint8_t> &rom);
void set_output_device(Outputs::CRT::OutputDevice output_device);
void set_video_signal(Outputs::CRT::VideoSignal output_device);
private:
uint8_t *ram_;
@@ -33,7 +33,7 @@ class VideoOutput {
// Output target and device
uint16_t *pixel_target_;
uint16_t colour_forms_[8];
Outputs::CRT::OutputDevice output_device_;
Outputs::CRT::VideoSignal video_signal_;
// Registers
uint8_t ink_, paper_;

View File

@@ -10,6 +10,7 @@
#include "../AmstradCPC/AmstradCPC.hpp"
#include "../Atari2600/Atari2600.hpp"
#include "../ColecoVision/ColecoVision.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
#include "../MSX/MSX.hpp"
@@ -21,17 +22,18 @@
namespace {
::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) {
::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) {
error = Machine::Error::None;
::Machine::DynamicMachine *machine = nullptr;
switch(target.machine) {
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); break;
case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric()); break;
case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20()); break;
case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target)); break;
switch(target->machine) {
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break;
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); break;
case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric()); break;
case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20()); break;
case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target)); break;
default:
error = Machine::Error::UnknownMachine;
@@ -69,7 +71,7 @@ namespace {
if(targets.size() > 1) {
std::vector<std::unique_ptr<Machine::DynamicMachine>> machines;
for(const auto &target: targets) {
machines.emplace_back(MachineForTarget(*target, rom_fetcher, error));
machines.emplace_back(MachineForTarget(target.get(), rom_fetcher, error));
// Exit early if any errors have occurred.
if(error != Error::None) {
@@ -77,17 +79,24 @@ namespace {
}
}
return new Analyser::Dynamic::MultiMachine(std::move(machines));
// If a multimachine would just instantly collapse the list to a single machine, do
// so without the ongoing baggage of a multimachine.
if(Analyser::Dynamic::MultiMachine::would_collapse(machines)) {
return machines.front().release();
} else {
return new Analyser::Dynamic::MultiMachine(std::move(machines));
}
}
// There's definitely exactly one target.
return MachineForTarget(*targets.front(), rom_fetcher, error);
return MachineForTarget(targets.front().get(), rom_fetcher, error);
}
std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) {
switch(machine) {
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
case Analyser::Machine::Atari2600: return "Atari2600";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";
@@ -102,6 +111,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
switch(machine) {
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
case Analyser::Machine::Atari2600: return "Atari 2600";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";

View File

@@ -47,7 +47,7 @@ class CharacterMapper {
*/
class Typer {
public:
class Delegate: public KeyboardMachine::Machine {
class Delegate: public KeyboardMachine::KeyActions {
public:
virtual void typer_reset(Typer *typer) = 0;
};
@@ -56,7 +56,6 @@ class Typer {
void run_for(const HalfCycles duration);
bool type_next_character();
bool is_completed();
const char BeginString = 0x02; // i.e. ASCII start of text
@@ -81,7 +80,7 @@ class Typer {
which may or may not want to nominate an initial delay and typing frequency.
*/
class TypeRecipient: public Typer::Delegate {
public:
protected:
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
void add_typer(const std::string &string, std::unique_ptr<CharacterMapper> character_mapper) {
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this));
@@ -101,7 +100,6 @@ class TypeRecipient: public Typer::Delegate {
typer_ = nullptr;
}
protected:
virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
std::unique_ptr<Typer> typer_;

View File

@@ -22,6 +22,7 @@ Video::Video() :
"}");
// Show only the centre 80% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
}

View File

@@ -8,6 +8,11 @@
#include "ZX8081.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
@@ -18,6 +23,10 @@
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Static/ZX8081/Target.hpp"
#include "Keyboard.hpp"
#include "Video.hpp"
@@ -31,6 +40,11 @@ namespace {
const unsigned int ZX8081ClockRate = 3250000;
}
// TODO:
// Quiksilva sound support:
// 7FFFh.W PSG index
// 7FFEh.R/W PSG data
namespace ZX8081 {
enum ROMType: uint8_t {
@@ -44,20 +58,32 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
}
template<bool is_zx81> class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public Machine {
public:
ConcreteMachine() :
z80_(*this),
tape_player_(ZX8081ClockRate) {
tape_player_(ZX8081ClockRate),
ay_(audio_queue_),
speaker_(ay_) {
set_clock_rate(ZX8081ClockRate);
speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f);
clear_all_keys();
}
~ConcreteMachine() {
audio_queue_.flush();
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
HalfCycles previous_counter = horizontal_counter_;
const HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length;
time_since_ay_update_ += cycle.length;
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_->run_for(vsync_start_ - previous_counter);
@@ -94,7 +120,7 @@ template<bool is_zx81> class ConcreteMachine:
return Cycles(0);
}
uint16_t address = cycle.address ? *cycle.address : 0;
const uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output:
@@ -106,6 +132,15 @@ template<bool is_zx81> class ConcreteMachine:
if(vsync_) line_counter_ = 0;
set_vsync(false);
}
// The below emulates the ZonX AY expansion device.
if(is_zx81) {
if((address&0xef) == 0xcf) {
ay_set_register(*cycle.value);
} else if((address&0xef) == 0x0f) {
ay_set_data(*cycle.value);
}
}
break;
case CPU::Z80::PartialMachineCycle::Input: {
@@ -121,6 +156,13 @@ template<bool is_zx81> class ConcreteMachine:
value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
}
// The below emulates the ZonX AY expansion device.
if(is_zx81) {
if((address&0xef) == 0x0f) {
value &= ay_read_data();
}
}
*cycle.value = value;
} break;
@@ -144,7 +186,7 @@ template<bool is_zx81> class ConcreteMachine:
}
if(has_latched_video_byte_) {
std::size_t char_address = static_cast<std::size_t>((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) {
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
} else {
@@ -159,10 +201,10 @@ template<bool is_zx81> class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) {
uint64_t prior_offset = tape_player_.get_tape()->get_offset();
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
const uint64_t prior_offset = tape_player_.get_tape()->get_offset();
const int next_byte = parser_.get_next_byte(tape_player_.get_tape());
if(next_byte != -1) {
uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
const uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = static_cast<uint8_t>(next_byte);
*cycle.value = 0x00;
z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
@@ -187,7 +229,7 @@ template<bool is_zx81> class ConcreteMachine:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
} else {
uint8_t value = ram_[address & ram_mask_];
const uint8_t value = ram_[address & ram_mask_];
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
@@ -210,12 +252,15 @@ template<bool is_zx81> class ConcreteMachine:
}
if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0);
}
forceinline void flush() {
video_->flush();
if(is_zx81) {
update_audio();
audio_queue_.perform();
}
}
void setup_output(float aspect_ratio) override final {
@@ -231,15 +276,16 @@ template<bool is_zx81> class ConcreteMachine:
}
Outputs::Speaker::Speaker *get_speaker() override final {
return nullptr;
return is_zx81 ? &speaker_ : nullptr;
}
void run_for(const Cycles cycles) override final {
z80_.run_for(cycles);
}
void configure_as_target(const Analyser::Static::Target &target) override final {
is_zx81_ = target.zx8081.isZX81;
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
is_zx81_ = zx8081_target->is_ZX81;
if(is_zx81_) {
rom_ = zx81_rom_;
tape_trap_address_ = 0x37c;
@@ -249,7 +295,7 @@ template<bool is_zx81> class ConcreteMachine:
automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3;
} else {
rom_ = zx80_rom_;
rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_;
tape_trap_address_ = 0x220;
tape_return_address_ = 0x248;
vsync_start_ = HalfCycles(26);
@@ -259,18 +305,18 @@ template<bool is_zx81> class ConcreteMachine:
}
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
switch(target.zx8081.memory_model) {
case Analyser::Static::ZX8081MemoryModel::Unexpanded:
switch(zx8081_target->memory_model) {
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case Analyser::Static::ZX8081MemoryModel::SixteenKB:
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case Analyser::Static::ZX8081MemoryModel::SixtyFourKB:
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
@@ -278,11 +324,11 @@ template<bool is_zx81> class ConcreteMachine:
}
Memory::Fuzz(ram_);
if(target.loading_command.length()) {
type_string(target.loading_command);
if(target->loading_command.length()) {
type_string(target->loading_command);
}
insert_media(target.media);
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
@@ -301,7 +347,7 @@ template<bool is_zx81> class ConcreteMachine:
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
const auto roms = roms_with_names(
"ZX8081",
{
"zx80.rom", "zx81.rom",
@@ -320,8 +366,8 @@ template<bool is_zx81> class ConcreteMachine:
}
// MARK: - Keyboard
void set_key_state(uint16_t key, bool isPressed) override final {
if(isPressed)
void set_key_state(uint16_t key, bool is_pressed) override final {
if(is_pressed)
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
else
key_states_[key >> 8] |= static_cast<uint8_t>(key);
@@ -443,6 +489,34 @@ template<bool is_zx81> class ConcreteMachine:
inline void update_sync() {
video_->set_sync(vsync_ || hsync_);
}
// MARK: - Audio
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
}
inline void ay_set_data(uint8_t value) {
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
}
inline uint8_t ay_read_data() {
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
const uint8_t value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
return value;
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
};
}
@@ -450,9 +524,11 @@ template<bool is_zx81> class ConcreteMachine:
using namespace ZX8081;
// See header; constructs and returns an instance of the ZX80 or 81.
Machine *Machine::ZX8081(const Analyser::Static::Target &target_hint) {
Machine *Machine::ZX8081(const Analyser::Static::Target *target_hint) {
const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint);
// Instantiate the correct type of machine.
if(target_hint.zx8081.isZX81)
if(hint->is_ZX81)
return new ZX8081::ConcreteMachine<true>();
else
return new ZX8081::ConcreteMachine<false>();

View File

@@ -10,24 +10,18 @@
#define ZX8081_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace ZX8081 {
/// @returns The options available for a ZX80 or ZX81.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
class Machine {
public:
virtual ~Machine();
static Machine *ZX8081(const Analyser::Static::Target &target_hint);
static Machine *ZX8081(const Analyser::Static::Target *target_hint);
virtual void set_tape_is_playing(bool is_playing) = 0;
virtual bool get_tape_is_playing() = 0;

View File

@@ -157,7 +157,6 @@
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; };
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; };
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */; };
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; };
@@ -220,6 +219,8 @@
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 */; };
4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90E42041097C008514A2 /* ColecoVision.cpp */; };
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; };
@@ -278,7 +279,6 @@
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; };
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; };
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; };
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */; };
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
@@ -291,12 +291,18 @@
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */; };
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */; };
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; };
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; };
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; };
4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A6592044FD3000FB3688 /* SN76489.cpp */; };
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A6592044FD3000FB3688 /* SN76489.cpp */; };
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90E42041097C008514A2 /* ColecoVision.cpp */; };
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
@@ -599,6 +605,7 @@
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; };
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
@@ -717,8 +724,6 @@
4B2A53971D117D36003C6002 /* KeyCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; };
4B2A53991D117D36003C6002 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; };
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.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>"; };
4B2B3A471F9B8FA70062DABF /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; };
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
@@ -751,7 +756,6 @@
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; };
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; };
4B38F34E1F2EC6BA00D9235D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AmstradCPCOptions.xib"; sourceTree = SOURCE_ROOT; };
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; };
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; };
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
@@ -774,6 +778,7 @@
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; };
4B449C942063389900A095C8 /* TimeTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimeTypes.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>"; };
@@ -880,6 +885,10 @@
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>"; };
4B7A90E32041097C008514A2 /* ColecoVision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ColecoVision.hpp; sourceTree = "<group>"; };
4B7A90E42041097C008514A2 /* ColecoVision.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColecoVision.cpp; sourceTree = "<group>"; };
4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = "<group>"; };
4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = "<group>"; };
4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = "<group>"; };
@@ -952,7 +961,6 @@
4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; };
4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; };
4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadCompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
4B8FE21A1DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Options.xib"; sourceTree = SOURCE_ROOT; };
4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600OptionsPanel.swift; sourceTree = "<group>"; };
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePanel.swift; sourceTree = "<group>"; };
4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; };
@@ -967,9 +975,11 @@
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; };
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; };
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20OptionsPanel.swift; sourceTree = "<group>"; };
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; };
4BA141BC2072E8A400A31EC9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
@@ -978,6 +988,8 @@
4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; };
4BAF2B4D2004580C00480230 /* DMK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMK.hpp; sourceTree = "<group>"; };
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; };
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; };
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
@@ -1315,9 +1327,16 @@
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.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE3231220532443006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE32313205327D7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE32314205328FF006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.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>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
@@ -1356,7 +1375,6 @@
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSZX8081+Instantiation.h"; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = "<group>"; };
@@ -1562,11 +1580,8 @@
isa = PBXGroup;
children = (
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
4B2A539D1D117D36003C6002 /* CSVic20.h */,
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */,
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
4B2A539E1D117D36003C6002 /* CSVic20.mm */,
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */,
);
path = Wrappers;
@@ -1862,14 +1877,12 @@
4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */,
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */,
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */,
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */,
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */,
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */,
);
path = Documents;
@@ -2020,6 +2033,24 @@
name = MSX;
sourceTree = "<group>";
};
4B7A90E22041097C008514A2 /* ColecoVision */ = {
isa = PBXGroup;
children = (
4B7A90E32041097C008514A2 /* ColecoVision.hpp */,
4B7A90E42041097C008514A2 /* ColecoVision.cpp */,
);
path = ColecoVision;
sourceTree = "<group>";
};
4B7A90EA20410A85008514A2 /* Coleco */ = {
isa = PBXGroup;
children = (
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */,
4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */,
);
path = Coleco;
sourceTree = "<group>";
};
4B8334881F5DB8470097E338 /* Implementation */ = {
isa = PBXGroup;
children = (
@@ -2114,6 +2145,7 @@
4B8944EB201967B4007DE474 /* Acorn */,
4B894514201967B4007DE474 /* AmstradCPC */,
4B8944F3201967B4007DE474 /* Atari */,
4B7A90EA20410A85008514A2 /* Coleco */,
4B8944FB201967B4007DE474 /* Commodore */,
4B894507201967B4007DE474 /* Disassembler */,
4B89450F201967B4007DE474 /* MSX */,
@@ -2133,6 +2165,7 @@
4B8944EE201967B4007DE474 /* File.hpp */,
4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944EF201967B4007DE474 /* Tape.hpp */,
4BE32313205327D7006EF799 /* Target.hpp */,
);
path = Acorn;
sourceTree = "<group>";
@@ -2142,6 +2175,7 @@
children = (
4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */,
4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */,
4BE3231720532CC0006EF799 /* Target.hpp */,
);
path = Atari;
sourceTree = "<group>";
@@ -2153,6 +2187,7 @@
4B8944F9201967B4007DE474 /* Tape.cpp */,
4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944F8201967B4007DE474 /* Tape.hpp */,
4BE3231620532BED006EF799 /* Target.hpp */,
);
path = Oric;
sourceTree = "<group>";
@@ -2168,6 +2203,7 @@
4B8944FE201967B4007DE474 /* File.hpp */,
4B8944FD201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944FF201967B4007DE474 /* Tape.hpp */,
4BE3231520532AA7006EF799 /* Target.hpp */,
);
path = Commodore;
sourceTree = "<group>";
@@ -2177,6 +2213,7 @@
children = (
4B894506201967B4007DE474 /* StaticAnalyser.cpp */,
4B894505201967B4007DE474 /* StaticAnalyser.hpp */,
4BE3231220532443006EF799 /* Target.hpp */,
);
path = ZX8081;
sourceTree = "<group>";
@@ -2203,6 +2240,7 @@
4B047075201ABC180047AB0D /* Cartridge.hpp */,
4B894510201967B4007DE474 /* StaticAnalyser.hpp */,
4B894511201967B4007DE474 /* Tape.hpp */,
4BA141C12073100800A31EC9 /* Target.hpp */,
);
path = MSX;
sourceTree = "<group>";
@@ -2212,6 +2250,7 @@
children = (
4B894516201967B4007DE474 /* StaticAnalyser.cpp */,
4B894515201967B4007DE474 /* StaticAnalyser.hpp */,
4BE32314205328FF006EF799 /* Target.hpp */,
);
path = AmstradCPC;
sourceTree = "<group>";
@@ -2226,6 +2265,16 @@
path = Implementation;
sourceTree = "<group>";
};
4BA141C02072E8B300A31EC9 /* MachinePicker */ = {
isa = PBXGroup;
children = (
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */,
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */,
);
name = MachinePicker;
path = "New Group";
sourceTree = "<group>";
};
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
isa = PBXGroup;
children = (
@@ -2242,6 +2291,15 @@
path = Disk;
sourceTree = "<group>";
};
4BB0A6582044FD3000FB3688 /* SN76489 */ = {
isa = PBXGroup;
children = (
4BB0A6592044FD3000FB3688 /* SN76489.cpp */,
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */,
);
path = SN76489;
sourceTree = "<group>";
};
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = {
isa = PBXGroup;
children = (
@@ -2580,11 +2638,12 @@
4BB73EAD1B587A5100552FC2 /* Info.plist */,
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4B2A538F1D117D36003C6002 /* Audio */,
4B643F3D1D77B88000D431D6 /* Document Controller */,
4B55CE551C3B7D360093A61B /* Documents */,
4B2A53921D117D36003C6002 /* Machine */,
4BA141C02072E8B300A31EC9 /* MachinePicker */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BD5F1961D1352A000631CD1 /* Updater */,
4B55CE5A1C3B7D6F0093A61B /* Views */,
@@ -2647,6 +2706,7 @@
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B7A90E22041097C008514A2 /* ColecoVision */,
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
4B2E2D9E1C3A070900138695 /* Electron */,
4B79A4FC1FC8FF9800EEDAD5 /* MSX */,
@@ -2753,6 +2813,7 @@
4B0E04F71FC9F2C800F43484 /* 9918 */,
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
4B4B1A39200198C900A0F866 /* KonamiSCC */,
4BB0A6582044FD3000FB3688 /* SN76489 */,
);
name = Components;
path = ../../Components;
@@ -2918,6 +2979,7 @@
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
@@ -3012,7 +3074,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "Thomas Harte";
TargetAttributes = {
4B055A691FAE763F0060FFFF = {
@@ -3069,16 +3131,16 @@
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */,
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
4BD61664206B2AC800236112 /* QuickLoadOptions.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 /* QuickLoadCompositeOptions.xib in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */,
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
);
@@ -3395,6 +3457,7 @@
4B894529201967B4007DE474 /* Disk.cpp in Sources */,
4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */,
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
@@ -3505,6 +3568,8 @@
4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */,
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */,
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */,
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
4B89452D201967B4007DE474 /* Tape.cpp in Sources */,
@@ -3520,6 +3585,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */,
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
@@ -3528,6 +3594,7 @@
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */,
@@ -3565,6 +3632,7 @@
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
4B894532201967B4007DE474 /* 6502.cpp in Sources */,
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */,
@@ -3600,6 +3668,7 @@
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
@@ -3755,14 +3824,6 @@
name = OricOptions.xib;
sourceTree = "<group>";
};
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4B38F34E1F2EC6BA00D9235D /* Base */,
);
name = AmstradCPCOptions.xib;
sourceTree = "<group>";
};
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -3787,12 +3848,12 @@
name = QuickLoadCompositeOptions.xib;
sourceTree = "<group>";
};
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */ = {
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = {
isa = PBXVariantGroup;
children = (
4B8FE21A1DA19D5F0090D3CE /* Base */,
4BA141BC2072E8A400A31EC9 /* Base */,
);
name = Vic20Options.xib;
name = MachinePicker.xib;
sourceTree = "<group>";
};
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = {
@@ -3803,6 +3864,14 @@
name = MainMenu.xib;
sourceTree = "<group>";
};
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4BD61663206B2AC700236112 /* Base */,
);
name = QuickLoadOptions.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
@@ -3856,12 +3925,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -3909,12 +3980,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,9 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@@ -74,7 +73,6 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"
disableMainThreadChecker = "YES"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@@ -3,46 +3,55 @@
{
"idiom" : "mac",
"size" : "16x16",
"filename" : "Icon16.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"filename" : "Icon32.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"filename" : "Icon32.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"filename" : "Icon64.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"filename" : "Icon128.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"filename" : "Icon256.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"filename" : "Icon256.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"filename" : "Icon512.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"filename" : "Icon512.png",
"scale" : "1x"
},
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,10 +1,12 @@
<?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="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Vic20Document" customModule="Clock_Signal" customModuleProvider="target">
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
@@ -16,15 +18,15 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="133" y="235" width="400" height="300"/>
<rect key="contentRect" x="133" y="235" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<value key="minSize" type="size" width="228" height="171"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<openGLView useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
<openGLView hidden="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
</openGLView>
</subviews>
<constraints>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -93,30 +94,30 @@
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<menuItem title="Save…" enabled="NO" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<menuItem title="Save As…" enabled="NO" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" id="KaW-ft-85H">
<menuItem title="Revert to Saved" enabled="NO" id="KaW-ft-85H">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<menuItem title="Print…" enabled="NO" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/>
</connections>
@@ -128,23 +129,23 @@
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
@@ -154,19 +155,19 @@
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<menuItem title="Paste and Match Style" enabled="NO" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<menuItem title="Delete" enabled="NO" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +17,7 @@
<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="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
<autoresizingMask key="autoresizingMask"/>
@@ -40,7 +40,7 @@
<menu key="menu" id="L06-TO-EF0">
<items>
<menuItem title="SCART" state="on" id="tJM-kX-gaK"/>
<menuItem title="Composite" id="fFm-fS-rWG"/>
<menuItem title="Composite" tag="1" id="fFm-fS-rWG"/>
</items>
</menu>
</popUpButtonCell>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +17,7 @@
<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="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
<autoresizingMask key="autoresizingMask"/>
@@ -34,13 +34,14 @@
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n">
<rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" title="Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1">
<popUpButtonCell key="cell" type="push" title="RGB Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="L06-TO-EF0">
<items>
<menuItem title="Monitor" state="on" id="tJM-kX-gaK"/>
<menuItem title="Television" id="fFm-fS-rWG"/>
<menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/>
<menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/>
<menuItem title="Television" tag="1" id="fFm-fS-rWG"/>
</items>
</menu>
</popUpButtonCell>

View File

@@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/>
@@ -34,9 +34,9 @@
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
</constraints>
</view>

View File

@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="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">
<connections>
<outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="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="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="112"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
<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"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MlB-rE-TXV" userLabel="Country Selector">
<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"/>
<menu key="menu" id="ajo-u0-WGk">
<items>
<menuItem title="Danish Machine" id="38Y-Wm-1uo"/>
<menuItem title="European Machine" id="5ju-Z0-BDa"/>
<menuItem title="Japanese Machine" id="YlT-9e-azY"/>
<menuItem title="Swedish Machine" id="joU-Bt-XFb"/>
<menuItem title="US Machine" id="FXe-ca-cTY"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<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 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="8" id="NbW-5e-wGB"/>
<constraint firstAttribute="trailing" secondItem="0NP-x1-qH2" secondAttribute="trailing" constant="20" id="ero-D6-tJj"/>
<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"/>
</constraints>
</view>
<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="memorySizeButton" destination="0NP-x1-qH2" id="qYy-3f-o94"/>
</connections>
<point key="canvasLocation" x="-2" y="21"/>
</window>
</objects>
</document>

View File

@@ -6,7 +6,6 @@
#import "CSFastLoading.h"
#import "CSAtari2600.h"
#import "CSVic20.h"
#import "CSZX8081.h"
#import "CSStaticAnalyser.h"

View File

@@ -28,6 +28,7 @@ class MachineDocument:
return nil
}
}
var optionsPanelNibName: String?
func aspectRatio() -> NSSize {
return NSSize(width: 4.0, height: 3.0)
@@ -48,43 +49,64 @@ class MachineDocument:
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)
aController.window?.contentAspectRatio = self.aspectRatio()
setupMachineOutput()
// establish the output aspect ratio and audio
let displayAspectRatio = self.aspectRatio()
aController.window?.contentAspectRatio = displayAspectRatio
openGLView.perform(glContext: {
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
})
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!) {
setupClockRate()
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
// is visible, though it's a little premature.
func windowDidUpdate(_ notification: Notification) {
if self.shouldShowNewMachinePanel {
self.shouldShowNewMachinePanel = false
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil)
self.machinePicker?.establishStoredOptions()
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
}
}
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
bestEffortLock.lock()
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
bestEffortLock.unlock()
fileprivate func setupMachineOutput() {
if let machine = self.machine, let openGLView = self.openGLView {
// establish the output aspect ratio and audio
let aspectRatio = self.aspectRatio()
openGLView.perform(glContext: {
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
})
// attach an options panel if one is available
if let optionsPanelNibName = self.optionsPanelNibName {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
self.optionsPanel.machine = machine
self.optionsPanel?.establishStoredOptions()
showOptions(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
openGLView.delegate = self
openGLView.responderDelegate = self
setupAudioQueueClockRate()
// bring OpenGL view-holding window on top of the options panel and show the content
openGLView.isHidden = false
openGLView.window!.makeKeyAndOrderFront(self)
openGLView.window!.makeFirstResponder(openGLView)
// start accepting best effort updates
self.bestEffortUpdater!.delegate = self
}
}
fileprivate func setupClockRate() {
func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) {
setupAudioQueueClockRate()
}
fileprivate func setupAudioQueueClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
@@ -94,10 +116,6 @@ class MachineDocument:
self.machine.audioQueue = self.audioQueue
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
bestEffortLock.lock()
self.bestEffortUpdater?.clockRate = self.machine.clockRate
bestEffortLock.unlock()
}
override func close() {
@@ -105,8 +123,11 @@ class MachineDocument:
optionsPanel = nil
bestEffortLock.lock()
bestEffortUpdater!.delegate = nil
bestEffortUpdater = nil
if let bestEffortUpdater = bestEffortUpdater {
bestEffortUpdater.delegate = nil
bestEffortUpdater.flush()
self.bestEffortUpdater = nil
}
bestEffortLock.unlock()
actionLock.lock()
@@ -124,15 +145,12 @@ class MachineDocument:
func configureAs(_ analysis: CSStaticAnalyser) {
if let machine = CSMachine(analyser: analysis) {
self.machine = machine
}
if let optionsPanelNibName = analysis.optionsPanelNibName {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
self.optionsPanel.machine = self.machine
showOptions(self)
self.optionsPanelNibName = analysis.optionsPanelNibName
setupMachineOutput()
}
}
fileprivate var shouldShowNewMachinePanel = false
override func read(from url: URL, ofType typeName: String) throws {
if let analyser = CSStaticAnalyser(fileAt: url) {
self.displayName = analyser.displayName
@@ -142,6 +160,12 @@ class MachineDocument:
}
}
convenience init(type typeName: String) throws {
self.init()
self.fileType = typeName
self.shouldShowNewMachinePanel = true
}
// MARK: the pasteboard
func paste(_ sender: AnyObject!) {
let pasteboard = NSPasteboard.general
@@ -151,21 +175,10 @@ class MachineDocument:
}
// MARK: CSBestEffortUpdaterDelegate
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
runForNumberOfCycles(Int32(cycles))
}
func runForNumberOfCycles(_ numberOfCycles: Int32) {
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortLock.unlock()
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
if actionLock.try() {
self.machine.runForNumber(ofCycles: cyclesToRunFor)
actionLock.unlock()
}
} else {
bestEffortLock.unlock()
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
if actionLock.try() {
self.machine.run(forInterval: duration)
actionLock.unlock()
}
}
@@ -205,21 +218,42 @@ class MachineDocument:
// MARK: Input management
func windowDidResignKey(_ notification: Notification) {
self.machine.clearAllKeys()
if let machine = self.machine {
machine.clearAllKeys()
}
}
func keyDown(_ event: NSEvent) {
self.machine.setKey(event.keyCode, isPressed: true)
if let machine = self.machine {
machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
}
}
func keyUp(_ event: NSEvent) {
self.machine.setKey(event.keyCode, isPressed: false)
if let machine = self.machine {
machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
}
}
func flagsChanged(_ newModifiers: NSEvent) {
self.machine.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
self.machine.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
self.machine.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
self.machine.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
if let machine = self.machine {
machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control))
machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command))
machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option))
}
}
// MARK: New machine creation
@IBOutlet var machinePicker: MachinePicker?
@IBOutlet var machinePickerPanel: NSWindow?
@IBAction func createMachine(_ sender: NSButton?) {
self.configureAs(machinePicker!.selectedMachine())
machinePicker = nil
sender?.window?.close()
}
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
close()
}
}

View File

@@ -28,13 +28,24 @@ class MachinePanel: NSPanel {
}
}
fileprivate func signalForTag(tag: Int) -> CSMachineVideoSignal {
switch tag {
case 1: return .composite
case 2: return .sVideo
default: break
}
return .RGB
}
var displayTypeUserDefaultsKey: String {
return prefixedUserDefaultsKey("displayType")
}
@IBOutlet var displayTypeButton: NSPopUpButton?
@IBAction func setDisplayType(_ sender: NSPopUpButton!) {
machine.useCompositeOutput = (sender.indexOfSelectedItem == 1)
UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey)
if let selectedItem = sender.selectedItem {
machine.videoSignal = signalForTag(tag: selectedItem.tag)
UserDefaults.standard.set(selectedItem.tag, forKey: self.displayTypeUserDefaultsKey)
}
}
func establishStoredOptions() {
@@ -50,8 +61,21 @@ class MachinePanel: NSPanel {
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
}
let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey)
machine.useCompositeOutput = (displayType == 1)
self.displayTypeButton?.selectItem(at: displayType)
if let displayTypeButton = self.displayTypeButton {
// Enable or disable options as per machine support.
var titlesToRemove: [String] = []
for item in displayTypeButton.itemArray {
if !machine.supportsVideoSignal(signalForTag(tag: item.tag)) {
titlesToRemove.append(item.title)
}
}
for title in titlesToRemove {
displayTypeButton.removeItem(withTitle: title)
}
let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey)
displayTypeButton.selectItem(withTag: displayType)
setDisplayType(displayTypeButton)
}
}
}

View File

@@ -1,97 +0,0 @@
//
// Vic20OptionsPanel.swift
// Clock Signal
//
// Created by Thomas Harte on 03/10/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
class Vic20OptionsPanel: MachinePanel {
var vic20: CSVic20! {
get {
return self.machine as! CSVic20
}
}
// MARK: country selector
@IBOutlet var countryButton: NSPopUpButton?
var countryUserDefaultsKey: String {
get { return prefixedUserDefaultsKey("country") }
}
@IBAction func setCountry(_ sender: NSPopUpButton!) {
UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.countryUserDefaultsKey)
setCountry(sender.indexOfSelectedItem)
}
fileprivate func setCountry(_ countryID: Int) {
switch countryID {
case 0: // Danish
vic20.country = .danish
case 1: // European
vic20.country = .european
case 2: // Japanese
vic20.country = .japanese
case 3: // Swedish
vic20.country = .swedish
case 4: // US
vic20.country = .american
default: break
}
}
// MARK: memory model selector
@IBOutlet var memorySizeButton: NSPopUpButton?
var memorySizeUserDefaultsKey: String {
get { return prefixedUserDefaultsKey("memorySize") }
}
@IBAction func setMemorySize(_ sender: NSPopUpButton!) {
var selectedSize: Int?
switch sender.indexOfSelectedItem {
case 0: selectedSize = 5
case 1: selectedSize = 8
case 2: selectedSize = 32
default: break
}
if let selectedSize = selectedSize {
UserDefaults.standard.set(selectedSize, forKey: self.memorySizeUserDefaultsKey)
setMemorySize(sender.indexOfSelectedItem)
}
}
fileprivate func setMemorySize(_ sizeIndex: Int) {
switch sizeIndex {
case 2: vic20.memorySize = .size32Kb
case 1: vic20.memorySize = .size8Kb
default: vic20.memorySize = .size5Kb
}
}
// MARK: option restoration
override func establishStoredOptions() {
super.establishStoredOptions()
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.register(defaults: [
self.memorySizeUserDefaultsKey: 5,
self.countryUserDefaultsKey: 1
])
// 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)
setCountry(country)
self.countryButton?.selectItem(at: country)
}
}

View File

@@ -373,6 +373,26 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>col</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge</string>
<key>CFBundleTypeName</key>
<string>ColecoVision Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

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