1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-26 16:16:42 +00:00

Compare commits

...

186 Commits

Author SHA1 Message Date
Thomas Harte
5f661adb7f Merge pull request #750 from TomHarte/CatchupCap
Avoids trying to paper over huge gaps in running time.
2020-02-12 23:57:01 -05:00
Thomas Harte
109d072cb6 Avoids trying to paper over huge gaps in running time. Also attempts to improve SDL shutdown reliability. 2020-02-12 23:47:04 -05:00
Thomas Harte
0c1c5a0ab8 Merge pull request #749 from TomHarte/DiskIndicator
Moves ownership of drives inside Disk::Controller.
2020-02-12 23:37:32 -05:00
Thomas Harte
e01c66fd65 Implements multidrive support. 2020-02-12 23:32:01 -05:00
Thomas Harte
9f32fa7f5b Resolves potential random RAM writes at startup. 2020-02-12 23:31:48 -05:00
Thomas Harte
91a3d42919 Ensures no DMA clocking whatsoever when asleep. 2020-02-12 23:23:42 -05:00
Thomas Harte
3cb6bbf771 Uses the union of all drive statuses to determine Drive::Controller's preferred clocking. 2020-02-12 22:28:42 -05:00
Thomas Harte
452e281009 Ensures what is currently the only drive is selected. 2020-02-11 22:13:13 -05:00
Thomas Harte
3da948db52 Eliminates local drive. They're not local any more. 2020-02-11 22:12:54 -05:00
Thomas Harte
0c2f77305f Eliminates dangling printf. 2020-02-11 22:12:30 -05:00
Thomas Harte
05bcd73f82 Attempts to pull drive ownership into DiskController.
For the sake of being more intelligent as to drive clocking, hopefully. And, eventually, to support multiple drive selection.
2020-02-11 21:59:13 -05:00
Thomas Harte
654f5b0478 Merge pull request #748 from TomHarte/SDLLatency
Introduces sync matching to the SDL version.
2020-02-10 23:38:24 -05:00
Thomas Harte
886d923e30 Attempts to permit fixed speed multiplication. 2020-02-10 23:30:32 -05:00
Thomas Harte
6624cb7a78 Corrects set_ram macro to act more like a function. 2020-02-10 23:19:47 -05:00
Thomas Harte
6147134423 Introduces frame locking for SDL. 2020-02-10 23:07:09 -05:00
Thomas Harte
bf6bc7c684 Adds speed control into the SDL build.
If I can just figure out how to manipulate OpenGL from the timer thread to SDL's satisfaction, this'll be as good as it probably gets via SDL.
2020-02-09 22:27:02 -05:00
Thomas Harte
0b0a7e241b Factors out the stuff of time warping. 2020-02-09 22:11:06 -05:00
Thomas Harte
705d14259c Experimentally switches to a 'high-resolution' clock for SDL. 2020-02-09 21:44:55 -05:00
Thomas Harte
f1cd35fa16 Merge pull request #746 from TomHarte/LatencyChop
Reduces latency in macOS, improves concurrency
2020-02-09 21:07:40 -05:00
Thomas Harte
6bda4034c6 Ensures no input data is dropped when changing output rates.
I think this 'completely' deals with the problem. At least until someone wants dynamic output buffer sizes or something like that. We'll see.
2020-02-09 19:14:25 -05:00
Thomas Harte
b04daca98e Picks a safer default construction. 2020-02-09 19:13:21 -05:00
Thomas Harte
85dcdbfe9e Adopts a log prefix for the Master System. 2020-02-09 19:12:44 -05:00
Thomas Harte
24340d1d4f Resolves fetch errors. 2020-02-09 17:04:49 -05:00
Thomas Harte
6ae42d07a7 Retains existing output when switching filter coefficients.
This eliminates an issue with dynamic rate matching and throwing away the beginnings of buffers.
2020-02-09 16:42:07 -05:00
Thomas Harte
2ea1e059a8 Softens swings in emulated machine speed. 2020-02-09 16:34:13 -05:00
Thomas Harte
b5d6126a2d Avoids unnecessary filter recalculation. 2020-02-09 16:32:32 -05:00
Thomas Harte
dac217c98c Defers starting the macOS audio queue, and attempts to restart it upon packet loss.
Hopefully forever to vanish permanent audio loss?
2020-02-08 22:08:27 -05:00
Thomas Harte
c26c8992ae Reintroduces joystick support; eliminates CSBestEffortUpdater. 2020-02-08 21:27:04 -05:00
Thomas Harte
b76a5870b3 Moves drawing into the next timer tick after retrace if sync locked.
... which should mean it occurs within 1/600th of a second of announced retrace, which I assume always will be less than the retrace period. So: does the frame buffer update during retrace.

