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

Compare commits

...

190 Commits

Author SHA1 Message Date
Thomas Harte
2e1b245cd8 Merge pull request #758 from TomHarte/JasminDriveSelection
Switches Jasmin drive selection logic.
2020-02-16 21:21:41 -05:00
Thomas Harte
5400c47f07 Merge pull request #757 from TomHarte/ByteDrive
Updates Byte Drive implementation as per latest information.
2020-02-16 21:16:58 -05:00
Thomas Harte
4153442703 Switches Jasmin drive selection logic. 2020-02-16 21:15:16 -05:00
Thomas Harte
5e4b721e97 Updates Byte Drive implementation as per latest information. 2020-02-16 21:07:03 -05:00
Thomas Harte
aca41ac089 Merge pull request #756 from TomHarte/Stereo
Adds stereo sound processing.
2020-02-16 19:20:21 -05:00
Thomas Harte
01a883e669 Corrects fullscreen switch. 2020-02-16 19:07:13 -05:00
Thomas Harte
1e4356f83a Adds a sensible is_stereo to the MultiSpeaker. 2020-02-16 18:50:34 -05:00
Thomas Harte
545a6177bb Makes CompoundSource mono/stereo-aware. 2020-02-16 18:45:36 -05:00
Thomas Harte
50d356be2f Ensures all audio sources, including compound sources, announce whether they're stereo correctly. 2020-02-16 18:31:45 -05:00
Thomas Harte
9835e800ec Fixed: individual audio generators now either are or are not stereo. The speaker acts accordingly. 2020-02-16 18:28:03 -05:00
Thomas Harte
5242362f31 Slightly shuffles preprocessor use, the better for testing. 2020-02-16 17:53:26 -05:00
Thomas Harte
808e4e8537 Adds comment to explain channel allocations. 2020-02-16 15:04:52 -05:00
Thomas Harte
43740a4b2f Adds support for stereo output. 2020-02-16 14:14:10 -05:00
Thomas Harte
f99d672237 The macOS port now selects stereo output if appropriate. 2020-02-16 14:05:50 -05:00
Thomas Harte
337cb4fb86 Resolves implicit type conversion warnings. 2020-02-16 14:05:23 -05:00
Thomas Harte
90856a0e7a Adds mixdown/up capability to Speaker.
To deal with occasions when the host machine just always is either mono or stereo, and the emulated machine must cope.
2020-02-16 13:50:18 -05:00
Thomas Harte
ea1c8a3b81 Ensures the stereo audio queue is the same length (in time) as the mono. 2020-02-16 12:46:25 -05:00
Thomas Harte
d55d077a95 Fixes the input buffer partial-keep step in stereo. 2020-02-16 00:20:22 -05:00
Thomas Harte
f760a68173 Corrects stereo audio generation. 2020-02-16 00:19:49 -05:00
Thomas Harte
e66a3523b6 Makes some attempt at stereo support, with the Amstrad CPC being the test case. 2020-02-15 18:55:19 -05:00
Thomas Harte
89d6b85b83 Adds optional stereo output for the AY.
The real chip provides the three tone channels as separate outputs, so a variety of different mixings can exist.
2020-02-15 18:09:17 -05:00
Thomas Harte
e02d109864 Nudges the LowpassSpeaker towards supporting stereo generation. 2020-02-15 18:03:12 -05:00
Thomas Harte
743981e9ad Adds stereo output for SDL. 2020-02-15 17:23:40 -05:00
Thomas Harte
49b8e771b5 Adds the messaging that would allow a Speaker to output stereo, semantically. 2020-02-15 13:40:19 -05:00
Thomas Harte
dde672701f Merge pull request #755 from TomHarte/ExpliticLambdas
Tries to be less lazy with lambda captures.
2020-02-15 12:38:12 -05:00
Thomas Harte
9ca2d8f9f2 Tried to be less lazy with lambda captures.
This is primarily defensive.
2020-02-14 23:39:08 -05:00
Thomas Harte
fd786412aa Merge pull request #754 from TomHarte/AYVolume
Introduces more correct AY volume levels.
2020-02-14 23:38:17 -05:00
Thomas Harte
eb88c7cfba Allows up to half a second of hard processing. 2020-02-14 23:24:51 -05:00
Thomas Harte
e1892ff370 Resolves crash upon File -> New..., Cancel; also ensures slow performance can't equal no progression. 2020-02-14 23:16:44 -05:00
Thomas Harte
763159a6f6 More neatly ties volume level 0 to silence. 2020-02-14 23:16:10 -05:00
Thomas Harte
6810a6ee58 Adjusts the AY volume scale.
Hopefully more accurately to model the real thing.
2020-02-14 22:51:20 -05:00
Thomas Harte
65e6c3a9fe Merge pull request #753 from TomHarte/IconForReal
Experimentally introduces an application icon to the README
2020-02-13 23:39:06 -05:00
Thomas Harte
dcbbf988c1 Crops empty space around icon, and increases size slightly. 2020-02-13 23:27:33 -05:00
Thomas Harte
199cafebcf Attempts direct HTML tag for image sizing. 2020-02-13 23:25:26 -05:00
Thomas Harte
555d807d76 Attempt smaller icon. 2020-02-13 23:23:09 -05:00
Thomas Harte
003c6ad11b Fixes image link. 2020-02-13 23:21:48 -05:00
Thomas Harte
dc77d87427 Experiments with a README icon. 2020-02-13 23:20:19 -05:00
Thomas Harte
cfc44cf778 Merge pull request #752 from TomHarte/MacintoshBus
Reduces 8-bit memory access costs for the Macintosh.
2020-02-13 23:03:07 -05:00
Thomas Harte
3df99788ff Removes TODOs, as I think they're probably inappropriate. 2020-02-13 21:19:23 -05:00
Thomas Harte
3600d2d193 Starts switching towards a byte-oriented bus. 2020-02-13 21:14:13 -05:00
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
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
173 changed files with 3360 additions and 1678 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,12 +60,19 @@ 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_;
}
void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([=](::CRTMachine::Machine *machine) {
perform_parallel([duration](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
@@ -75,7 +82,7 @@ void MultiCRTMachine::run_for(Time::Seconds duration) {
void MultiCRTMachine::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([=](::CRTMachine::Machine *machine) {
perform_serial([](::CRTMachine::Machine *machine) {
machine->set_scan_target(nullptr);
});
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();

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,12 +37,23 @@ 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, bool stereo) {
stereo_output_ = stereo;
for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size);
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
}
}
bool MultiSpeaker::get_is_stereo() {
// Return as stereo if any subspeaker is stereo.
for(const auto &speaker: speakers_) {
if(speaker->get_is_stereo()) {
return true;
}
}
return false;
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
}
@@ -53,7 +64,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, stereo_output_);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {

View File

@@ -39,18 +39,21 @@ 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, bool stereo) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
bool get_is_stereo() 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_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
std::mutex front_speaker_mutex_;
bool stereo_output_ = false;
};
}

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

@@ -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

@@ -481,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;
}
@@ -564,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;
}

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.

View File

@@ -17,13 +17,13 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.defer([=]() {
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
audio_queue_.defer([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_;
});
}
void AudioGenerator::set_control(int channel, uint8_t value) {
audio_queue_.defer([=]() {
audio_queue_.defer([this, channel, value]() {
control_registers_[channel] = value;
});
}

View File

@@ -30,6 +30,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return false; }
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -83,8 +84,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

@@ -6,13 +6,17 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#include <cmath>
#include "AY38910.hpp"
#include <cmath>
//namespace GI {
//namespace AY38910 {
using namespace GI::AY38910;
AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
template <bool is_stereo>
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// Don't use the low bit of the envelope position if this is an AY.
envelope_position_mask_ |= personality == Personality::AY38910;
@@ -70,18 +74,34 @@ AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &
set_sample_volume_range(0);
}
void AY38910::set_sample_volume_range(std::int16_t range) {
// set up volume lookup table
template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::int16_t range) {
// Set up volume lookup table; the function below is based on a combination of the graph
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
// values reported elsewhere.
const float max_volume = float(range) / 3.0f; // As there are three channels.
constexpr float root_two = 1.414213562373095f;
for(int v = 0; v < 32; v++) {
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f));
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f));
}
volumes_[0] = 0; // Tie level 0 to silence.
// Tie level 0 to silence.
for(int v = 31; v >= 0; --v) {
volumes_[v] -= volumes_[0];
}
evaluate_output_volume();
}
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
a_left_ = uint8_t(a_left * 255.0f);
b_left_ = uint8_t(b_left * 255.0f);
c_left_ = uint8_t(c_left * 255.0f);
a_right_ = uint8_t(a_right * 255.0f);
b_right_ = uint8_t(b_right * 255.0f);
c_right_ = uint8_t(c_right * 255.0f);
}
template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t number_of_samples, int16_t *target) {
// Note on structure below: the real AY has a built-in divider of 8
// prior to applying its tone and noise dividers. But the YM fills the
// same total periods for noise and tone with double-precision envelopes.
@@ -93,7 +113,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
std::size_t c = 0;
while((master_divider_&3) && c < number_of_samples) {
target[c] = output_volume_;
if constexpr (is_stereo) {
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
} else {
target[c] = int16_t(output_volume_);
}
master_divider_++;
c++;
}
@@ -135,7 +159,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
evaluate_output_volume();
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
target[c] = output_volume_;
if constexpr (is_stereo) {
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
} else {
target[c] = int16_t(output_volume_);
}
c++;
master_divider_++;
}
@@ -144,7 +172,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
master_divider_ &= 3;
}
void AY38910::evaluate_output_volume() {
template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
// The output level for a channel is:
@@ -184,34 +212,47 @@ void AY38910::evaluate_output_volume() {
};
#undef channel_volume
// Mix additively.
output_volume_ = static_cast<int16_t>(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
// Mix additively, weighting if in stereo.
if constexpr (is_stereo) {
int16_t *const output_volumes = reinterpret_cast<int16_t *>(&output_volume_);
output_volumes[0] = int16_t((
volumes_[volumes[0]] * channel_levels[0] * a_left_ +
volumes_[volumes[1]] * channel_levels[1] * b_left_ +
volumes_[volumes[2]] * channel_levels[2] * c_left_
) >> 8);
output_volumes[1] = int16_t((
volumes_[volumes[0]] * channel_levels[0] * a_right_ +
volumes_[volumes[1]] * channel_levels[1] * b_right_ +
volumes_[volumes[2]] * channel_levels[2] * c_right_
) >> 8);
} else {
output_volume_ = uint32_t(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
}
}
bool AY38910::is_zero_level() {
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() {
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
}
// MARK: - Register manipulation
void AY38910::select_register(uint8_t r) {
template <bool is_stereo> void AY38910<is_stereo>::select_register(uint8_t r) {
selected_register_ = r;
}
void AY38910::set_register_value(uint8_t value) {
template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t value) {
// There are only 16 registers.
if(selected_register_ > 15) return;
// If this is a register that affects audio output, enqueue a mutation onto the
// audio generation thread.
if(selected_register_ < 14) {
const int selected_register = selected_register_;
task_queue_.defer([=] () {
task_queue_.defer([this, selected_register = selected_register_, value] () {
// Perform any register-specific mutation to output generation.
uint8_t masked_value = value;
switch(selected_register) {
@@ -220,7 +261,7 @@ void AY38910::set_register_value(uint8_t value) {
int channel = selected_register >> 1;
if(selected_register & 1)
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | uint16_t((value&0xf) << 8);
else
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
}
@@ -235,7 +276,7 @@ void AY38910::set_register_value(uint8_t value) {
break;
case 12:
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
envelope_period_ = (envelope_period_ & 0xff) | int(value << 8);
break;
case 13:
@@ -273,7 +314,7 @@ void AY38910::set_register_value(uint8_t value) {
if(update_port_a) set_port_output(false);
}
uint8_t AY38910::get_register_value() {
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() {
// This table ensures that bits that aren't defined within the AY are returned as 0s
// when read, conforming to CPC-sourced unit tests.
const uint8_t register_masks[16] = {
@@ -287,24 +328,24 @@ uint8_t AY38910::get_register_value() {
// MARK: - Port querying
uint8_t AY38910::get_port_output(bool port_b) {
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
// MARK: - Bus handling
void AY38910::set_port_handler(PortHandler *handler) {
template <bool is_stereo> void AY38910<is_stereo>::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
set_port_output(true);
set_port_output(false);
}
void AY38910::set_data_input(uint8_t r) {
template <bool is_stereo> void AY38910<is_stereo>::set_data_input(uint8_t r) {
data_input_ = r;
update_bus();
}
void AY38910::set_port_output(bool port_b) {
template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b) {
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
// so that when in the "input" mode, all pins will read normally high". Therefore,
// report programmer selection of input mode as creating an output of 0xff.
@@ -314,7 +355,7 @@ void AY38910::set_port_output(bool port_b) {
}
}
uint8_t AY38910::get_data_output() {
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_data_output() {
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
// value returned to the CPU when reading it is the and of the output value and any input.
@@ -330,22 +371,22 @@ uint8_t AY38910::get_data_output() {
return data_output_;
}
void AY38910::set_control_lines(ControlLines control_lines) {
switch(static_cast<int>(control_lines)) {
template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLines control_lines) {
switch(int(control_lines)) {
default: control_state_ = Inactive; break;
case static_cast<int>(BDIR | BC2 | BC1):
case int(BDIR | BC2 | BC1):
case BDIR:
case BC1: control_state_ = LatchAddress; break;
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
case int(BC2 | BC1): control_state_ = Read; break;
case int(BDIR | BC2): control_state_ = Write; break;
}
update_bus();
}
void AY38910::update_bus() {
template <bool is_stereo> void AY38910<is_stereo>::update_bus() {
// Assume no output, unless this turns out to be a read.
data_output_ = 0xff;
switch(control_state_) {
@@ -355,3 +396,7 @@ void AY38910::update_bus() {
case Read: data_output_ = get_register_value(); break;
}
}
// Ensure both mono and stereo versions of the AY are built.
template class GI::AY38910::AY38910<true>;
template class GI::AY38910::AY38910<false>;

View File

@@ -63,8 +63,10 @@ enum class Personality {
Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
noise generator and a volume envelope generator, which also provides two bidirectional
interface ports.
This AY has an attached mono or stereo mixer.
*/
class AY38910: public ::Outputs::Speaker::SampleSource {
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new AY38910.
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
@@ -91,10 +93,23 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
*/
void set_port_handler(PortHandler *);
/*!
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
channels in each of the output channels.
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
*/
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return is_stereo; }
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;
@@ -135,14 +150,21 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
uint8_t data_input_, data_output_;
int16_t output_volume_;
void evaluate_output_volume();
uint32_t output_volume_;
void update_bus();
PortHandler *port_handler_ = nullptr;
void set_port_output(bool port_b);
void evaluate_output_volume();
// Output mixing control.
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
};
}
}

View File

@@ -28,7 +28,7 @@ void Toggle::skip_samples(const std::size_t number_of_samples) {}
void Toggle::set_output(bool enabled) {
if(is_enabled_ == enabled) return;
is_enabled_ = enabled;
audio_queue_.defer([=] {
audio_queue_.defer([this, enabled] {
level_ = enabled ? volume_ : 0;
});
}

View File

@@ -24,6 +24,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
void skip_samples(const std::size_t number_of_samples);
static constexpr bool get_is_stereo() { return false; }
void set_output(bool enabled);
bool get_output();

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

@@ -55,7 +55,7 @@ void SCC::write(uint16_t address, uint8_t value) {
address &= 0xff;
if(address < 0x80) ram_[address] = value;
task_queue_.defer([=] {
task_queue_.defer([this, address, value] {
// Check for a write into waveform memory.
if(address < 0x80) {
waves_[address >> 5].samples[address & 0x1f] = value;

View File

@@ -32,6 +32,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return false; }
/// Writes to the SCC.
void write(uint16_t address, uint8_t value);

View File

@@ -32,6 +32,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
void get_samples(std::size_t number_of_samples, std::int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return false; }
private:
int master_divider_ = 0;

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

@@ -126,6 +126,9 @@ class AYDeferrer {
/// Constructs a new AY instance and sets its clock rate.
AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) {
speaker_.set_input_rate(1000000);
// Per the CPC Wiki:
// "A is output to the right, channel C is output left, and channel B is output to both left and right".
ay_.set_output_mixing(0.0, 0.5, 1.0, 1.0, 0.5, 0.0);
}
~AYDeferrer() {
@@ -153,14 +156,14 @@ class AYDeferrer {
}
/// @returns the AY itself.
GI::AY38910::AY38910 &ay() {
GI::AY38910::AY38910<true> &ay() {
return ay_;
}
private:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
GI::AY38910::AY38910<true> ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<true>> speaker_;
HalfCycles cycles_since_update_;
};
@@ -335,6 +338,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 +635,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 +664,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 +680,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 +1008,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 +1049,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);
}
@@ -56,7 +60,7 @@ void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
*/
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
set_alternative_character_set_ = alternative_character_set;
deferrer_.defer(Cycles(2), [=] {
deferrer_.defer(Cycles(2), [this, alternative_character_set] {
alternative_character_set_ = alternative_character_set;
if(alternative_character_set) {
character_zones[1].address_mask = 0xff;
@@ -74,7 +78,7 @@ bool VideoBase::get_alternative_character_set() {
void VideoBase::set_80_columns(bool columns_80) {
set_columns_80_ = columns_80;
deferrer_.defer(Cycles(2), [=] {
deferrer_.defer(Cycles(2), [this, columns_80] {
columns_80_ = columns_80;
});
}
@@ -101,7 +105,7 @@ bool VideoBase::get_page2() {
void VideoBase::set_text(bool text) {
set_text_ = text;
deferrer_.defer(Cycles(2), [=] {
deferrer_.defer(Cycles(2), [this, text] {
text_ = text;
});
}
@@ -112,7 +116,7 @@ bool VideoBase::get_text() {
void VideoBase::set_mixed(bool mixed) {
set_mixed_ = mixed;
deferrer_.defer(Cycles(2), [=] {
deferrer_.defer(Cycles(2), [this, mixed] {
mixed_ = mixed;
});
}
@@ -123,7 +127,7 @@ bool VideoBase::get_mixed() {
void VideoBase::set_high_resolution(bool high_resolution) {
set_high_resolution_ = high_resolution;
deferrer_.defer(Cycles(2), [=] {
deferrer_.defer(Cycles(2), [this, high_resolution] {
high_resolution_ = high_resolution;
});
}
@@ -134,7 +138,7 @@ bool VideoBase::get_high_resolution() {
void VideoBase::set_annunciator_3(bool annunciator_3) {
set_annunciator_3_ = annunciator_3;
deferrer_.defer(Cycles(2), [=] {
deferrer_.defer(Cycles(2), [this, annunciator_3] {
annunciator_3_ = annunciator_3;
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
});

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,14 +255,14 @@ 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 {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }),
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
/*!

View File

@@ -35,7 +35,7 @@ void Audio::set_volume(int volume) {
posted_volume_ = volume;
// Post the volume change as a deferred event.
task_queue_.defer([=] () {
task_queue_.defer([this, volume] () {
volume_ = volume;
set_volume_multiplier();
});
@@ -47,7 +47,7 @@ void Audio::set_enabled(bool on) {
posted_enable_mask_ = int(on);
// Post the enabled mask change as a deferred event.
task_queue_.defer([=] () {
task_queue_.defer([this, on] () {
enabled_mask_ = int(on);
set_volume_multiplier();
});

View File

@@ -55,6 +55,7 @@ class Audio: public ::Outputs::Speaker::SampleSource {
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
constexpr static bool get_is_stereo() { return false; }
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;

View File

@@ -123,10 +123,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
} break;
}
ram_mask_ = (ram_size >> 1) - 1;
rom_mask_ = (rom_size >> 1) - 1;
ram_.resize(ram_size >> 1);
video_.set_ram(ram_.data(), ram_mask_);
ram_mask_ = ram_size - 1;
rom_mask_ = rom_size - 1;
ram_.resize(ram_size);
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
// Grab a copy of the ROM and convert it into big-endian data.
const auto roms = rom_fetcher(rom_descriptions);
@@ -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);
}
@@ -192,11 +196,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
// Grab the value on the address bus, at word precision.
uint32_t word_address = cycle.active_operation_word_address();
// Grab the address.
auto address = cycle.host_endian_byte_address();
// Everything above E0 0000 is signalled as being on the peripheral bus.
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
mc68000_.set_is_peripheral_address(address >= 0xe0'0000);
// All code below deals only with reads and writes — cycles in which a
// data select is active. So quit now if this is not the active part of
@@ -209,9 +213,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0);
// Grab the word-precision address being accessed.
uint16_t *memory_base = nullptr;
uint8_t *memory_base = nullptr;
HalfCycles delay;
switch(memory_map_[word_address >> 16]) {
switch(memory_map_[address >> 17]) {
default: assert(false);
case BusDevice::Unassigned:
@@ -222,7 +226,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
if(*cycle.address & 1) {
fill_unmapped(cycle);
} else {
const int register_address = word_address >> 8;
const int register_address = address >> 9;
// VIA accesses are via address 0xefe1fe + register*512,
// which at word precision is 0x77f0ff + register*256.
@@ -246,7 +250,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
case BusDevice::IWM: {
if(*cycle.address & 1) {
const int register_address = word_address >> 8;
const int register_address = address >> 9;
// The IWM; this is a purely polled device, so can be run on demand.
if(cycle.operation & Microcycle::Read) {
@@ -262,8 +266,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
} return delay;
case BusDevice::SCSI: {
const int register_address = word_address >> 3;
const bool dma_acknowledge = word_address & 0x100;
const int register_address = address >> 4;
const bool dma_acknowledge = address & 0x200;
// Even accesses = read; odd = write.
if(*cycle.address & 1) {
@@ -304,7 +308,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
cycle.value->halves.low = 0xff;
}
} else {
const auto read = scc_.read(int(word_address));
const auto read = scc_.read(int(address >> 1));
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = read;
}
@@ -319,10 +323,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
} else {
if(*cycle.address & 1) {
if(cycle.operation & Microcycle::Read) {
scc_.write(int(word_address), 0xff);
scc_.write(int(address >> 1), 0xff);
cycle.value->halves.low = 0xff;
} else {
scc_.write(int(word_address), cycle.value->halves.low);
scc_.write(int(address >> 1), cycle.value->halves.low);
}
} else {
fill_unmapped(cycle);
@@ -334,13 +338,13 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// This is coupled with the Macintosh implementation of video; the magic
// constant should probably be factored into the Video class.
// It embodies knowledge of the fact that video (and audio) will always
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
// be fetched from the final $d900 bytes of memory.
// (And that ram_mask_ = ram size - 1).
if(word_address > ram_mask_ - 0x6c80)
if(address > ram_mask_ - 0xd900)
update_video();
memory_base = ram_.data();
word_address &= ram_mask_;
address &= ram_mask_;
// Apply a delay due to video contention if applicable; scheme applied:
// only every other access slot is available during the period of video
@@ -355,7 +359,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
case BusDevice::ROM: {
if(!(cycle.operation & Microcycle::Read)) return delay;
memory_base = rom_;
word_address &= rom_mask_;
address &= rom_mask_;
} break;
}
@@ -365,19 +369,16 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory_base[word_address];
cycle.value->full = *reinterpret_cast<uint16_t *>(&memory_base[address]);
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
cycle.value->halves.low = memory_base[address];
break;
case Microcycle::SelectWord:
memory_base[word_address] = cycle.value->full;
*reinterpret_cast<uint16_t *>(&memory_base[address]) = cycle.value->full;
break;
case Microcycle::SelectByte:
memory_base[word_address] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(memory_base[word_address] & cycle.untouched_byte_mask())
);
memory_base[address] = cycle.value->halves.low;
break;
}
@@ -395,7 +396,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 +456,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;
@@ -468,7 +470,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
drives_[0].set_disk(media.disks[0]);
}
// TODO: allow this only at machine startup.
// TODO: allow this only at machine startup?
if(!media.mass_storage_devices.empty()) {
const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get());
if(volume) {
@@ -482,11 +484,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 +496,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 +513,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,40 +522,42 @@ 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) {
// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
ram_[0x02ae >> 1] = 0x40;
ram_[0x02b0 >> 1] = 0x00;
ram_[0x02ae] = 0x40;
ram_[0x02af] = 0x00;
ram_[0x02b0] = 0x00;
ram_[0x02b1] = 0x00;
}
}
}
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);
@@ -565,11 +569,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
forceinline void fill_unmapped(const Microcycle &cycle) {
if(!(cycle.operation & Microcycle::Read)) return;
if(cycle.operation & Microcycle::SelectWord) {
cycle.value->full = 0xffff;
} else {
cycle.value->halves.low = 0xff;
}
cycle.set_value16(0xffff);
}
/// Advances all non-CPU components by @c duration half cycles.
@@ -652,7 +652,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_;
}
@@ -852,8 +852,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
uint32_t ram_mask_ = 0;
uint32_t rom_mask_ = 0;
uint16_t rom_[64*1024]; // i.e. up to 128kb in size.
std::vector<uint16_t> ram_;
uint8_t rom_[128*1024];
std::vector<uint8_t> ram_;
};
}

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

@@ -18,21 +18,21 @@ Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue)
{}
void Atari2600::TIASound::set_volume(int channel, uint8_t volume) {
audio_queue_.defer([=]() {
volume_[channel] = volume & 0xf;
audio_queue_.defer([target = &volume_[channel], volume]() {
*target = volume & 0xf;
});
}
void Atari2600::TIASound::set_divider(int channel, uint8_t divider) {
audio_queue_.defer([=]() {
audio_queue_.defer([this, channel, divider]() {
divider_[channel] = divider & 0x1f;
divider_counter_[channel] = 0;
});
}
void Atari2600::TIASound::set_control(int channel, uint8_t control) {
audio_queue_.defer([=]() {
control_[channel] = control & 0xf;
audio_queue_.defer([target = &control_[channel], control]() {
*target = control & 0xf;
});
}

View File

@@ -29,6 +29,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target);
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return false; }
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;

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();
@@ -489,8 +497,8 @@ class ConcreteMachine:
JustInTimeActor<Motorola::ACIA::ACIA, 16> midi_acia_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
GI::AY38910::AY38910<false> ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<false>> speaker_;
HalfCycles cycles_since_audio_update_;
JustInTimeActor<DMAController> dma_;
@@ -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_);
@@ -465,7 +485,7 @@ void Video::write(int address, uint16_t value) {
// Sync mode and pixel mode.
case 0x05:
// Writes to sync mode have a one-cycle delay in effect.
deferrer_.defer(HalfCycles(2), [=] {
deferrer_.defer(HalfCycles(2), [this, value] {
sync_mode_ = value;
update_output_mode();
});
@@ -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;
@@ -401,9 +405,9 @@ class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910>> speaker_;
GI::AY38910::AY38910<false> ay_;
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
std::vector<uint8_t> bios_;
std::vector<uint8_t> cartridge_;

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

@@ -36,13 +36,13 @@ void SoundGenerator::skip_samples(std::size_t number_of_samples) {
}
void SoundGenerator::set_divider(uint8_t divider) {
audio_queue_.defer([=]() {
audio_queue_.defer([this, divider]() {
divider_ = divider * 32 / clock_rate_divider;
});
}
void SoundGenerator::set_is_enabled(bool is_enabled) {
audio_queue_.defer([=]() {
audio_queue_.defer([this, is_enabled]() {
is_enabled_ = is_enabled;
counter_ = 0;
});

View File

@@ -28,6 +28,7 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return false; }
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;

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();
}
@@ -756,11 +760,11 @@ class ConcreteMachine:
Intel::i8255::i8255<i8255PortHandler> i8255_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
GI::AY38910::AY38910<false> ay_;
Audio::Toggle audio_toggle_;
Konami::SCC scc_;
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC>> speaker_;
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC>> speaker_;
Storage::Tape::BinaryTapePlayer tape_player_;
bool tape_player_is_sleeping_ = false;

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;

View File

@@ -10,10 +10,18 @@
using namespace Oric;
BD500::BD500() : DiskController(P1793, 9000000) {
/*
Notes on the code below: the Byte Drive 500 isn't well documented; this implementation is based on
experimentation without access to real hardware as documented in http://forum.defence-force.org/viewtopic.php?f=25&t=2055
and with additional guidance from iss of Oricutron (amongst other things) elsewhere,
e.g. http://forum.defence-force.org/viewtopic.php?f=22&t=1698&start=105#p21468
*/
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) {
@@ -23,6 +31,18 @@ void BD500::write(int address, uint8_t value) {
// 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) {
@@ -44,9 +64,10 @@ void BD500::access(int address) {
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
return;
case 0x310: enable_overlay_ram_ = true; break;
case 0x311: disable_basic_rom_ = true; break;
case 0x313: enable_overlay_ram_ = false; break;
case 0x317: disable_basic_rom_ = false; break; // Could be 0x311.
case 0x314: enable_overlay_ram_ = true; break;
case 0x317: disable_basic_rom_ = false; break;
default:
// printf("Switch %04x???\n", address);
@@ -56,73 +77,19 @@ void BD500::access(int address) {
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(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);
});
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_ && drives_[selected_drive_] && drives_[selected_drive_]->get_is_ready()) {
if(is_loading_head_ && get_drive().get_is_ready()) {
set_head_loaded(true);
is_loading_head_ = false;
}

View File

@@ -13,16 +13,14 @@ namespace Oric {
class DiskController: public WD::WD1770 {
public:
DiskController(WD::WD1770::Personality personality, int clock_rate) :
WD::WD1770(personality), clock_rate_(clock_rate) {}
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) {
const size_t drive = size_t(d);
if(!drives_[drive]) {
drives_[drive] = std::make_unique<Storage::Disk::Drive>(clock_rate_, 300, 2);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
get_drive(size_t(d)).set_disk(disk);
}
enum class PagedItem {
@@ -44,14 +42,6 @@ class DiskController: public WD::WD1770 {
}
protected:
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_ = 0;
void select_drive(size_t drive) {
if(drive != selected_drive_) {
selected_drive_ = drive;
set_drive(drives_[selected_drive_]);
}
}
Delegate *delegate_ = nullptr;
bool enable_overlay_ram_ = false;
@@ -67,6 +57,7 @@ class DiskController: public WD::WD1770 {
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;

View File

@@ -12,7 +12,7 @@ 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() : DiskController(P1770, 8000000) {
Jasmin::Jasmin() : DiskController(P1770, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
set_is_double_density(true);
select_paged_item();
}
@@ -20,11 +20,12 @@ Jasmin::Jasmin() : DiskController(P1770, 8000000) {
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. */
@@ -43,9 +44,19 @@ void Jasmin::write(int address, uint8_t value) {
break;
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
select_drive(size_t(address - 0x3fc));
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
// Updated selected drives mask.
const uint8_t mask = uint8_t(1 << (address - 0x3fc));
selected_drives_ = (selected_drives_ & ~mask) | (mask * (value & 1));
// Select drive.
set_drive(selected_drives_);
// Update motor inputs: apply the motor only to selected drives.
// Which may or may not be correct.
for_all_drives([mask, this] (Storage::Disk::Drive &drive, size_t index) {
const uint8_t shift_mask = uint8_t(1 << index);
drive.set_motor_on( (mask & shift_mask) ? motor_on_ : false );
});
} break;
default:
@@ -55,9 +66,9 @@ 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("Microdisc", on);
observer_->set_led_status("Jasmin", on);
}
}

View File

@@ -26,6 +26,7 @@ class Jasmin: public DiskController {
private:
void set_motor_on(bool on) final;
bool motor_on_ = false;
uint8_t selected_drives_ = 0;
Activity::Observer *observer_ = nullptr;
};

View File

@@ -18,7 +18,7 @@ namespace {
const Cycles::IntType head_load_request_counter_target = 7653333;
}
Microdisc::Microdisc() : DiskController(P1793, 8000000) {
Microdisc::Microdisc() : DiskController(P1793, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
set_control_register(last_control_, 0xff);
}
@@ -33,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)
@@ -86,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.

View File

@@ -43,6 +43,8 @@
namespace Oric {
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
using AY = GI::AY38910::AY38910<false>;
using Speaker = Outputs::Speaker::LowpassSpeaker<AY>;
enum ROM {
BASIC10 = 0, BASIC11, Microdisc, Colour
@@ -146,7 +148,7 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
*/
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
public:
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, GI::AY38910::AY38910 &ay8910, Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
/*!
@@ -209,8 +211,8 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
HalfCycles cycles_since_ay_update_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
GI::AY38910::AY38910 &ay8910_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker_;
AY &ay8910_;
Speaker &speaker_;
TapePlayer &tape_player_;
Keyboard &keyboard_;
};
@@ -556,6 +558,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
video_output_.set_scan_target(scan_target);
}
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);
}
@@ -604,20 +610,20 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
// 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);
@@ -629,21 +635,21 @@ 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:
@@ -661,7 +667,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();
}
@@ -687,8 +693,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
VideoOutput video_output_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay8910_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
GI::AY38910::AY38910<false> ay8910_;
Speaker speaker_;
// Inputs
Oric::KeyboardMapper keyboard_mapper_;
@@ -743,7 +749,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;

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);
@@ -463,8 +467,9 @@ template<bool is_zx81> class ConcreteMachine:
// MARK: - Audio
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
using AY = GI::AY38910::AY38910<false>;
AY ay_;
Outputs::Speaker::LowpassSpeaker<AY> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();

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

@@ -158,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 */; };
@@ -809,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 */; };
@@ -1000,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>"; };
@@ -1125,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>"; };
@@ -1185,6 +1190,8 @@
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>"; };
@@ -1590,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>"; };
@@ -1683,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>"; };
@@ -1740,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>"; };
@@ -2061,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 = (
@@ -2593,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 = (
@@ -3181,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 = (
@@ -3217,7 +3230,7 @@
4B055A761FAE78210060FFFF /* Frameworks */,
4B86E2581F8C628F006FAA45 /* Inputs */,
4BB73EDC1B587CA500552FC2 /* Machines */,
4BB697C81D4B559300248BDF /* NumberTheory */,
4B7BA03C23D55E7900B98D9E /* Numeric */,
4B366DFD1B5C165F0026627B /* Outputs */,
4BB73EDD1B587CA500552FC2 /* Processors */,
4BB73E9F1B587A5100552FC2 /* Products */,
@@ -3254,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";
@@ -3608,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 = (
@@ -3723,6 +3727,7 @@
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4B80214322EE7C3E00068002 /* JustInTime.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
@@ -3836,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;
@@ -3854,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;
};
@@ -4250,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 */,
@@ -4301,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 */,
@@ -4436,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 */,
@@ -4468,7 +4482,6 @@
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 */,
@@ -4905,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",
@@ -4925,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",
@@ -5053,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",
@@ -5071,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;
@@ -5095,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",
@@ -5115,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;
};
@@ -5132,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;
@@ -5150,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";
@@ -5166,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";
@@ -5180,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";

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