This should completely eliminate tearing for machines that can be synced to the native output rate.
2020-02-08 18:07:13 -05:00
Thomas Harte
7c0f3bb237 Gets to slightly adjusting execution speed and matching up respective vertical syncs.
I probably still need to move the ->draw inline.
2020-02-08 18:01:48 -05:00
Thomas Harte
f615d096ca Switch to obtaining refresh periods ephemerally.
Which simplifies the necessary delegate protocol.
2020-02-08 15:03:18 -05:00
Thomas Harte
09132306e4 Removes two temporary debugging steps. 2020-02-06 23:35:23 -05:00
Thomas Harte
f95b07efea Continues edging towards raster racing and/or time warping. 2020-02-06 23:35:03 -05:00
Thomas Harte
14d976eecb Starts towards an implementation of time warping. 2020-02-04 23:08:54 -05:00
Thomas Harte
e1cbad0b6d Ensures new displayLinkDelegates get a nudge with the initial display link. 2020-02-04 23:08:25 -05:00
Thomas Harte
e7410b8ed8 Uses objective clock for updates. 2020-02-04 22:24:54 -05:00
Thomas Harte
5caf74b930 Corrects typo. 2020-02-04 22:24:37 -05:00
Thomas Harte
b41920990f Moves submit step to end of line, rather than end of scan. 2020-02-04 22:15:20 -05:00
Thomas Harte
709c229cd7 Gets a bit more explicit with ScanTarget documentation. 2020-02-04 20:19:46 -05:00
Thomas Harte
01fd1b1a2e Pulls out ticks as a macro constant.
For playing.
2020-02-03 22:44:39 -05:00
Thomas Harte
96769c52f6 Prevents an endless queue of backlogged updates. 2020-02-03 22:08:07 -05:00
Thomas Harte
cf9729c74f Takes a first shot at running OpenGL work throughout a frame.
Rather than en masse at the end. But it seems I've been lazy with my threading. Work to do!
2020-02-03 21:58:29 -05:00
Thomas Harte
0f2783075f Moves responsibility for timed updates to CSMachine, which gives the CSHighPrecisionTimer a shot. 2020-02-02 21:39:20 -05:00
Thomas Harte
256f4a6679 Fixes -invalidate: cancel the dispatch source, don't just suspend it, and wait until that is done. 2020-02-02 21:29:22 -05:00
Thomas Harte
0310f94f0c Merge pull request #745 from TomHarte/STMonochrome
Adds some amount of 1bpp/72Hz output support for the Atari ST.
2020-02-02 17:46:36 -05:00
Thomas Harte
085529ed72 Makes the shifter behaviour conform to its documentation. 2020-02-02 17:26:39 -05:00
Thomas Harte
8aabf1b374 Allows receivers of nullptr from begin_data to output any quantity of data. 2020-02-01 21:43:48 -05:00
Thomas Harte
ff39f71ca0 Eliminates meaningless constants from the Macintosh video's CRT setup. 2020-01-30 23:29:04 -05:00
Thomas Harte
019474300d Centralises responsibility for picking irrelevant numbers for a computer-style monitor. 2020-01-30 23:26:02 -05:00
Thomas Harte
af976b8b3d Eliminates modulus operation per ROM access. 2020-01-30 23:09:24 -05:00
Thomas Harte
f3db1a0c60 Eliminates ad hoc scheduling for delayed DE -> LOAD. 2020-01-29 22:50:22 -05:00
Thomas Harte
ce28213a5e [Mostly] unifies deferral process. 2020-01-29 22:46:08 -05:00
Thomas Harte
f9ce50d2bb Adds some debugging `asserts. 2020-01-29 22:45:44 -05:00
Thomas Harte
ee16095863 Withdraws advance_to_next; once it has to cope with simultaneous events it stops being faster than advance.
I could possibly try to deal with those at insertion time, but it'd get messy.
2020-01-29 22:45:10 -05:00
Thomas Harte
f0a6e0f3d5 Splits out the queue management stuff from queue+action.
Temporarily breaks ST video in the endeavour.
2020-01-29 22:18:41 -05:00
Thomas Harte
8c4fb0f688 Extends the DeferredQueue to allow out-of-order enqueing. 2020-01-29 21:49:52 -05:00
Thomas Harte
baa51853c4 Introduces RealTimeActor, providing the same interface as JustInTimeActor. 2020-01-29 21:26:15 -05:00
Thomas Harte
0e29c6b0ab On further reflection, I think events should occur after the running period.
I'm testing this now for sanity in 2/4bpp mode.
2020-01-28 23:26:37 -05:00
Thomas Harte
1b27eedf6b Ensure this can definitely never divide by 0. 2020-01-28 23:25:21 -05:00
Thomas Harte
8b1f183198 Reduce test duration much closer to two frames. 2020-01-28 23:25:01 -05:00
Thomas Harte
4766ec55fe Documents units. 2020-01-28 23:23:51 -05:00
Thomas Harte
c5edc879b6 Switches back to testing the monochrome monitor. 2020-01-28 22:12:57 -05:00
Thomas Harte
65309e60c4 Corrects sequence point generation by allowing for hsync_end != end of line. 2020-01-28 20:38:20 -05:00
Thomas Harte
5c4623e9f7 Adds a sequence-point test for 72Hz mode.
Which immediately appears to trigger the hsync issue I'm also seeing in manual testing.
2020-01-28 20:27:24 -05:00
Thomas Harte
2c0cab9e4d Adds line length latching as a line event. 2020-01-28 20:22:37 -05:00
Thomas Harte
d0117556d1 Reintroduces CSHighPrecisionTimer. 2020-01-28 20:09:46 -05:00
Thomas Harte
b1ff031b54 Fixes runtime test. 2020-01-27 23:41:08 -05:00
Thomas Harte
7e8405e68a Makes 72Hz horizontal sync independently relocatable.
... and moves and shortens it, based on my guesswork as to requirements.
2020-01-27 23:40:01 -05:00
Thomas Harte
c8fd00217d Resolves loss of horizontal resolution in 1bpp mode. 2020-01-27 23:08:28 -05:00
Thomas Harte
9d340599a6 Towards ST 1bpp support: puts vsync in an appropriate location, starts experimenting with proper CRT timings. 2020-01-27 23:00:30 -05:00
Thomas Harte
8e094598ca Merge pull request #744 from TomHarte/CRCTemplate
Better templates the CRC generator.
2020-01-27 21:54:32 -05:00
Thomas Harte
189122ab84 Fixes test units. 2020-01-27 20:35:58 -05:00
Thomas Harte
4b53f6a9f0 Renames T to the more-communicative IntType, adds some explicit constexpra. 2020-01-27 08:28:20 -05:00
Thomas Harte
561e149058 Better templates the CRC generator. 2020-01-27 00:03:01 -05:00
Thomas Harte
5975fc8e63 Merge pull request #738 from TomHarte/FinalOverride
Switches to [just] `final` where relevant to mark overrides.
2020-01-26 23:50:05 -05:00
Thomas Harte
7316a3aa88 Merge branch 'master' into FinalOverride 2020-01-26 23:42:25 -05:00
Thomas Harte
50be991415 Merge pull request #743 from TomHarte/macOSScreens
Ensures macOS window can properly be dragged between screens, Retina or not.
2020-01-26 23:33:50 -05:00
Thomas Harte
52e49439a6 Recreates display link upon a screen change.
Different screens may have different refresh rates, and I can find no guarantees about how Apple handles that.
2020-01-26 23:23:33 -05:00
Thomas Harte
6bcdd3177d Ensures that a change of screen issues a reshape. Just in case.
Thereby resolves display mis-sizing when dragging from a Retina display to a regular one, or vice versa.
2020-01-26 18:04:25 -05:00
Thomas Harte
83dbd257e1 Merge pull request #742 from TomHarte/SpeedMultiplier
Adds the option to run machines at a multiple of their real speeds.
2020-01-26 13:30:43 -05:00
Thomas Harte
b514756272 Adds the option to run machines at a multiple of their real speeds.
Exposed to SDL users only, for now.
2020-01-26 13:25:23 -05:00
Thomas Harte
7e4c13c43e Merge pull request #741 from TomHarte/ZX80RateReporting
Corrects ZX80, ZX81 and Amstrad CPC scan status scales.
2020-01-26 11:42:54 -05:00
Thomas Harte
79bb0f8222 Updates comment. 2020-01-26 11:36:06 -05:00
Thomas Harte
43bf6aca67 Corrects reported scan status for the Amstrad CPC. 2020-01-25 23:46:18 -05:00
Thomas Harte
03d23aad41 Corrects reported ZX80/81 scan status. 2020-01-25 23:27:09 -05:00
Thomas Harte
c398aa60c1 Merge pull request #739 from TomHarte/SDLThreadSafety
Resolves thread safety issues in SDK kiosk mode.
2020-01-25 14:56:29 -05:00
Thomas Harte
9666193c67 Pulls the call to .update out of the critical section. 2020-01-25 14:50:28 -05:00
Thomas Harte
3f57020b00 Resolves thread safety oversights in SDK kiosk mode. 2020-01-25 14:48:00 -05:00
Thomas Harte
294e09f275 All these 'override's can be 'final's.
At least for the purpose of being communicative. I doubt there's much to gain in terms of compiler output — the DiskImageHolder can avoid some virtual lookups but nothing else leaps out.
2020-01-23 22:57:51 -05:00
Thomas Harte
ba516387ba In all these instances, final => override. So no need to repeat myself. 2020-01-23 22:35:39 -05:00
Thomas Harte
2103e1b470 Merge pull request #737 from TomHarte/Multisync
Adds multisync monitor support to the Oric.
2020-01-23 22:20:34 -05:00
Thomas Harte
7bac439e95 Adds, and comments out, a useful temporary piece of debugging logging. 2020-01-23 22:14:22 -05:00
Thomas Harte
9136917f00 Enables the Oric for 50/60Hz mode switching, inventing PAL60 for the purpose. 2020-01-23 22:14:02 -05:00
Thomas Harte
6802318784 Removes audio_queue_.flush() calls; I don't think I really need to block. At least, not usually. 2020-01-23 20:13:16 -05:00
Thomas Harte
428d141bc9 Factors out the logic behind the Atari 2600's frequency switching. 2020-01-23 20:12:44 -05:00
Thomas Harte
a86fb33789 Ensures that the ColecoVision, MSX and Master System fully flush. 2020-01-22 22:57:16 -05:00
Thomas Harte
beefb70f75 Adds vertical sync as something that can be run_until. 2020-01-22 22:20:56 -05:00
Thomas Harte
3c6a00dc3c Breaks a potential deadlock. 2020-01-22 22:10:20 -05:00
Thomas Harte
8404409c0d Causes the Atari 2600 to obey normal flush semantics.
This stuff is going to become more important with run_until.
2020-01-22 22:05:51 -05:00
Thomas Harte
a5f285b4ce Enhances reported data. 2020-01-22 22:01:17 -05:00
Thomas Harte
9d97a294a7 Corrects the TMS' get_scaled_scan_status.
I think all platforms are now returning credible numbers.
2020-01-22 19:34:10 -05:00
Thomas Harte
56448373ae Splits one line into two, for the benefit of step debugging. 2020-01-22 19:32:23 -05:00
Thomas Harte
a71c5946f0 Ensures proper manipulation of scan_statuses, leading to the correct result out of a CRTMachine.
Possibly with the exception of the TMS, as I appear to have uncovered an unrelated issue there.
2020-01-21 22:28:25 -05:00
Thomas Harte
e7fff6e123 Minor step towards correct answers: divide by time_multiplier_. 2020-01-20 22:33:51 -05:00
Thomas Harte
82e5def7c4 Implements get_scan_status, but for scale being incorrect. 2020-01-20 21:58:34 -05:00
Thomas Harte
d97a073d1b Adds the necessary routine for all machines to be able to respond to get_scan_status.
They all just as the CRT, as all are currently based on the CRT. Which doesn't currently know the total clock rate it would need to in order properly to scale the answer to the question. Further thought coming.
2020-01-20 21:45:10 -05:00
Thomas Harte
e74f37d6ed Merge pull request #736 from TomHarte/RunUntil
Implements a nascent `run_until`
2020-01-20 17:48:13 -05:00
Thomas Harte
3aa2c297a2 Adds feedback to the best-effort updater; enables the Cocoa port for audio event requests. 2020-01-20 17:38:25 -05:00
Thomas Harte
290db67f09 Adds a forward route for event flags. Doesn't yet account for extra time expended. 2020-01-20 17:09:01 -05:00
Thomas Harte
4de121142b Adds a flags parameter to the BestEffortUpdater delegate.
On the Cocoa side, cuts Swift out of the update loop, as that seems merely to add code.
2020-01-20 16:21:53 -05:00
Thomas Harte
3c760e585a Switches to accepting a bit mask of events to run_until. 2020-01-20 16:08:21 -05:00
Thomas Harte
8adb2283b5 Rewrites the best-effort updater to try to get better thread affinity. 2020-01-20 13:38:46 -05:00
Thomas Harte
cb61e84868 Starts building out higher-level run_until functionality.
Specifically: you can now run until the next set of speaker samples has been delivered.
2020-01-20 12:12:23 -05:00
Thomas Harte
8349005c4b Adds CRTMachine::run_until, which will run until a condition is true.
I want to get to being able to say "run until the beam is 60% of the way down", "run until a new packet of audio has been delivered", etc.
2020-01-19 23:52:47 -05:00
Thomas Harte
a2847f4f8e Merge pull request #735 from TomHarte/Numeric
Eliminates homegrown factoring code
2020-01-19 23:32:50 -05:00
Thomas Harte
add3ebcb44 Updates Xcode project. 2020-01-19 23:23:44 -05:00
Thomas Harte
98daad45c7 Removers Factors.hpp; now this is a C++17 project. 2020-01-19 23:18:59 -05:00
Thomas Harte
1b4b6b0aee Renames: NumberTheory -> Numeric. 2020-01-19 23:14:35 -05:00
Thomas Harte
8f94da9daf Merge pull request #734 from TomHarte/FuzzyBits
Adds PCMSegementEventSource support for 'fuzzy' bits
2020-01-19 21:48:07 -05:00
Thomas Harte
357137918d Adds fuzzy but marking through the GetTrackWithSectors interface. 2020-01-19 21:41:10 -05:00
Thomas Harte
b0f7b762af Adds a possible const. 2020-01-19 21:40:30 -05:00
Thomas Harte
da3ee381f4 Attempts a full wiring up of fuzzy bits. 2020-01-19 21:20:21 -05:00
Thomas Harte
d27d14d2b0 Supplies fuzzy masks where specified. 2020-01-19 21:08:49 -05:00
Thomas Harte
b0326530d6 Allows fuzzy masks to be fed into the FM and MFM encoders. 2020-01-19 21:08:15 -05:00
Thomas Harte
c2bd5be51a This seems to be the proper interpretation of speeds? 2020-01-19 20:42:51 -05:00
Thomas Harte
84f5feab70 Properly flags up overloads. 2020-01-19 20:37:54 -05:00
Thomas Harte
4b2c68c3d3 Documents next. 2020-01-19 20:32:58 -05:00
Thomas Harte
5391a699a4 Adds the ability for a PCMSegment to maintain 'fuzzy' (i.e. random) bits. Implements an LFSR for bit generation.
I'm not necessarily happy with the idea of just shoving in a [pseudo-]random number generator in rather than emulating the proper process underneath, but for now I throw my arms up.
2020-01-19 20:09:11 -05:00
Thomas Harte
f3f8345e5e Corrects spelling mistake. 2020-01-19 20:05:52 -05:00
Thomas Harte
c755411636 Slightly improves comments. 2020-01-19 20:05:22 -05:00
Thomas Harte
f02759b76b Merge pull request #733 from TomHarte/STXTiming
Adds support for STX speed zones.
2020-01-19 12:41:31 -05:00
Thomas Harte
f34ddce28f Adds support for STX speed zones. 2020-01-19 12:38:33 -05:00
Thomas Harte
50348c9fe7 Adds the ability to substitute a target during encoding. 2020-01-19 12:11:56 -05:00
Thomas Harte
3bfeebf2a1 Merge pull request #732 from TomHarte/TraceFlag
Improves 68000 trace support
2020-01-18 23:17:21 -05:00
Thomas Harte
dca79ea10e Requires trace flag currently set. 2020-01-18 22:52:53 -05:00
Thomas Harte
b7fd4de32f Ensures a one-instruction latency on the trace flag. 2020-01-18 22:06:00 -05:00
Thomas Harte
78d08278ed Merge pull request #731 from TomHarte/ShifterSync
Improves STX track locating, plus minor WD emulation improvements
2020-01-18 14:59:07 -05:00
Thomas Harte
d4be052e76 Switch to matching fragments. 2020-01-18 14:18:59 -05:00
Thomas Harte
d674fd0e67 The WD uses only the low two bits for sector size. 2020-01-18 13:40:50 -05:00
Thomas Harte
229b7b36ed Merge branch 'master' into ShifterSync 2020-01-18 13:38:56 -05:00
Thomas Harte
8a8b8db5d1 Merge pull request #729 from TomHarte/JasminLED
Corrects Jasmin activity light.
2020-01-16 23:16:55 -05:00
Thomas Harte
d30f83871d Corrects Jasmin activity light. 2020-01-16 22:59:43 -05:00
Thomas Harte
1422f8a93a Merge pull request #728 from TomHarte/HardenedRuntime
Opts in for the hardened macOS runtime.
2020-01-16 22:27:58 -05:00
Thomas Harte
f0da75f8e9 Opts in for the hardened macOS runtime.
Seemingly with no ill effects.
2020-01-16 22:18:18 -05:00
Thomas Harte
cb8a7a4137 Merge pull request #727 from TomHarte/RDYs
Adds emulation of non-default types of floppy drive RDY output
2020-01-16 22:07:41 -05:00
Thomas Harte
efd684dc56 Opts the BD-500 in for modified Shugart RDY.
Hopefully this is correct. I'm presently mystified as to other options.
2020-01-16 21:34:57 -05:00
Thomas Harte
aeac6b5888 Allows the type of RDY signal to be specified. 2020-01-16 21:34:48 -05:00
Thomas Harte
9bb294a023 Merge pull request #726 from TomHarte/BD-DOS
Implements highly-provisional Byte Drive 500 support for the Oric.
2020-01-16 00:09:34 -05:00
Thomas Harte
1972ca00a4 Fixes quick-NTSC-avoidance fix.
I suspect this is very temporary, but here it is.
2020-01-16 00:01:16 -05:00
Thomas Harte
6a185a574a Adds the BD-500 to the Mac GUI. 2020-01-15 23:56:56 -05:00
Thomas Harte
c606931c93 Ensures a safe default-selected drive. 2020-01-15 23:56:44 -05:00
Thomas Harte
93cecf0882 Ensures no possible initial NTSC, removes printfs. 2020-01-15 23:47:45 -05:00
Thomas Harte
aac3d27c10 Adds activity indicators for the BD-500 and Jasmin.
Also slightly cleans up DiskController a little further.
2020-01-15 23:39:15 -05:00
Thomas Harte
99122efbbc Adds a slight cool-down period on end-of-rotation.
Along with the corresponding inactive transition of the ready signal.
2020-01-15 23:29:52 -05:00
Thomas Harte
30e856b9e4 Renames motor_is_on_ to motor_input_is_on_ to start to disambiguate the two things. 2020-01-15 23:16:25 -05:00
Thomas Harte
91fae86e73 Factors out paging, implements a bit more of the BD500.
That is, enough seemingly fully to work, if I force the drive to report ready.
2020-01-15 23:15:39 -05:00
Thomas Harte
f5c194386c Ties head load to ready.
BD-DOS no longer perpetually retries.
2020-01-14 23:45:36 -05:00
Thomas Harte
98f7662185 Force BASIC 1.0 for the BD-500. 2020-01-14 23:33:52 -05:00
Thomas Harte
62c3720c97 Adds status register and shout-outs on other address access. 2020-01-14 23:24:11 -05:00
Thomas Harte
6b08239199 Adapts slightly; it would seem that BD-DOS disks really fill up space. 2020-01-14 23:16:06 -05:00
Thomas Harte
f258fc2971 Adds enough of a BD500 for the boot sector seemingly to load. 2020-01-14 23:15:27 -05:00
Thomas Harte
6b84ae3095 Makes the Microdisc also a DiskController, and simplifies delegate interface. 2020-01-14 22:53:27 -05:00
Thomas Harte
5dd8c677f1 Factors out from the Jasmin the stuff that I'm going to need to repeat for the BD-500. 2020-01-14 22:23:00 -05:00
Thomas Harte
1cbcd5355f Adds a detector and enumerated Byte Drive 500 disk interface type. 2020-01-14 21:55:04 -05:00
Thomas Harte
9799250f2c Updates to mention the Jasmin's ROM and list the BD-DOS. 2020-01-14 21:54:37 -05:00
Thomas Harte
ecb5807ec0 Enssures STX interprets sector sizes correctly. 2020-01-14 21:35:37 -05:00
Thomas Harte
942986aadc Insures against badly-placed locations. 2020-01-13 22:49:12 -05:00
Thomas Harte
1f539822ee Adds better support for WD-esque false sync, improves STX track patching. 2020-01-13 22:19:48 -05:00
Thomas Harte
fab35b360a Ensure an encoder is created even if no sectors are placed. 2020-01-12 22:37:00 -05:00
Thomas Harte
80fcf5b5c0 Merge pull request #724 from TomHarte/STX2
Adds some support for the STX file format.
2020-01-12 22:28:50 -05:00
Thomas Harte
b3b2e18c4b Ensures head and track counts are reported accurately. 2020-01-12 22:23:34 -05:00
Thomas Harte
2d233b6358 Makes a more concrete attempt at track/sector combination. 2020-01-12 22:18:31 -05:00
Thomas Harte
83ed36eb08 Add missing #include. 2020-01-12 17:56:04 -05:00
Thomas Harte
89f4032ffc Merge branch 'master' into STX2 2020-01-12 17:55:19 -05:00
Thomas Harte
8c90ec4636 Merge pull request #725 from TomHarte/FasterDPLL
Improve DPLL implementation.
2020-01-12 17:54:55 -05:00
Thomas Harte
514141f8c5 Eliminates the optionality of a DPLL receiver. 2020-01-12 17:45:02 -05:00
Thomas Harte
8e3a618619 Corrects Mac build, shrinks default history [back] to 3 slots. 2020-01-12 17:33:34 -05:00
Thomas Harte
6df6af09de Remove dead .cpp. 2020-01-12 17:25:59 -05:00
Thomas Harte
f42655a0fc Promote DigitalPhaseLockedLoop to a template, simplify to O(1) add_pulse. 2020-01-12 17:25:21 -05:00
Thomas Harte
f81a7f0faf Ensures prefixes are MFM encoded and decoded. 2020-01-11 22:10:41 -05:00
Thomas Harte
2b4c924399 Makes an effort to locate address and data bodies within track.
"Not completely successful" would be the polite term.
2020-01-09 23:28:07 -05:00
Thomas Harte
64517a02b7 Adds code to deal with sector-free tracks. 2020-01-09 21:50:32 -05:00
Thomas Harte
b4befd57a9 Advances to being able to cope with STXs with no special features whatsoever.
Well, other than perhaps a broken data CRC. Fuzzy bits, timing differences and the stuff between sectors are all currently absent.
2020-01-09 21:03:01 -05:00
Thomas Harte
2c742a051e Merge pull request #723 from TomHarte/LSLTiming
Introduces a timing test for LSL. Which already passes.
2020-01-08 22:43:50 -05:00
Thomas Harte
6595f8f527 Introduces a timing test for LSL. Which already passes. 2020-01-08 22:35:28 -05:00
Thomas Harte
985b36da73 Starts towards STX support. 2020-01-07 23:21:32 -05:00
174 changed files with 4110 additions and 1746 deletions

View File

@@ -22,7 +22,7 @@ namespace Dynamic {
class ConfidenceCounter: public ConfidenceSource {
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() override;
float get_confidence() final;
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
void add_hit();

View File

@@ -32,7 +32,7 @@ class ConfidenceSummary: public ConfidenceSource {
const std::vector<float> &weights);
/*! @returns The weighted sum of all sources. */
float get_confidence() override;
float get_confidence() final;
private:
std::vector<ConfidenceSource *> sources_;

View File

@@ -60,6 +60,13 @@ void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target)
if(crt_machine) crt_machine->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus MultiCRTMachine::get_scan_status() const {
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->get_scan_status();
return Outputs::Display::ScanStatus();
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}

View File

@@ -53,12 +53,13 @@ class MultiCRTMachine: public CRTMachine::Machine {
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(Time::Seconds duration) override;
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
Outputs::Display::ScanStatus get_scan_status() const final;
Outputs::Speaker::Speaker *get_speaker() final;
void run_for(Time::Seconds duration) final;
private:
void run_for(const Cycles cycles) override {}
void run_for(const Cycles cycles) final {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;

View File

@@ -28,10 +28,10 @@ class MultiConfigurable: public Configurable::Device {
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard Configurable::Device interface; see there for documentation.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
Configurable::SelectionSet get_accurate_selections() override;
Configurable::SelectionSet get_user_friendly_selections() override;
std::vector<std::unique_ptr<Configurable::Option>> get_options() final;
void set_selections(const Configurable::SelectionSet &selection_by_option) final;
Configurable::SelectionSet get_accurate_selections() final;
Configurable::SelectionSet get_user_friendly_selections() final;
private:
std::vector<Configurable::Device *> devices_;

View File

@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
}
}
std::vector<Input> &get_inputs() override {
std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
@@ -40,19 +40,19 @@ class MultiJoystick: public Inputs::Joystick {
return inputs;
}
void set_input(const Input &digital_input, bool is_active) override {
void set_input(const Input &digital_input, bool is_active) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, is_active);
}
}
void set_input(const Input &digital_input, float value) override {
void set_input(const Input &digital_input, float value) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, value);
}
}
void reset_all_inputs() override {
void reset_all_inputs() final {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}

View File

@@ -28,7 +28,7 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;

View File

@@ -32,10 +32,10 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
void set_key_pressed(Key key, char value, bool is_pressed) override;
void reset_all_keys() override;
const std::set<Key> &observed_keys() override;
bool is_exclusive() override;
void set_key_pressed(Key key, char value, bool is_pressed) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() final;
bool is_exclusive() final;
private:
const std::vector<::KeyboardMachine::Machine *> &machines_;
@@ -48,10 +48,10 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
void clear_all_keys() override;
void set_key_state(uint16_t key, bool is_pressed) override;
void type_string(const std::string &) override;
Inputs::Keyboard &get_keyboard() override;
void clear_all_keys() final;
void set_key_state(uint16_t key, bool is_pressed) final;
void type_string(const std::string &) final;
Inputs::Keyboard &get_keyboard() final;
};
}

View File

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

View File

@@ -37,9 +37,9 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
return ideal / static_cast<float>(speakers_.size());
}
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size) {
for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size);
speaker->set_computed_output_rate(cycles_per_second, buffer_size);
}
}
@@ -53,7 +53,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_complete_samples(this, buffer);
did_complete_samples(this, buffer);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {

View File

@@ -39,12 +39,12 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_output_rate(float cycles_per_second, int buffer_size) override;
void set_computed_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) override;
void speaker_did_change_input_clock(Speaker *speaker) override;
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker *speaker) final;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;

View File

@@ -50,17 +50,17 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
Activity::Source *activity_source() override;
Configurable::Device *configurable_device() override;
CRTMachine::Machine *crt_machine() override;
JoystickMachine::Machine *joystick_machine() override;
MouseMachine::Machine *mouse_machine() override;
KeyboardMachine::Machine *keyboard_machine() override;
MediaTarget::Machine *media_target() override;
void *raw_pointer() override;
Activity::Source *activity_source() final;
Configurable::Device *configurable_device() final;
CRTMachine::Machine *crt_machine() final;
JoystickMachine::Machine *joystick_machine() final;
MouseMachine::Machine *mouse_machine() final;
KeyboardMachine::Machine *keyboard_machine() final;
MediaTarget::Machine *media_target() final;
void *raw_pointer() final;
private:
void multi_crt_did_run_machines() override;
void multi_crt_did_run_machines() final;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;

View File

@@ -10,7 +10,7 @@
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Numeric/CRC.hpp"
#include <algorithm>

View File

@@ -10,7 +10,7 @@
#include <deque>
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Numeric/CRC.hpp"
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
using namespace Analyser::Static::Acorn;

View File

@@ -19,12 +19,10 @@ using namespace Analyser::Static::Commodore;
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
drive = std::make_shared<Storage::Disk::Drive>(4000000, 300, 2);
set_drive(drive);
drive->set_motor_on(true);
emplace_drive(4000000, 300, 2);
set_drive(1);
get_drive().set_motor_on(true);
}
struct Sector {
@@ -61,6 +59,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
return get_sector(sector);
}
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
get_drive().set_disk(disk);
}
private:
unsigned int shift_register_;
int index_count_;
@@ -170,7 +172,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::vector<File> files;
CommodoreGCRParser parser;
parser.drive->set_disk(disk);
parser.set_disk(disk);
// find any sector whatsoever to establish the current track
std::shared_ptr<CommodoreGCRParser::Sector> sector;

View File

@@ -20,7 +20,9 @@
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) {
namespace {
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons
return score;
}
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
@@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
@@ -100,17 +102,22 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
/*
The Jasmin boot sector is sector 1 of track 0 and is loaded at $400;
disassemble it to test it for validity.
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
use disassembly to test for likely matches.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
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;
// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
std::vector<uint8_t> first_sample = sector->samples[0];
if(first_sample.size() < 256) return false;
if(first_sample.size() > 256) {
first_sample.erase(first_sample.end() - 256, first_sample.end());
}
// Grab a disassembly.
const auto disassembly =
@@ -120,14 +127,24 @@ static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
int register_hits = 0;
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
for(auto address : list) {
register_hits += (address >= 0x3f4 && address <= 0x3ff);
register_hits += (address >= range_start && address <= range_end);
}
}
// Arbitrary, sure, but as long as at least two accesses to Jasmin registers are found, accept this.
// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this.
return register_hits >= 2;
}
bool is_jasmin(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x3f4, 0x3ff);
}
bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x310, 0x323);
}
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
auto target = std::make_unique<Target>();
target->machine = Machine::Oric;
@@ -146,9 +163,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
const Analyser::Static::MOS6502::Disassembly disassembly =
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
int basic10_score = Basic10Score(disassembly);
int basic11_score = Basic11Score(disassembly);
if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
}
}
@@ -158,17 +173,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
}
if(!media.disks.empty()) {
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc and
// Jasmin formats here.
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;
target->media.disks.push_back(disk);
} else if(IsJasmin(parser)) {
} else if(is_jasmin(parser)) {
target->disk_interface = Target::DiskInterface::Jasmin;
target->should_start_jasmin = true;
target->media.disks.push_back(disk);
} else if(is_bd500(parser)) {
target->disk_interface = Target::DiskInterface::BD500;
target->media.disks.push_back(disk);
target->rom = Target::ROM::BASIC10;
}
}
}

View File

@@ -27,6 +27,7 @@ struct Target: public ::Analyser::Static::Target {
Microdisc,
Pravetz,
Jasmin,
BD500,
None
};

View File

@@ -47,6 +47,7 @@
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
@@ -147,6 +148,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
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

View File

@@ -13,62 +13,68 @@
#include <vector>
/*!
A DeferredQueue maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
Provides the logic to insert into and traverse a list of future scheduled items.
*/
template <typename TimeUnit> class DeferredQueue {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Schedules @c action to occur in @c delay units of time.
Actions must be scheduled in the order they will occur. It is undefined behaviour
to schedule them out of order.
*/
void defer(TimeUnit delay, const std::function<void(void)> &action) {
pending_actions_.emplace_back(delay, action);
}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
// If there are no pending actions, just run for the entire length.
// This should be the normal branch.
if(pending_actions_.empty()) {
target_(length);
// Apply immediately if there's no delay (or a negative delay).
if(delay <= TimeUnit(0)) {
action();
return;
}
// Divide the time to run according to the pending actions.
while(length > TimeUnit(0)) {
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
target_(next_period);
length -= next_period;
if(!pending_actions_.empty()) {
// Otherwise enqueue, having subtracted the delay for any preceding events,
// and subtracting from the subsequent, if any.
auto insertion_point = pending_actions_.begin();
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
delay -= insertion_point->delay;
++insertion_point;
}
if(insertion_point != pending_actions_.end()) {
insertion_point->delay -= delay;
}
off_t performances = 0;
for(auto &action: pending_actions_) {
action.delay -= next_period;
if(!action.delay) {
action.action();
++performances;
}
}
if(performances) {
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
pending_actions_.emplace(insertion_point, delay, action);
} else {
pending_actions_.emplace_back(delay, action);
}
}
/*!
@returns The amount of time until the next enqueued action will occur,
or TimeUnit(-1) if the queue is empty.
*/
TimeUnit time_until_next_action() {
if(pending_actions_.empty()) return TimeUnit(-1);
return pending_actions_.front().delay;
}
/*!
Advances the queue the specified amount of time, performing any actions it reaches.
*/
void advance(TimeUnit time) {
auto erase_iterator = pending_actions_.begin();
while(erase_iterator != pending_actions_.end()) {
erase_iterator->delay -= time;
if(erase_iterator->delay <= TimeUnit(0)) {
time = -erase_iterator->delay;
erase_iterator->action();
++erase_iterator;
} else {
break;
}
}
if(erase_iterator != pending_actions_.begin()) {
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
}
}
private:
std::function<void(TimeUnit)> target_;
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
@@ -79,4 +85,40 @@ template <typename TimeUnit> class DeferredQueue {
std::vector<DeferredAction> pending_actions_;
};
/*!
A DeferredQueue maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
This list is efficient only for short queues.
*/
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
target_(time_to_next);
length -= time_to_next;
DeferredQueue<TimeUnit>::advance(time_to_next);
}
DeferredQueue<TimeUnit>::advance(length);
target_(length);
// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
}
private:
std::function<void(TimeUnit)> target_;
};
#endif /* DeferredQueue_h */

View File

@@ -43,6 +43,13 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
return &object_;
}
/// Acts exactly as per the standard ->, but preserves constness.
forceinline const T *operator->() const {
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
non_const_this->flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
forceinline T *last_valid() {
return &object_;
@@ -53,7 +60,8 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
if(!is_flushed_) {
is_flushed_ = true;
if constexpr (divider == 1) {
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
const auto duration = time_since_update_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
@@ -68,6 +76,48 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
bool is_flushed_ = true;
};
/*!
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
Time added will be performed immediately.
Its primary purpose is to allow consumers to remain flexible in their scheduling.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
public:
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier == 1 && divider == 1) {
object_.run_for(TargetTimeScale(rhs));
return;
}
if constexpr (multiplier == 1) {
accumulated_time_ += rhs;
} else {
accumulated_time_ += rhs * multiplier;
}
if constexpr (divider == 1) {
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
}
forceinline T *operator->() { return &object_; }
forceinline const T *operator->() const { return &object_; }
forceinline T *last_valid() { return &object_; }
forceinline void flush() {}
private:
T object_;
LocalTimeScale accumulated_time_;
};
/*!
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
Any time the amount of accumulated time crosses a threshold provided at construction time,

View File

@@ -0,0 +1,88 @@
//
// ScanSynchroniser.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef ScanSynchroniser_h
#define ScanSynchroniser_h
#include "../Outputs/ScanTarget.hpp"
#include <cmath>
namespace Time {
/*!
Where an emulated machine is sufficiently close to a host machine's frame rate that a small nudge in
its speed multiplier will bring it into frame synchronisation, the ScanSynchroniser provides a sequence of
speed multipliers designed both to adjust the machine to the proper speed and, in a reasonable amount
of time, to bring it into phase.
*/
class ScanSynchroniser {
public:
/*!
@returns @c true if the emulated machine can be synchronised with the host frame output based on its
current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
*/
bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) {
ratio_ = 1.0;
if(scan_status.field_duration_gradient < 0.00001) {
// Check out the machine's current frame time.
// If it's within 3% of a non-zero integer multiple of the
// display rate, mark this time window to be split over the sync.
ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
const double integer_ratio = round(ratio_);
if(integer_ratio > 0.0) {
ratio_ /= integer_ratio;
return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
}
}
return false;
}
/*!
@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
Results are undefined if @c can_synchroise returned @c false.
*/
double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
// no benefit to second guessing it here — just take it to be correct.
//
// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
// So the set speed multiplier may be adjusted slightly to aim for that.
double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
if(scan_status.current_position > 0.0) {
if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
else speed_multiplier *= phase_adjustment_ratio;
}
speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
return speed_multiplier_ * base_multiplier_;
}
void set_base_speed_multiplier(double multiplier) {
base_multiplier_ = multiplier;
}
double get_base_speed_multiplier() {
return base_multiplier_;
}
private:
static constexpr double maximum_rate_adjustment = 1.03;
static constexpr double phase_adjustment_ratio = 1.005;
// Managed local state.
double speed_multiplier_ = 1.0;
double base_multiplier_ = 1.0;
// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
double ratio_ = 1.0;
};
}
#endif /* ScanSynchroniser_h */

View File

@@ -9,9 +9,16 @@
#ifndef TimeTypes_h
#define TimeTypes_h
#include <chrono>
namespace Time {
typedef double Seconds;
typedef int64_t Nanos;
inline Nanos nanos_now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
}

View File

@@ -336,6 +336,7 @@ void WD1770::posit_event(int new_event_type) {
READ_ID();
if(index_hole_count_ == 6) {
LOG("Nothing found to verify");
update_status([] (Status &status) {
status.seek_error = true;
});
@@ -480,7 +481,7 @@ void WD1770::posit_event(int new_event_type) {
status.data_request = true;
});
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3]) {
if(distance_into_section_ == 128 << (header_[3]&3)) {
distance_into_section_ = 0;
goto type2_check_crc;
}
@@ -563,7 +564,7 @@ void WD1770::posit_event(int new_event_type) {
*/
write_byte(data_);
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3]) {
if(distance_into_section_ == 128 << (header_[3]&3)) {
goto type2_write_crc;
}
@@ -823,6 +824,10 @@ void WD1770::set_head_loaded(bool head_loaded) {
if(head_loaded) posit_event(int(Event1770::HeadLoad));
}
bool WD1770::get_head_loaded() {
return head_is_loaded_;
}
ClockingHint::Preference WD1770::preferred_clocking() {
if(status_.busy) return ClockingHint::Preference::RealTime;
return Storage::Disk::MFMController::preferred_clocking();

View File

@@ -45,20 +45,20 @@ class WD1770: public Storage::Disk::MFMController {
void run_for(const Cycles cycles);
enum Flag: uint8_t {
NotReady = 0x80,
NotReady = 0x80, // 0x80
MotorOn = 0x80,
WriteProtect = 0x40,
RecordType = 0x20,
WriteProtect = 0x40, // 0x40
RecordType = 0x20, // 0x20
SpinUp = 0x20,
HeadLoaded = 0x20,
RecordNotFound = 0x10,
RecordNotFound = 0x10, // 0x10
SeekError = 0x10,
CRCError = 0x08,
LostData = 0x04,
CRCError = 0x08, // 0x08
LostData = 0x04, // 0x04
TrackZero = 0x04,
DataRequest = 0x02,
DataRequest = 0x02, // 0x02
Index = 0x02,
Busy = 0x01
Busy = 0x01 // 0x01
};
/// @returns The current value of the IRQ line output.
@@ -80,6 +80,9 @@ class WD1770: public Storage::Disk::MFMController {
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded();
private:
Personality personality_;
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }

View File

@@ -83,8 +83,9 @@ template <class BusHandler> class MOS6560 {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {

View File

@@ -67,7 +67,7 @@ class i8272 : public Storage::Disk::MFMController {
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type) override;
void posit_event(int type) final;
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;

View File

@@ -117,6 +117,14 @@ void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus TMS9918::get_scaled_scan_status() const {
// The input was scaled by 3/4 to convert half cycles to internal ticks,
// so undo that and also allow for: (i) the multiply by 4 that it takes
// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
// and this should really reply in whole cycles.
return crt_.get_scaled_scan_status() * (4.0f / (3.0f * 8.0f));
}
void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}

View File

@@ -44,6 +44,9 @@ class TMS9918: public Base {
/*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/*! Sets the type of display the CRT will request. */
void set_display_type(Outputs::Display::DisplayType);

View File

@@ -26,7 +26,7 @@ namespace Apple {
/*!
Provides an emulation of the Apple Disk II.
*/
class DiskII final:
class DiskII :
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
@@ -98,8 +98,8 @@ class DiskII final:
void select_drive(int drive);
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Drive::Event &event) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
void process_event(const Storage::Disk::Drive::Event &event) final;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
const Cycles::IntType clock_rate_ = 0;

View File

@@ -76,7 +76,7 @@ class IWM:
private:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &event) override;
void process_event(const Storage::Disk::Drive::Event &event) final;
const int clock_rate_;
@@ -91,7 +91,7 @@ class IWM:
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;

View File

@@ -32,14 +32,14 @@ class DoubleDensityDrive: public IWMDrive {
*/
void set_rotation_speed(float revolutions_per_minute);
void set_enabled(bool) override;
void set_control_lines(int) override;
bool read() override;
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
// To receive the proper notifications from Storage::Disk::Drive.
void did_step(Storage::Disk::HeadPosition to_position) override;
void did_set_disk() override;
void did_step(Storage::Disk::HeadPosition to_position) final;
void did_set_disk() final;
const bool is_800k_;
bool has_new_disk_ = false;

View File

@@ -12,61 +12,89 @@
using namespace Concurrency;
BestEffortUpdater::BestEffortUpdater() {
// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means.
update_is_ongoing_.clear();
}
BestEffortUpdater::BestEffortUpdater() :
update_thread_([this]() {
this->update_loop();
}) {}
BestEffortUpdater::~BestEffortUpdater() {
// Don't allow further deconstruction until the task queue is stopped.
// Sever the delegate now, as soon as possible, then wait for any
// pending tasks to finish.
set_delegate(nullptr);
flush();
// Wind up the update thread.
should_quit_ = true;
update();
update_thread_.join();
}
void BestEffortUpdater::update() {
// Perform an update only if one is not currently ongoing.
if(!update_is_ongoing_.test_and_set()) {
async_task_queue_.enqueue([this]() {
// Get time now using the highest-resolution clock provided by the implementation, and determine
// the duration since the last time this section was entered.
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
const auto elapsed = now - previous_time_point_;
previous_time_point_ = now;
void BestEffortUpdater::update(int flags) {
// Bump the requested target time and set the update requested flag.
{
std::lock_guard<decltype(update_mutex_)> lock(update_mutex_);
has_skipped_ = update_requested_;
update_requested_ = true;
flags_ |= flags;
target_time_ = std::chrono::high_resolution_clock::now().time_since_epoch().count();
}
update_condition_.notify_one();
}
if(has_previous_time_point_) {
// 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 as
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(integer_duration > 0) {
if(delegate_) {
// Cap running at 1/5th of a second, to avoid doing a huge amount of work after any
// brief system interruption.
const double duration = std::min(static_cast<double>(integer_duration) / 1e9, 0.2);
delegate_->update(this, duration, has_skipped_);
}
has_skipped_ = false;
}
} else {
has_previous_time_point_ = true;
}
void BestEffortUpdater::update_loop() {
while(true) {
std::unique_lock<decltype(update_mutex_)> lock(update_mutex_);
is_updating_ = false;
// Allow furthers updates to occur.
update_is_ongoing_.clear();
});
} else {
async_task_queue_.enqueue([this]() {
has_skipped_ = true;
// Wait to be signalled.
update_condition_.wait(lock, [this]() -> bool {
return update_requested_;
});
// Possibly this signalling really means 'quit'.
if(should_quit_) return;
// Note update started, crib the target time.
auto target_time = target_time_;
update_requested_ = false;
// If this was actually the first update request, silently swallow it.
if(!has_previous_time_point_) {
has_previous_time_point_ = true;
previous_time_point_ = target_time;
continue;
}
// Release the lock on requesting new updates.
is_updating_ = true;
const int flags = flags_;
flags_ = 0;
lock.unlock();
// Invoke the delegate, if supplied, in order to run.
const int64_t integer_duration = std::max(target_time - previous_time_point_, int64_t(0));
const auto delegate = delegate_.load();
if(delegate) {
// Cap running at 1/5th of a second, to avoid doing a huge amount of work after any
// brief system interruption.
const double duration = std::min(double(integer_duration) / 1e9, 0.2);
const double elapsed_duration = delegate->update(this, duration, has_skipped_, flags);
previous_time_point_ += int64_t(elapsed_duration * 1e9);
has_skipped_ = false;
}
}
}
void BestEffortUpdater::flush() {
async_task_queue_.flush();
// Spin lock; this is allowed to be slow.
while(true) {
std::lock_guard<decltype(update_mutex_)> lock(update_mutex_);
if(!is_updating_) return;
}
}
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
async_task_queue_.enqueue([this, delegate]() {
delegate_ = delegate;
});
delegate_.store(delegate);
}

View File

@@ -11,8 +11,10 @@
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "AsyncTaskQueue.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
namespace Concurrency {
@@ -31,7 +33,13 @@ class BestEffortUpdater {
/// A delegate receives timing cues.
struct Delegate {
virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
/*!
Instructs the delegate to run for at least @c duration, providing hints as to whether multiple updates were requested before the previous had completed
(as @c did_skip_previous_update) and providing the union of any flags supplied to @c update.
@returns The amount of time actually run for.
*/
virtual Time::Seconds update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update, int flags) = 0;
};
/// Sets the current delegate.
@@ -41,20 +49,32 @@ class BestEffortUpdater {
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.
*/
void update();
void update(int flags = 0);
/// Blocks until any ongoing update is complete.
/// Blocks until any ongoing update is complete; may spin.
void flush();
private:
std::atomic_flag update_is_ongoing_;
AsyncTaskQueue async_task_queue_;
std::atomic<bool> should_quit_;
std::atomic<bool> is_updating_;
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
int64_t target_time_;
int flags_ = 0;
bool update_requested_;
std::mutex update_mutex_;
std::condition_variable update_condition_;
decltype(target_time_) previous_time_point_;
bool has_previous_time_point_ = false;
bool has_skipped_ = false;
std::atomic<bool> has_skipped_ = false;
Delegate *delegate_ = nullptr;
std::atomic<Delegate *>delegate_ = nullptr;
void update_loop();
// This is deliberately at the bottom, to ensure it constructs after the various
// mutexs, conditions, etc, that it'll depend upon.
std::thread update_thread_;
};
}

View File

@@ -170,11 +170,11 @@ class ConcreteJoystick: public Joystick {
}
}
std::vector<Input> &get_inputs() override final {
std::vector<Input> &get_inputs() final {
return inputs_;
}
void set_input(const Input &input, bool is_active) override final {
void set_input(const Input &input, bool is_active) final {
// If this is a digital setting to a digital property, just pass it along.
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
did_set_input(input, is_active);
@@ -193,7 +193,7 @@ class ConcreteJoystick: public Joystick {
}
}
void set_input(const Input &input, float value) override final {
void set_input(const Input &input, float value) final {
// If this is an analogue setting to an analogue property, just pass it along.
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
did_set_input(input, value);

View File

@@ -34,24 +34,24 @@ class QuadratureMouse: public Mouse {
/*
Inputs, to satisfy the Mouse interface.
*/
void move(int x, int y) override {
void move(int x, int y) final {
// Accumulate all provided motion.
axes_[0] += x;
axes_[1] += y;
}
int get_number_of_buttons() override {
int get_number_of_buttons() final {
return number_of_buttons_;
}
void set_button_pressed(int index, bool is_pressed) override {
void set_button_pressed(int index, bool is_pressed) final {
if(is_pressed)
button_flags_ |= (1 << index);
else
button_flags_ &= ~(1 << index);
}
void reset_all_buttons() override {
void reset_all_buttons() final {
button_flags_ = 0;
}

View File

@@ -335,6 +335,11 @@ class CRTCBusHandler {
crt_.set_scan_target(scan_target);
}
/// @returns The current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 4.0f;
}
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
@@ -627,7 +632,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
}),
state_(state) {}
void did_set_input(const Input &input, bool is_active) override {
void did_set_input(const Input &input, bool is_active) final {
uint8_t mask = 0;
switch(input.type) {
default: return;
@@ -656,17 +661,15 @@ class KeyboardState: public GI::AY38910::PortHandler {
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
std::shared_ptr<Storage::Disk::Drive> drive_;
public:
FDC() :
i8272(bus_handler_, Cycles(8000000)),
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
set_drive(drive_);
FDC() : i8272(bus_handler_, Cycles(8000000)) {
emplace_drive(8000000, 300, 1);
set_drive(1);
}
void set_motor_on(bool on) {
drive_->set_motor_on(on);
get_drive().set_motor_on(on);
}
void select_drive(int c) {
@@ -674,11 +677,11 @@ class FDC: public Intel::i8272::i8272 {
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
drive_->set_disk(disk);
get_drive().set_disk(disk);
}
void set_activity_observer(Activity::Observer *observer) {
drive_->set_activity_observer(observer, "Drive 1", true);
get_drive().set_activity_observer(observer, "Drive 1", true);
}
};
@@ -1002,26 +1005,31 @@ template <bool has_fdc> class ConcreteMachine:
}
/// A CRTMachine function; sets the destination for video.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
crtc_bus_handler_.set_scan_target(scan_target);
}
/// A CRTMachine function; returns the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return crtc_bus_handler_.get_scaled_scan_status();
}
/// A CRTMachine function; sets the output display type.
void set_display_type(Outputs::Display::DisplayType display_type) override final {
void set_display_type(Outputs::Display::DisplayType display_type) final {
crtc_bus_handler_.set_display_type(display_type);
}
/// @returns the speaker in use.
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Speaker::Speaker *get_speaker() final {
return ay_.get_speaker();
}
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
@@ -1038,70 +1046,70 @@ template <bool has_fdc> class ConcreteMachine:
return !media.tapes.empty() || (!media.disks.empty() && has_fdc);
}
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
}
// MARK: - Keyboard
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::add_typer(string, std::move(mapper));
}
HalfCycles get_typer_delay() override final {
HalfCycles get_typer_delay() final {
return Cycles(4000000); // Wait 1 second before typing.
}
HalfCycles get_typer_frequency() override final {
HalfCycles get_typer_frequency() final {
return Cycles(160000); // Type one character per frame.
}
// See header; sets a key as either pressed or released.
void set_key_state(uint16_t key, bool isPressed) override final {
void set_key_state(uint16_t key, bool isPressed) final {
key_state_.set_is_pressed(isPressed, key >> 4, key & 7);
}
// See header; sets all keys to released.
void clear_all_keys() override final {
void clear_all_keys() final {
key_state_.clear_all_keys();
}
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
if constexpr (has_fdc) fdc_.set_activity_observer(observer);
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return AmstradCPC::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
// MARK: - Joysticks
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return key_state_.get_joysticks();
}

View File

@@ -147,7 +147,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
card_became_just_in_time_ |= !is_every_cycle;
}
void card_did_change_select_constraints(Apple::II::Card *card) override {
void card_did_change_select_constraints(Apple::II::Card *card) final {
pick_card_messaging_group(card);
}
@@ -283,12 +283,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) override {
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) override {
void did_set_input(const Input &input, bool value) final {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
@@ -417,16 +417,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_queue_.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) override {
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
@@ -799,15 +803,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_queue_.perform();
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
void reset_all_keys() override {
void reset_all_keys() final {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
void set_key_pressed(Key key, char value, bool is_pressed) final {
switch(key) {
default: break;
case Key::F12:
@@ -846,38 +850,38 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
Inputs::Keyboard &get_keyboard() override {
Inputs::Keyboard &get_keyboard() final {
return *this;
}
void type_string(const std::string &string) override {
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
}
// MARK:: Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Apple::II::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
return get_accurate_selections();
}
// MARK: MediaTarget
bool insert_media(const Analyser::Static::Media &media) override {
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.disks.empty()) {
auto diskii = diskii_card();
if(diskii) diskii->set_disk(media.disks[0], 0);
@@ -886,14 +890,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
// MARK: Activity::Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
for(const auto &card: cards_) {
if(card) card->set_activity_observer(observer);
}
}
// MARK: JoystickMachine
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
};

View File

@@ -27,16 +27,16 @@ class DiskIICard: public Card, public ClockingHint::Observer {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) override;
void run_for(Cycles cycles, int stretches) override;
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;
void set_activity_observer(Activity::Observer *observer) override;
void set_activity_observer(Activity::Observer *observer) final;
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
Storage::Disk::Drive &get_drive(int drive);
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
std::vector<uint8_t> boot_;
Apple::DiskII diskii_;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;

View File

@@ -47,6 +47,10 @@ void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus VideoBase::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 14.0f;
}
void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}

View File

@@ -40,6 +40,9 @@ class VideoBase {
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
@@ -252,7 +255,7 @@ class VideoBase {
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
// Maintain a DeferredQueue for delayed mode switches.
DeferredQueue<Cycles> deferrer_;
DeferredQueuePerformer<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {

View File

@@ -171,15 +171,19 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
audio_.queue.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
Outputs::Speaker::Speaker *get_speaker() final {
return &audio_.speaker;
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
mc68000_.run_for(cycles);
}
@@ -395,7 +399,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
via_.flush();
audio_.queue.perform();
// Experimental?
// This avoids deferring IWM costs indefinitely, until
// they become artbitrarily large.
iwm_.flush();
}
@@ -454,7 +459,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
}
bool insert_media(const Analyser::Static::Media &media) override {
bool insert_media(const Analyser::Static::Media &media) final {
if(media.disks.empty() && media.mass_storage_devices.empty())
return false;
@@ -482,11 +487,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// MARK: Keyboard input.
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) override {
void set_key_state(uint16_t key, bool is_pressed) final {
keyboard_.enqueue_key_state(key, is_pressed);
}
@@ -494,7 +499,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// MARK: Interrupt updates.
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) final {
update_interrupt_input();
}
@@ -511,7 +516,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
iwm_->set_activity_observer(observer);
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
@@ -520,11 +525,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Apple::Macintosh::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quick_boot;
if(Configurable::get_quick_boot(selections_by_option, quick_boot)) {
if(quick_boot) {
@@ -536,24 +541,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_boot_selection(selection_set, false);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_boot_selection(selection_set, true);
return selection_set;
}
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None;
}
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override {
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final {
iwm_.flush();
drives_[0].set_rotation_speed(speed);
drives_[1].set_rotation_speed(speed);
@@ -652,7 +657,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
time_until_video_event_ = video_.get_next_sequence_point();
}
Inputs::Mouse &get_mouse() override {
Inputs::Mouse &get_mouse() final {
return mouse_;
}

View File

@@ -26,7 +26,7 @@ using namespace Apple::Macintosh;
Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
audio_(audio),
drive_speed_accumulator_(drive_speed_accumulator),
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) {
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
@@ -37,6 +37,10 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 2.0f;
}
void Video::run_for(HalfCycles duration) {
// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately.
const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;

View File

@@ -42,6 +42,9 @@ class Video {
*/
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/*!
Produces the next @c duration period of pixels.
*/

View File

@@ -48,7 +48,7 @@ class Joystick: public Inputs::ConcreteJoystick {
}),
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
void did_set_input(const Input &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
@@ -77,12 +77,9 @@ using Target = Analyser::Static::Atari2600::Target;
class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public JoystickMachine::Machine,
public Outputs::CRT::Delegate {
public JoystickMachine::Machine {
public:
ConcreteMachine(const Target &target) {
set_clock_rate(NTSC_clock_rate);
ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) {
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
using PagingModel = Target::PagingModel;
@@ -122,13 +119,15 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0));
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
set_is_ntsc(is_ntsc_);
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
void set_switch_is_enabled(Atari2600Switch input, bool state) override {
void set_switch_is_enabled(Atari2600Switch input, bool state) final {
switch(input) {
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
@@ -138,7 +137,7 @@ class ConcreteMachine:
}
}
bool get_switch_is_enabled(Atari2600Switch input) override {
bool get_switch_is_enabled(Atari2600Switch input) final {
uint8_t port_input = bus_->mos6532_.get_port_input(1);
switch(input) {
case Atari2600SwitchReset: return !!(port_input & 0x01);
@@ -150,83 +149,62 @@ class ConcreteMachine:
}
}
void set_reset_switch(bool state) override {
void set_reset_switch(bool state) final {
bus_->set_reset_line(state);
}
// to satisfy CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
bus_->speaker_.set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
bus_->tia_.set_crt_delegate(this);
bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_);
bus_->tia_.set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return bus_->tia_.get_scaled_scan_status() / 3.0f;
}
Outputs::Speaker::Speaker *get_speaker() final {
return &bus_->speaker_;
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
bus_->run_for(cycles);
bus_->apply_confidence(confidence_counter_);
}
// to satisfy Outputs::CRT::Delegate
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) override {
const std::size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
frame_record_pointer_ ++;
if(frame_record_pointer_ >= 6) {
int total_number_of_frames = 0;
int total_number_of_unexpected_vertical_syncs = 0;
for(std::size_t c = 0; c < number_of_frame_records; c++) {
total_number_of_frames += frame_records_[c].number_of_frames;
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
}
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
for(std::size_t c = 0; c < number_of_frame_records; c++) {
frame_records_[c].number_of_frames = 0;
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
}
is_ntsc_ ^= true;
double clock_rate;
if(is_ntsc_) {
clock_rate = NTSC_clock_rate;
bus_->tia_.set_output_mode(TIA::OutputMode::NTSC);
} else {
clock_rate = PAL_clock_rate;
bus_->tia_.set_output_mode(TIA::OutputMode::PAL);
}
bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
bus_->speaker_.set_high_frequency_cutoff(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0)));
set_clock_rate(clock_rate);
}
}
void flush() {
bus_->flush();
}
float get_confidence() override {
void register_crt_frequency_mismatch() {
is_ntsc_ ^= true;
set_is_ntsc(is_ntsc_);
}
float get_confidence() final {
return confidence_counter_.get_confidence();
}
private:
// the bus
// The bus.
std::unique_ptr<Bus> bus_;
// output frame rate tracker
struct FrameRecord {
int number_of_frames = 0;
int number_of_unexpected_vertical_syncs = 0;
} frame_records_[4];
unsigned int frame_record_pointer_ = 0;
// Output frame rate tracker.
Outputs::CRT::CRTFrequencyMismatchWarner<ConcreteMachine> frequency_mismatch_warner_;
bool is_ntsc_ = true;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
// a confidence counter
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
void set_is_ntsc(bool is_ntsc) {
bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL);
const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate;
bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
set_clock_rate(clock_rate);
}
};
}

View File

@@ -33,6 +33,7 @@ class Bus {
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;
virtual void flush() = 0;
// the RIOT, TIA and speaker
PIA mos6532_;

View File

@@ -39,7 +39,7 @@ template<class T> class Cartridge:
// consider doing something less fragile.
}
void run_for(const Cycles cycles) {
void run_for(const Cycles cycles) override {
// 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;
@@ -50,13 +50,13 @@ template<class T> class Cartridge:
/*!
Adjusts @c confidence_counter according to the results of the most recent run_for.
*/
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) {
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
if(cycle_count_.as_integral() < 200) return;
if(horizontal_counter_resets_ > 10)
confidence_counter.add_miss();
}
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
void set_reset_line(bool state) override { m6502_.set_reset_line(state); }
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@@ -197,7 +197,7 @@ template<class T> class Cartridge:
return Cycles(cycles_run_for / 3);
}
void flush() {
void flush() override {
update_audio();
update_video();
audio_queue_.perform();

View File

@@ -142,6 +142,10 @@ void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus TIA::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 2.0f;
}
void TIA::run_for(const Cycles cycles) {
int number_of_cycles = int(cycles.as_integral());

View File

@@ -75,6 +75,7 @@ class TIA {
void set_crt_delegate(Outputs::CRT::Delegate *);
void set_scan_target(Outputs::Display::ScanTarget *);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
private:
Outputs::CRT::CRT crt_;

View File

@@ -101,8 +101,10 @@ class ConcreteMachine:
const bool is_early_tos = true;
if(is_early_tos) {
rom_start_ = 0xfc0000;
for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM;
} else {
rom_start_ = 0xe00000;
for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM;
}
@@ -139,6 +141,10 @@ class ConcreteMachine:
video_->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_->set_display_type(display_type);
}
@@ -241,7 +247,7 @@ class ConcreteMachine:
case BusDevice::ROM:
memory = rom_.data();
address %= rom_.size();
address -= rom_start_;
break;
case BusDevice::Floating:
@@ -435,7 +441,8 @@ class ConcreteMachine:
// Advance the relevant counters.
cycles_since_audio_update_ += length;
mfp_ += length;
dma_ += length;
if(dma_clocking_preference_ != ClockingHint::Preference::None)
dma_ += length;
keyboard_acia_ += length;
midi_acia_ += length;
bus_phase_ += length;
@@ -456,7 +463,7 @@ class ConcreteMachine:
mfp_.flush();
}
if(dma_is_realtime_) {
if(dma_clocking_preference_ == ClockingHint::Preference::RealTime) {
dma_.flush();
}
@@ -465,6 +472,7 @@ class ConcreteMachine:
length -= cycles_until_video_event_;
video_ += cycles_until_video_event_;
cycles_until_video_event_ = video_->get_next_sequence_point();
assert(cycles_until_video_event_ > HalfCycles(0));
mfp_->set_timer_event_input(1, video_->display_enabled());
update_interrupt_input();
@@ -500,6 +508,7 @@ class ConcreteMachine:
std::vector<uint8_t> ram_;
std::vector<uint8_t> rom_;
uint32_t rom_start_ = 0;
enum class BusDevice {
/// A mostly RAM page is one that returns ROM for the first 8 bytes, RAM elsewhere.
@@ -523,7 +532,7 @@ class ConcreteMachine:
bool may_defer_acias_ = true;
bool keyboard_needs_clock_ = false;
bool mfp_is_realtime_ = false;
bool dma_is_realtime_ = false;
ClockingHint::Preference dma_clocking_preference_ = ClockingHint::Preference::None;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
// This is being called by one of the components; avoid any time flushing here as that's
// already dealt with (and, just to be absolutely sure, to avoid recursive mania).
@@ -532,7 +541,7 @@ class ConcreteMachine:
(midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime);
keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None;
mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
dma_is_realtime_ = dma_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
dma_clocking_preference_ = dma_.last_valid()->preferred_clocking();
}
// MARK: - GPIP input.
@@ -563,7 +572,7 @@ class ConcreteMachine:
GPIP 0: centronics busy
*/
mfp_->set_port_input(
0x80 | // b7: Monochrome monitor detect (1 = is monochrome).
0x80 | // b7: Monochrome monitor detect (0 = is monochrome).
0x40 | // b6: RS-232 ring indicator.
(dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested).
((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested).
@@ -658,7 +667,7 @@ class ConcreteMachine:
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
dma_->set_activity_observer(observer);
}

View File

@@ -126,7 +126,7 @@ void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool si
}
void DMAController::set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
fdc_.drives_[drive]->set_disk(disk);
fdc_.set_disk(disk, drive);
}
void DMAController::run_for(HalfCycles duration) {
@@ -256,6 +256,5 @@ ClockingHint::Preference DMAController::preferred_clocking() {
}
void DMAController::set_activity_observer(Activity::Observer *observer) {
fdc_.drives_[0]->set_activity_observer(observer, "Internal", true);
fdc_.drives_[1]->set_activity_observer(observer, "External", true);
fdc_.set_activity_observer(observer);
}

View File

@@ -56,30 +56,36 @@ class DMAController: public WD::WD1770::Delegate, public ClockingHint::Source, p
HalfCycles running_time_;
struct WD1772: public WD::WD1770 {
WD1772(): WD::WD1770(WD::WD1770::P1772) {
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
set_drive(drives_[0]);
emplace_drives(2, 8000000, 300, 2);
set_is_double_density(true); // TODO: is this selectable on the ST?
}
void set_motor_on(bool motor_on) final {
drives_[0]->set_motor_on(motor_on);
drives_[1]->set_motor_on(motor_on);
for_all_drives([motor_on] (Storage::Disk::Drive &drive, size_t) {
drive.set_motor_on(motor_on);
});
}
void set_floppy_drive_selection(bool drive1, bool drive2, bool side2) {
// TODO: handle no drives and/or both drives selected.
if(drive1) {
set_drive(drives_[0]);
} else {
set_drive(drives_[1]);
}
set_drive(
(drive1 ? 1 : 0) |
(drive2 ? 2 : 0)
);
drives_[0]->set_head(side2);
drives_[1]->set_head(side2);
for_all_drives([side2] (Storage::Disk::Drive &drive, size_t) {
drive.set_head(side2);
});
}
void set_activity_observer(Activity::Observer *observer) {
get_drive(0).set_activity_observer(observer, "Internal", true);
get_drive(1).set_activity_observer(observer, "External", true);
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
get_drive(drive).set_disk(disk);
}
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
} fdc_;
void wd1770_did_change_output(WD::WD1770 *) final;

View File

@@ -162,7 +162,7 @@ class IntelligentKeyboard:
Input(Input::Fire, 0),
}) {}
void did_set_input(const Input &input, bool is_active) override {
void did_set_input(const Input &input, bool is_active) final {
uint8_t mask = 0;
switch(input.type) {
default: return;

View File

@@ -13,6 +13,8 @@
#include <algorithm>
#include <cstring>
#define CYCLE(x) ((x) * 2)
using namespace Atari::ST;
namespace {
@@ -29,7 +31,7 @@ const struct VerticalParams {
} vertical_params[3] = {
{63, 263, 313}, // 47 rather than 63 on early machines.
{34, 234, 263},
{1, 401, 500} // 72 Hz mode: who knows?
{34, 434, 500} // Guesswork: (i) nobody ever recommends 72Hz mode for opening the top border, so it's likely to be the same as another mode; (ii) being the same as PAL feels too late.
};
/// @returns The correct @c VerticalParams for output at @c frequency.
@@ -37,8 +39,6 @@ const VerticalParams &vertical_parameters(Video::FieldFrequency frequency) {
return vertical_params[int(frequency)];
}
#define CYCLE(x) ((x) * 2)
/*!
Defines the horizontal counts at which mode-specific events will occur:
horizontal enable being set and being reset, blank being set and reset, and the
@@ -57,13 +57,23 @@ const struct HorizontalParams {
const int set_blank;
const int reset_blank;
const int length;
const int vertical_decision;
LineLength length;
} horizontal_params[3] = {
{CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(512)},
{CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(508)},
{CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(224)} // 72Hz mode doesn't set or reset blank.
{CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(502), { CYCLE(512), CYCLE(464), CYCLE(504) }},
{CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(502), { CYCLE(508), CYCLE(460), CYCLE(500) }},
{CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(214), { CYCLE(224), CYCLE(194), CYCLE(212) }}
// 72Hz mode doesn't set or reset blank.
};
// Re: 'vertical_decision':
// This is cycle 502 if in 50 or 60 Hz mode; in 70 Hz mode I've put it on cycle 214
// in order to be analogous to 50 and 60 Hz mode. I have no idea where it should
// actually go.
//
// Ditto the horizontal sync timings for 72Hz are plucked out of thin air.
const HorizontalParams &horizontal_parameters(Video::FieldFrequency frequency) {
return horizontal_params[int(frequency)];
}
@@ -79,10 +89,10 @@ struct Checker {
assert(horizontal.reset_blank < horizontal.set_enable);
assert(horizontal.set_enable < horizontal.reset_enable);
assert(horizontal.reset_enable < horizontal.set_blank);
assert(horizontal.set_blank+50 < horizontal.length);
assert(horizontal.set_blank+50 < horizontal.length.length);
} else {
assert(horizontal.set_enable < horizontal.reset_enable);
assert(horizontal.set_enable+50 <horizontal.length);
assert(horizontal.set_enable+50 <horizontal.length.length);
}
// Expected vertical order of events: reset blank, enable display, disable display, enable blank (at least 50 before end of line), end of line
@@ -97,12 +107,13 @@ struct Checker {
const int de_delay_period = CYCLE(28); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX.
const int vsync_x_position = CYCLE(56); // Horizontal cycle on which vertical sync changes happen.
const int hsync_start = CYCLE(48); // Cycles before end of line when hsync starts.
const int hsync_end = CYCLE(8); // Cycles before end of line when hsync ends.
const int line_length_latch_position = CYCLE(54);
const int hsync_delay_period = hsync_end; // Signal hsync at the end of the line.
const int hsync_delay_period = CYCLE(8); // Signal hsync at the end of the line.
const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same delay as hsync.
const int load_delay_period = CYCLE(4); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX.
// "VSYNC starts 104 cycles after the start of the previous line's HSYNC, so that's 4 cycles before DE would be activated. ";
// that's an inconsistent statement since it would imply VSYNC at +54, which is 2 cycles before DE in 60Hz mode and 6 before
// in 50Hz mode. I've gone with 56, to be four cycles ahead of DE in 50Hz mode.
@@ -110,13 +121,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same
}
Video::Video() :
deferrer_([=] (HalfCycles duration) { advance(duration); }),
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
// crt_(896, 1, 500, 5, Outputs::Display::InputDataType::Red4Green4Blue4),
video_stream_(crt_, palette_) {
// Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's
// usual output height of 200 lines.
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 220, 850, 4.0f / 3.0f));
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f));
}
void Video::set_ram(uint16_t *ram, size_t size) {
@@ -127,52 +138,41 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 4.0f;
}
void Video::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
void Video::run_for(HalfCycles duration) {
deferrer_.run_for(duration);
}
void Video::advance(HalfCycles duration) {
const auto horizontal_timings = horizontal_parameters(field_frequency_);
const auto vertical_timings = vertical_parameters(field_frequency_);
int integer_duration = int(duration.as_integral());
// Effect any changes in visible state out here; they're not relevant in the inner loop.
if(!pending_events_.empty()) {
auto erase_iterator = pending_events_.begin();
int duration_remaining = integer_duration;
while(erase_iterator != pending_events_.end()) {
erase_iterator->delay -= duration_remaining;
if(erase_iterator->delay <= 0) {
duration_remaining = -erase_iterator->delay;
erase_iterator->apply(public_state_);
++erase_iterator;
} else {
break;
}
}
if(erase_iterator != pending_events_.begin()) {
pending_events_.erase(pending_events_.begin(), erase_iterator);
}
}
assert(integer_duration >= 0);
while(integer_duration) {
const auto horizontal_timings = horizontal_parameters(field_frequency_);
const auto vertical_timings = vertical_parameters(field_frequency_);
// Determine time to next event; this'll either be one of the ones informally scheduled in here,
// or something from the deferral queue.
// Seed next event to end of line.
int next_event = line_length_;
int next_event = line_length_.length;
const int next_deferred_event = deferrer_.time_until_next_action().as<int>();
if(next_deferred_event >= 0)
next_event = std::min(next_event, next_deferred_event + x_);
// Check the explicitly-placed events.
if(horizontal_timings.reset_blank > x_) next_event = std::min(next_event, horizontal_timings.reset_blank);
if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank);
if(horizontal_timings.reset_enable > x_) next_event = std::min(next_event, horizontal_timings.reset_enable);
if(horizontal_timings.set_enable > x_) next_event = std::min(next_event, horizontal_timings.set_enable);
if(next_load_toggle_ > x_) next_event = std::min(next_event, next_load_toggle_);
// Check for events that are relative to existing latched state.
if(line_length_ - hsync_start > x_) next_event = std::min(next_event, line_length_ - hsync_start);
if(line_length_ - hsync_end > x_) next_event = std::min(next_event, line_length_ - hsync_end);
if(line_length_.hsync_start > x_) next_event = std::min(next_event, line_length_.hsync_start);
if(line_length_.hsync_end > x_) next_event = std::min(next_event, line_length_.hsync_end);
// Also, a vertical sync event might intercede.
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < vsync_x_position && next_event >= vsync_x_position) {
@@ -185,6 +185,8 @@ void Video::advance(HalfCycles duration) {
const bool hsync = horizontal_.sync;
const bool vsync = vertical_.sync;
assert(run_length > 0);
// Ensure proper fetching irrespective of the output.
if(load_) {
const int since_load = x_ - load_base_;
@@ -192,8 +194,8 @@ void Video::advance(HalfCycles duration) {
// There will be pixels this line, subject to the shifter pipeline.
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
// and during the rest of the window, shift out.
int start_column = since_load >> 3;
const int end_column = (since_load + run_length) >> 3;
int start_column = (since_load - 1) >> 3;
const int end_column = (since_load + run_length - 1) >> 3;
while(start_column != end_column) {
data_latch_[data_latch_position_] = ram_[current_address_ & 262143];
@@ -210,13 +212,16 @@ void Video::advance(HalfCycles duration) {
} else if(!load_) {
video_stream_.output(run_length, VideoStream::OutputMode::Pixels);
} else {
const int since_load = x_ - load_base_;
const int start = x_ - load_base_;
const int end = start + run_length;
// There will be pixels this line, subject to the shifter pipeline.
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
// and during the rest of the window, shift out.
int start_column = since_load >> 3;
const int end_column = (since_load + run_length) >> 3;
int start_column = start >> 3;
const int end_column = end >> 3;
const int start_offset = start & 7;
const int end_offset = end & 7;
// Rules obeyed below:
//
@@ -225,35 +230,40 @@ void Video::advance(HalfCycles duration) {
// was reloaded by the fetch depends on the FIFO.
if(start_column == end_column) {
if(!start_offset) {
push_latched_data();
}
video_stream_.output(run_length, VideoStream::OutputMode::Pixels);
} else {
// Continue the current column if partway across.
if(since_load&7) {
if(start_offset) {
// If at least one column boundary is crossed, complete this column.
video_stream_.output(8 - (since_load & 7), VideoStream::OutputMode::Pixels);
video_stream_.output(8 - start_offset, VideoStream::OutputMode::Pixels);
++start_column; // This starts a new column, so latch a new word.
push_latched_data();
}
// Run for all columns that have their starts in this time period.
int complete_columns = end_column - start_column;
while(complete_columns--) {
video_stream_.output(8, VideoStream::OutputMode::Pixels);
push_latched_data();
video_stream_.output(8, VideoStream::OutputMode::Pixels);
}
// Output the start of the next column, if necessary.
if((since_load + run_length) & 7) {
video_stream_.output((since_load + run_length) & 7, VideoStream::OutputMode::Pixels);
if(end_offset) {
push_latched_data();
video_stream_.output(end_offset, VideoStream::OutputMode::Pixels);
}
}
}
// Check for whether line length should have been latched during this run.
if(x_ <= CYCLE(54) && (x_ + run_length) > CYCLE(54)) line_length_ = horizontal_timings.length;
if(x_ < line_length_latch_position && (x_ + run_length) >= line_length_latch_position) {
line_length_ = horizontal_timings.length;
}
// Make a decision about vertical state on cycle 502.
if(x_ <= CYCLE(502) && (x_ + run_length) > CYCLE(502)) {
// Make a decision about vertical state on the appropriate cycle.
if(x_ < horizontal_timings.vertical_decision && (x_ + run_length) >= horizontal_timings.vertical_decision) {
next_y_ = y_ + 1;
next_vertical_ = vertical_;
next_vertical_.sync_schedule = VerticalState::SyncSchedule::None;
@@ -278,23 +288,17 @@ void Video::advance(HalfCycles duration) {
// Apply the next event.
x_ += run_length;
assert(integer_duration >= run_length);
integer_duration -= run_length;
deferrer_.advance(HalfCycles(run_length));
// Check horizontal events; the first six are guaranteed to occur separately.
if(horizontal_timings.reset_blank == x_) horizontal_.blank = false;
else if(horizontal_timings.set_blank == x_) horizontal_.blank = true;
else if(horizontal_timings.reset_enable == x_) horizontal_.enable = false;
else if(horizontal_timings.set_enable == x_) horizontal_.enable = true;
else if(line_length_ - hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; }
else if(line_length_ - hsync_end == x_) horizontal_.sync = false;
// next_load_toggle_ is less predictable; test separately because it may coincide
// with one of the above tests.
if(next_load_toggle_ == x_) {
next_load_toggle_ = -1;
load_ ^= true;
load_base_ = x_;
}
else if(line_length_.hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; }
else if(line_length_.hsync_end == x_) horizontal_.sync = false;
// Check vertical events.
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == vsync_x_position) {
@@ -306,7 +310,7 @@ void Video::advance(HalfCycles duration) {
// Check whether the terminating event was end-of-line; if so then advance
// the vertical bits of state.
if(x_ == line_length_) {
if(x_ == line_length_.length) {
x_ = 0;
vertical_ = next_vertical_;
y_ = next_y_;
@@ -329,21 +333,30 @@ void Video::advance(HalfCycles duration) {
// Chuck any deferred output changes into the queue.
const bool next_display_enable = vertical_.enable && horizontal_.enable;
if(display_enable != next_display_enable) {
// Schedule change in outwardly-visible DE line.
add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable);
// Schedule change in load line.
deferrer_.defer(load_delay_period, [this, next_display_enable] {
this->load_ = next_display_enable;
this->load_base_ = this->x_;
});
// Schedule change in inwardly-visible effect.
next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles
// Schedule change in outwardly-visible DE line.
deferrer_.defer(de_delay_period, [this, next_display_enable] {
this->public_state_.display_enable = next_display_enable;
});
}
if(horizontal_.sync != hsync) {
// Schedule change in outwardly-visible hsync line.
add_event(hsync_delay_period - integer_duration, horizontal_.sync ? Event::Type::SetHsync : Event::Type::ResetHsync);
deferrer_.defer(hsync_delay_period, [this, next_horizontal_sync = horizontal_.sync] {
this->public_state_.hsync = next_horizontal_sync;
});
}
if(vertical_.sync != vsync) {
// Schedule change in outwardly-visible hsync line.
add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync);
deferrer_.defer(vsync_delay_period, [this, next_vertical_sync = vertical_.sync] {
this->public_state_.vsync = next_vertical_sync;
});
}
}
}
@@ -396,11 +409,12 @@ HalfCycles Video::get_next_sequence_point() {
const auto horizontal_timings = horizontal_parameters(field_frequency_);
int event_time = line_length_; // Worst case: report end of line.
int event_time = line_length_.length; // Worst case: report end of line.
// If any events are pending, give the first of those the chance to be next.
if(!pending_events_.empty()) {
event_time = std::min(event_time, x_ + pending_events_.front().delay);
const auto next_deferred_item = deferrer_.time_until_next_action();
if(next_deferred_item != HalfCycles(-1)) {
event_time = std::min(event_time, x_ + next_deferred_item.as<int>());
}
// If this is a vertically-enabled line, check for the display enable boundaries, + the standard delay.
@@ -419,11 +433,17 @@ HalfCycles Video::get_next_sequence_point() {
}
// Test for beginning and end of horizontal sync.
if(x_ < line_length_ - hsync_start + hsync_delay_period) {
event_time = std::min(line_length_ - hsync_start + hsync_delay_period, event_time);
if(x_ < line_length_.hsync_start + hsync_delay_period) {
event_time = std::min(line_length_.hsync_start + hsync_delay_period, event_time);
}
if(x_ < line_length_.hsync_end + hsync_delay_period) {
event_time = std::min(line_length_.hsync_end + hsync_delay_period, event_time);
}
// Also factor in the line length latching time.
if(x_ < line_length_latch_position) {
event_time = std::min(line_length_latch_position, event_time);
}
/* Hereby assumed: hsync end will be communicated at end of line: */
static_assert(hsync_end == hsync_delay_period);
// It wasn't any of those, just supply end of line. That's when the static_assert above assumes a visible hsync transition.
return HalfCycles(event_time - x_);
@@ -555,12 +575,12 @@ void Video::VideoStream::generate(int duration, OutputMode mode, bool is_termina
if(mode != OutputMode::Pixels) {
switch(mode) {
default:
case OutputMode::Sync: crt_.output_sync(duration_); break;
case OutputMode::Blank: crt_.output_blank(duration_); break;
case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_); break;
case OutputMode::Sync: crt_.output_sync(duration_*2); break;
case OutputMode::Blank: crt_.output_blank(duration_*2); break;
case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_*2); break;
}
// Reseed duration
// Reseed duration.
duration_ = duration;
// The shifter should keep running, so throw away the proper amount of content.
@@ -610,7 +630,7 @@ void Video::VideoStream::flush_border() {
// Output colour 0 for the entirety of duration_ (or black, if this is 1bpp mode).
uint16_t *const colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = (bpp_ != OutputBpp::One) ? palette_[0] : 0;
crt_.output_level(duration_);
crt_.output_level(duration_*2);
duration_ = 0;
}
@@ -711,7 +731,7 @@ void Video::VideoStream::output_pixels(int duration) {
}
// Check whether the limit has been reached.
if(pixel_pointer_ == allocation_size) {
if(pixel_pointer_ >= allocation_size - 32) {
flush_pixels();
}
}
@@ -721,12 +741,12 @@ void Video::VideoStream::output_pixels(int duration) {
if(pixels) {
int leftover_duration = pixels;
switch(bpp_) {
case OutputBpp::One: leftover_duration >>= 1; break;
default: break;
case OutputBpp::Four: leftover_duration <<= 1; break;
default: leftover_duration >>= 1; break;
case OutputBpp::Two: break;
case OutputBpp::Four: leftover_duration <<= 1; break;
}
shift(leftover_duration);
crt_.output_data(leftover_duration);
crt_.output_data(leftover_duration*2);
}
}
@@ -734,9 +754,9 @@ void Video::VideoStream::flush_pixels() {
// Flush only if there's something to flush.
if(pixel_pointer_) {
switch(bpp_) {
case OutputBpp::One: crt_.output_data(pixel_pointer_ >> 1, size_t(pixel_pointer_)); break;
default: crt_.output_data(pixel_pointer_); break;
case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break;
case OutputBpp::One: crt_.output_data(pixel_pointer_); break;
default: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break;
case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 2, size_t(pixel_pointer_)); break;
}
}
@@ -757,7 +777,13 @@ void Video::VideoStream::set_bpp(OutputBpp bpp) {
}
void Video::VideoStream::load(uint64_t value) {
output_shifter_ = value;
// In 1bpp mode, a 0 bit is white and a 1 bit is black.
// Invert the input so that the 'just output the border colour
// when the shifter is empty' optimisation works.
if(bpp_ == OutputBpp::One)
output_shifter_ = ~value;
else
output_shifter_ = value;
}
// MARK: - Range observer.

View File

@@ -21,6 +21,12 @@ class VideoTester;
namespace Atari {
namespace ST {
struct LineLength {
int length = 1024;
int hsync_start = 1024;
int hsync_end = 1024;
};
/*!
Models a combination of the parts of the GLUE, MMU and Shifter that in net
form the video subsystem of the Atari ST. So not accurate to a real chip, but
@@ -40,6 +46,9 @@ class Video {
*/
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/*!
Sets the type of output.
*/
@@ -112,7 +121,6 @@ class Video {
Range get_memory_access_range();
private:
void advance(HalfCycles duration);
DeferredQueue<HalfCycles> deferrer_;
Outputs::CRT::CRT crt_;
@@ -125,10 +133,8 @@ class Video {
int current_address_ = 0;
uint16_t *ram_ = nullptr;
uint16_t line_buffer_[256];
int x_ = 0, y_ = 0, next_y_ = 0;
int next_load_toggle_ = -1;
bool load_ = false;
int load_base_ = 0;
@@ -160,7 +166,7 @@ class Video {
} sync_schedule = SyncSchedule::None;
bool sync = false;
} vertical_, next_vertical_;
int line_length_ = 1024;
LineLength line_length_;
int data_latch_position_ = 0;
int data_latch_read_position_ = 0;
@@ -234,59 +240,6 @@ class Video {
bool vsync = false;
} public_state_;
struct Event {
int delay;
enum class Type {
SetDisplayEnable, ResetDisplayEnable,
SetHsync, ResetHsync,
SetVsync, ResetVsync,
} type;
Event(Type type, int delay) : delay(delay), type(type) {}
void apply(PublicState &state) {
apply(type, state);
}
static void apply(Type type, PublicState &state) {
switch(type) {
default:
case Type::SetDisplayEnable: state.display_enable = true; break;
case Type::ResetDisplayEnable: state.display_enable = false; break;
case Type::SetHsync: state.hsync = true; break;
case Type::ResetHsync: state.hsync = false; break;
case Type::SetVsync: state.vsync = true; break;
case Type::ResetVsync: state.vsync = false; break;
}
}
};
std::vector<Event> pending_events_;
void add_event(int delay, Event::Type type) {
// Apply immediately if there's no delay (or a negative delay).
if(delay <= 0) {
Event::apply(type, public_state_);
return;
}
if(!pending_events_.empty()) {
// Otherwise enqueue, having subtracted the delay for any preceding events,
// and subtracting from the subsequent, if any.
auto insertion_point = pending_events_.begin();
while(insertion_point != pending_events_.end() && insertion_point->delay < delay) {
delay -= insertion_point->delay;
++insertion_point;
}
if(insertion_point != pending_events_.end()) {
insertion_point->delay -= delay;
}
pending_events_.emplace(insertion_point, type, delay);
} else {
pending_events_.emplace_back(type, delay);
}
}
friend class ::VideoTester;
};

View File

@@ -17,6 +17,7 @@
#include "../Configurable/StandardOptions.hpp"
#include <array>
#include <cmath>
// TODO: rename.
@@ -37,6 +38,13 @@ class Machine {
*/
virtual void set_scan_target(Outputs::Display::ScanTarget *scan_target) = 0;
/*!
@returns The current scan status.
*/
virtual Outputs::Display::ScanStatus get_scan_status() const {
return get_scaled_scan_status() / float(clock_rate_);
}
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
@@ -46,11 +54,89 @@ class Machine {
/// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) {
const double cycles = (duration * clock_rate_) + clock_conversion_error_;
const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(static_cast<int>(cycles)));
}
/*!
Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
emulated machine to run 50% faster than a real machine. This speed-up is an emulation
fiction: it will apply across the system, including to the CRT.
*/
virtual void set_speed_multiplier(double multiplier) {
speed_multiplier_ = multiplier;
auto speaker = get_speaker();
if(speaker) {
speaker->set_input_rate_multiplier(float(multiplier));
}
}
/*!
@returns The current speed multiplier.
*/
virtual double get_speed_multiplier() {
return speed_multiplier_;
}
/*!
Runs for the machine for at least @c duration seconds, and then until @c condition is true.
@returns The amount of time run for.
*/
Time::Seconds run_until(Time::Seconds minimum_duration, std::function<bool()> condition) {
Time::Seconds total_runtime = minimum_duration;
run_for(minimum_duration);
while(!condition()) {
// Advance in increments of one 500th of a second until the condition
// is true; that's 1/10th of a 50Hz frame, but more like 1/8.33 of a
// 60Hz frame. Though most machines aren't exactly 50Hz or 60Hz, and some
// are arbitrary other refresh rates. So those observations are merely
// for scale.
run_for(0.002);
total_runtime += 0.002;
}
return total_runtime;
}
enum MachineEvent: int {
/// At least one new packet of audio has been delivered to the spaker's delegate.
NewSpeakerSamplesGenerated = 1 << 0,
/// The next vertical retrace has begun.
VerticalSync = 1 << 1,
};
/*!
Runs for at least @c duration seconds, and then every one of the @c events has occurred at least once since this
call to @c run_until_event.
@param events A bitmask comprised of @c MachineEvent flags.
@returns The amount of time run for.
*/
Time::Seconds run_until(Time::Seconds minimum_duration, int events) {
// Tie up a wait-for-samples, if requested.
const Outputs::Speaker::Speaker *speaker = nullptr;
int sample_sets = 0;
if(events & MachineEvent::NewSpeakerSamplesGenerated) {
speaker = get_speaker();
if(!speaker) events &= ~MachineEvent::NewSpeakerSamplesGenerated;
sample_sets = speaker->completed_sample_sets();
}
int retraces = 0;
if(events & MachineEvent::VerticalSync) {
retraces = get_scan_status().hsync_count;
}
// Run until all requested events are satisfied.
return run_until(minimum_duration, [=]() {
return
(!(events & MachineEvent::NewSpeakerSamplesGenerated) || (sample_sets != speaker->completed_sample_sets())) &&
(!(events & MachineEvent::VerticalSync) || (retraces != get_scan_status().hsync_count));
});
}
protected:
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
@@ -61,6 +147,15 @@ class Machine {
return clock_rate_;
}
virtual Outputs::Display::ScanStatus get_scaled_scan_status() const {
// This deliberately sets up an infinite loop if the user hasn't
// overridden at least one of this or get_scan_status.
//
// Most likely you want to override this, and let the base class
// throw in a divide-by-clock-rate at the end for you.
return get_scan_status();
}
/*!
Maps from Configurable::Display to Outputs::Display::VideoSignal and calls
@c set_display_type with the result.
@@ -90,9 +185,11 @@ class Machine {
*/
virtual void set_display_type(Outputs::Display::DisplayType display_type) {}
private:
double clock_rate_ = 1.0;
double clock_conversion_error_ = 0.0;
double speed_multiplier_ = 1.0;
};
}

View File

@@ -57,7 +57,7 @@ class Joystick: public Inputs::ConcreteJoystick {
Input('9'), Input('*'), Input('#'),
}) {}
void did_set_input(const Input &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
default: return;
@@ -177,23 +177,27 @@ class ConcreteMachine:
audio_queue_.flush();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_->set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
@@ -358,30 +362,30 @@ class ConcreteMachine:
audio_queue_.perform();
}
float get_confidence() override {
float get_confidence() final {
if(pc_zero_accesses_ > 1) return 0.0f;
return confidence_counter_.get_confidence();
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Coleco::Vision::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::SVideo);
return selection_set;

View File

@@ -19,7 +19,6 @@ using namespace Commodore::C1540;
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
Storage::Disk::Controller(1000000),
m6502_(*this),
drive_(new Storage::Disk::Drive(1000000, 300, 2)),
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
serial_port_(new SerialPort),
drive_VIA_(drive_VIA_port_handler_),
@@ -37,7 +36,8 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
// attach the only drive there is
set_drive(drive_);
emplace_drive(1000000, 300, 2);
set_drive(1);
std::string device_name;
uint32_t crc = 0;
@@ -103,21 +103,21 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
drive_->set_disk(disk);
get_drive().set_disk(disk);
}
void Machine::run_for(const Cycles cycles) {
m6502_.run_for(cycles);
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
drive_->set_motor_on(drive_motor);
const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
get_drive().set_motor_on(drive_motor);
if(drive_motor)
Storage::Disk::Controller::run_for(cycles);
}
void MachineBase::set_activity_observer(Activity::Observer *observer) {
drive_VIA_.bus_handler().set_activity_observer(observer);
drive_->set_activity_observer(observer, "Drive", false);
get_drive().set_activity_observer(observer, "Drive", false);
}
// MARK: - 6522 delegate
@@ -154,7 +154,7 @@ void MachineBase::process_index_hole() {}
// MARK: - Drive VIA delegate
void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) {
drive_->step(Storage::Disk::HeadPosition(direction, 2));
get_drive().step(Storage::Disk::HeadPosition(direction, 2));
}
void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) {

View File

@@ -144,7 +144,6 @@ class MachineBase:
protected:
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
std::shared_ptr<Storage::Disk::Drive> drive_;
uint8_t ram_[0x800];
uint8_t rom_[0x4000];

View File

@@ -259,7 +259,7 @@ class Joystick: public Inputs::ConcreteJoystick {
user_port_via_port_handler_(user_port_via_port_handler),
keyboard_via_port_handler_(keyboard_via_port_handler) {}
void did_set_input(const Input &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) final {
JoystickInput mapped_input;
switch(digital_input.type) {
default: return;
@@ -397,9 +397,10 @@ class ConcreteMachine:
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
memset(mos6560_bus_handler_.video_memory_map, 0, sizeof(mos6560_bus_handler_.video_memory_map));
#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);
#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.
set_ram(0x0000, 0x0400);
@@ -453,7 +454,7 @@ class ConcreteMachine:
}
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_->set_tape(media.tapes.front());
}
@@ -477,18 +478,18 @@ class ConcreteMachine:
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
}
void set_key_state(uint16_t key, bool is_pressed) override final {
void set_key_state(uint16_t key, bool is_pressed) final {
if(key != KeyRestore)
keyboard_via_port_handler_->set_key_state(key, is_pressed);
else
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
}
void clear_all_keys() override final {
void clear_all_keys() final {
keyboard_via_port_handler_->clear_all_keys();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
@@ -618,45 +619,49 @@ class ConcreteMachine:
mos6560_.flush();
}
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
mos6560_.set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override final {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return mos6560_.get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
mos6560_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Speaker::Speaker *get_speaker() final {
return mos6560_.get_speaker();
}
void mos6522_did_change_interrupt_status(void *mos6522) override final {
void mos6522_did_change_interrupt_status(void *mos6522) final {
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
}
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>());
}
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final {
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) final {
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
}
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Commodore::Vic20::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
allow_fast_tape_hack_ = quickload;
@@ -669,27 +674,27 @@ class ConcreteMachine:
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
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_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
set_use_fast_tape();
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
if(c1540_) c1540_->set_activity_observer(observer);
}

View File

@@ -114,7 +114,7 @@ class ConcreteMachine:
audio_queue_.flush();
}
void set_key_state(uint16_t key, bool isPressed) override final {
void set_key_state(uint16_t key, bool isPressed) final {
if(key == KeyBreak) {
m6502_.set_reset_line(isPressed);
} else {
@@ -125,12 +125,12 @@ class ConcreteMachine:
}
}
void clear_all_keys() override final {
void clear_all_keys() final {
memset(key_states_, 0, sizeof(key_states_));
if(is_holding_shift_) set_key_state(KeyShift, true);
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_.set_tape(media.tapes.front());
}
@@ -379,49 +379,53 @@ class ConcreteMachine:
audio_queue_.perform();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_output_.set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_output_.get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_output_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
void tape_did_change_interrupt_status(Tape *tape) override final {
void tape_did_change_interrupt_status(Tape *tape) final {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts();
}
HalfCycles get_typer_delay() override final {
HalfCycles get_typer_delay() final {
return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting
}
HalfCycles get_typer_frequency() override final {
HalfCycles get_typer_frequency() final {
return Cycles(625*128*2); // accept a new character every two frames
}
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>());
}
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Electron::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
allow_fast_tape_hack_ = quickload;
@@ -434,14 +438,14 @@ class ConcreteMachine:
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
@@ -449,7 +453,7 @@ class ConcreteMachine:
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
activity_observer_ = observer;
if(activity_observer_) {
activity_observer_->register_led(caps_led);

View File

@@ -34,7 +34,7 @@ enum Key: uint16_t {
};
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
};
struct CharacterMapper: public ::Utility::CharacterMapper {

View File

@@ -11,13 +11,12 @@
using namespace Electron;
Plus3::Plus3() : WD1770(P1770) {
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
emplace_drives(2, 8000000, 300, 2);
set_control_register(last_control_, 0xff);
}
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
drives_[drive]->set_disk(disk);
get_drive(drive).set_disk(disk);
}
void Plus3::set_control_register(uint8_t control) {
@@ -33,16 +32,15 @@ void Plus3::set_control_register(uint8_t control) {
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
if(changes&3) {
switch(control&3) {
case 0: selected_drive_ = -1; set_drive(nullptr); break;
default: selected_drive_ = 0; set_drive(drives_[0]); break;
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
}
set_drive(control&3);
}
// Select the side on both drives at once.
if(changes & 0x04) {
drives_[0]->set_head((control & 0x04) ? 1 : 0);
drives_[1]->set_head((control & 0x04) ? 1 : 0);
get_drive(0).set_head((control & 0x04) ? 1 : 0);
get_drive(1).set_head((control & 0x04) ? 1 : 0);
}
if(changes & 0x08) set_is_double_density(!(control & 0x08));
}
@@ -53,9 +51,7 @@ void Plus3::set_motor_on(bool on) {
}
void Plus3::set_activity_observer(Activity::Observer *observer) {
size_t index = 0;
for(const auto &drive: drives_) {
drive->set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
++index;
}
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
});
}

View File

@@ -24,8 +24,6 @@ class Plus3 : public WD::WD1770 {
private:
void set_control_register(uint8_t control, uint8_t changes);
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
int selected_drive_ = 0;
uint8_t last_control_ = 0;
void set_motor_on(bool on);

View File

@@ -56,6 +56,10 @@ void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / float(crt_cycles_multiplier);
}
void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}

View File

@@ -39,6 +39,9 @@ class VideoOutput {
/// Sets the destination for output.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);

View File

@@ -19,7 +19,7 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler {
ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
@@ -39,7 +39,7 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler {
}
}
virtual std::string debug_type() override {
virtual std::string debug_type() final {
return "A16";
}

View File

@@ -19,7 +19,7 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler {
ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
@@ -51,7 +51,7 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler {
}
}
virtual std::string debug_type() override {
virtual std::string debug_type() final {
return "A8";
}

View File

@@ -19,7 +19,7 @@ class KonamiROMSlotHandler: public ROMSlotHandler {
KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 13) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
@@ -45,7 +45,7 @@ class KonamiROMSlotHandler: public ROMSlotHandler {
}
}
virtual std::string debug_type() override {
virtual std::string debug_type() final {
return "K";
}
private:

View File

@@ -20,7 +20,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) :
map_(map), slot_(slot), scc_(scc) {}
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
switch(address >> 11) {
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
@@ -66,7 +66,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
}
}
uint8_t read(uint16_t address) override {
uint8_t read(uint16_t address) final {
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
confidence_counter_.add_hit();
return scc_.read(address);
@@ -75,7 +75,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
return 0xff;
}
virtual std::string debug_type() override {
virtual std::string debug_type() final {
return "KSCC";
}

View File

@@ -13,8 +13,7 @@ using namespace MSX;
DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
WD1770(P1793),
rom_(rom) {
drives_[0] = std::make_shared<Storage::Disk::Drive>(8000000, 300, 2);
drives_[1] = std::make_shared<Storage::Disk::Drive>(8000000, 300, 2);
emplace_drives(2, 8000000, 300, 2);
set_is_double_density(true);
}
@@ -23,18 +22,19 @@ void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) {
case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb:
WD::WD1770::write(address, value);
break;
case 0x7ffc:
selected_head_ = value & 1;
drives_[0]->set_head(selected_head_);
drives_[1]->set_head(selected_head_);
break;
case 0x7ffc: {
const int selected_head = value & 1;
for_all_drives([selected_head] (Storage::Disk::Drive &drive, size_t index) {
drive.set_head(selected_head);
});
} break;
case 0x7ffd: {
selected_drive_ = value & 1;
set_drive(drives_[selected_drive_]);
set_drive(1 << (value & 1));
bool drive_motor = !!(value & 0x80);
drives_[0]->set_motor_on(drive_motor);
drives_[1]->set_motor_on(drive_motor);
const bool drive_motor = value & 0x80;
for_all_drives([drive_motor] (Storage::Disk::Drive &drive, size_t index) {
drive.set_motor_on(drive_motor);
});
} break;
}
}
@@ -59,7 +59,7 @@ void DiskROM::run_for(HalfCycles half_cycles) {
}
void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
drives_[drive]->set_disk(disk);
get_drive(drive).set_disk(disk);
}
void DiskROM::set_head_load_request(bool head_load) {
@@ -68,9 +68,7 @@ void DiskROM::set_head_load_request(bool head_load) {
}
void DiskROM::set_activity_observer(Activity::Observer *observer) {
size_t c = 1;
for(auto &drive: drives_) {
drive->set_activity_observer(observer, "Drive " + std::to_string(c), true);
++c;
}
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index), true);
});
}

View File

@@ -25,9 +25,9 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
public:
DiskROM(const std::vector<uint8_t> &rom);
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override;
uint8_t read(uint16_t address) override;
void run_for(HalfCycles half_cycles) override;
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final;
uint8_t read(uint16_t address) final;
void run_for(HalfCycles half_cycles) final;
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_activity_observer(Activity::Observer *observer);
@@ -36,11 +36,8 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
const std::vector<uint8_t> &rom_;
long int controller_cycles_ = 0;
size_t selected_drive_ = 0;
int selected_head_ = 0;
std::array<std::shared_ptr<Storage::Disk::Drive>, 2> drives_;
void set_head_load_request(bool head_load) override;
void set_head_load_request(bool head_load) final;
};
}

View File

@@ -110,7 +110,7 @@ class AYPortHandler: public GI::AY38910::PortHandler {
Input(Input::Fire, 1),
}) {}
void did_set_input(const Input &input, bool is_active) override {
void did_set_input(const Input &input, bool is_active) final {
uint8_t mask = 0;
switch(input.type) {
default: return;
@@ -278,23 +278,27 @@ class ConcreteMachine:
audio_queue_.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_->set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
float get_confidence() override {
float get_confidence() final {
if(performed_unmapped_access_ || pc_zero_accesses_ > 1) return 0.0f;
if(memory_slots_[1].handler) {
return memory_slots_[1].handler->get_confidence();
@@ -302,14 +306,14 @@ class ConcreteMachine:
return 0.5f;
}
std::string debug_type() override {
std::string debug_type() final {
if(memory_slots_[1].handler) {
return "MSX:" + memory_slots_[1].handler->debug_type();
}
return "MSX";
}
bool insert_media(const Analyser::Static::Media &media) override {
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
memory_slots_[1].source = segment.data;
@@ -356,7 +360,7 @@ class ConcreteMachine:
return true;
}
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
std::transform(
string.begin(),
string.end(),
@@ -366,7 +370,7 @@ class ConcreteMachine:
}
// MARK: MSX::MemoryMap
void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) override {
void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) final {
assert(!(destination_address & 8191));
assert(!(length & 8191));
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
@@ -381,7 +385,7 @@ class ConcreteMachine:
page_memory(paged_memory_);
}
void unmap(int slot, uint16_t destination_address, std::size_t length) override {
void unmap(int slot, uint16_t destination_address, std::size_t length) final {
assert(!(destination_address & 8191));
assert(!(length & 8191));
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
@@ -624,26 +628,26 @@ class ConcreteMachine:
return key_states_[selected_key_line_];
}
void clear_all_keys() override {
void clear_all_keys() final {
std::memset(key_states_, 0xff, sizeof(key_states_));
}
void set_key_state(uint16_t key, bool is_pressed) override {
void set_key_state(uint16_t key, bool is_pressed) final {
int mask = 1 << (key & 7);
int line = key >> 4;
if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask;
}
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return MSX::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
allow_fast_tape_ = quickload;
@@ -656,14 +660,14 @@ class ConcreteMachine:
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
@@ -671,13 +675,13 @@ class ConcreteMachine:
}
// MARK: - Sleeper
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
set_use_fast_tape();
}
// MARK: - Activity::Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
DiskROM *disk_rom = get_disk_rom();
if(disk_rom) {
disk_rom->set_activity_observer(observer);
@@ -686,7 +690,7 @@ class ConcreteMachine:
}
// MARK: - Joysticks
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return ay_port_handler_.get_joysticks();
}

View File

@@ -21,6 +21,8 @@
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#define LOG_PREFIX "[SMS] "
#include "../../Outputs/Log.hpp"
#include "../../Analyser/Static/Sega/Target.hpp"
@@ -54,7 +56,7 @@ class Joystick: public Inputs::ConcreteJoystick {
Input(Input::Fire, 1)
}) {}
void did_set_input(const Input &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) final {
switch(digital_input.type) {
default: return;
@@ -168,7 +170,7 @@ class ConcreteMachine:
audio_queue_.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
vdp_->set_tv_standard(
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
@@ -177,15 +179,19 @@ class ConcreteMachine:
vdp_->set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return vdp_->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
@@ -340,16 +346,16 @@ class ConcreteMachine:
audio_queue_.perform();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
// MARK: - Keyboard (i.e. the pause and reset buttons).
Inputs::Keyboard &get_keyboard() override {
Inputs::Keyboard &get_keyboard() final {
return keyboard_;
}
void keyboard_did_change_key(Inputs::Keyboard *, Inputs::Keyboard::Key key, bool is_pressed) override {
void keyboard_did_change_key(Inputs::Keyboard *, Inputs::Keyboard::Key key, bool is_pressed) final {
if(key == Inputs::Keyboard::Key::Enter) {
pause_is_pressed_ = is_pressed;
} else if(key == Inputs::Keyboard::Key::Escape) {
@@ -357,28 +363,28 @@ class ConcreteMachine:
}
}
void reset_all_keys(Inputs::Keyboard *) override {
void reset_all_keys(Inputs::Keyboard *) final {
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Sega::MasterSystem::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;

159
Machines/Oric/BD500.cpp Normal file
View File

@@ -0,0 +1,159 @@
//
// BD500.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "BD500.hpp"
using namespace Oric;
BD500::BD500() : DiskController(P1793, 9000000, Storage::Disk::Drive::ReadyType::ShugartModifiedRDY) {
disable_basic_rom_ = true;
select_paged_item();
set_is_double_density(true);
set_drive(1);
}
void BD500::write(int address, uint8_t value) {
access(address);
if(address >= 0x0320 && address <= 0x0323) {
// if(address == 0x320) printf("Command %02x\n", value);
WD::WD1770::write(address, value);
}
if(address == 0x031a) {
// Drive select; kudos to iss of Oricutron for figuring this one out;
// cf. http://forum.defence-force.org/viewtopic.php?f=25&p=21409#p21393
switch(value & 0xe0) {
default: set_drive(0); break;
case 0x20: set_drive(1); break;
case 0x40: set_drive(2); break;
case 0x80: set_drive(4); break;
case 0xc0: set_drive(8); break;
}
}
}
uint8_t BD500::read(int address) {
access(address);
switch(address) {
default: return 0xff;
case 0x0320: case 0x0321: case 0x0322: case 0x0323:
return WD::WD1770::read(address);
case 0x312: return (get_data_request_line() ? 0x80 : 0x00) | (get_interrupt_request_line() ? 0x40 : 0x00);
}
}
void BD500::access(int address) {
// Determine whether to perform a command.
switch(address) {
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
return;
case 0x310: enable_overlay_ram_ = true; break;
case 0x313: enable_overlay_ram_ = false; break;
case 0x317: disable_basic_rom_ = false; break; // Could be 0x311.
default:
// printf("Switch %04x???\n", address);
break;
}
select_paged_item();
}
/*
The following was used when trying to find appropriate soft switch locations. It is preserved
as the values I have above are unlikely to be wholly correct and further research might be
desirable.
void BD500::access(int address) {
// 0,1,4,5,10,11 -> 64kb Atmos
// 2,3,9 -> 56kb Atmos.
// Broken: 6, 7, 8
int order = 5;
int commands[4];
std::vector<int> available_commands = {0, 1, 2, 3};
const int modulos[] = {6, 2, 1, 1};
for(int c = 0; c < 4; ++c) {
const int index = order / modulos[c];
commands[c] = available_commands[size_t(index)];
available_commands.erase(available_commands.begin() + index);
order %= modulos[c];
}
// Determine whether to perform a command.
int index = -1;
switch(address) {
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
return;
case 0x310: index = 0; break;
case 0x313: index = 1; break;
case 0x314: index = 2; break;
case 0x317: index = 3; break;
default:
printf("Switch %04x???\n", address);
break;
}
select_paged_item();
if(index >= 0) {
switch(commands[index]) {
case 0: enable_overlay_ram_ = true; break; // +RAM
case 1: disable_basic_rom_ = false; break; // -rom
case 2: disable_basic_rom_ = true; break; // +rom
case 3: enable_overlay_ram_ = false; break; // -RAM
}
select_paged_item();
}
}
*/
void BD500::set_head_load_request(bool head_load) {
// Turn all motors on or off; if off then unload the head instantly.
is_loading_head_ |= head_load;
for_all_drives([head_load] (Storage::Disk::Drive &drive, size_t) {
drive.set_motor_on(head_load);
});
if(!head_load) set_head_loaded(false);
}
void BD500::run_for(const Cycles cycles) {
// If a head load is in progress and the selected drive is now ready,
// declare head loaded.
if(is_loading_head_ && get_drive().get_is_ready()) {
set_head_loaded(true);
is_loading_head_ = false;
}
WD::WD1770::run_for(cycles);
}
void BD500::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("BD-500");
observer_->set_led_status("BD-500", get_head_loaded());
}
}
void BD500::set_head_loaded(bool loaded) {
WD::WD1770::set_head_loaded(loaded);
if(observer_) {
observer_->set_led_status("BD-500", loaded);
}
}

43
Machines/Oric/BD500.hpp Normal file
View File

@@ -0,0 +1,43 @@
//
// BD500.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef BD500_hpp
#define BD500_hpp
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
#include "DiskController.hpp"
#include <array>
#include <memory>
namespace Oric {
class BD500: public DiskController {
public:
BD500();
void write(int address, uint8_t value);
uint8_t read(int address);
void run_for(const Cycles cycles);
void set_activity_observer(Activity::Observer *observer);
private:
void set_head_load_request(bool head_load) final;
bool is_loading_head_ = false;
Activity::Observer *observer_ = nullptr;
void access(int address);
void set_head_loaded(bool loaded);
};
};
#endif /* BD500_hpp */

View File

@@ -0,0 +1,75 @@
//
// DiskController.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef DiskController_h
#define DiskController_h
namespace Oric {
class DiskController: public WD::WD1770 {
public:
DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) :
WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) {
emplace_drives(4, clock_rate_, 300, 2, ready_type_);
// TODO: don't assume four drives?
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
get_drive(size_t(d)).set_disk(disk);
}
enum class PagedItem {
DiskROM,
BASIC,
RAM
};
struct Delegate: public WD1770::Delegate {
virtual void disk_controller_did_change_paged_item(DiskController *controller) = 0;
};
inline void set_delegate(Delegate *delegate) {
delegate_ = delegate;
WD1770::set_delegate(delegate);
if(delegate) delegate->disk_controller_did_change_paged_item(this);
}
inline PagedItem get_paged_item() {
return paged_item_;
}
protected:
Delegate *delegate_ = nullptr;
bool enable_overlay_ram_ = false;
bool disable_basic_rom_ = false;
void select_paged_item() {
PagedItem item = PagedItem::RAM;
if(!enable_overlay_ram_) {
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
}
set_paged_item(item);
}
private:
PagedItem paged_item_ = PagedItem::DiskROM;
int clock_rate_;
Storage::Disk::Drive::ReadyType ready_type_;
inline void set_paged_item(PagedItem item) {
if(paged_item_ == item) return;
paged_item_ = item;
if(delegate_) {
delegate_->disk_controller_did_change_paged_item(this);
}
}
};
};
#endif /* DiskController_h */

View File

@@ -12,27 +12,20 @@ using namespace Oric;
// NB: there's some controversy here on WD1770 versus WD1772, but between those two I think
// the only difference is stepping rates, and it says 1770 on the schematic I'm looking at.
Jasmin::Jasmin() : WD1770(P1770) {
Jasmin::Jasmin() : DiskController(P1770, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
set_is_double_density(true);
}
void Jasmin::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
const size_t drive = size_t(d);
if(!drives_[drive]) {
drives_[drive] = std::make_unique<Storage::Disk::Drive>(8000000, 300, 2);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
select_paged_item();
}
void Jasmin::write(int address, uint8_t value) {
switch(address) {
// Set side.
case 0x3f8:
for(auto &drive : drives_) {
if(drive) drive->set_head(value & 1);
}
break;
case 0x3f8: {
const int head = value & 1;
for_all_drives([head] (Storage::Disk::Drive &drive, size_t) {
drive.set_head(head);
});
} break;
case 0x3f9:
/* TODO: reset. */
@@ -40,24 +33,21 @@ void Jasmin::write(int address, uint8_t value) {
case 0x3fa: {
// If b0, enable overlay RAM.
posit_paging_flags((paging_flags_ & BASICDisable) | ((value & 1) ? OverlayRAMEnable : 0));
enable_overlay_ram_ = value & 1;
select_paged_item();
} break;
case 0x3fb:
// If b0, disable BASIC ROM.
posit_paging_flags((paging_flags_ & OverlayRAMEnable) | ((value & 1) ? BASICDisable : 0));
disable_basic_rom_ = value & 1;
select_paged_item();
break;
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
const size_t new_selected_drive = size_t(address - 0x3fc);
if(new_selected_drive != selected_drive_) {
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
selected_drive_ = new_selected_drive;
set_drive(drives_[selected_drive_]);
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
}
} break;
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff:
get_drive().set_motor_on(false);
set_drive(1 << (address - 0x3fc));
get_drive().set_motor_on(motor_on_);
break;
default:
return WD::WD1770::write(address, value);
@@ -66,5 +56,16 @@ void Jasmin::write(int address, uint8_t value) {
void Jasmin::set_motor_on(bool on) {
motor_on_ = on;
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
get_drive().set_motor_on(motor_on_);
if(observer_) {
observer_->set_led_status("Jasmin", on);
}
}
void Jasmin::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("Jasmin");
observer_->set_led_status("Jasmin", motor_on_);
}
}

View File

@@ -11,48 +11,23 @@
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
#include <array>
#include <memory>
#include "DiskController.hpp"
namespace Oric {
class Jasmin: public WD::WD1770 {
class Jasmin: public DiskController {
public:
Jasmin();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void write(int address, uint8_t value);
enum PagingFlags {
/// Indicates that overlay RAM is enabled, implying no ROM is visible.
OverlayRAMEnable = (1 << 0),
/// Indicates that the BASIC ROM is disabled, implying that the JASMIN ROM
/// fills its space.
BASICDisable = (1 << 1)
};
struct Delegate: public WD1770::Delegate {
virtual void jasmin_did_change_paging_flags(Jasmin *jasmin) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
inline int get_paging_flags() { return paging_flags_; }
void set_activity_observer(Activity::Observer *observer);
private:
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_;
int paging_flags_ = 0;
Delegate *delegate_ = nullptr;
void posit_paging_flags(int new_flags) {
if(new_flags != paging_flags_) {
paging_flags_ = new_flags;
if(delegate_) delegate_->jasmin_did_change_paging_flags(this);
}
}
void set_motor_on(bool on) final;
bool motor_on_ = false;
Activity::Observer *observer_ = nullptr;
};
};

View File

@@ -18,20 +18,10 @@ namespace {
const Cycles::IntType head_load_request_counter_target = 7653333;
}
Microdisc::Microdisc() : WD1770(P1793) {
Microdisc::Microdisc() : DiskController(P1793, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
set_control_register(last_control_, 0xff);
}
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
const size_t drive = size_t(d);
if(!drives_[drive]) {
drives_[drive] = std::make_unique<Storage::Disk::Drive>(8000000, 300, 2);
if(drive == selected_drive_) set_drive(drives_[drive]);
drives_[drive]->set_activity_observer(observer_, drive_name(drive), false);
}
drives_[drive]->set_disk(disk);
}
void Microdisc::set_control_register(uint8_t control) {
const uint8_t changes = last_control_ ^ control;
last_control_ = control;
@@ -43,16 +33,15 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b65: drive select
if((changes >> 5)&3) {
selected_drive_ = (control >> 5)&3;
set_drive(drives_[selected_drive_]);
set_drive(1 << (control >> 5)&3);
}
// b4: side select
if(changes & 0x10) {
const int head = (control & 0x10) ? 1 : 0;
for(auto &drive : drives_) {
if(drive) drive->set_head(head);
}
for_all_drives([head] (Storage::Disk::Drive &drive, size_t) {
drive.set_head(head);
});
}
// b3: double density select (0 = double)
@@ -73,8 +62,9 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b7: EPROM select (0 = select)
// b1: ROM disable (0 = disable)
if(changes & 0x82) {
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodiscDisable : 0);
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
enable_overlay_ram_ = control & 0x80;
disable_basic_rom_ = !(control & 0x02);
select_paged_item();
}
}
@@ -95,9 +85,9 @@ void Microdisc::set_head_load_request(bool head_load) {
// The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive
// the current head load request state.
for(auto &drive : drives_) {
if(drive) drive->set_motor_on(head_load);
}
for_all_drives([head_load] (Storage::Disk::Drive &drive, size_t) {
drive.set_motor_on(head_load);
});
// A request to load the head results in a delay until the head is confirmed loaded. This delay is handled
// in ::run_for. A request to unload the head results in an instant answer that the head is unloaded.
@@ -121,23 +111,10 @@ void Microdisc::run_for(const Cycles cycles) {
WD::WD1770::run_for(cycles);
}
bool Microdisc::get_drive_is_ready() {
return true;
}
void Microdisc::set_activity_observer(Activity::Observer *observer) {
observer_ = observer;
if(observer) {
observer->register_led("Microdisc");
observer_->set_led_status("Microdisc", head_load_request_);
}
size_t c = 0;
for(auto &drive : drives_) {
if(drive) drive->set_activity_observer(observer, drive_name(c), false);
++c;
}
}
std::string Microdisc::drive_name(size_t index) {
return "Drive " + std::to_string(index);
}

View File

@@ -11,16 +11,14 @@
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
#include <array>
#include "DiskController.hpp"
namespace Oric {
class Microdisc: public WD::WD1770 {
class Microdisc: public DiskController {
public:
Microdisc();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void set_control_register(uint8_t control);
uint8_t get_interrupt_request_register();
uint8_t get_data_request_register();
@@ -29,42 +27,19 @@ class Microdisc: public WD::WD1770 {
void run_for(const Cycles cycles);
enum PagingFlags {
/// Indicates that the BASIC ROM should be disabled; if this is set then either
/// the Microdisc ROM or overlay RAM will be visible. If it is not set, BASIC
/// should be visible.
BASICDisable = (1 << 0),
/// Indicates that the Microdisc ROM is disabled. If BASIC is disabled and the Microdisc
/// is also disabled, overlay RAM should be visible.
MicrodiscDisable = (1 << 1)
};
class Delegate: public WD1770::Delegate {
public:
virtual void microdisc_did_change_paging_flags(Microdisc *microdisc) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
inline int get_paging_flags() { return paging_flags_; }
void set_activity_observer(Activity::Observer *observer);
private:
void set_control_register(uint8_t control, uint8_t changes);
void set_head_load_request(bool head_load) override;
bool get_drive_is_ready();
void set_head_load_request(bool head_load) final;
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_;
void set_control_register(uint8_t control, uint8_t changes);
uint8_t last_control_ = 0;
bool irq_enable_ = false;
int paging_flags_ = BASICDisable;
Cycles::IntType head_load_request_counter_ = -1;
bool head_load_request_ = false;
Delegate *delegate_ = nullptr;
uint8_t last_control_ = 0;
Activity::Observer *observer_ = nullptr;
std::string drive_name(size_t index);
Activity::Observer *observer_ = nullptr;
};
}

View File

@@ -8,6 +8,7 @@
#include "Oric.hpp"
#include "BD500.hpp"
#include "Jasmin.hpp"
#include "Keyboard.hpp"
#include "Microdisc.hpp"
@@ -223,8 +224,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
public Jasmin::Delegate,
public DiskController::Delegate,
public ClockingHint::Observer,
public Activity::Source,
public Machine,
@@ -244,7 +244,16 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
speaker_.set_input_rate(1000000.0f);
via_port_handler_.set_interrupt_delegate(this);
tape_player_.set_delegate(this);
// Slight hack here: I'm unclear what RAM should look like at startup.
// Actually, I think completely random might be right since the Microdisc
// sort of assumes it, but also the BD-500 never explicitly sets PAL mode
// so I can't have any switch-to-NTSC bytes in the display area. Hence:
// disallow all atributes.
Memory::Fuzz(ram_, sizeof(ram_));
for(size_t c = 0; c < sizeof(ram_); ++c) {
ram_[c] |= 0x40;
}
if constexpr (disk_interface == DiskInterface::Pravetz) {
diskii_.set_clocking_hint_observer(this);
@@ -266,6 +275,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
size_t diskii_state_machine_index = 0;
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
rom_names.emplace_back(machine_name, "the ORIC Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34);
break;
case DiskInterface::Jasmin:
rom_names.emplace_back(machine_name, "the ORIC Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
break;
@@ -293,13 +305,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
break;
case DiskInterface::Jasmin:
jasmin_rom_ = std::move(*roms[2]);
jasmin_rom_.resize(2048);
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(2048);
break;
case DiskInterface::Microdisc:
microdisc_rom_ = std::move(*roms[2]);
microdisc_rom_.resize(8192);
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
break;
case DiskInterface::Pravetz: {
pravetz_rom_ = std::move(*roms[2]);
@@ -314,14 +330,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
switch(target.disk_interface) {
default: break;
case DiskInterface::Microdisc:
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
case DiskInterface::BD500:
bd500_.set_delegate(this);
break;
case DiskInterface::Jasmin:
jasmin_did_change_paging_flags(&jasmin_);
jasmin_.set_delegate(this);
break;
case DiskInterface::Microdisc:
microdisc_.set_delegate(this);
break;
}
if(!target.loading_command.empty()) {
@@ -353,7 +370,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
audio_queue_.flush();
}
void set_key_state(uint16_t key, bool is_pressed) override final {
void set_key_state(uint16_t key, bool is_pressed) final {
if(key == KeyNMI) {
m6502_.set_nmi_line(is_pressed);
} else {
@@ -361,7 +378,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
}
void clear_all_keys() override final {
void clear_all_keys() final {
keyboard_.clear_all_keys();
}
@@ -380,7 +397,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
return true;
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
bool inserted = false;
if(!media.tapes.empty()) {
@@ -390,16 +407,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
if(!media.disks.empty()) {
switch(disk_interface) {
case DiskInterface::Jasmin:
inserted |= insert_disks(media, jasmin_, 4);
break;
case DiskInterface::Microdisc: {
inserted |= insert_disks(media, microdisc_, 4);
} break;
case DiskInterface::Pravetz: {
inserted |= insert_disks(media, diskii_, 2);
} break;
case DiskInterface::BD500: inserted |= insert_disks(media, bd500_, 4); break;
case DiskInterface::Jasmin: inserted |= insert_disks(media, jasmin_, 4); break;
case DiskInterface::Microdisc: inserted |= insert_disks(media, microdisc_, 4); break;
case DiskInterface::Pravetz: inserted |= insert_disks(media, diskii_, 2); break;
default: break;
}
}
@@ -434,6 +445,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
} else {
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
if(isReadOperation(operation)) *value = bd500_.read(address);
else bd500_.write(address, *value);
break;
case DiskInterface::Jasmin:
if(address >= 0x3f4) {
if(isReadOperation(operation)) *value = jasmin_.read(address);
@@ -497,8 +512,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
tape_player_.run_for(Cycles(1));
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
bd500_.run_for(Cycles(9)); // i.e. effective clock rate of 9Mhz.
break;
case DiskInterface::Jasmin:
jasmin_.run_for(Cycles(8));
jasmin_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
// Jasmin autostart hack: wait for a period, then trigger a reset, having forced
// the Jasmin to page its ROM in first. I assume the latter being what the Jasmin's
@@ -511,12 +529,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
break;
case DiskInterface::Microdisc:
microdisc_.run_for(Cycles(8));
microdisc_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
break;
case DiskInterface::Pravetz:
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
diskii_.set_data_input(*value);
diskii_.run_for(Cycles(2));
diskii_.run_for(Cycles(2));; // i.e. effective clock rate of 2Mhz.
} else {
cycles_since_diskii_update_ += Cycles(2);
}
@@ -534,93 +552,76 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
// to satisfy CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_output_.set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_output_.get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_output_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
// to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522) override final {
void mos6522_did_change_interrupt_status(void *mos6522) final {
set_interrupt_line();
}
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) final {
// set CB1
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
}
// for Utility::TypeRecipient::Delegate
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
}
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
const int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = basic_visible_ram_top_;
paged_rom_ = rom_.data();
} else {
if(flags&Microdisc::PagingFlags::MicrodiscDisable) {
ram_top_ = basic_invisible_ram_top_;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
// Jasmin::Delegate
void jasmin_did_change_paging_flags(Jasmin *jasmin) override final {
const int flags = jasmin->get_paging_flags();
switch(flags) {
// BASIC enabled, overlay disabled.
// DiskController::Delegate
void disk_controller_did_change_paged_item(DiskController *controller) final {
switch(controller->get_paged_item()) {
default:
ram_top_ = basic_visible_ram_top_;
paged_rom_ = rom_.data();
break;
// Overlay RAM enabled, with or without BASIC.
case Jasmin::OverlayRAMEnable:
case Jasmin::OverlayRAMEnable | Jasmin::BASICDisable:
case DiskController::PagedItem::RAM:
ram_top_ = basic_invisible_ram_top_;
break;
// BASIC disabled, overlay disabled.
case Jasmin::BASICDisable:
ram_top_ = 0xf7ff;
paged_rom_ = jasmin_rom_.data();
case DiskController::PagedItem::DiskROM:
ram_top_ = uint16_t(0xffff - disk_rom_.size());
paged_rom_ = disk_rom_.data();
break;
}
}
// WD::WD1770::Delegate
void wd1770_did_change_output(WD::WD1770 *wd1770) override final {
void wd1770_did_change_output(WD::WD1770 *wd1770) final {
set_interrupt_line();
}
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Oric::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
set_use_fast_tape_hack(quickload);
@@ -632,23 +633,29 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
bd500_.set_activity_observer(observer);
break;
case DiskInterface::Jasmin:
jasmin_.set_activity_observer(observer);
break;
case DiskInterface::Microdisc:
microdisc_.set_activity_observer(observer);
break;
@@ -658,7 +665,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
}
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final {
diskii_clocking_preference_ = diskii_.preferred_clocking();
}
@@ -669,7 +676,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
// RAM and ROM
std::vector<uint8_t> rom_, microdisc_rom_, jasmin_rom_;
std::vector<uint8_t> rom_, disk_rom_;
uint8_t ram_[65536];
Cycles cycles_since_video_update_;
inline void update_video() {
@@ -705,6 +712,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
Jasmin jasmin_;
int jasmin_reset_counter_ = 0;
// the BD-500, if in use.
BD500 bd500_;
// the Pravetz/Disk II, if in use.
Apple::DiskII diskii_;
Cycles cycles_since_diskii_update_;
@@ -737,7 +747,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
// Keys that aren't read by polling.
void perform_special_key(Oric::Key key) override {
void perform_special_key(Oric::Key key) final {
switch(key) {
default: break;
@@ -771,6 +781,7 @@ Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMac
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
case DiskInterface::Jasmin: return new ConcreteMachine<DiskInterface::Jasmin>(*oric_target, rom_fetcher);
case DiskInterface::BD500: return new ConcreteMachine<DiskInterface::BD500>(*oric_target, rom_fetcher);
}
}

View File

@@ -26,12 +26,27 @@ namespace {
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
crt_(64*6, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1),
frequency_mismatch_warner_(*this),
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
counter_period_(PAL50Period) {
crt_.set_visible_area(crt_.get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
crt_.set_phase_linked_luminance_offset(-1.0f / 8.0f);
data_type_ = Outputs::Display::InputDataType::Red1Green1Blue1;
crt_.set_input_data_type(data_type_);
crt_.set_delegate(&frequency_mismatch_warner_);
update_crt_frequency();
}
void VideoOutput::register_crt_frequency_mismatch() {
crt_is_60Hz_ ^= true;
update_crt_frequency();
}
void VideoOutput::update_crt_frequency() {
// Set the proper frequency...
crt_.set_new_display_type(64*6, crt_is_60Hz_ ? Outputs::Display::Type::PAL60 : Outputs::Display::Type::PAL50);
// ... but also pick an appropriate crop rectangle.
crt_.set_visible_area(crt_.get_rect_for_area(crt_is_60Hz_ ? 26 : 54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
@@ -56,6 +71,10 @@ void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 6.0f;
}
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
for(std::size_t c = 0; c < 8; c++) {
colour_forms_[c] = 0;
@@ -88,8 +107,8 @@ void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
}
void VideoOutput::run_for(const Cycles cycles) {
// Vertical: 0-39: pixels; otherwise blank; 48-53 sync, 54-56 colour burst
// Horizontal: 0-223: pixels; otherwise blank; 256-259 sync
// Horizontal: 0-39: pixels; otherwise blank; 48-53 sync, 54-56 colour burst.
// Vertical: 0-223: pixels; otherwise blank; 256-259 (50Hz) or 234-238 (60Hz) sync.
#define clamp(action) \
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;

View File

@@ -27,22 +27,29 @@ class VideoOutput {
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
void set_display_type(Outputs::Display::DisplayType display_type);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void register_crt_frequency_mismatch();
private:
uint8_t *ram_;
Outputs::CRT::CRT crt_;
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
bool crt_is_60Hz_ = false;
// Counters and limits
void update_crt_frequency();
// Counters and limits.
int counter_ = 0, frame_counter_ = 0;
int v_sync_start_position_, v_sync_end_position_, counter_period_;
// Output target and device
uint8_t *rgb_pixel_target_;
uint32_t *composite_pixel_target_;
// Output target and device.
uint8_t *rgb_pixel_target_ = nullptr;
uint32_t *composite_pixel_target_ = nullptr;
uint32_t colour_forms_[8];
Outputs::Display::InputDataType data_type_;
// Registers
// Registers.
uint8_t ink_, paper_;
int character_set_base_address_ = 0xb400;

View File

@@ -25,35 +25,35 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
return *this;
}
Activity::Source *activity_source() override {
Activity::Source *activity_source() final {
return get<Activity::Source>();
}
MediaTarget::Machine *media_target() override {
MediaTarget::Machine *media_target() final {
return get<MediaTarget::Machine>();
}
CRTMachine::Machine *crt_machine() override {
CRTMachine::Machine *crt_machine() final {
return get<CRTMachine::Machine>();
}
JoystickMachine::Machine *joystick_machine() override {
JoystickMachine::Machine *joystick_machine() final {
return get<JoystickMachine::Machine>();
}
KeyboardMachine::Machine *keyboard_machine() override {
KeyboardMachine::Machine *keyboard_machine() final {
return get<KeyboardMachine::Machine>();
}
MouseMachine::Machine *mouse_machine() override {
MouseMachine::Machine *mouse_machine() final {
return get<MouseMachine::Machine>();
}
Configurable::Device *configurable_device() override {
Configurable::Device *configurable_device() final {
return get<Configurable::Device>();
}
void *raw_pointer() override {
void *raw_pointer() final {
return get();
}

View File

@@ -109,3 +109,7 @@ void Video::output_byte(uint8_t byte) {
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 2.0f;
}

View File

@@ -36,12 +36,16 @@ class Video {
/// Sets the current sync output.
void set_sync(bool sync);
/// Causes @c byte to be serialised into pixels and output over the next four cycles.
void output_byte(uint8_t byte);
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
private:
bool sync_ = false;
uint8_t *line_data_ = nullptr;

View File

@@ -314,19 +314,23 @@ template<bool is_zx81> class ConcreteMachine:
}
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
Outputs::Speaker::Speaker *get_speaker() final {
return is_zx81 ? &speaker_ : nullptr;
}
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
}
@@ -335,19 +339,19 @@ template<bool is_zx81> class ConcreteMachine:
return !media.tapes.empty();
}
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>(is_zx81));
}
// MARK: - Keyboard
void set_key_state(uint16_t key, bool is_pressed) override final {
void set_key_state(uint16_t key, bool is_pressed) final {
if(is_pressed)
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
else
key_states_[key >> 8] |= static_cast<uint8_t>(key);
}
void clear_all_keys() override final {
void clear_all_keys() final {
memset(key_states_, 0xff, 8);
}
@@ -360,28 +364,28 @@ template<bool is_zx81> class ConcreteMachine:
}
}
void set_tape_is_playing(bool is_playing) override final {
void set_tape_is_playing(bool is_playing) final {
tape_player_.set_motor_control(is_playing);
}
bool get_tape_is_playing() override final {
bool get_tape_is_playing() final {
return tape_player_.get_motor_control();
}
// MARK: - Typer timing
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
HalfCycles get_typer_delay() final { return Cycles(7000000); }
HalfCycles get_typer_frequency() final { return Cycles(390000); }
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return ZX8081::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
allow_fast_tape_hack_ = quickload;
@@ -394,14 +398,14 @@ template<bool is_zx81> class ConcreteMachine:
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_automatic_tape_motor_control_selection(selection_set, false);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_automatic_tape_motor_control_selection(selection_set, true);

View File

@@ -1,50 +0,0 @@
//
// Factors.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#ifndef Factors_hpp
#define Factors_hpp
#include <numeric>
#include <utility>
namespace NumberTheory {
/*!
@returns The greatest common divisor of @c a and @c b.
*/
template<class T> T greatest_common_divisor(T a, T b) {
#if __cplusplus > 201402L
return std::gcd(a, b);
#else
if(a < b) {
std::swap(a, b);
}
while(1) {
if(!a) return b;
if(!b) return a;
T remainder = a%b;
a = b;
b = remainder;
}
#endif
}
/*!
@returns The least common multiple of @c a and @c b computed indirectly via the greatest
common divisor.
*/
template<class T> T least_common_multiple(T a, T b) {
if(a == b) return a;
T gcd = greatest_common_divisor<T>(a, b);
return (a / gcd) * (b / gcd) * gcd;
}
}
#endif /* Factors_hpp */

View File

@@ -15,19 +15,19 @@
namespace CRC {
/*! Provides a class capable of generating a CRC from source data. */
template <typename T, T reset_value, T xor_output, bool reflect_input, bool reflect_output> class Generator {
template <typename IntType, IntType reset_value, IntType output_xor, bool reflect_input, bool reflect_output> class Generator {
public:
/*!
Instantiates a CRC16 that will compute the CRC16 specified by the supplied
@c polynomial and @c reset_value.
*/
Generator(T polynomial): value_(reset_value) {
const T top_bit = T(~(T(~0) >> 1));
Generator(IntType polynomial): value_(reset_value) {
const IntType top_bit = IntType(~(IntType(~0) >> 1));
for(int c = 0; c < 256; c++) {
T shift_value = static_cast<T>(c << multibyte_shift);
IntType shift_value = IntType(c << multibyte_shift);
for(int b = 0; b < 8; b++) {
T exclusive_or = (shift_value&top_bit) ? polynomial : 0;
shift_value = static_cast<T>(shift_value << 1) ^ exclusive_or;
IntType exclusive_or = (shift_value&top_bit) ? polynomial : 0;
shift_value = IntType(shift_value << 1) ^ exclusive_or;
}
xor_table[c] = shift_value;
}
@@ -38,17 +38,17 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
/// Updates the CRC to include @c byte.
void add(uint8_t byte) {
if(reflect_input) byte = reverse_byte(byte);
value_ = static_cast<T>((value_ << 8) ^ xor_table[(value_ >> multibyte_shift) ^ byte]);
if constexpr (reflect_input) byte = reverse_byte(byte);
value_ = IntType((value_ << 8) ^ xor_table[(value_ >> multibyte_shift) ^ byte]);
}
/// @returns The current value of the CRC.
inline T get_value() const {
T result = value_^xor_output;
if(reflect_output) {
T reflected_output = 0;
for(std::size_t c = 0; c < sizeof(T); ++c) {
reflected_output = T(reflected_output << 8) | T(reverse_byte(result & 0xff));
inline IntType get_value() const {
IntType result = value_ ^ output_xor;
if constexpr (reflect_output) {
IntType reflected_output = 0;
for(std::size_t c = 0; c < sizeof(IntType); ++c) {
reflected_output = IntType(reflected_output << 8) | IntType(reverse_byte(result & 0xff));
result >>= 8;
}
return reflected_output;
@@ -57,7 +57,7 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
}
/// Sets the current value of the CRC.
inline void set_value(T value) { value_ = value; }
inline void set_value(IntType value) { value_ = value; }
/*!
A compound for:
@@ -66,16 +66,30 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
[add all data from @c data]
get_value()
*/
T compute_crc(const std::vector<uint8_t> &data) {
template <typename Collection> IntType compute_crc(const Collection &data) {
return compute_crc(data.begin(), data.end());
}
/*!
A compound for:
reset()
[add all data from @c begin to @c end]
get_value()
*/
template <typename Iterator> IntType compute_crc(Iterator begin, Iterator end) {
reset();
for(const auto &byte: data) add(byte);
while(begin != end) {
add(*begin);
++begin;
}
return get_value();
}
private:
static constexpr int multibyte_shift = (sizeof(T) * 8) - 8;
T xor_table[256];
T value_;
static constexpr int multibyte_shift = (sizeof(IntType) * 8) - 8;
IntType xor_table[256];
IntType value_;
constexpr uint8_t reverse_byte(uint8_t byte) const {
return

67
Numeric/LFSR.hpp Normal file
View File

@@ -0,0 +1,67 @@
//
// LFSR.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/01/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef LFSR_h
#define LFSR_h
namespace Numeric {
template <typename IntType> struct LSFRPolynomial {};
// The following were taken 'at random' from https://users.ece.cmu.edu/~koopman/lfsr/index.html
template <> struct LSFRPolynomial<uint64_t> {
static constexpr uint64_t value = 0x80000000000019E2;
};
template <> struct LSFRPolynomial<uint32_t> {
static constexpr uint32_t value = 0x80000C34;
};
template <> struct LSFRPolynomial<uint16_t> {
static constexpr uint16_t value = 0x853E;
};
template <> struct LSFRPolynomial<uint8_t> {
static constexpr uint8_t value = 0xAF;
};
/*!
Provides a linear-feedback shift register with a random initial state; if no polynomial is supplied
then one will be picked that is guaranteed to give the maximal number of LFSR states that can fit
in the specified int type.
*/
template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntType>::value> class LFSR {
public:
LFSR() {
// Randomise the value, ensuring it doesn't end up being 0.
while(!value_) {
uint8_t *value_byte = reinterpret_cast<uint8_t *>(&value_);
for(size_t c = 0; c < sizeof(IntType); ++c) {
*value_byte = uint8_t(uint64_t(rand()) * 255 / RAND_MAX);
++value_byte;
}
}
}
/*!
Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0,
determining the bit that was just shifted out.
*/
IntType next() {
const auto result = value_ & 1;
value_ = (value_ >> 1) ^ (result * polynomial);
return result;
}
private:
IntType value_ = 0;
};
}
#endif /* LFSR_h */

View File

@@ -39,7 +39,6 @@
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
@@ -159,6 +158,9 @@
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; };
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */; };
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
@@ -189,7 +191,6 @@
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187A1F75E91900926311 /* DiskController.cpp */; };
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187C1F75E91900926311 /* MFMDiskController.cpp */; };
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188D1F75FD1B00926311 /* AcornADF.cpp */; };
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */; };
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518911F75FD1B00926311 /* D64.cpp */; };
@@ -248,7 +249,6 @@
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
@@ -371,6 +371,8 @@
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
@@ -810,7 +812,6 @@
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
@@ -1001,6 +1002,8 @@
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = "<group>"; };
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSHighPrecisionTimer.h; sourceTree = "<group>"; };
4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSHighPrecisionTimer.m; sourceTree = "<group>"; };
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
@@ -1063,7 +1066,6 @@
4B45187B1F75E91900926311 /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
4B45187C1F75E91900926311 /* MFMDiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFMDiskController.cpp; sourceTree = "<group>"; };
4B45187D1F75E91900926311 /* MFMDiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFMDiskController.hpp; sourceTree = "<group>"; };
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigitalPhaseLockedLoop.cpp; sourceTree = "<group>"; };
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
4B4518881F75ECB100926311 /* Track.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Track.hpp; sourceTree = "<group>"; };
4B45188B1F75FD1B00926311 /* DiskImage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskImage.hpp; sourceTree = "<group>"; };
@@ -1127,6 +1129,7 @@
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanSynchroniser.hpp; sourceTree = "<group>"; };
4B65085F22F4CF8D009C1100 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = "<group>"; };
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = "<group>"; };
@@ -1182,6 +1185,13 @@
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Jasmin.cpp; path = Oric/Jasmin.cpp; sourceTree = "<group>"; };
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Jasmin.hpp; path = Oric/Jasmin.hpp; sourceTree = "<group>"; };
4B7BA03223C58B1E00B98D9E /* STX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = STX.hpp; sourceTree = "<group>"; };
4B7BA03323C58B1E00B98D9E /* STX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = STX.cpp; sourceTree = "<group>"; };
4B7BA03523CEB86000B98D9E /* BD500.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BD500.cpp; path = Oric/BD500.cpp; sourceTree = "<group>"; };
4B7BA03623CEB86000B98D9E /* BD500.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BD500.hpp; path = Oric/BD500.hpp; sourceTree = "<group>"; };
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskController.hpp; path = Oric/DiskController.hpp; sourceTree = "<group>"; };
4B7BA03E23D55E7900B98D9E /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRC.hpp; sourceTree = "<group>"; };
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LFSR.hpp; sourceTree = "<group>"; };
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
@@ -1587,7 +1597,6 @@
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MacintoshIMG.hpp; sourceTree = "<group>"; };
4BB4BFB722A4372E0069048D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreGCR.cpp; path = Encodings/CommodoreGCR.cpp; sourceTree = "<group>"; };
@@ -1680,8 +1689,6 @@
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTargetGLSLFragments.cpp; 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>"; };
4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; };
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
@@ -1737,7 +1744,6 @@
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ScanTarget.hpp; path = ../../Outputs/ScanTarget.hpp; 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>"; };
4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AllRAMProcessor.hpp; sourceTree = "<group>"; };
4BFCA1251ECBE33200AC40C1 /* TestMachineZ80.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachineZ80.h; sourceTree = "<group>"; };
@@ -2058,6 +2064,15 @@
path = Utility;
sourceTree = "<group>";
};
4B2BF19323E10F0000C3AD60 /* High Precision Timer */ = {
isa = PBXGroup;
children = (
4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */,
4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */,
);
path = "High Precision Timer";
sourceTree = "<group>";
};
4B2E2D9E1C3A070900138695 /* Electron */ = {
isa = PBXGroup;
children = (
@@ -2235,7 +2250,6 @@
4B45187E1F75E91900926311 /* DPLL */ = {
isa = PBXGroup;
children = (
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */,
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */,
);
path = DPLL;
@@ -2269,6 +2283,7 @@
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
4B4518991F75FD1B00926311 /* SSD.cpp */,
4BE0A3EC237BB170002AB46F /* ST.cpp */,
4B7BA03323C58B1E00B98D9E /* STX.cpp */,
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
@@ -2285,6 +2300,7 @@
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
4B45189A1F75FD1B00926311 /* SSD.hpp */,
4BE0A3ED237BB170002AB46F /* ST.hpp */,
4B7BA03223C58B1E00B98D9E /* STX.hpp */,
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
4BFDD7891F7F2DB4008579B9 /* Utility */,
);
@@ -2589,6 +2605,16 @@
path = Coleco;
sourceTree = "<group>";
};
4B7BA03C23D55E7900B98D9E /* Numeric */ = {
isa = PBXGroup;
children = (
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
);
name = Numeric;
path = ../../Numeric;
sourceTree = "<group>";
};
4B7F188B2154825D00388727 /* MasterSystem */ = {
isa = PBXGroup;
children = (
@@ -3177,15 +3203,6 @@
path = Macintosh;
sourceTree = "<group>";
};
4BB697C81D4B559300248BDF /* NumberTheory */ = {
isa = PBXGroup;
children = (
4BB697C61D4B558F00248BDF /* Factors.hpp */,
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
);
name = NumberTheory;
sourceTree = "<group>";
};
4BB697CF1D4BA44900248BDF /* Encodings */ = {
isa = PBXGroup;
children = (
@@ -3213,7 +3230,7 @@
4B055A761FAE78210060FFFF /* Frameworks */,
4B86E2581F8C628F006FAA45 /* Inputs */,
4BB73EDC1B587CA500552FC2 /* Machines */,
4BB697C81D4B559300248BDF /* NumberTheory */,
4B7BA03C23D55E7900B98D9E /* Numeric */,
4B366DFD1B5C165F0026627B /* Outputs */,
4BB73EDD1B587CA500552FC2 /* Processors */,
4BB73E9F1B587A5100552FC2 /* Products */,
@@ -3250,13 +3267,13 @@
4B2A538F1D117D36003C6002 /* Audio */,
4B643F3D1D77B88000D431D6 /* Document Controller */,
4B55CE551C3B7D360093A61B /* Documents */,
4B2BF19323E10F0000C3AD60 /* High Precision Timer */,
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
4B2A53921D117D36003C6002 /* Machine */,
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
4BD5F1961D1352A000631CD1 /* Updater */,
4B55CE5A1C3B7D6F0093A61B /* Views */,
);
path = "Clock Signal";
@@ -3533,16 +3550,19 @@
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
isa = PBXGroup;
children = (
4B7BA03523CEB86000B98D9E /* BD500.cpp */,
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */,
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */,
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */,
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */,
4B7BA03623CEB86000B98D9E /* BD500.hpp */,
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */,
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */,
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */,
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */,
);
name = Oric;
sourceTree = "<group>";
@@ -3601,15 +3621,6 @@
name = 1770;
sourceTree = "<group>";
};
4BD5F1961D1352A000631CD1 /* Updater */ = {
isa = PBXGroup;
children = (
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */,
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */,
);
name = Updater;
sourceTree = "<group>";
};
4BD67DC8209BE4D600AB2146 /* DiskII */ = {
isa = PBXGroup;
children = (
@@ -3716,6 +3727,7 @@
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4B80214322EE7C3E00068002 /* JustInTime.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
@@ -3829,16 +3841,19 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 1100;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = "Thomas Harte";
TargetAttributes = {
4B055A691FAE763F0060FFFF = {
CreatedOnToolsVersion = 9.1;
DevelopmentTeam = CP2SKEB3XT;
ProvisioningStyle = Automatic;
};
4BB73E9D1B587A5100552FC2 = {
CreatedOnToolsVersion = 7.0;
DevelopmentTeam = CP2SKEB3XT;
LastSwiftMigration = 1020;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
@@ -3847,11 +3862,14 @@
};
4BB73EB11B587A5100552FC2 = {
CreatedOnToolsVersion = 7.0;
DevelopmentTeam = CP2SKEB3XT;
LastSwiftMigration = 1020;
ProvisioningStyle = Automatic;
TestTargetID = 4BB73E9D1B587A5100552FC2;
};
4BB73EBC1B587A5100552FC2 = {
CreatedOnToolsVersion = 7.0;
DevelopmentTeam = CP2SKEB3XT;
LastSwiftMigration = 1020;
TestTargetID = 4BB73E9D1B587A5100552FC2;
};
@@ -4214,7 +4232,6 @@
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
@@ -4244,6 +4261,7 @@
4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */,
4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */,
4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */,
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */,
4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */,
4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */,
4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */,
@@ -4295,6 +4313,7 @@
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */,
4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */,
4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */,
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */,
4B0ACC2723775819008902D0 /* AtariST.cpp in Sources */,
4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */,
4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */,
@@ -4430,6 +4449,7 @@
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */,
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,
4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */,
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */,
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */,
4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
@@ -4456,12 +4476,12 @@
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */,
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
4B4518A41F75FD1C00926311 /* OricMFMDSK.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 */,
4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
@@ -4541,8 +4561,8 @@
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */,
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */,
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
@@ -4642,7 +4662,6 @@
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */,
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */,
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */,
4B778F4923A5F1F40000D260 /* StaticAnalyser.cpp in Sources */,
@@ -4899,6 +4918,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = CP2SKEB3XT;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Frameworks",
@@ -4919,6 +4939,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = CP2SKEB3XT;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Frameworks",
@@ -5047,8 +5068,11 @@
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Frameworks",
@@ -5065,12 +5089,14 @@
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = "Clock Signal/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-Wreorder",
);
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -5089,8 +5115,11 @@
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Frameworks",
@@ -5109,12 +5138,14 @@
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = "Clock Signal/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-Wreorder",
);
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
@@ -5126,11 +5157,17 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -5144,12 +5181,18 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GCC_OPTIMIZATION_LEVEL = 2;
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clock Signal.app/Contents/MacOS/Clock Signal";
@@ -5160,6 +5203,7 @@
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = CP2SKEB3XT;
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests";
@@ -5174,6 +5218,7 @@
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = CP2SKEB3XT;
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests";

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4B055A691FAE763F0060FFFF"
BuildableName = "Clock Signal Kiosk"
BlueprintName = "Clock Signal Kiosk"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4B055A691FAE763F0060FFFF"
BuildableName = "Clock Signal Kiosk"
BlueprintName = "Clock Signal Kiosk"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Master System/R-Type (NTSC).sms&quot;"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--speed=5"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--rompath=/Users/thomasharte/Projects/CLK/ROMImages"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4B055A691FAE763F0060FFFF"
BuildableName = "Clock Signal Kiosk"
BlueprintName = "Clock Signal Kiosk"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -67,7 +67,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"

View File

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

View File

@@ -117,7 +117,6 @@ static void audioOutputCallback(
kCFRunLoopCommonModes,
0,
&_audioQueue)) {
AudioQueueStart(_audioQueue, NULL);
}
}
@@ -175,6 +174,10 @@ static void audioOutputCallback(
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
[_storedBuffersLock unlock];
// 'Start' the queue. This is documented to be a no-op if the queue is already started,
// and it's better to defer starting it until at least some data is available.
AudioQueueStart(_audioQueue, NULL);
}
#pragma mark - Sampling Rate getters

View File

@@ -13,7 +13,6 @@
#import "CSOpenGLView.h"
#import "CSROMReceiverView.h"
#import "CSBestEffortUpdater.h"
#import "CSJoystickManager.h"
#import "NSData+CRC32.h"

View File

@@ -15,7 +15,6 @@ class MachineDocument:
CSMachineDelegate,
CSOpenGLViewDelegate,
CSOpenGLViewResponderDelegate,
CSBestEffortUpdaterDelegate,
CSAudioQueueDelegate,
CSROMReciverViewDelegate
{
@@ -25,8 +24,6 @@ class MachineDocument:
private let actionLock = NSLock()
/// Ensures exclusive access between calls to machine.updateView and machine.drawView, and close().
private let drawLock = NSLock()
/// Ensures exclusive access to the best-effort updater.
private let bestEffortLock = NSLock()
// MARK: - Machine details.
@@ -44,9 +41,6 @@ class MachineDocument:
/// The output audio queue, if any.
private var audioQueue: CSAudioQueue!
/// The best-effort updater.
private var bestEffortUpdater: CSBestEffortUpdater?
// MARK: - Main NIB connections.
/// The OpenGL view to receive this machine's display.
@@ -90,20 +84,14 @@ class MachineDocument:
}
override func close() {
machine.stop()
activityPanel?.setIsVisible(false)
activityPanel = nil
optionsPanel?.setIsVisible(false)
optionsPanel = nil
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortUpdater.delegate = nil
bestEffortUpdater.flush()
self.bestEffortUpdater = nil
}
bestEffortLock.unlock()
actionLock.lock()
drawLock.lock()
machine = nil
@@ -189,9 +177,7 @@ class MachineDocument:
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))
})
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
// Attach an options panel if one is available.
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
@@ -202,7 +188,6 @@ class MachineDocument:
}
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.
@@ -221,22 +206,31 @@ class MachineDocument:
openGLView.window!.makeKeyAndOrderFront(self)
openGLView.window!.makeFirstResponder(openGLView)
// Start accepting best effort updates.
self.bestEffortUpdater!.delegate = self
// Start forwarding best-effort updates.
machine.start()
}
}
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
setupAudioQueueClockRate()
// setupAudioQueueClockRate not only needs blocking access to the machine,
// but may be triggered on an arbitrary thread by a running machine, and that
// running machine may not be able to stop running until it has been called
// (e.g. if it is currently trying to run_until an audio event). Break the
// deadlock with an async dispatch.
DispatchQueue.main.async {
self.setupAudioQueueClockRate()
}
}
private func setupAudioQueueClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
// Establish and provide the audio queue, taking advice as to an appropriate sampling rate.
//
// TODO: this needs to be threadsafe. FIX!
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
if selectedSamplingRate > 0 {
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
audioQueue.delegate = self
self.audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
self.audioQueue.delegate = self
self.machine.audioQueue = self.audioQueue
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
@@ -244,24 +238,11 @@ class MachineDocument:
/// Responds to the CSAudioQueueDelegate dry-queue warning message by requesting a machine update.
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
bestEffortLock.lock()
bestEffortUpdater?.update()
bestEffortLock.unlock()
}
/// Responds to the CSOpenGLViewDelegate redraw message by requesting a machine update if this is a timed
/// request, and ordering a redraw regardless of the motivation.
final func openGLViewRedraw(_ view: CSOpenGLView, event redrawEvent: CSOpenGLViewRedrawEvent) {
if redrawEvent == .timer {
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortLock.unlock()
bestEffortUpdater.update()
} else {
bestEffortLock.unlock()
}
}
if drawLock.try() {
if redrawEvent == .timer {
machine.updateView(forPixelSize: view.backingSize)
@@ -271,14 +252,6 @@ class MachineDocument:
}
}
/// Responds to CSBestEffortUpdaterDelegate update message by running the machine.
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
if let machine = self.machine, actionLock.try() {
machine.run(forInterval: duration)
actionLock.unlock()
}
}
// MARK: - Pasteboard Forwarding.
/// Forwards any text currently on the pasteboard into the active machine.

View File

@@ -19,6 +19,9 @@
/// Initialises a new instance of the high precision timer; the timer will begin
/// ticking immediately.
///
/// @param task The block to perform each time the timer fires.
/// @param interval The interval at which to fire the timer, in nanoseconds.
- (instancetype)initWithTask:(dispatch_block_t)task interval:(uint64_t)interval;
/// Stops the timer.

View File

@@ -31,7 +31,15 @@
}
- (void)invalidate {
dispatch_suspend(_timer);
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_source_set_cancel_handler(_timer, ^{
[lock lock];
[lock unlockWithCondition:1];
});
dispatch_source_cancel(_timer);
[lock lockWhenCondition:1];
}
@end

View File

@@ -394,6 +394,7 @@
<array>
<string>msa</string>
<string>st</string>
<string>stx</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35.png</string>

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