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

Compare commits

..

431 Commits

Author SHA1 Message Date
Thomas Harte
b1e062945e Merge pull request #821 from TomHarte/QtThreading
Qt issue mega ticket
2020-07-27 21:19:12 -04:00
Thomas Harte
3db4a8c312 Attempts to improve vsync deadline estimation.
Also increases probability of bad estimation being discarded.
2020-07-27 21:08:00 -04:00
Thomas Harte
db8e1b0edf Adds feedback on unidentified ROMs. 2020-07-27 20:45:47 -04:00
Thomas Harte
71c3f58c99 Provides user feedback upon improper command-line usage. 2020-07-27 20:40:38 -04:00
Thomas Harte
7c05b1788e Ensures proper thread confinement for updateStatusBarText. 2020-07-27 20:25:52 -04:00
Thomas Harte
a2db6ddea5 Add link to Snap releases. 2020-07-25 23:00:29 -04:00
Thomas Harte
6ad1e3e17e Merge pull request #819 from TomHarte/Warnings
Resolves GCC 7 warnings.
2020-07-24 22:17:50 -04:00
Thomas Harte
e097a841d2 Adds a c++1z fallback for SDL builds, too. 2020-07-24 22:01:22 -04:00
Thomas Harte
fa95a17af5 Resolves receive_bit_count-unused warnings. 2020-07-24 21:59:27 -04:00
Thomas Harte
b961665985 Ensures WOZ2 behaviour even if type_ has an invalid value.
This pleases GCC 7.
2020-07-24 21:56:20 -04:00
Thomas Harte
8af35bc6bb Resolves signed comparison mismatches. 2020-07-24 21:55:33 -04:00
Thomas Harte
9b75287a52 Merge pull request #818 from TomHarte/QtC++1z
Add c++1z as a config option, for older versions of Qt.
2020-07-24 16:34:04 -04:00
Thomas Harte
84d5316aa7 Add c++1z as a config option, for older versions of Qt. 2020-07-24 16:32:59 -04:00
Thomas Harte
e8cd5a0511 Merge pull request #816 from TomHarte/RelaxedTracks
Corrects a regression in disk image handling; liberalises Disk II analyser
2020-07-20 19:53:34 -04:00
Thomas Harte
5ebbab6f35 Further relax Apple GCR static analysis requirements. 2020-07-20 19:50:33 -04:00
Thomas Harte
84dd194afd Corrects test for ::tracks_differ. 2020-07-20 19:48:20 -04:00
Thomas Harte
e1ad1a4cb6 Update Qt status. 2020-07-20 09:17:43 -04:00
Thomas Harte
9de43dac95 Merge pull request #815 from TomHarte/WOZ2
Support WOZ 2 disk images
2020-07-19 23:22:55 -04:00
Thomas Harte
47f121ee4c Mark WOZs as read-only, with exposition as to why. 2020-07-19 00:08:49 -04:00
Thomas Harte
d8b699c869 Corrects index pulse signalling. 2020-07-19 00:06:27 -04:00
Thomas Harte
a7855e8c98 Ensure float literals are floats. 2020-07-17 23:18:41 -04:00
Thomas Harte
8dcb48254a Simplifies calculations very slightly. 2020-07-17 23:18:08 -04:00
Thomas Harte
f6b7467d75 Implement custom tracks_differ; support WOZ 2 3.5" drive geometry properly. 2020-07-17 22:09:55 -04:00
Thomas Harte
9d1d162cc8 Add an ability to avoid track flushing when file formats have sub-track precision. 2020-07-17 22:09:21 -04:00
Thomas Harte
4ee29b3266 Switches disk seeking logic fully to floating point. 2020-07-17 22:08:58 -04:00
Thomas Harte
cbb0594e6b Use 16-sector state machine even with the 13-sector boot ROM.
I think I've proven that the Disk II doesn't decode the 13-sector state machine correctly. Work to do there.
2020-07-16 23:27:27 -04:00
Thomas Harte
8aeebdbc99 Remove redundant comment. 2020-07-16 23:26:45 -04:00
Thomas Harte
c7ef258494 Ensures that five-and-three sectors pass static analysis. 2020-07-16 21:44:14 -04:00
Thomas Harte
4fec7c82ab Very minor grammar improvement. 2020-07-16 21:43:03 -04:00
Thomas Harte
9a952c889f Fixes exit from random gain noise. 2020-07-15 22:44:54 -04:00
Thomas Harte
8da7806ee9 Liberalises segment parser not necessarily to require a standard epilogue.
It seems that real disks don't always have them; I guess the boot ROM doesn't require one.
2020-07-15 22:27:04 -04:00
Thomas Harte
aed61f6251 Implements latest advocated MC3470 behaviour. 2020-07-15 19:34:05 -04:00
Thomas Harte
d065d6d98f Adds read-only WOZ 2 support. 2020-07-15 19:15:03 -04:00
Thomas Harte
ab20a23f2b Merge pull request #814 from TomHarte/ZX81Autosrun
Ensures the ZX80 and ZX81 automatically RUN software that doesn't do that itself
2020-07-14 23:53:17 -04:00
Thomas Harte
e1c57b6fbe Further ensures both ZX80 and ZX81 functionality. 2020-07-14 23:45:51 -04:00
Thomas Harte
371c26251c Switches strategy for the ZX80. 2020-07-14 22:36:04 -04:00
Thomas Harte
645d198bee Causes ZX80 and ZX81 software that doesn't already autorun to do so. 2020-07-14 22:17:56 -04:00
Thomas Harte
ab1a999df4 Merge pull request #812 from TomHarte/QtLoadAtLaunch
Qt: Attempt to load a file if passed on the command line.
2020-07-13 22:00:11 -04:00
Thomas Harte
42f2bf05bf Attempt to load a file if passed on the command line. 2020-07-13 21:51:55 -04:00
Thomas Harte
5e55d3d7c7 Merge pull request #660 from TomHarte/FurtherSCC
IN PROGRESS. More fully implements the Macintosh's SCC.
2020-07-12 00:05:59 -04:00
Thomas Harte
1288369865 Merge branch 'master' into FurtherSCC 2020-07-11 23:54:40 -04:00
Thomas Harte
d86b0d4213 Merge pull request #811 from TomHarte/QtPaths
Ensures qmake paths are explicit.
2020-07-11 17:47:29 -04:00
Thomas Harte
d91cf598be Ensures qmake paths are explicit. 2020-07-11 17:46:32 -04:00
Thomas Harte
9be56aa4a2 Merge pull request #809 from TomHarte/QtActivity
Exposes activity lights in Qt.
2020-07-10 23:32:38 -04:00
Thomas Harte
86737454a0 Exposes activity lights in Qt.
As a status bar, which is a bit of a quick fix, but it's better than not displaying this information.
2020-07-10 23:18:38 -04:00
Thomas Harte
f0c0caf800 Merge pull request #807 from TomHarte/QtSDL
Works around Qt's keyboard limitations, under X11 at least
2020-07-10 22:36:27 -04:00
Thomas Harte
223a960a06 Implements standard keyboard -> joystick mapping. 2020-07-10 22:30:43 -04:00
Thomas Harte
f72570386c Installs and removes an 'Input' menu where required.
Also ensures safe shutdown of a second machine.
2020-07-09 23:47:38 -04:00
Thomas Harte
56e5491e5c Ensures safe startup. 2020-07-09 23:06:32 -04:00
Thomas Harte
be1c3e9136 Ensures key state is cleared upon activation changes. 2020-07-08 21:31:29 -04:00
Thomas Harte
2d223305eb Correct subject of switch. 2020-07-08 00:49:29 -04:00
Thomas Harte
48c2dcf50e Introduce provisional X11 bindings. 2020-07-08 00:46:29 -04:00
Thomas Harte
fa26c82273 Undoes extra dependency, checks for X11 at runtime. 2020-07-08 00:15:44 -04:00
Thomas Harte
0763ae38dd Attempts to add conditional include for non-Mac UNIX only. 2020-07-07 23:57:32 -04:00
Thomas Harte
2230ac6c38 Merge pull request #800 from TomHarte/QtProject
UNREADY. Adds a provisional Qt target.
2020-07-06 22:33:39 -04:00
Thomas Harte
abe1e7f244 Provide current thoughts on Qt and the keyboard. 2020-07-06 22:27:50 -04:00
Thomas Harte
24b03f733f Switches to an image more distinct from the existing.
i.e. less heavy on blue and green.
2020-07-04 22:03:08 -04:00
Thomas Harte
5a729f92c1 Attempts to move the 'Help' menu to the correct place. 2020-07-04 19:19:41 -04:00
Thomas Harte
366793498a Ensures ScanTargetWidget doesn't eat irrelevant keypresses. 2020-07-04 19:12:34 -04:00
Thomas Harte
cdda3f74ab Attempts mouse event capture. 2020-07-04 00:29:37 -04:00
Thomas Harte
51f4242e9b Reapplies image optimisation, saving a few bytes. 2020-07-04 00:29:10 -04:00
Thomas Harte
d97ebae200 Request Qt deprecation warnings. 2020-07-03 23:16:49 -04:00
Thomas Harte
daa195a7fb Add Qt to the main README. 2020-07-03 23:16:35 -04:00
Thomas Harte
2d5e9bf1bb Declines to set up audio output if none is available. 2020-07-02 22:58:15 -04:00
Thomas Harte
402f2ddbd9 Increases likelihood of 68000 Program offset-size assumptions being met. 2020-07-02 22:24:04 -04:00
Thomas Harte
8bf5ed52ea Ensures keyboard events are restricted to single windows. 2020-07-02 22:03:12 -04:00
Thomas Harte
b850183a1e Switches to an alternative to window(), for older Qt support. 2020-07-02 21:53:30 -04:00
Thomas Harte
f7e13356c4 FunctionThreads no longer automatically start.
Improvements as a result: audio works in a second machine started in an existing window; there is no audio thread footprint if there is no audio.
2020-07-01 18:55:42 -04:00
Thomas Harte
55cc3089f9 Ensures complete deallocation of the QAudioOutput. 2020-06-30 23:33:41 -04:00
Thomas Harte
a096a09c72 Trusts Qt to supply a refresh rate, and handles retina <-> non-retina window transitions. 2020-06-30 23:03:39 -04:00
Thomas Harte
365e1f2e85 Merge pull request #803 from Margen67/actions
ccpp.yml: Changes
2020-06-29 15:26:17 -04:00
Thomas Harte
b9e117cdcf Centralises window title responsibility. 2020-06-28 23:08:40 -04:00
Margen67
fc3a03a856 ccpp.yml: Changes
Build all branches.
Delete whitespace.
Use checkout v2.
Change ; to &&.
Use working-directory instead of cd.
Add -j to speed up build.
2020-06-28 19:53:19 -07:00
Thomas Harte
f6e5a2fb04 Resolves duplicative enums. 2020-06-28 22:50:24 -04:00
Thomas Harte
404c35feb5 Implements Atari 2600 switches menu. 2020-06-28 17:57:20 -04:00
Thomas Harte
b5962c58bb Completes ZX80/81-specific menu. 2020-06-28 16:23:35 -04:00
Thomas Harte
74da762ae1 Starts sketching out the ZX80/81 menu items. 2020-06-28 01:04:32 -04:00
Thomas Harte
d87c840b76 Adds quick load and quick boot options.
This should leave only the ZX80/81 and 2600 as special cases.
2020-06-27 17:08:29 -04:00
Thomas Harte
afb835398f Ensures display selection is preserved in the app settings. 2020-06-27 16:26:39 -04:00
Thomas Harte
c982c78285 Ensures Reflection::set is widely available. 2020-06-27 16:26:01 -04:00
Thomas Harte
b4cdf8d987 Separates runtime TypeInfo into its own header. 2020-06-27 15:53:06 -04:00
Thomas Harte
6925a04088 Ensures 'Display' menu is removed if machine is closed. 2020-06-26 23:27:14 -04:00
Thomas Harte
a0e534b309 Starts towards offering display-type selection. 2020-06-26 23:04:45 -04:00
Thomas Harte
74d1ca4fa8 Simplifies indentation, correcting flow while there. 2020-06-26 21:16:15 -04:00
Thomas Harte
3c896050fb Ensures proper output sizeing on HiDPI displays. 2020-06-26 21:14:43 -04:00
Thomas Harte
387500f01a Implements 'Insert...' menu item. 2020-06-26 18:25:56 -04:00
Thomas Harte
21c41ed4cb Reduces boilerplate and key repetition. 2020-06-26 00:39:30 -04:00
Thomas Harte
293ab25634 Ensures complete machine picker state is stored. 2020-06-26 00:23:52 -04:00
Thomas Harte
3ddc1a1722 Eliminates hard-coded concept of timer jitter. 2020-06-25 23:59:44 -04:00
Thomas Harte
478d081095 Ensures machines take user-friendly default settings. 2020-06-23 23:27:56 -04:00
Thomas Harte
9d4b49bbb5 Attempts to be more rigorous in vsync prediction. 2020-06-23 22:59:12 -04:00
Thomas Harte
4417f81014 Attempts to set a meaningful window title. 2020-06-22 22:58:58 -04:00
Thomas Harte
b96f7711e3 Corrects attempt at back-to-UI final window SDI behaviour.
Maybe it'll turn out to be not what I want, but at least now it works.
2020-06-22 22:36:36 -04:00
Thomas Harte
1875a03757 Plugs a per-window memory leak.
While also ensuring proper OpenGL resource destruction.
2020-06-22 20:32:44 -04:00
Thomas Harte
13336b8ad5 Consolidates and disables failed attempt at final-window close behaviour. 2020-06-21 23:52:41 -04:00
Thomas Harte
b17cceaeaf Tidies up and makes a failing attempt at SDI improvements. 2020-06-21 23:50:18 -04:00
Thomas Harte
782a62585e Preserves open path between launches. 2020-06-21 19:10:06 -04:00
Thomas Harte
c5d8d9127b Rejigs ScanTarget relationship from pull to push, so it can be set whenever it is safe. 2020-06-21 18:25:38 -04:00
Thomas Harte
336dffefe0 Ensures changes in the framebuffer are passed onward. 2020-06-21 17:25:21 -04:00
Thomas Harte
e297d4cced Decouples scan target drawing and lifetime. 2020-06-21 17:20:44 -04:00
Thomas Harte
b052ca5ca2 Switch to Qt-style member naming. 2020-06-21 17:16:11 -04:00
Thomas Harte
68d4d7d10a Ensures no out-of-bounds access for unlabelled keys. 2020-06-21 17:11:24 -04:00
Thomas Harte
a03211c410 Makes an attempt at the single document interface. 2020-06-21 12:30:18 -04:00
Thomas Harte
c953ab09db Ensures overloaded assignments work. 2020-06-20 00:17:16 -04:00
Thomas Harte
68645742f7 This is a deliberate fallthrough. 2020-06-20 00:12:08 -04:00
Thomas Harte
1fbb733f7f Expands upon comment. 2020-06-20 00:05:41 -04:00
Thomas Harte
6e4b8d58a5 Completes [[fallthrough]]s. 2020-06-19 23:50:37 -04:00
Thomas Harte
7af8646470 Allowing for the constexpr, this is maybe_unused. 2020-06-19 23:47:43 -04:00
Thomas Harte
945a9da94f Adds further [[fallthrough]]s. 2020-06-19 23:44:20 -04:00
Thomas Harte
2477752fa4 Adds further [[fallthrough]] attributes. 2020-06-19 23:36:51 -04:00
Thomas Harte
240d3c482b Removes redundant constructors. 2020-06-19 23:26:22 -04:00
Thomas Harte
91229a1dbd Adds overt fallthrough attributes. 2020-06-19 23:22:29 -04:00
Thomas Harte
4f9b3259d5 Adds explicit conversions to qint64. 2020-06-19 23:12:18 -04:00
Thomas Harte
3cb1072c29 Adds an explicit [[fallthrough]] tag. 2020-06-19 23:10:25 -04:00
Thomas Harte
12ee8e4db4 Ensures audio is not being pumped while the AudioBuffer is being destructed. 2020-06-19 23:09:39 -04:00
Thomas Harte
95e98323c5 Adds missing header for lock_guard and mutex. 2020-06-19 23:09:20 -04:00
Thomas Harte
7431d56166 Ensures is_in_retrace is properly initialised. 2020-06-19 23:09:02 -04:00
Thomas Harte
222c16c5b8 Ensures newly-hidden widgets aren't still in focus. 2020-06-19 22:01:53 -04:00
Thomas Harte
4e83e80962 Goes further in ensuring safe shutdown.
Especially if no machine has been started.
2020-06-19 20:17:47 -04:00
Thomas Harte
4fdbe578cc Wires up all new machine options. 2020-06-18 23:34:37 -04:00
Thomas Harte
c5cad865d7 Tidies up: arranges initialisers into alphabetical order, shortens some enum references. 2020-06-18 23:34:28 -04:00
Thomas Harte
ae5fe9225f Fills in all machine options. 2020-06-18 22:24:45 -04:00
Thomas Harte
327b9051c8 Adds necessary layouts for Apple II type selection. 2020-06-18 20:30:50 -04:00
Thomas Harte
8151c24cf5 Starts the machine-picker side of the interface. 2020-06-18 20:05:46 -04:00
Thomas Harte
ee659095c2 Retains the default window background colour until a machine is running. 2020-06-17 23:16:29 -04:00
Thomas Harte
8c35fe1062 Finally succeeds at making the missingROMsBox resize with the window. 2020-06-17 22:22:15 -04:00
Thomas Harte
9ca6a1031c Adds an 'about' box and a hypothetical 'New' file option. 2020-06-16 23:15:47 -04:00
Thomas Harte
59458f6444 Resolves errant spaces. 2020-06-16 23:15:38 -04:00
Thomas Harte
e8939aada4 Now that this spin blocks at startup, I can use a standard atomic_flag. 2020-06-16 23:12:58 -04:00
Thomas Harte
17bb3dce26 Makes a firmer attempt at enforcing safe shutdown. 2020-06-16 22:33:50 -04:00
Thomas Harte
495024d6fe Cleans up all redundant lock/unique_guard declarations. 2020-06-15 00:24:10 -04:00
Thomas Harte
902b33d25d Makes more failing attempts at a clean shutdown. 2020-06-15 00:00:44 -04:00
Thomas Harte
ac732e2e7b Attempts to ensure clean shutdown. 2020-06-14 23:38:44 -04:00
Thomas Harte
d08ffd6c8b Makes sure the timer really, really is on a different thread.
Thereby allows me substantially to reduce audio latency.
2020-06-14 23:22:00 -04:00
Thomas Harte
530ff7471d Adds a virtual destructor, given how these things might be held. 2020-06-14 21:14:51 -04:00
Thomas Harte
79833deeaf With some attempt at vsync prediction, seeks to smooth audio/video output.
There's plenty more work to do here, but hopefully it takes the issue immediately off the table.
2020-06-14 19:26:56 -04:00
Thomas Harte
405e9e7c68 Shunts audio into its own QThread.
For the record, this was the first means I found of attempting that which actually seemed to work. A plain QThread, with something `connect`ed to its `started` signal didn't seem to work (perhaps `connect` is smart at thread confinement?), `moveToThread` didn't work on the audio output after the fact, etc.
2020-06-10 22:14:54 -04:00
Thomas Harte
5f13ee7c19 Simplifies AudioBuffer by consolidating logic into writes.
This is kind of fiddling in the margins though; I'm having a lot of difficulty determining the semantically-correct way to get Qt not to funnel all activity through a single thread.
2020-06-09 23:56:08 -04:00
Thomas Harte
d9f02aecdf Adds an additional buffer. To reduce latency. No, really.
Specifically: there's no way to guarantee no overbuffering due to the startup race, other than having QAudioOutput obtain data by pull rather than push. But if it's pulling then that implies an extra buffer. And since the sizes it may pull are not explicit, there's guesswork involved there.

So: no extra buffer => uncontrollable risk of over-buffering. Extra buffer => a controllable risk of over-buffering.
2020-06-09 00:01:22 -04:00
Thomas Harte
bcb23d9a15 Attempt to reduce audio latency. Unsuccessfully. 2020-06-08 21:30:35 -04:00
Thomas Harte
d027450502 Consolidates timer/thread ownership for the timer. 2020-06-07 00:31:46 -04:00
Thomas Harte
63ad1290f4 Actually, QIODevice is listed as reentrant. So no need to forward audio.
That said, latency is still absurd for some reason.
2020-06-06 23:47:57 -04:00
Thomas Harte
7c7cb61d2f Corrects missing audio, at the cost of frame rate.
I'm now spinning on the ability of QAudioOutput to accept additional data.
2020-06-06 22:35:50 -04:00
Thomas Harte
68b165e244 QIODevice isn't guaranteed thread safe, so use of it is now thread confined. 2020-06-06 21:14:04 -04:00
Thomas Harte
fe1b6812f1 Fixes processing cap and attempts full-rate video output.
Audio now seems to be present, though hugely stuttered.
2020-06-06 19:47:35 -04:00
Thomas Harte
378ff39e5e Makes an unsuccessful attempt at producing audio.
On the plus side, it does seem successfully to sniff out an appropriate audio format and to communicate mono/stereo onward.
2020-06-06 19:19:01 -04:00
Thomas Harte
e47cb91223 Pushes rudimentary keyboard input. 2020-06-05 23:06:28 -04:00
Thomas Harte
d62fb16a58 Adds an eventFilter, in order to steal keypresses. 2020-06-05 22:11:17 -04:00
Thomas Harte
235efcb2d4 Attempts to silence asserts, etc, for release builds. 2020-06-04 23:14:51 -04:00
Thomas Harte
a6ada129e8 Adds very low quality, race-condition infested video output. 2020-06-04 22:58:02 -04:00
Thomas Harte
a681576d6c Adds redraw logic.
If you sit around and constantly reeize the window, you can now see that a machine is running.
2020-06-04 22:39:32 -04:00
Thomas Harte
fdc234ed3b Advances to having a selected machine actually run.
Albeit, invisibly.
2020-06-03 23:39:16 -04:00
Thomas Harte
e2ceb77501 Attempts to start updating a started machine.
No real progress on graphics output though.
2020-06-03 00:21:37 -04:00
Thomas Harte
11c28357a1 Implements a basic ROM installation loop.
Albeit that I need to figure out how layouts work to keep that request view at least centred.
2020-06-02 23:35:01 -04:00
Thomas Harte
f215405beb Corrects capitalisation errors. 2020-06-02 23:27:29 -04:00
Thomas Harte
ba2a0600dc Adds a basic Qt ROM fetcher and attempt to create a machine. 2020-06-01 23:14:57 -04:00
Thomas Harte
ab53165b34 Adds note on implementation obstacle. 2020-06-01 22:08:21 -04:00
Thomas Harte
a30723c3d4 Cleans up a little and ensures a safe exit of the timer thread. 2020-05-31 23:58:19 -04:00
Thomas Harte
d64b4fbc26 Adds a Qt timer class. Precision seems to be 'acceptable'. 2020-05-31 23:39:08 -04:00
Thomas Harte
73131735fa Further qmake warning corrections. 2020-05-30 19:31:17 -04:00
Thomas Harte
5e0bea9d1c Adds all header files to the QMake project. 2020-05-30 16:48:52 -04:00
Thomas Harte
48afc54af6 Cuts down unused parameter warnings to just a few that may well indicate implementation errors. 2020-05-30 01:06:43 -04:00
Thomas Harte
d066dd2b44 Resolves further unused parameter warnings.
Also adds warning to Xcode build, for better symmetry with Qt defaults.
2020-05-30 00:58:10 -04:00
Thomas Harte
0bf7de9d43 Advances to actually completing a build.
Many more warnings to iron out, however.
2020-05-30 00:47:43 -04:00
Thomas Harte
267006782f Starts to add Qt target; resolves many build warnings. 2020-05-30 00:37:06 -04:00
Thomas Harte
a9de745e51 Merge pull request #798 from qeeg/master
Fix Windows MSYS2 build (mostly)
2020-05-27 21:52:49 -04:00
Thomas Harte
ca1f3c600d Merge branch 'master' into master 2020-05-27 21:52:39 -04:00
Thomas Harte
743353a0ed Merge pull request #799 from TomHarte/WindowsBuildErrors
Fixes a couple of Windows build errors.
2020-05-27 21:42:06 -04:00
Thomas Harte
f7c10ef9e9 Replaces POSIX stpncpy with ANSI strlen, memcpy and memset. 2020-05-27 21:31:46 -04:00
Thomas Harte
ecb44711d1 Add glext.h. 2020-05-27 21:20:43 -04:00
Melissa Goad
603b747ac5 Fix Windows MSYS2 build (mostly) 2020-05-27 18:09:56 -05:00
Thomas Harte
0f2f776e6a Merge pull request #797 from TomHarte/Serialisation
Adds BSON serialisation and deserialisation for all reflectable structs.
2020-05-26 23:18:41 -04:00
Thomas Harte
1308f119a6 Relocates cassert. 2020-05-26 23:07:26 -04:00
Thomas Harte
51d684820f Attempts to add array support to ::set and BSON deserialisation. 2020-05-26 22:55:55 -04:00
Thomas Harte
023d76a3e7 Permits int truncation, adds double decoder. Arrays still TODO. 2020-05-26 22:20:15 -04:00
Thomas Harte
4d34d9ae2b Implements BSON deserialisation, other than arrays. 2020-05-25 23:39:00 -04:00
Thomas Harte
c83c827484 Adds necessary header for math. 2020-05-24 12:19:20 -04:00
Thomas Harte
b8b880a91d Extends encoding to handle vector<uint8_t>, floats and doubles. 2020-05-24 01:20:48 -04:00
Thomas Harte
bb2f21a22e Encodes enumerated values as strings. 2020-05-23 22:54:43 -04:00
Thomas Harte
b3587d4cde Corrects: logic for int promotion, object sizes, int64_t gets, sizes prefixed to objects. 2020-05-22 23:38:07 -04:00
Thomas Harte
39ffe45f3c Attempts to add support for arrays. 2020-05-22 21:55:12 -04:00
Thomas Harte
d36e592afb Starts towards BSON serialisation for all deflectable structs.
Still to be tackled: arrays, enumerated types should probably be encoded as strings, deserialisation, probably distinguish get and fuzzy_get...
2020-05-22 00:31:40 -04:00
Thomas Harte
74fb697fa6 Merge pull request #796 from TomHarte/MintBuildIssues
Increases const correctness.
2020-05-20 23:52:16 -04:00
Thomas Harte
512a52e88d Increases const correctness, marks some additional constructors as constexpr, switches std::atomic construction style. 2020-05-20 23:34:26 -04:00
Thomas Harte
41fc6c20a0 Merge pull request #794 from TomHarte/68000State
Adds a `State` for the 68000.
2020-05-19 22:33:57 -04:00
Thomas Harte
28881cb391 Implements apply. 2020-05-19 18:27:10 -04:00
Thomas Harte
a16b710d22 Removes <cassert> from Struct.h (which means it's needed in the 68000's State). 2020-05-19 00:06:29 -04:00
Thomas Harte
a3d4c7599b Attempts fully to capture 68000 state.
Albeit that it can't be put back yet.
2020-05-18 23:55:54 -04:00
Thomas Harte
6f16928215 Adds all remaining simple scalar fields. 2020-05-16 22:47:04 -04:00
Thomas Harte
ff3c2fdc59 Adds 68000 state to SConstruct. 2020-05-16 18:33:36 -04:00
Thomas Harte
57edfe8751 Formalises TODO list and marches onward into execution state. 2020-05-16 18:31:43 -04:00
Thomas Harte
dcc0ee3679 Adds input line capture. 2020-05-16 17:44:15 -04:00
Thomas Harte
f7a16762b4 Starts populating the 68000 state registers. 2020-05-16 00:06:04 -04:00
Thomas Harte
375835a950 Extends .description() to handle arrays. 2020-05-14 23:58:17 -04:00
Thomas Harte
4481386a3d Extends Reflection::Struct slightly to capture the lengths of arrays. 2020-05-14 22:59:44 -04:00
Thomas Harte
8b76d4007e Starts adding State for the 68000. 2020-05-14 22:46:40 -04:00
Thomas Harte
4f30118b37 Merge pull request #793 from TomHarte/Z80State
Adds reflective state for the Z80.
2020-05-14 00:18:58 -04:00
Thomas Harte
c5b746543b Factors the half mask into steps count. 2020-05-14 00:09:01 -04:00
Thomas Harte
11d936331d Attempts to preserve scheduled_program_counter_. 2020-05-13 23:58:04 -04:00
Thomas Harte
4f619de675 Permits ::get from a reflective enum to an int. 2020-05-13 23:48:28 -04:00
Thomas Harte
80f2836cb8 Adds Z80 state to SConstruct. 2020-05-13 22:05:23 -04:00
Thomas Harte
3709aa7555 Edges almost up to an initially complete implementation. 2020-05-13 22:04:04 -04:00
Thomas Harte
7c9d9ee048 Adds basic Z80 state. 2020-05-13 20:15:22 -04:00
Thomas Harte
e4335577ca Merge pull request #792 from TomHarte/BIOSFreeMasterSystem
Ensures the Master System makes a genuine attempt to boot sans BIOS
2020-05-12 22:35:17 -04:00
Thomas Harte
66c2eb0414 Further tightens const and constexpr usage. 2020-05-12 22:22:21 -04:00
Thomas Harte
f82e4ee923 Makes additional minor const improvements. 2020-05-12 00:31:16 -04:00
Thomas Harte
b62ee33318 Improves constness of Joystick interface. 2020-05-12 00:19:48 -04:00
Thomas Harte
8596a9826f Whether the BIOS is available in hardware is now decided entirely based on whether it is on disk. 2020-05-12 00:11:46 -04:00
Thomas Harte
3f2fb1fa58 Merge pull request #790 from ajacocks/master
Create RPM package and man page
2020-05-11 13:06:47 -04:00
Alexander Jacocks
5f39938a19 add Ansible build playbook to create RPM package for clksignal and create basic man page 2020-05-11 00:52:51 -04:00
Thomas Harte
d964ebd4c1 Merge pull request #789 from TomHarte/OPLLDrums
Softens OPLL tremolo and vibrato; adds drum damping.
2020-05-10 15:51:34 -04:00
Thomas Harte
9458963311 Factors out shift by 7. 2020-05-10 13:57:50 -04:00
Thomas Harte
44690b1066 Halves effect of vibrato. 2020-05-10 12:05:14 -04:00
Thomas Harte
c41028cdc7 Adds further exposition. 2020-05-10 00:44:03 -04:00
Thomas Harte
64c62c16fb Adjusts tremolo scale. 2020-05-10 00:43:46 -04:00
Thomas Harte
afef4f05fe Adds damping and phase resets for the rhythm section. 2020-05-10 00:10:51 -04:00
Thomas Harte
fc0f290c85 Merge pull request #788 from TomHarte/ConstFun
Cleans up a variety of dangling issues.
2020-05-09 23:57:22 -04:00
Thomas Harte
81d70ee325 Adds in a few further consts. 2020-05-09 23:49:37 -04:00
Thomas Harte
6dc7a4471d Removes unused .cpp file. 2020-05-09 23:43:05 -04:00
Thomas Harte
fcb8bd00b6 Adds further costs. 2020-05-09 23:42:42 -04:00
Thomas Harte
05c3f2a30d Adds some further `costs. 2020-05-09 23:03:33 -04:00
Thomas Harte
25996ce180 Further doubles down on construction syntax for type conversions. 2020-05-09 23:00:39 -04:00
Thomas Harte
3729bddb2a Farewell, BestEffortUpdater. 2020-05-09 21:48:04 -04:00
Thomas Harte
4136428db3 Removes dead StandardOptions.cpp. 2020-05-09 21:35:15 -04:00
Thomas Harte
31c6faf3c8 Adds a bunch of consts. 2020-05-09 21:23:52 -04:00
Thomas Harte
5c1ae40a9c Merge pull request #783 from TomHarte/OPL2
Adds provisional OPLL emulation.
2020-05-09 18:28:03 -04:00
Thomas Harte
4c6d0f7fa0 Corrects SConstruct; applies default initialisation in Struct.cpp. 2020-05-09 18:11:50 -04:00
Thomas Harte
40b60fe5d4 Renames folder as per intended scope. 2020-05-09 18:04:11 -04:00
Thomas Harte
eed357abb4 Introduces concept of 'average peak volume' in order better to normalise audio sources like the OPLL. 2020-05-09 17:57:21 -04:00
Thomas Harte
8f541602c1 Moves modulator updates a sample behind operator updates. 2020-05-08 21:14:25 -04:00
Thomas Harte
668f4b77f3 Implements feedback. 2020-05-08 21:05:23 -04:00
Thomas Harte
303965fbb8 Removes the crutch of my first-attempt implementation. 2020-05-08 20:53:34 -04:00
Thomas Harte
792aed242d Fixes the use-sustain flag. 2020-05-08 20:49:39 -04:00
Thomas Harte
dc5654b941 Attempts to implement the proper attack phase.
It's sounding pretty good now, but for sustain.
2020-05-08 18:59:05 -04:00
Thomas Harte
e51e2425cc Attempts to implement decay and release the right way around and with full precision.
Higher numbers = decay/release more quickly, not more slowly.
2020-05-08 18:40:49 -04:00
Thomas Harte
95c6b9b55d Declare proper envelope precision. 2020-05-08 17:58:50 -04:00
Thomas Harte
ea25ead19d Ensures rhythm envelope generators don't pick up should_damp state. 2020-05-08 00:18:31 -04:00
Thomas Harte
24100ec3b0 Switches snare and high-hat envelope generators. 2020-05-08 00:08:14 -04:00
Thomas Harte
32437fbf8b Attempts to use the proper rhythm mode envelope generators. 2020-05-07 23:56:15 -04:00
Thomas Harte
5219a86a41 In principle fully implements rhythm mode. 2020-05-07 23:38:51 -04:00
Thomas Harte
e12dc5d894 Reduce the amount of time spent installing instruments. 2020-05-06 00:15:28 -04:00
Thomas Harte
75315406bb Ensure all channels begin in 'release' phase, which is currently code for 'off' in conjunction with attenuation of 511. 2020-05-06 00:13:01 -04:00
Thomas Harte
ea42fe638a Corrects channel attenuation and carrier sustain level settings. 2020-05-05 23:41:15 -04:00
Thomas Harte
744211cec0 Ensures rhythm instruments are installed. 2020-05-05 23:13:13 -04:00
Thomas Harte
1a4321d7d0 Attempts better to balance attenuations. 2020-05-05 22:14:11 -04:00
Thomas Harte
b943441901 Marks up more specific TODOs.
I think I'm already much happier with this factoring.
2020-05-05 00:35:03 -04:00
Thomas Harte
0505b82384 Restores top bit of channel period, propagates it to the envelope generator. 2020-05-05 00:28:24 -04:00
Thomas Harte
c9fb5721cd Makes first attempt to reintroduce full-melodic output. 2020-05-05 00:16:45 -04:00
Thomas Harte
386a7ca442 Continues doing away with the attempt heavily to interleave the OPLL and OPL2, creating a new OPLL class. 2020-05-04 21:14:51 -04:00
Thomas Harte
e929d5d819 Ensures proper dereferencing of the std::optional. 2020-05-03 21:57:15 -04:00
Thomas Harte
94614ae4c3 Shifts the LFO implementation inline. 2020-05-03 21:44:22 -04:00
Thomas Harte
1223c99e0f Adds waveform generation logic to the new factoring. 2020-05-03 21:38:20 -04:00
Thomas Harte
1ff5ea0a6e Adds KeyLevelScaler, implements EnvelopeGenerator, adds reset to PhaseGenerator. 2020-05-03 16:24:55 -04:00
Thomas Harte
9d2691d1d2 Taking it as given that outstanding deficiencies are mostly due to poor design, starts breaking out the envelope and phase generators. 2020-05-01 23:46:42 -04:00
Thomas Harte
e4ef2c68bb Feeds through drum volume levels. 2020-04-30 19:35:09 -04:00
Thomas Harte
7fffafdfd4 Wires the high-hat through, possibly incorrectly. 2020-04-29 22:44:15 -04:00
Thomas Harte
5896288edd Adapts to new interface. 2020-04-29 22:08:36 -04:00
Thomas Harte
c4135fad2b Attempts completely to decouple updates and audio outputs. 2020-04-29 22:07:40 -04:00
Thomas Harte
1f34214fb3 Imagines a future of being able to boot into the BIOS. 2020-04-29 22:07:20 -04:00
Thomas Harte
f899af0eef Fixes OPL tests. 2020-04-28 20:17:16 -04:00
Thomas Harte
9f0c8bcae7 Attempts to add the missing noise generators. I think I may still be astray on volumes. 2020-04-26 15:51:33 -04:00
Thomas Harte
2bc36a6cde Eliminates branch within snare output. 2020-04-26 00:21:15 -04:00
Thomas Harte
ee10fe3d2c Fully separates updates and outputs in operators; takes a shot at the snare. 2020-04-26 00:18:09 -04:00
Thomas Harte
a424e867f9 Continues factoring this apart, albeit with a decision on whether to retain update-and-output still pending. 2020-04-25 23:07:40 -04:00
Thomas Harte
f52b40396a Re-ups output level.
Though it's still quiet compared to the SN.
2020-04-25 23:07:06 -04:00
Thomas Harte
cd2ab70a58 Moves the LFSR to the LowFrequencyOscillator.
Possibly I should come up with a better name for that?
2020-04-25 22:21:42 -04:00
Thomas Harte
a5d1941d28 Adds necessary standalone #imports; makes safe for signed types. 2020-04-25 22:21:10 -04:00
Thomas Harte
65a3783dd2 Attempts the tom tom. 2020-04-25 19:21:55 -04:00
Thomas Harte
b9b5c2a3bc Takes a first run at proper slot mixing and the bass drum. 2020-04-25 18:01:05 -04:00
Thomas Harte
12c618642e Corrects output range. 2020-04-25 00:07:58 -04:00
Thomas Harte
6ebc93c995 Switches to maximum-rate multiplexing. Hopefully to eliminate the mixer as a consideration for now. 2020-04-24 23:50:06 -04:00
Thomas Harte
6d4e29c851 Strips mixer back to basics in search of audio issues. 2020-04-24 23:32:02 -04:00
Thomas Harte
b3979e2fda Looking towards rhythm mode, and in search of bugs: factors out ADSR.
Further factorings to come.
2020-04-24 18:48:32 -04:00
Thomas Harte
983c32bf75 Adds vibrato.
This would complete melodic output, subject to bug fixes.
2020-04-24 18:02:41 -04:00
Thomas Harte
9e3614066a Adds tremolo support, switches to global timer for ADSR stages other than attack. 2020-04-23 23:55:49 -04:00
Thomas Harte
c7ad6b1b50 Minor layout and commenting improvements. 2020-04-21 23:35:48 -04:00
Thomas Harte
676dcf7fbb Calculates the proper key scale rate, though ADSR itself is still lacking that precision. 2020-04-21 22:57:56 -04:00
Thomas Harte
50d725330c Adds missing header. 2020-04-21 22:48:52 -04:00
Thomas Harte
2886dd1dae Collapses key-level scaling to a single 2d table.
I dare imagine I can do better; the columns in particular look like arithmetic progressions.
2020-04-21 20:19:02 -04:00
Thomas Harte
40424ac38b Re-enables key-level scaling, with 3db and 1.5db the correct way around. 2020-04-21 20:10:40 -04:00
Thomas Harte
a4d3865394 Decreases sustain level attenuation; disables key-level scaling for now.
The latter was definitely wrong, I also think I don't need the big four tables.
2020-04-21 19:58:40 -04:00
Thomas Harte
0ac99e8d42 Disables low low-pass filter, honours audio control bits for better volume usage. 2020-04-21 19:57:13 -04:00
Thomas Harte
bdce1c464a Takes a shot at key-level scaling. Testing to come. 2020-04-21 00:09:42 -04:00
Thomas Harte
475d75c16a Preserves fractional part of modulator phase. 2020-04-20 23:35:37 -04:00
Thomas Harte
32fd1897d0 Via a unit test, confirms and fixes relative volumes of OPLL channels.
Also rejigs responsibility for scaling to emulator-standard volume.
2020-04-20 23:17:29 -04:00
Thomas Harte
39e6a28730 Rearranges file. 2020-04-20 19:41:04 -04:00
Thomas Harte
3852e119aa Adds test data for FM wave generation. 2020-04-20 19:33:03 -04:00
Thomas Harte
f19fd7c166 Pulls out common melodic update calls. 2020-04-20 18:58:31 -04:00
Thomas Harte
100fddcee1 Corrects divider, takes another whack at ADSR. 2020-04-20 18:58:10 -04:00
Thomas Harte
99fa86a67e Adds a test for lookup sine. And fixes lookup sine. 2020-04-20 18:40:47 -04:00
Thomas Harte
6568c29c54 Improves commentary. 2020-04-19 22:42:25 -04:00
Thomas Harte
c54bbc5a04 Rename Table.h; LogSin -> LogSign and make it a bit more typer. 2020-04-19 13:33:17 -04:00
Thomas Harte
92d0c466c2 Moves complete phase -> output calculation inside Operator.
Reasoning being: otherwise I wasn't currently enforcing non-sine waveforms.
2020-04-19 13:27:24 -04:00
Thomas Harte
020c760976 Simplifies the phase counter. 2020-04-19 00:30:14 -04:00
Thomas Harte
cdfd7de221 Minor: enables all melodic channels when rhythm mode is disabled; supports non-modulated channels. 2020-04-18 17:48:29 -04:00
Thomas Harte
3da2e91acf Adjusts range of output, makes declaration of level full owner of type information. 2020-04-17 23:29:09 -04:00
Thomas Harte
3948304172 Attempts to use table-based maths. 2020-04-17 23:23:16 -04:00
Thomas Harte
4a295cd95e Wraps log_sin in an access function to enshrine sign and mask rules; switches both functions to non-math.h clashing names. 2020-04-17 23:22:42 -04:00
Thomas Harte
6f7c8b35c5 Applies an ahead-of-time transformation to the exp table, and wraps it in a helper function. 2020-04-17 22:33:13 -04:00
Thomas Harte
e58ba27c00 Clarifies meaning of scaling. Though it isn't yet applied. 2020-04-17 22:30:10 -04:00
Thomas Harte
0aceddd088 Starts tidying up the OPL2.
This is as a precursor to switching to using the proper table lookups, which I hope will automatically fix my range issues.
2020-04-15 22:10:50 -04:00
Thomas Harte
30ff399218 With some fixes for scale, I think possibly this is close for melodic channels. 2020-04-15 21:27:27 -04:00
Thomas Harte
a7e63b61eb Just from printing numbers: corrects transition from attack to decay. 2020-04-15 00:26:01 -04:00
Thomas Harte
b13b0d9311 Starts towards implementing some OPL test cases. 2020-04-14 23:51:45 -04:00
Thomas Harte
d8380dc3e2 Tries to be a little neater in spelling out the work here.
I think I'm somewhat circling here now; I need to think of a way of getting clean comparison data.
2020-04-14 21:55:42 -04:00
Thomas Harte
d805e9a8f0 Actually, octave probably works this way around? Higher octaves = higher frequencies. 2020-04-14 21:39:12 -04:00
Thomas Harte
aa45142728 Endeavours to fix attenuation and add FM synthesis.
I now definitely think my frequency counting is wrong.
2020-04-14 18:32:06 -04:00
Thomas Harte
09d1aed3a5 Attempts to voice the current attenuation (and, therefore, the ADSR output), even if linearly rather than logarithmically. 2020-04-13 22:12:55 -04:00
Thomas Harte
a1f80b5142 Takes a stab at per-operator ADSR.
Heavy caveats apply: no KSR is applied, non-ADSR attenuation isn't applied, attenuation isn't voiced in general.
2020-04-13 21:39:06 -04:00
Thomas Harte
cb1970ebab Switch to more compact form of output for bool.
This also will hopefully deal with GCC's slightly confused claim that 'value' may be used without having been initialised down at #define OutputIntC (i.e. after it's out of scope, but I can sort of see why GCC might get confused while it remains in scope).
2020-04-12 14:40:32 -04:00
Thomas Harte
d3fbdba77c Add missing #include. 2020-04-12 14:20:02 -04:00
Thomas Harte
632d797c9d Adjusts frequency formula. This could be close.
I guess next I need to get ADSR/volume in general working, before I can go FM? Then I'll worry about using the proper log-sin/exp tables.
2020-04-12 14:15:09 -04:00
Thomas Harte
559a2d81c1 Baby step: starts trying to output the raw FM carrier, no modulation, no ADSR. 2020-04-12 12:46:40 -04:00
Thomas Harte
7a5f23c0a5 Adds accommodations for the OPLL. 2020-04-10 22:05:22 -04:00
Thomas Harte
84b115f15f Attempts to move forward in defining what the parts of an OPL are meant to do. 2020-04-10 19:13:52 -04:00
Thomas Harte
a0d14f4030 Starts trying to make sense of the various fields at play. 2020-04-08 23:15:44 -04:00
Thomas Harte
dd6769bfbc Splits OPLL and OPL2 classes.
Logic is: they have different mixers (additive in the OPL2, time-division multiplexing in the OPLL) as well as different register sets. So I'll put operator and channel logic directly into those structs.
2020-04-07 23:15:26 -04:00
Thomas Harte
027af5acca Allow LFSR to be instantiated with a given value. 2020-04-05 22:58:09 -04:00
Thomas Harte
db4b71fc9a Adds correct LSFR, something of OPLL -> OPL2 logic. 2020-04-05 22:57:53 -04:00
Thomas Harte
d9e41d42b5 Adds the OPL2 to SConstruct. 2020-04-05 21:34:19 -04:00
Thomas Harte
0ed7d257e1 Add some extra notes, implement correct mapping to only 18 operators. Not 22. 2020-04-05 14:32:55 -04:00
Thomas Harte
335a68396f Attempts to complete OPL2 register decoding. 2020-04-04 23:39:09 -04:00
Thomas Harte
84cdf6130f Starts at least trying to decode OPL2 register writes. 2020-04-04 23:29:25 -04:00
Thomas Harte
b0abc4f7bb Implements enough wiring that the Master System will instantiate and talk to an OPLL. 2020-04-03 20:05:36 -04:00
Thomas Harte
ab81d1093d Merge pull request #782 from TomHarte/6502Tidy
Makes `State`, and therefore the 'Reflection' dependency, an optional adjunct to the 6502.
2020-04-02 20:49:18 -04:00
Thomas Harte
e4d4e4e002 Adds 6502 State to the SConstruct file.
On the assumption I'll actually use it at some point.
2020-04-02 19:16:22 -04:00
Thomas Harte
cc357a6afa Removes boilerplate from header. 2020-04-02 19:15:57 -04:00
Thomas Harte
dfc1c7d358 Separates 6502 State object to make it optional.
Also makes a few minor const improvements while I'm poking around.
2020-04-02 19:11:27 -04:00
Thomas Harte
7ed8e33622 Eliminates unused 6502 counter. 2020-04-02 18:49:28 -04:00
Thomas Harte
474822e83d Merge pull request #781 from TomHarte/NoMoreCRTMachine
Splits 'CRTMachine' into three parts: ScanProducer, AudioProducer, TimedMachine.
2020-04-02 09:46:54 -04:00
Thomas Harte
fe3942c5b3 Updates comments. 2020-04-01 23:49:07 -04:00
Thomas Harte
f417fa82a4 Splits 'CRTMachine' into three parts: ScanProducer, AudioProducer, TimedMachine.
Simultaneously cleans up some of the naming conventions and tries to make things a bit more template-compatible.
2020-04-01 23:19:34 -04:00
Thomas Harte
c4b114133a Merge pull request #779 from TomHarte/6502State
Provisionally adds `State` and `get/set_state` to the 6502.
2020-03-31 21:05:29 -04:00
Thomas Harte
2f4b0c2b9a Removes non-functional assert. 2020-03-30 21:48:07 -04:00
Thomas Harte
a491650c8b Adds safety asserts. 2020-03-30 21:39:31 -04:00
Thomas Harte
6805acd74f Adds padding for all integer types. 2020-03-30 00:31:25 -04:00
Thomas Harte
95c68c76e1 Corrects use of StructImpl. 2020-03-30 00:27:40 -04:00
Thomas Harte
60aa383c95 Makes a not-quite-correct attempt at a .description for reflective structs. 2020-03-30 00:24:49 -04:00
Thomas Harte
edc553fa1d Removes duplicative 'register'. 2020-03-29 22:58:00 -04:00
Thomas Harte
4f2ebad8e0 Takes a shot a set_state. 2020-03-29 22:50:30 -04:00
Thomas Harte
1810ef60be Adds --fix-missing in the hope of catching more issues automatically. 2020-03-29 18:41:30 -04:00
Thomas Harte
f720a6201b Adds explicit type cast. 2020-03-29 18:36:57 -04:00
Thomas Harte
cfb75b58ca Pulls all 6502 MicroOp sequences into the main operations_ table.
This will make state restoration somewhat more tractable.
2020-03-29 18:36:41 -04:00
Thomas Harte
4fbe983527 Provisionally adds State and get_state to the 6502.
`set_state` may be a little more complicated, requiring a way to advance in single-cycle steps **without applying bus accesses**.
2020-03-28 00:33:27 -04:00
Thomas Harte
272383cac7 Merge pull request #778 from TomHarte/AppleIIDisks
Resolves a potential crash with NIB files
2020-03-25 21:52:26 -04:00
Thomas Harte
39380c63cb Throws in some consts. 2020-03-25 21:25:50 -04:00
Thomas Harte
ea26f4f7bf Eliminates test code, adds a caveat. 2020-03-25 21:22:30 -04:00
Thomas Harte
5fd2be3c8e Makes a genuine attempt at five and three decoding. 2020-03-25 20:50:26 -04:00
Thomas Harte
2320b5c1fe Takes some steps towards five-and-three decoding.
Now I 'just' need to figure out how bits are distributed within the decoded sector. The XORing and data checksum seem the same (?)
2020-03-25 00:15:31 -04:00
Thomas Harte
e5cbdfc67c It turns out that 5-and-3 disks have a different header prologue. 2020-03-24 21:59:55 -04:00
Thomas Harte
894d196b64 Avoids massive overallocation where sync blocks overlap the index hole. 2020-03-24 21:34:33 -04:00
Thomas Harte
af037649c3 Merge pull request #777 from TomHarte/ShowCRCs
Show CRC32s of missing ROMs.
2020-03-23 21:33:10 -04:00
Thomas Harte
cfca3e2507 Adds missing header for std::setw, std::set fill. 2020-03-23 21:26:50 -04:00
Thomas Harte
7a12a0149a Ensures BIOS is really not paged if not loaded. 2020-03-23 20:00:31 -04:00
Thomas Harte
fcdc1bfbd0 Prints the CRC32(s) of any missing ROMs. 2020-03-23 20:00:13 -04:00
Thomas Harte
d1d14ba9a0 Merge pull request #775 from TomHarte/SavedVolume
Ensures the macOS version retains volume.
2020-03-23 00:18:51 -04:00
Thomas Harte
0e502f6d5c Ensures the macOS version retains volume. 2020-03-23 00:10:56 -04:00
Thomas Harte
d3bac57d6a Merge pull request #774 from TomHarte/VolumeControl
Adds output volume control.
2020-03-22 21:23:49 -04:00
Thomas Harte
bd1b4b8a9f Increases volume fade-out speed. 2020-03-22 21:13:55 -04:00
Thomas Harte
38d81c394f Switches OSAtomics to stdatomics. The former were deprecated by macOS 10.12. 2020-03-22 21:11:04 -04:00
Thomas Harte
72103a4adb Corrects execution cap for splitAndSync ticks. 2020-03-22 19:25:02 -04:00
Thomas Harte
e6bae261c4 Ensures volume controls appear for mouse-capture machines when not capturing. 2020-03-22 19:06:38 -04:00
Thomas Harte
5edb0c0ee7 Adds animated fade-out to volume control. Bumps macOS version to 10.12.2. 2020-03-22 18:45:24 -04:00
Thomas Harte
442ce403f9 It's a bit jarring, but ensures volume control shows and hides according to mouse cursor. 2020-03-22 16:25:07 -04:00
Thomas Harte
7398cb44e2 Adds a functioning volume control for macOS, it just doesn't know how to hide yet. 2020-03-22 13:24:23 -04:00
Thomas Harte
15d54dfb4c Adds 'volume' command-line parameter for kiosk mode. 2020-03-21 22:24:31 -04:00
Thomas Harte
9087bb9b08 Allows audio volume to be set. 2020-03-21 22:00:47 -04:00
Thomas Harte
0c689e85a5 Use screen number for spotting screen changes.
NSScreen implements Swift Equatable but doesn't seem officially to implement -isEqual:.
2020-03-21 17:01:57 -04:00
Thomas Harte
75f2b0487e Merge pull request #773 from TomHarte/MacCrashAgain
Ensures proper NSScreen comparison...
2020-03-20 23:19:53 -04:00
Thomas Harte
5a1bae8a9c Ensures proper NSScreen comparison, and no never-ending setupDisplayLink loop on exit. 2020-03-20 23:00:16 -04:00
Thomas Harte
129bc485bf Merge pull request #772 from TomHarte/ReflectiveEnum
Endeavours to bring introspection to machine selection options.
2020-03-19 23:30:19 -04:00
Thomas Harte
69277bbb27 Renames files to match project convention. 2020-03-19 23:24:06 -04:00
Thomas Harte
b8b335f67d Exposes the Master System's region for SDL selection. 2020-03-19 21:46:42 -04:00
Thomas Harte
eef7868199 Ensures 'new' overrides default selection; doesn't try to propagate multiple files if machines won't take them. 2020-03-19 21:15:38 -04:00
Thomas Harte
23aa7ea85f Revives MultiConfigurable. 2020-03-19 21:02:14 -04:00
Thomas Harte
c1b69fd091 Attempts to support multiple pieces of media on the SDL command line, ensures proper window titling. 2020-03-19 20:40:43 -04:00
Thomas Harte
7ab7efdbc1 Ensures consistent ordering. 2020-03-19 19:41:50 -04:00
Thomas Harte
b8ebdc012f Ensure normative construction declaration ordering. 2020-03-19 18:58:36 -04:00
Thomas Harte
9995d776de Attempts to fix the macOS version, plus some implicit type conversions. 2020-03-18 23:29:09 -04:00
Thomas Harte
c6f35c9aac Rejigs help output. 2020-03-18 23:11:25 -04:00
Thomas Harte
615ea2f573 Applies parsed arguments. 2020-03-18 22:31:32 -04:00
Thomas Harte
311458f41f Restores Macintosh 'runtime' options.
Also cleans up some leftover parts elsewhere.
2020-03-18 21:50:02 -04:00
Thomas Harte
b2a381d401 Restores Vic-20 runtime options. 2020-03-18 20:23:55 -04:00
Thomas Harte
ffc1b0ff29 Reintroduces Oric runtime options. 2020-03-18 18:31:31 -04:00
Thomas Harte
ead2823322 Reintroduces MSX and Master System runtime options. 2020-03-18 18:26:22 -04:00
Thomas Harte
a7e1920597 Restores ColecoVision runtime options. 2020-03-18 00:06:52 -04:00
Thomas Harte
ec6664f590 Takes steps to guarantee property naming; reintroduces Electron runtime options. 2020-03-17 23:52:55 -04:00
Thomas Harte
8c6ca89da2 Restores runtime options for the Acorn Electron. 2020-03-17 22:06:20 -04:00
Thomas Harte
b6e81242e7 Reintroduces Apple II runtime options. 2020-03-17 21:53:26 -04:00
Thomas Harte
f9ca443667 Adds the ability for reflective structs to limit the permitted values to enumerated properties. 2020-03-17 21:44:04 -04:00
Thomas Harte
394ee61c78 Starts a switch to reflectable-style runtime options.
The Amstrad CPC and ZX80/81 have made the jump so far, subject to caveats. The macOS build is unlikely currently to work properly.
2020-03-16 23:25:05 -04:00
Thomas Harte
1d40aa687e Adds necessary include for unique_ptr. 2020-03-15 23:52:24 -04:00
Thomas Harte
8e3bf0dbca Starts moving towards a Deflectable-based system of runtime options. 2020-03-15 23:48:53 -04:00
Thomas Harte
2031a33edf Technically SDL users can now start a new machine.
Missing though: all the old per-machine command-line options, and any control over the new one.
2020-03-15 21:50:43 -04:00
Thomas Harte
fc3d3c76f8 Edges further towards providing enough information for dynamic user-provided machine creation. 2020-03-15 12:54:55 -04:00
Thomas Harte
880bed04f5 Adds AllMachines, rounds out ConstructionOptionsByMachineName. 2020-03-15 00:15:19 -04:00
Thomas Harte
f9c8470b20 Ensure targets always nominate a machine. 2020-03-15 00:13:38 -04:00
Thomas Harte
36acc2dddd Add necessary include for std::find. 2020-03-14 00:22:23 -04:00
Thomas Harte
a59963b6a0 Adds necessary header for memcpy. 2020-03-14 00:17:58 -04:00
Thomas Harte
cab4bead72 Promotes explicit specialisations to namespace scope. 2020-03-13 23:38:29 -04:00
Thomas Harte
1a2872c815 Starts to build an easy set interface. 2020-03-13 22:42:37 -04:00
Thomas Harte
f27e0a141d Sketches but doesn't implement an interface for serialisation. 2020-03-13 20:16:36 -04:00
Thomas Harte
52f644c4f1 Ensures that reflection is completely blind; starts adding SDL instantiation logic. 2020-03-12 20:56:02 -04:00
Thomas Harte
06c08a0574 Merge branch 'ReflectiveEnum' of github.com:TomHarte/CLK into ReflectiveEnum 2020-03-11 23:30:27 -04:00
Thomas Harte
724e2e6d27 Withdraws ability to select an integer size for ReflectableEnums.
It isn't that useful, and this'll help if/when I get to serialisation.
2020-03-11 23:28:38 -04:00
Thomas Harte
fd052189ca Adds reflection to all of the other computer targets. 2020-03-11 23:25:29 -04:00
Thomas Harte
044a2b67e1 Beefs up documentation on this miniature sort-of reflection. 2020-03-11 23:03:05 -04:00
Thomas Harte
7e8b86e9bb Attempts to flesh out Reflection::Enum. 2020-03-11 23:03:05 -04:00
Thomas Harte
ce80825abb Starts working towards a registration-based model of reflective enums. 2020-03-11 23:03:05 -04:00
Thomas Harte
a99bb3ba6d Switches to class storage. 2020-03-11 23:03:05 -04:00
Thomas Harte
3428e9887d Starts experimenting with declared reflection. 2020-03-11 23:03:05 -04:00
Thomas Harte
5a8fcac4dc Gives function overloading a try. 2020-03-11 23:03:05 -04:00
Thomas Harte
6a9b14f7d1 Adds a prototype reflective enum.
I need to make this scopeable before it is acceptable.
2020-03-11 23:03:05 -04:00
Thomas Harte
a74d8bd6e8 Merge pull request #771 from TomHarte/MacShutdownRace
Ensure race condition workaround is applied for all CVDisplayLinkStops.
2020-03-11 22:47:17 -04:00
Thomas Harte
3c70f056ed Ensure race condition workaround is applied for all CVDisplayLinkStops.
This also centralises the workaround, the better for replacing it when I discover a safer alternative.
2020-03-11 22:09:36 -04:00
Thomas Harte
a546880a65 Beefs up documentation on this miniature sort-of reflection. 2020-03-11 22:06:16 -04:00
Thomas Harte
238145f27f Attempts to flesh out Reflection::Enum. 2020-03-10 23:36:52 -04:00
Thomas Harte
0502e6be67 Starts working towards a registration-based model of reflective enums. 2020-03-10 22:32:55 -04:00
Thomas Harte
6a8c6f5a06 Switches to class storage. 2020-03-10 22:32:55 -04:00
Thomas Harte
5248475e73 Starts experimenting with declared reflection. 2020-03-10 22:32:55 -04:00
Thomas Harte
d6c6b9bdb8 Gives function overloading a try. 2020-03-10 22:32:55 -04:00
Thomas Harte
7bf04d5338 Adds a prototype reflective enum.
I need to make this scopeable before it is acceptable.
2020-03-10 22:32:55 -04:00
Thomas Harte
9668ec789a Merge pull request #769 from TomHarte/SDLKeyboardAgain
Return to old SDL behaviour if --logical-keyboard isn't specified.
2020-03-09 23:18:14 -04:00
Thomas Harte
ead32fb6b2 Return to old behaviour if --logical-keyboard isn't specified.
This is at least until I'm more confident in the keypress/text input merging. Also, switches to a vector for intermediate keypresses, to ensure order is retained even if timestamps are absent.
2020-03-09 23:10:39 -04:00
Thomas Harte
45a391d69e Increases quantity of annotations.
I'm now at almost 500 lines, and I haven't even really written anything yet.
2019-12-18 22:57:12 -05:00
Thomas Harte
15bc18b64f Merge branch 'master' into FurtherSCC 2019-12-18 22:17:10 -05:00
Thomas Harte
fb0343cafb Merge branch 'FurtherSCC' of github.com:TomHarte/CLK into FurtherSCC 2019-12-17 22:39:49 -05:00
Thomas Harte
7d9bedf7de Merge branch 'master' into FurtherSCC 2019-12-17 22:39:39 -05:00
Thomas Harte
6c99048211 Copies in a few more hardware notes. 2019-10-02 19:18:09 -04:00
Thomas Harte
2638a901d9 Improves documentation of existing degree of implementation. 2019-09-30 21:36:37 -04:00
Thomas Harte
71083fd0f7 Improves documentation of existing degree of implementation. 2019-09-29 22:08:16 -04:00
381 changed files with 12654 additions and 3785 deletions

View File

@@ -1,22 +1,16 @@
name: SDL/Ubuntu
on:
push:
branches:
- master
pull_request:
branches:
- master
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
- name: Make
run: cd OSBindings/SDL; scons
working-directory: OSBindings/SDL
run: scons -j$(nproc --all)

View File

@@ -24,13 +24,13 @@ namespace Activity {
class Observer {
public:
/// Announces to the receiver that there is an LED of name @c name.
virtual void register_led(const std::string &name) {}
virtual void register_led([[maybe_unused]] const std::string &name) {}
/// Announces to the receiver that there is a drive of name @c name.
virtual void register_drive(const std::string &name) {}
virtual void register_drive([[maybe_unused]] const std::string &name) {}
/// Informs the receiver of the new state of the LED with name @c name.
virtual void set_led_status(const std::string &name, bool lit) {}
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
enum class DriveEvent {
StepNormal,
@@ -39,10 +39,10 @@ class Observer {
};
/// Informs the receiver that the named event just occurred for the drive with name @c name.
virtual void announce_drive_event(const std::string &name, DriveEvent event) {}
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
/// Informs the receiver of the motor-on status of the drive with name @c name.
virtual void set_drive_motor_status(const std::string &name, bool is_on) {}
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
};
}

View File

@@ -11,20 +11,20 @@
using namespace Analyser::Dynamic;
float ConfidenceCounter::get_confidence() {
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
return float(hits_) / float(hits_ + misses_);
}
void ConfidenceCounter::add_hit() {
hits_++;
++hits_;
}
void ConfidenceCounter::add_miss() {
misses_++;
++misses_;
}
void ConfidenceCounter::add_equivocal() {
if(hits_ > misses_) {
hits_++;
misses_++;
++hits_;
++misses_;
}
}

View File

@@ -35,8 +35,8 @@ class ConfidenceSummary: public ConfidenceSource {
float get_confidence() final;
private:
std::vector<ConfidenceSource *> sources_;
std::vector<float> weights_;
const std::vector<ConfidenceSource *> sources_;
const std::vector<float> weights_;
float weight_sum_;
};

View File

@@ -1,94 +0,0 @@
//
// MultiCRTMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiCRTMachine.hpp"
#include <condition_variable>
#include <mutex>
using namespace Analyser::Dynamic;
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
speaker_ = MultiSpeaker::create(machines);
}
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
// to a separate queue and this queue will block until all are done.
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines = machines_.size();
for(std::size_t index = 0; index < machines_.size(); ++index) {
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
if(crt_machine) function(crt_machine);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines--;
condition.notify_all();
});
}
}
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
}
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *const crt_machine = machine->crt_machine();
if(crt_machine) function(crt_machine);
}
}
void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus MultiCRTMachine::get_scan_status() const {
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->get_scan_status();
return Outputs::Display::ScanStatus();
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}
void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([duration](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->multi_crt_did_run_machines();
}
void MultiCRTMachine::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([](::CRTMachine::Machine *machine) {
machine->set_scan_target(nullptr);
});
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target_);
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}

View File

@@ -12,6 +12,97 @@
using namespace Analyser::Dynamic;
namespace {
class MultiStruct: public Reflection::Struct {
public:
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
for(auto device: devices) {
options_.emplace_back(device->get_options());
}
}
void apply() {
auto options = options_.begin();
for(auto device: devices_) {
device->set_options(*options);
++options;
}
}
std::vector<std::string> all_keys() const final {
std::set<std::string> keys;
for(auto &options: options_) {
const auto new_keys = options->all_keys();
keys.insert(new_keys.begin(), new_keys.end());
}
return std::vector<std::string>(keys.begin(), keys.end());
}
std::vector<std::string> values_for(const std::string &name) const final {
std::set<std::string> values;
for(auto &options: options_) {
const auto new_values = options->values_for(name);
values.insert(new_values.begin(), new_values.end());
}
return std::vector<std::string>(values.begin(), values.end());
}
const std::type_info *type_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return info;
}
return nullptr;
}
size_t count_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return options->count_of(name);
}
return 0;
}
const void *get(const std::string &name) const final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void *get(const std::string &name) final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void set(const std::string &name, const void *value, size_t offset) final {
const auto safe_type = type_of(name);
if(!safe_type) return;
// Set this property only where the child's type is the same as that
// which was returned from here for type_of.
for(auto &options: options_) {
const auto type = options->type_of(name);
if(!type) continue;
if(*type == *safe_type) {
options->set(name, value, offset);
}
}
}
private:
const std::vector<Configurable::Device *> &devices_;
std::vector<std::unique_ptr<Reflection::Struct>> options_;
};
}
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
Configurable::Device *device = machine->configurable_device();
@@ -19,46 +110,11 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine
}
}
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
std::vector<std::unique_ptr<Configurable::Option>> options;
// Produce the list of unique options.
for(const auto &device : devices_) {
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
for(auto &option : device_options) {
if(std::find(options.begin(), options.end(), option) == options.end()) {
options.push_back(std::move(option));
}
}
}
return options;
void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<MultiStruct *>(str.get());
options->apply();
}
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
for(const auto &device : devices_) {
device->set_selections(selection_by_option);
}
}
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_accurate_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
}
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
return std::make_unique<MultiStruct>(devices_);
}

View File

@@ -10,6 +10,7 @@
#define MultiConfigurable_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Configurable/Configurable.hpp"
#include <memory>
#include <vector>
@@ -28,10 +29,8 @@ class MultiConfigurable: public Configurable::Device {
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard Configurable::Device interface; see there for documentation.
std::vector<std::unique_ptr<Configurable::Option>> get_options() final;
void set_selections(const Configurable::SelectionSet &selection_by_option) final;
Configurable::SelectionSet get_accurate_selections() final;
Configurable::SelectionSet get_user_friendly_selections() final;
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
std::unique_ptr<Reflection::Struct> get_options() final;
private:
std::vector<Configurable::Device *> devices_;

View File

@@ -16,7 +16,7 @@ namespace {
class MultiJoystick: public Inputs::Joystick {
public:
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
}
}
std::vector<Input> &get_inputs() final {
const std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
@@ -67,9 +67,9 @@ class MultiJoystick: public Inputs::Joystick {
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::size_t total_joysticks = 0;
std::vector<JoystickMachine::Machine *> joystick_machines;
std::vector<MachineTypes::JoystickMachine *> joystick_machines;
for(const auto &machine: machines) {
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
auto joystick_machine = machine->joystick_machine();
if(joystick_machine) {
joystick_machines.push_back(joystick_machine);
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());

View File

@@ -23,7 +23,7 @@ namespace Dynamic {
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiJoystickMachine: public JoystickMachine::Machine {
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
public:
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);

View File

@@ -13,7 +13,7 @@ using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
auto keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
}
}
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
bool MultiKeyboardMachine::can_type(char c) {
bool MultiKeyboardMachine::can_type(char c) const {
bool can_type = true;
for(const auto &machine: machines_) {
can_type &= machine->can_type(c);
@@ -48,7 +48,7 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
: machines_(machines) {
for(const auto &machine: machines_) {
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
@@ -70,10 +70,10 @@ void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
}
}
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() const {
return observed_keys_;
}
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() const {
return is_exclusive_;
}

View File

@@ -24,21 +24,21 @@ namespace Dynamic {
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
private:
std::vector<::KeyboardMachine::Machine *> machines_;
std::vector<MachineTypes::KeyboardMachine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
bool set_key_pressed(Key key, char value, bool is_pressed) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() final;
bool is_exclusive() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
private:
const std::vector<::KeyboardMachine::Machine *> &machines_;
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
@@ -51,7 +51,7 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
void clear_all_keys() final;
void set_key_state(uint16_t key, bool is_pressed) final;
void type_string(const std::string &) final;
bool can_type(char c) final;
bool can_type(char c) const final;
Inputs::Keyboard &get_keyboard() final;
};

View File

@@ -12,7 +12,7 @@ using namespace Analyser::Dynamic;
MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
MediaTarget::Machine *media_target = machine->media_target();
auto media_target = machine->media_target();
if(media_target) targets_.push_back(media_target);
}
}

View File

@@ -24,7 +24,7 @@ namespace Dynamic {
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiMediaTarget: public MediaTarget::Machine {
struct MultiMediaTarget: public MachineTypes::MediaTarget {
public:
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
@@ -32,7 +32,7 @@ struct MultiMediaTarget: public MediaTarget::Machine {
bool insert_media(const Analyser::Static::Media &media) final;
private:
std::vector<MediaTarget::Machine *> targets_;
std::vector<MachineTypes::MediaTarget *> targets_;
};
}

View File

@@ -0,0 +1,105 @@
//
// MultiProducer.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiProducer.hpp"
#include <condition_variable>
#include <mutex>
using namespace Analyser::Dynamic;
// MARK: - MultiInterface
template <typename MachineType>
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) {
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
// to a separate queue and this queue will block until all are done.
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard machines_lock(machines_mutex_);
std::lock_guard lock(mutex);
outstanding_machines = machines_.size();
for(std::size_t index = 0; index < machines_.size(); ++index) {
const auto machine = ::Machine::get<MachineType>(*machines_[index].get());
queues_[index].enqueue([&mutex, &condition, machine, function, &outstanding_machines]() {
if(machine) function(machine);
std::lock_guard lock(mutex);
outstanding_machines--;
condition.notify_all();
});
}
}
std::unique_lock lock(mutex);
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
}
template <typename MachineType>
void MultiInterface<MachineType>::perform_serial(const std::function<void(MachineType *)> &function) {
std::lock_guard machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
const auto typed_machine = ::Machine::get<MachineType>(*machine.get());
if(typed_machine) function(typed_machine);
}
}
// MARK: - MultiScanProducer
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
std::lock_guard machines_lock(machines_mutex_);
const auto machine = machines_.front()->scan_producer();
if(machine) machine->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus MultiScanProducer::get_scan_status() const {
std::lock_guard machines_lock(machines_mutex_);
const auto machine = machines_.front()->scan_producer();
if(machine) return machine->get_scan_status();
return Outputs::Display::ScanStatus();
}
void MultiScanProducer::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([](MachineTypes::ScanProducer *machine) {
machine->set_scan_target(nullptr);
});
std::lock_guard machines_lock(machines_mutex_);
const auto machine = machines_.front()->scan_producer();
if(machine) machine->set_scan_target(scan_target_);
}
// MARK: - MultiAudioProducer
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
speaker_ = MultiSpeaker::create(machines);
}
Outputs::Speaker::Speaker *MultiAudioProducer::get_speaker() {
return speaker_;
}
void MultiAudioProducer::did_change_machine_order() {
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}
// MARK: - MultiTimedMachine
void MultiTimedMachine::run_for(Time::Seconds duration) {
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->did_run_machines(this);
}

View File

@@ -1,16 +1,16 @@
//
// MultiCRTMachine.hpp
// MultiProducer.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiCRTMachine_hpp
#define MultiCRTMachine_hpp
#ifndef MultiProducer_hpp
#define MultiProducer_hpp
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../../Machines/CRTMachine.hpp"
#include "../../../../Machines/MachineTypes.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "MultiSpeaker.hpp"
@@ -22,6 +22,91 @@
namespace Analyser {
namespace Dynamic {
template <typename MachineType> class MultiInterface {
public:
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
protected:
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(MachineType *)> &);
/*!
Performs a serial for operation across all machines, performing the supplied
function on each on the calling thread.
*/
void perform_serial(const std::function<void(MachineType *)> &);
protected:
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
private:
std::vector<Concurrency::AsyncTaskQueue> queues_;
};
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
public:
using MultiInterface::MultiInterface;
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void did_run_machines(MultiTimedMachine *) = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void run_for(Time::Seconds duration) final;
private:
void run_for(const Cycles) final {}
Delegate *delegate_ = nullptr;
};
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
public:
using MultiInterface::MultiInterface;
/*!
Informs the MultiScanProducer that the order of machines has changed; it
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
are necessary to bridge the gap between one machine and the next.
*/
void did_change_machine_order();
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
Outputs::Display::ScanStatus get_scan_status() const final;
private:
Outputs::Display::ScanTarget *scan_target_ = nullptr;
};
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
public:
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiAudio that the order of machines has changed; it
uses this as an opportunity to switch speaker delegates as appropriate.
*/
void did_change_machine_order();
Outputs::Speaker::Speaker *get_speaker() final;
private:
MultiSpeaker *speaker_ = nullptr;
};
/*!
Provides a class that multiplexes the CRT machine interface to multiple machines.
@@ -29,61 +114,9 @@ namespace Dynamic {
acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes.
*/
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
are necessary to bridge the gap between one machine and the next.
*/
void did_change_machine_order();
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void multi_crt_did_run_machines() = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
Outputs::Display::ScanStatus get_scan_status() const final;
Outputs::Speaker::Speaker *get_speaker() final;
void run_for(Time::Seconds duration) final;
private:
void run_for(const Cycles cycles) final {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
MultiSpeaker *speaker_ = nullptr;
Delegate *delegate_ = nullptr;
Outputs::Display::ScanTarget *scan_target_ = nullptr;
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
/*!
Performs a serial for operation across all machines, performing the supplied
function on each on the calling thread.
*/
void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
};
}
}
#endif /* MultiCRTMachine_hpp */
#endif /* MultiProducer_hpp */

View File

@@ -13,7 +13,7 @@ using namespace Analyser::Dynamic;
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::vector<Outputs::Speaker::Speaker *> speakers;
for(const auto &machine: machines) {
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
Outputs::Speaker::Speaker *speaker = machine->audio_producer()->get_speaker();
if(speaker) speakers.push_back(speaker);
}
if(speakers.empty()) return nullptr;
@@ -34,7 +34,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
}
return ideal / static_cast<float>(speakers_.size());
return ideal / float(speakers_.size());
}
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
@@ -54,6 +54,12 @@ bool MultiSpeaker::get_is_stereo() {
return false;
}
void MultiSpeaker::set_output_volume(float volume) {
for(const auto &speaker: speakers_) {
speaker->set_output_volume(volume);
}
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
}
@@ -61,7 +67,7 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
did_complete_samples(this, buffer, stereo_output_);
@@ -70,7 +76,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_change_input_clock(this);
@@ -78,8 +84,8 @@ void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
std::lock_guard lock_guard(front_speaker_mutex_);
front_speaker_ = machine->audio_producer()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);

View File

@@ -42,6 +42,7 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
bool get_is_stereo() override;
void set_output_volume(float) override;
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;

View File

@@ -16,74 +16,55 @@ using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
machines_(std::move(machines)),
configurable_(machines_),
crt_machine_(machines_, machines_mutex_),
joystick_machine_(machines),
timed_machine_(machines_, machines_mutex_),
scan_producer_(machines_, machines_mutex_),
audio_producer_(machines_, machines_mutex_),
joystick_machine_(machines_),
keyboard_machine_(machines_),
media_target_(machines_) {
crt_machine_.set_delegate(this);
timed_machine_.set_delegate(this);
}
Activity::Source *MultiMachine::activity_source() {
return nullptr; // TODO
}
MediaTarget::Machine *MultiMachine::media_target() {
if(has_picked_) {
return machines_.front()->media_target();
} else {
return &media_target_;
#define Provider(type, name, member) \
type *MultiMachine::name() { \
if(has_picked_) { \
return machines_.front()->name(); \
} else { \
return &member; \
} \
}
}
CRTMachine::Machine *MultiMachine::crt_machine() {
if(has_picked_) {
return machines_.front()->crt_machine();
} else {
return &crt_machine_;
}
}
Provider(Configurable::Device, configurable_device, configurable_)
Provider(MachineTypes::TimedMachine, timed_machine, timed_machine_)
Provider(MachineTypes::ScanProducer, scan_producer, scan_producer_)
Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
Provider(MachineTypes::MediaTarget, media_target, media_target_)
JoystickMachine::Machine *MultiMachine::joystick_machine() {
if(has_picked_) {
return machines_.front()->joystick_machine();
} else {
return &joystick_machine_;
}
}
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
if(has_picked_) {
return machines_.front()->keyboard_machine();
} else {
return &keyboard_machine_;
}
}
MouseMachine::Machine *MultiMachine::mouse_machine() {
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
// TODO.
return nullptr;
}
Configurable::Device *MultiMachine::configurable_device() {
if(has_picked_) {
return machines_.front()->configurable_device();
} else {
return &configurable_;
}
}
#undef Provider
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
return
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
(machines.front()->timed_machine()->get_confidence() > 0.9f) ||
(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
void MultiMachine::did_run_machines(MultiTimedMachine *) {
std::lock_guard machines_lock(machines_mutex_);
#ifndef NDEBUG
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
auto timed_machine = machine->timed_machine();
LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
}
LOGNBR(std::endl);
#endif
@@ -91,13 +72,14 @@ void MultiMachine::multi_crt_did_run_machines() {
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
auto lhs_timed = lhs->timed_machine();
auto rhs_timed = rhs->timed_machine();
return lhs_timed->get_confidence() > rhs_timed->get_confidence();
});
if(machines_.front().get() != front) {
crt_machine_.did_change_machine_order();
scan_producer_.did_change_machine_order();
audio_producer_.did_change_machine_order();
}
if(would_collapse(machines_)) {

View File

@@ -11,8 +11,9 @@
#include "../../../Machines/DynamicMachine.hpp"
#include "Implementation/MultiProducer.hpp"
#include "Implementation/MultiConfigurable.hpp"
#include "Implementation/MultiCRTMachine.hpp"
#include "Implementation/MultiProducer.hpp"
#include "Implementation/MultiJoystickMachine.hpp"
#include "Implementation/MultiKeyboardMachine.hpp"
#include "Implementation/MultiMediaTarget.hpp"
@@ -38,7 +39,7 @@ namespace Dynamic {
If confidence for any machine becomes disproportionately low compared to
the others in the set, that machine stops running.
*/
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
@@ -52,21 +53,25 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
Activity::Source *activity_source() final;
Configurable::Device *configurable_device() final;
CRTMachine::Machine *crt_machine() final;
JoystickMachine::Machine *joystick_machine() final;
MouseMachine::Machine *mouse_machine() final;
KeyboardMachine::Machine *keyboard_machine() final;
MediaTarget::Machine *media_target() final;
MachineTypes::TimedMachine *timed_machine() final;
MachineTypes::ScanProducer *scan_producer() final;
MachineTypes::AudioProducer *audio_producer() final;
MachineTypes::JoystickMachine *joystick_machine() final;
MachineTypes::KeyboardMachine *keyboard_machine() final;
MachineTypes::MouseMachine *mouse_machine() final;
MachineTypes::MediaTarget *media_target() final;
void *raw_pointer() final;
private:
void multi_crt_did_run_machines() final;
void did_run_machines(MultiTimedMachine *) final;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;
MultiConfigurable configurable_;
MultiCRTMachine crt_machine_;
MultiTimedMachine timed_machine_;
MultiScanProducer scan_producer_;
MultiAudioProducer audio_producer_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
MultiMediaTarget media_target_;

View File

@@ -21,8 +21,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(false, disk);
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr;
if(names->samples.empty() || details->samples.empty()) return nullptr;
@@ -48,18 +48,18 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
char name[10];
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
new_file.name = name;
new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = names->samples[0][file_offset + 7] & 0x80;
long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
new_file.data.reserve(static_cast<std::size_t>(data_length));
new_file.data.reserve(size_t(data_length));
if(start_sector < 2) continue;
while(data_length > 0) {
uint8_t sector = static_cast<uint8_t>(start_sector % 10);
uint8_t track = static_cast<uint8_t>(start_sector / 10);
uint8_t sector = uint8_t(start_sector % 10);
uint8_t track = uint8_t(start_sector / 10);
start_sector++;
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
@@ -84,7 +84,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) {
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
}

View File

@@ -29,7 +29,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
// is a copyright string present?
uint8_t copyright_offset = segment.data[7];
const uint8_t copyright_offset = segment.data[7];
if(
segment.data[copyright_offset] != 0x00 ||
segment.data[copyright_offset+1] != 0x28 ||
@@ -57,9 +57,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->machine = Machine::Electron;
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;
target->has_adfs = false;
@@ -84,8 +83,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN
std::size_t pointer = 0;
uint8_t *data = &files.front().data[0];
std::size_t data_size = files.front().data.size();
uint8_t *const data = &files.front().data[0];
const std::size_t data_size = files.front().data.size();
while(1) {
if(pointer >= data_size-1 || data[pointer] != 13) {
is_basic = false;

View File

@@ -41,24 +41,24 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
char name[11];
std::size_t name_ptr = 0;
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = (char)parser.get_next_byte(tape);
name[name_ptr] = char(parser.get_next_byte(tape));
if(!name[name_ptr]) break;
name_ptr++;
++name_ptr;
}
name[sizeof(name)-1] = '\0';
new_chunk->name = name;
// addresses
new_chunk->load_address = (uint32_t)parser.get_next_word(tape);
new_chunk->execution_address = (uint32_t)parser.get_next_word(tape);
new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
uint16_t calculated_header_crc = parser.get_crc();
uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
if(!new_chunk->header_crc_matched) return nullptr;
@@ -66,13 +66,13 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
} else {
new_chunk->data_crc_matched = true;

View File

@@ -9,6 +9,7 @@
#ifndef Analyser_Static_Acorn_Target_h
#define Analyser_Static_Acorn_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,11 +17,18 @@ namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_adfs = false;
bool has_dfs = false;
bool should_shift_restart = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_adfs);
DeclareField(has_dfs);
}
}
};
}

View File

@@ -179,10 +179,9 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
return false;
}
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
auto target = std::make_unique<Target>();
target->machine = Machine::AmstradCPC;
target->confidence = 0.5;
target->model = Target::Model::CPC6128;

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_AmstradCPC_Target_h
#define Analyser_Static_AmstradCPC_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,15 +18,17 @@ namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public ::Analyser::Static::Target {
enum class Model {
CPC464,
CPC664,
CPC6128
};
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
Model model = Model::CPC464;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::AmstradCPC) {
if(needs_declare()) {
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}

View File

@@ -9,9 +9,8 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->machine = Machine::AppleII;
target->media = media;
if(!target->media.disks.empty())

View File

@@ -9,27 +9,38 @@
#ifndef Target_h
#define Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
II,
IIplus,
IIe,
EnhancedIIe
};
enum class DiskController {
);
ReflectableEnum(DiskController,
None,
SixteenSector,
ThirteenSector
};
);
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
Target() : Analyser::Static::Target(Machine::AppleII) {
if(needs_declare()) {
DeclareField(model);
DeclareField(disk_controller);
AnnounceEnum(Model);
AnnounceEnum(DiskController);
}
}
};
}

View File

@@ -16,24 +16,22 @@ using namespace Analyser::Static::Atari2600;
using Target = Analyser::Static::Atari2600::Target;
static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
// If this is a 2kb cartridge then it's definitely either unpaged or a CommaVid.
const uint16_t entry_address = uint16_t(segment.data[0x7fc] | (segment.data[0x7fd] << 8)) & 0x1fff;
const uint16_t break_address = uint16_t(segment.data[0x7fe] | (segment.data[0x7ff] << 8)) & 0x1fff;
entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
// a CommaVid start address needs to be outside of its RAM
// A CommaVid start address needs to be outside of its RAM.
if(entry_address < 0x1800 || break_address < 0x1800) return;
std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
address &= 0x1fff;
return static_cast<std::size_t>(address - 0x1800);
return size_t(address - 0x1800);
};
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory
// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory.
bool has_wide_area_store = false;
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
@@ -45,17 +43,17 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
}
}
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
// Conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
// attempts to modify itself but it probably doesn't.
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
if(
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
@@ -65,7 +63,7 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
return;
}
// make an assumption that this is the Atari paging model
// Make an assumption that this is the Atari paging model.
target.paging_model = Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
@@ -90,8 +88,8 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Make an assumption that this is the Atari paging model.
target.paging_model = Target::PagingModel::Atari16k;
std::set<uint16_t> internal_accesses;
@@ -110,8 +108,8 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Make an assumption that this is a Tigervision if there is a write to 3F.
target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
@@ -123,17 +121,15 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
return;
}
uint16_t entry_address, break_address;
entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
return static_cast<std::size_t>(address & 0xfff);
if(!(address & 0x1000)) return size_t(-1);
return size_t(address & 0xfff);
};
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
switch(segment.data.size()) {
@@ -159,7 +155,7 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
break;
}
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// Check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.paging_model != Target::PagingModel::CBSRamPlus &&
@@ -174,17 +170,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
target.uses_superchip = has_superchip;
}
// check for a Tigervision or Tigervision-esque scheme
// Check for a Tigervision or Tigervision-esque scheme
if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
}
}
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// TODO: sanity checking; is this image really for an Atari 2600?
auto target = std::make_unique<Target>();
target->machine = Machine::Atari2600;
target->confidence = 0.5;
target->media.cartridges = media.cartridges;
target->paging_model = Target::PagingModel::None;

View File

@@ -34,6 +34,8 @@ struct Target: public ::Analyser::Static::Target {
// TODO: shouldn't these be properties of the cartridge?
PagingModel paging_model = PagingModel::None;
bool uses_superchip = false;
Target() : Analyser::Static::Target(Machine::Atari2600) {}
};
}

View File

@@ -9,16 +9,15 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Target;
auto *target = new Target;
target->machine = Analyser::Machine::AtariST;
using Target = Analyser::Static::AtariST::Target;
auto *const target = new Target();
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));

View File

@@ -9,11 +9,15 @@
#ifndef Analyser_Static_AtariST_Target_h
#define Analyser_Static_AtariST_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AtariST {
struct Target: public ::Analyser::Static::Target {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
Target() : Analyser::Static::Target(Machine::AtariST) {}
};
}

View File

@@ -22,7 +22,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
// the two bytes that will be first must be 0xaa and 0x55, either way around
auto *start = &segment.data[0];
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
if((data_size & size_t(~8191)) > 32768) {
start = &segment.data[segment.data.size() - 16384];
}
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
@@ -52,10 +52,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return coleco_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList targets;
auto target = std::make_unique<Target>();
target->machine = Machine::ColecoVision;
auto target = std::make_unique<Target>(Machine::ColecoVision);
target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty())

View File

@@ -38,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
int difference = static_cast<int>(track) - static_cast<int>(track_);
int difference = int(track) - int(track_);
track_ = track;
if(difference) {
@@ -71,7 +71,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
bit_count_++;
}
@@ -112,15 +112,15 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
std::shared_ptr<Sector> get_sector(uint8_t sector) {
uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
std::shared_ptr<Sector> first_sector = get_next_sector();
const std::shared_ptr<Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
const std::shared_ptr<Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
@@ -138,12 +138,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
// get sector details, skip if this looks malformed
uint8_t checksum = static_cast<uint8_t>(get_next_byte());
sector->sector = static_cast<uint8_t>(get_next_byte());
sector->track = static_cast<uint8_t>(get_next_byte());
uint8_t checksum = uint8_t(get_next_byte());
sector->sector = uint8_t(get_next_byte());
sector->track = uint8_t(get_next_byte());
uint8_t disk_id[2];
disk_id[0] = static_cast<uint8_t>(get_next_byte());
disk_id[1] = static_cast<uint8_t>(get_next_byte());
disk_id[0] = uint8_t(get_next_byte());
disk_id[1] = uint8_t(get_next_byte());
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
@@ -154,12 +154,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
checksum = 0;
for(std::size_t c = 0; c < 256; c++) {
sector->data[c] = static_cast<uint8_t>(get_next_byte());
sector->data[c] = uint8_t(get_next_byte());
checksum ^= sector->data[c];
}
if(checksum == get_next_byte()) {
uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
sector_cache_[sector_address] = sector;
return sector;
}
@@ -192,7 +192,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
}
// parse directory
std::size_t header_pointer = static_cast<std::size_t>(-32);
std::size_t header_pointer = size_t(-32);
while(header_pointer+32+31 < directory.size()) {
header_pointer += 32;
@@ -216,7 +216,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
}
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
bool is_first_sector = true;
@@ -227,7 +227,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
next_track = sector->data[0];
next_sector = sector->data[1];
if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8);
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
if(next_track)
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
else

View File

@@ -23,7 +23,7 @@ bool Analyser::Static::Commodore::File::is_basic() {
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)
while(1) {
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
if(size_t(line_address - starting_address) >= data.size() + 2) break;
uint16_t next_line_address = data[line_address - starting_address];
next_line_address |= data[line_address - starting_address + 1] << 8;
@@ -33,13 +33,13 @@ bool Analyser::Static::Commodore::File::is_basic() {
}
if(next_line_address < line_address + 5) break;
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break;
if(size_t(line_address - starting_address) >= data.size() + 5) break;
uint16_t next_line_number = data[line_address - starting_address + 2];
next_line_number |= data[line_address - starting_address + 3] << 8;
if(next_line_number <= line_number) break;
line_number = static_cast<uint16_t>(next_line_number);
line_number = uint16_t(next_line_number);
line_address = next_line_address;
}

View File

@@ -42,7 +42,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return vic20_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
TargetList destination;
auto target = std::make_unique<Target>();
@@ -94,6 +94,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
switch(files.front().starting_address) {
default:
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
[[fallthrough]];
case 0x1001:
memory_model = Target::MemoryModel::Unexpanded;
break;

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,20 +18,20 @@ namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public ::Analyser::Static::Target {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Region {
ReflectableEnum(Region,
American,
Danish,
Japanese,
European,
Swedish
};
);
/// Maps from a named memory model to a bank enabled/disabled set.
void set_memory_model(MemoryModel memory_model) {
@@ -54,6 +56,19 @@ struct Target: public ::Analyser::Static::Target {
Region region = Region::European;
bool has_c1540 = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Vic20) {
if(needs_declare()) {
DeclareField(enabled_ram.bank0);
DeclareField(enabled_ram.bank1);
DeclareField(enabled_ram.bank2);
DeclareField(enabled_ram.bank3);
DeclareField(enabled_ram.bank5);
DeclareField(region);
DeclareField(has_c1540);
AnnounceEnum(Region);
}
}
};
}

View File

@@ -26,12 +26,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
Instruction instruction;
instruction.address = address;
address++;
++address;
// get operation
uint8_t operation = memory[local_address];
// Get operation.
const uint8_t operation = memory[local_address];
// decode addressing mode
// Decode addressing mode.
switch(operation&0x1f) {
case 0x00:
if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
@@ -74,7 +74,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
break;
}
// decode operation
// Decode operation.
#define RM_INSTRUCTION(base, op) \
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
instruction.operation = op; \
@@ -222,14 +222,14 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
#undef M_INSTRUCTION
#undef IM_INSTRUCTION
// get operand
// Get operand.
switch(instruction.addressing_mode) {
// zero-byte operands
// Zero-byte operands.
case Instruction::Implied:
instruction.operand = 0;
break;
// one-byte operands
// One-byte operands.
case Instruction::Immediate:
case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
@@ -242,7 +242,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
break;
// two-byte operands
// Two-byte operands.
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
case Instruction::Indirect: {
std::size_t low_operand_address = address_mapper(address);
@@ -250,18 +250,18 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
address += 2;
instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
instruction.operand = memory[low_operand_address] | uint16_t(memory[high_operand_address] << 8);
}
break;
}
// store the instruction away
// Store the instruction.
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// TODO: something wider-ranging than this
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
std::size_t mapped_address = address_mapper(instruction.operand);
bool is_external = mapped_address >= memory.size();
const size_t mapped_address = address_mapper(instruction.operand);
const bool is_external = mapped_address >= memory.size();
switch(instruction.operation) {
default: break;
@@ -290,7 +290,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
}
// decide on overall flow control
// Decide on overall flow control.
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR) {
@@ -302,7 +302,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
return;
}
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
disassembly.remaining_entry_points.push_back(destination);
}
}

View File

@@ -1,9 +0,0 @@
//
// AddressMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#include "AddressMapper.hpp"

View File

@@ -21,7 +21,7 @@ namespace Disassembler {
*/
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
return [start_address](T argument) {
return static_cast<std::size_t>(argument - start_address);
return size_t(argument - start_address);
};
}

View File

@@ -33,7 +33,7 @@ class Accessor {
uint16_t word() {
uint8_t low = byte();
uint8_t high = byte();
return static_cast<uint16_t>(low | (high << 8));
return uint16_t(low | (high << 8));
}
bool overrun() {
@@ -562,7 +562,7 @@ struct Z80Disassembler {
int access_type =
((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
uint16_t address = static_cast<uint16_t>(instruction.operand);
uint16_t address = uint16_t(instruction.operand);
bool is_internal = address_mapper(address) < memory.size();
switch(access_type) {
default: break;
@@ -594,7 +594,7 @@ struct Z80Disassembler {
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
instruction.operation == Instruction::Operation::RST) {
disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand));
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.

View File

@@ -20,8 +20,7 @@ namespace {
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
using Target = Analyser::Static::AppleII::Target;
auto *target = new Target;
target->machine = Analyser::Machine::AppleII;
auto *const target = new Target;
if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
target->disk_controller = Target::DiskController::ThirteenSector;
@@ -32,10 +31,9 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector
return target;
}
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) {
using Target = Analyser::Static::Oric::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Oric;
auto *const target = new Target;
target->rom = Target::ROM::Pravetz;
target->disk_interface = Target::DiskInterface::Pravetz;
target->loading_command = "CALL 800\n";
@@ -44,13 +42,13 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
}
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// Grab track 0, sector 0: the boot sector.
auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
const auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
@@ -77,7 +75,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
// If the boot sector looks like it's intended for the Oric, create an Oric.
// Otherwise go with the Apple II.
auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
bool did_read_shift_register = false;
bool is_oric = false;

View File

@@ -27,7 +27,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff;
std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(start_address, truncated_data);
} else {
@@ -35,7 +35,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
}
auto target = std::make_unique<Analyser::Static::MSX::Target>();
target->machine = Analyser::Machine::MSX;
target->confidence = confidence;
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
@@ -97,7 +96,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// Reject cartridge if the ROM header wasn't found.
if(!found_start) continue;
uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
// TODO: check for a rational init address?
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
@@ -147,7 +146,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
// const uint16_t address = uint16_t(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
@@ -208,13 +207,13 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
if( instruction_pair.second.operation == Instruction::Operation::LD &&
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
instruction_pair.second.source == Instruction::Location::A) {
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
address_counts[uint16_t(instruction_pair.second.operand)]++;
}
}
// Weight confidences by number of observed hits.
float total_hits =
static_cast<float>(
float(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
@@ -226,42 +225,42 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
static_cast<float>( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
float( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
static_cast<float>( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
float( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
static_cast<float>( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
float( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
static_cast<float>( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
float( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}
return targets;
}
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
// Append targets for any cartridges that look correct.
@@ -295,7 +294,6 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
target->has_disk_drive = !media.disks.empty();
if(!target->media.empty()) {
target->machine = Machine::MSX;
target->confidence = 0.5;
destination.push_back(std::move(target));
}

View File

@@ -44,7 +44,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
for(std::size_t c = 0; c < sizeof(header); ++c) {
int next_byte = Parser::get_byte(*file_speed, tape_player);
if(next_byte == -1) break;
header[c] = static_cast<uint8_t>(next_byte);
header[c] = uint8_t(next_byte);
}
bool bytes_are_same = true;
@@ -67,7 +67,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
// Read file name.
char name[7];
for(std::size_t c = 1; c < 6; ++c)
name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
name[c] = char(Parser::get_byte(*file_speed, tape_player));
name[6] = '\0';
file.name = name;
@@ -82,7 +82,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
contains_end_of_file |= (byte == 0x1a);
file.data.push_back(static_cast<uint8_t>(byte));
file.data.push_back(uint8_t(byte));
}
if(c != -1) break;
if(contains_end_of_file) {
@@ -105,13 +105,13 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
for(c = 0; c < sizeof(locations); ++c) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
locations[c] = static_cast<uint8_t>(byte);
locations[c] = uint8_t(byte);
}
if(c != sizeof(locations)) continue;
file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
file.starting_address = uint16_t(locations[0] | (locations[1] << 8));
end_address = uint16_t(locations[2] | (locations[3] << 8));
file.entry_address = uint16_t(locations[4] | (locations[5] << 8));
if(end_address < file.starting_address) continue;
@@ -119,7 +119,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
while(length--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) continue;
file.data.push_back(static_cast<uint8_t>(byte));
file.data.push_back(uint8_t(byte));
}
files.push_back(std::move(file));
@@ -135,10 +135,10 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
file.data.push_back(uint8_t(next_address_buffer[0]));
file.data.push_back(uint8_t(next_address_buffer[1]));
uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
uint16_t next_address = uint16_t(next_address_buffer[0] | (next_address_buffer[1] << 8));
if(!next_address) {
files.push_back(std::move(file));
break;
@@ -155,7 +155,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
found_error = true;
break;
}
file.data.push_back(static_cast<uint8_t>(byte));
file.data.push_back(uint8_t(byte));
}
if(found_error) break;
}

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_MSX_Target_h
#define Analyser_Static_MSX_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,15 +18,24 @@ namespace Analyser {
namespace Static {
namespace MSX {
struct Target: public ::Analyser::Static::Target {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_disk_drive = false;
std::string loading_command;
enum class Region {
ReflectableEnum(Region,
Japan,
USA,
Europe
} region = Region::USA;
);
Region region = Region::USA;
Target(): Analyser::Static::Target(Machine::MSX) {
if(needs_declare()) {
DeclareField(has_disk_drive);
DeclareField(region);
AnnounceEnum(Region);
}
}
};
}

View File

@@ -9,7 +9,7 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
@@ -17,8 +17,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Macintosh::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Macintosh;
auto *const target = new Target;
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));

View File

@@ -9,19 +9,25 @@
#ifndef Analyser_Static_Macintosh_Target_h
#define Analyser_Static_Macintosh_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public ::Analyser::Static::Target {
enum class Model {
Mac128k,
Mac512k,
Mac512ke,
MacPlus
};
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
Model model = Model::MacPlus;
Target() : Analyser::Static::Target(Machine::Macintosh) {
// Boilerplate for declaring fields and potential values.
if(needs_declare()) {
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}

View File

@@ -145,9 +145,8 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->machine = Machine::Oric;
target->confidence = 0.5;
int basic10_votes = 0;

View File

@@ -49,10 +49,10 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
}
// read end and start addresses
new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
// skip an empty byte
parser.get_next_byte(tape, is_fast);
@@ -61,7 +61,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
char file_name[17];
int name_pos = 0;
while(name_pos < 16) {
file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
if(!file_name[name_pos]) break;
name_pos++;
}
@@ -72,7 +72,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
new_file.data.reserve(body_length);
for(std::size_t c = 0; c < body_length; c++) {
new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
}
// only one validation check: was there enough tape?

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,25 +18,34 @@ namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public ::Analyser::Static::Target {
enum class ROM {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ROM,
BASIC10,
BASIC11,
Pravetz
};
);
enum class DiskInterface {
ReflectableEnum(DiskInterface,
None,
Microdisc,
Pravetz,
Jasmin,
BD500,
None
};
BD500
);
ROM rom = ROM::BASIC11;
DiskInterface disk_interface = DiskInterface::None;
std::string loading_command;
bool should_start_jasmin = false;
Target(): Analyser::Static::Target(Machine::Oric) {
if(needs_declare()) {
DeclareField(rom);
DeclareField(disk_interface);
AnnounceEnum(ROM);
AnnounceEnum(DiskInterface);
}
}
};
}

View File

@@ -13,15 +13,13 @@
#include <algorithm>
#include <cstring>
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
if(media.cartridges.empty())
return {};
TargetList targets;
auto target = std::make_unique<Target>();
target->machine = Machine::MasterSystem;
// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
target->model = Target::Model::SG1000;

View File

@@ -9,23 +9,27 @@
#ifndef Analyser_Static_Sega_Target_h
#define Analyser_Static_Sega_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public ::Analyser::Static::Target {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class Model {
SG1000,
MasterSystem,
MasterSystem2,
};
enum class Region {
ReflectableEnum(Region,
Japan,
USA,
Europe,
Brazil
};
);
enum class PagingScheme {
Sega,
@@ -35,6 +39,13 @@ struct Target: public ::Analyser::Static::Target {
Model model = Model::MasterSystem;
Region region = Region::Japan;
PagingScheme paging_scheme = PagingScheme::Sega;
Target() : Analyser::Static::Target(Machine::MasterSystem) {
if(needs_declare()) {
DeclareField(region);
AnnounceEnum(Region);
}
}
};
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem

View File

@@ -35,6 +35,16 @@ struct Media {
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
}
Media &operator +=(const Media &rhs) {
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
append(disks);
append(tapes);
append(cartridges);
append(mass_storage_devices);
#undef append
return *this;
}
};
/*!
@@ -42,11 +52,12 @@ struct Media {
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() {}
Machine machine;
Media media;
float confidence;
float confidence = 0.0f;
};
typedef std::vector<std::unique_ptr<Target>> TargetList;

View File

@@ -28,13 +28,13 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
return files;
}
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
TargetList destination;
if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset();
if(!files.empty()) {
Target *target = new Target;
Target *const target = new Target;
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
target->machine = Machine::ZX8081;

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,17 +18,26 @@ namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemoryModel,
Unexpanded,
SixteenKB,
SixtyFourKB
};
);
MemoryModel memory_model = MemoryModel::Unexpanded;
bool is_ZX81 = false;
bool ZX80_uses_ZX81_ROM = false;
std::string loading_command;
Target(): Analyser::Static::Target(Machine::ZX8081) {
if(needs_declare()) {
DeclareField(memory_model);
DeclareField(is_ZX81);
DeclareField(ZX80_uses_ZX81_ROM);
AnnounceEnum(MemoryModel);
}
}
};
}

View File

@@ -176,7 +176,6 @@ class Cycles: public WrappedInt<Cycles> {
public:
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
private:
friend WrappedInt;
@@ -198,7 +197,6 @@ class HalfCycles: public WrappedInt<HalfCycles> {
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
forceinline constexpr Cycles cycles() const {

View File

@@ -67,7 +67,7 @@ class Source {
}
/// @returns the current preferred clocking strategy.
virtual Preference preferred_clocking() = 0;
virtual Preference preferred_clocking() const = 0;
private:
Observer *observer_ = nullptr;

View File

@@ -49,7 +49,7 @@ template <typename TimeUnit> class DeferredQueue {
@returns The amount of time until the next enqueued action will occur,
or TimeUnit(-1) if the queue is empty.
*/
TimeUnit time_until_next_action() {
TimeUnit time_until_next_action() const {
if(pending_actions_.empty()) return TimeUnit(-1);
return pending_actions_.front().delay;
}
@@ -95,7 +95,7 @@ template <typename TimeUnit> class DeferredQueue {
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Runs for @c length units of time.

View File

@@ -0,0 +1,155 @@
//
// VSyncPredictor.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef VSyncPredictor_hpp
#define VSyncPredictor_hpp
#include "TimeTypes.hpp"
#include <cassert>
#include <cmath>
#include <cstdio>
namespace Time {
/*!
For platforms that provide no avenue into vsync tracking other than block-until-sync,
this class tracks: (i) how long frame draw takes; (ii) the apparent frame period; and
(iii) optionally, timer jitter; in order to suggest when you should next start drawing.
*/
class VSyncPredictor {
public:
/*!
Announces to the predictor that the work of producing an output frame has begun.
*/
void begin_redraw() {
redraw_begin_time_ = nanos_now();
}
/*!
Announces to the predictor that the work of producing an output frame has ended;
the predictor will use the amount of time between each begin/end pair to modify
its expectations as to how long it takes to draw a frame.
*/
void end_redraw() {
redraw_period_.post(nanos_now() - redraw_begin_time_);
}
/*!
Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
machine calls retrace is now. The predictor uses these notifications to estimate output
frame rate.
*/
void announce_vsync() {
const auto now = nanos_now();
if(last_vsync_) {
last_vsync_ += frame_duration_;
vsync_jitter_.post(last_vsync_ - now);
last_vsync_ = (last_vsync_ + now) >> 1;
} else {
last_vsync_ = now;
}
}
/*!
Sets the frame rate for the target display.
*/
void set_frame_rate(float rate) {
frame_duration_ = Nanos(1'000'000'000.0f / rate);
}
/*!
@returns The time this class currently believes a whole frame occupies.
*/
Time::Nanos frame_duration() {
return frame_duration_;
}
/*!
Adds a record of how much jitter was experienced in scheduling; these values will be
factored into the @c suggested_draw_time if supplied.
A positive number means the timer occurred late. A negative number means it occurred early.
*/
void add_timer_jitter(Time::Nanos jitter) {
timer_jitter_.post(jitter);
}
/*!
Announces to the vsync predictor that output is now paused. This ends frame period
calculations until the next announce_vsync() restarts frame-length counting.
*/
void pause() {
last_vsync_ = 0;
}
/*!
@return The time at which redrawing should begin, given the predicted frame period, how
long it appears to take to draw a frame and how much jitter there is in scheduling
(if those figures are being supplied).
*/
Nanos suggested_draw_time() {
const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean();
const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance();
// Permit three standard deviations from the mean, to cover 99.9% of cases.
const auto period = mean + Nanos(3.0f * sqrt(float(variance)));
return last_vsync_ + frame_duration_ - period;
}
private:
class VarianceCollector {
public:
VarianceCollector(Time::Nanos default_value) {
sum_ = default_value * 128;
for(int c = 0; c < 128; ++c) {
history_[c] = default_value;
}
}
void post(Time::Nanos value) {
sum_ -= history_[write_pointer_];
sum_ += value;
history_[write_pointer_] = value;
write_pointer_ = (write_pointer_ + 1) & 127;
}
Time::Nanos mean() {
return sum_ / 128;
}
Time::Nanos variance() {
// I haven't yet come up with a better solution that calculating this
// in whole every time, given the way that the mean mutates.
Time::Nanos variance = 0;
for(int c = 0; c < 128; ++c) {
const auto difference = ((history_[c] * 128) - sum_) / 128;
variance += (difference * difference);
}
return variance / 128;
}
private:
Time::Nanos sum_;
Time::Nanos history_[128];
size_t write_pointer_ = 0;
};
Nanos redraw_begin_time_ = 0;
Nanos last_vsync_ = 0;
Nanos frame_duration_ = 1'000'000'000 / 60;
VarianceCollector vsync_jitter_{0};
VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess.
VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner.
};
}
#endif /* VSyncPredictor_hpp */

View File

@@ -498,7 +498,7 @@ void WD1770::posit_event(int new_event_type) {
if(get_crc_generator().get_value()) {
LOG("CRC error; terminating");
update_status([this] (Status &status) {
update_status([] (Status &status) {
status.crc_error = true;
});
goto wait_for_command;
@@ -816,19 +816,19 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
if(status_.busy != old_status.busy) update_clocking_observer();
}
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_load_request(bool) {}
void WD1770::set_motor_on(bool) {}
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(int(Event1770::HeadLoad));
}
bool WD1770::get_head_loaded() {
bool WD1770::get_head_loaded() const {
return head_is_loaded_;
}
ClockingHint::Preference WD1770::preferred_clocking() {
ClockingHint::Preference WD1770::preferred_clocking() const {
if(status_.busy) return ClockingHint::Preference::RealTime;
return Storage::Disk::MFMController::preferred_clocking();
}

View File

@@ -31,6 +31,7 @@ class WD1770: public Storage::Disk::MFMController {
@param p The type of controller to emulate.
*/
WD1770(Personality p);
virtual ~WD1770() {}
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
using Storage::Disk::MFMController::set_is_double_density;
@@ -62,18 +63,18 @@ class WD1770: public Storage::Disk::MFMController {
};
/// @returns The current value of the IRQ line output.
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
inline bool get_interrupt_request_line() const { return status_.interrupt_request; }
/// @returns The current value of the DRQ line output.
inline bool get_data_request_line() { return status_.data_request; }
inline bool get_data_request_line() const { return status_.data_request; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
ClockingHint::Preference preferred_clocking() final;
ClockingHint::Preference preferred_clocking() const final;
protected:
virtual void set_head_load_request(bool head_load);
@@ -81,12 +82,12 @@ class WD1770: public Storage::Disk::MFMController {
void set_head_loaded(bool head_loaded);
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded();
bool get_head_loaded() const;
private:
Personality personality_;
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
inline bool has_head_load_line() { return (personality_ == P1793 ); }
const Personality personality_;
bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); }
bool has_head_load_line() const { return (personality_ == P1793 ); }
struct Status {
bool write_protect = false;

View File

@@ -18,9 +18,14 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
clock_rate_(clock_rate) {
device_id_ = bus_.add_device();
bus_.add_observer(this);
// TODO: use clock rate and expected phase. This implementation currently
// provides only CPU-driven polling behaviour.
(void)clock_rate_;
(void)expected_phase_;
}
void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
void NCR5380::write(int address, uint8_t value, bool) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
@@ -128,7 +133,7 @@ void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
}
}
uint8_t NCR5380::read(int address, bool dma_acknowledge) {
uint8_t NCR5380::read(int address, bool) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
@@ -258,6 +263,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
case ExecutionState::WaitingForBusy:
if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
state_ = ExecutionState::WatchingBusy;
[[fallthrough]];
case ExecutionState::WatchingBusy:
if(!(new_state & SCSI::Line::Busy)) {

View File

@@ -37,22 +37,22 @@ enum Line {
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input(Port port) { return 0xff; }
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output(Port port, Line line, bool value) {}
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status(bool status) {}
void set_interrupt_status([[maybe_unused]] bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for(HalfCycles duration) {}
void run_for([[maybe_unused]] HalfCycles duration) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
void flush() {}
};
/*!
@@ -112,7 +112,7 @@ template <class T> class MOS6522: public MOS6522Storage {
void run_for(const Cycles cycles);
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
bool get_interrupt_line();
bool get_interrupt_line() const;
/// Updates the port handler to the current time and then requests that it flush.
void flush();

View File

@@ -69,7 +69,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
@@ -82,7 +82,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
@@ -281,11 +281,11 @@ template <typename T> void MOS6522<T>::do_phase2() {
registers_.timer[1] --;
if(registers_.next_timer[0] >= 0) {
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
registers_.timer[0] = uint16_t(registers_.next_timer[0]);
registers_.next_timer[0] = -1;
}
if(registers_.next_timer[1] >= 0) {
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
registers_.timer[1] = uint16_t(registers_.next_timer[1]);
registers_.next_timer[1] = -1;
}
@@ -383,9 +383,9 @@ template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
}
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
template <typename T> bool MOS6522<T>::get_interrupt_line() {
template <typename T> bool MOS6522<T>::get_interrupt_line() const {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
return interrupt_status;
}
template <typename T> void MOS6522<T>::evaluate_cb2_output() {

View File

@@ -14,6 +14,6 @@ void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
void IRQDelegatePortHandler::set_interrupt_status(bool) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}

View File

@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
timer_.value = (unsigned(value) << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
// Timer and interrupt control
case 0x04: case 0x06: {
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
uint8_t value = uint8_t(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral());
unsigned int number_of_cycles = unsigned(cycles.as_integral());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {
@@ -122,7 +122,7 @@ template <class T> class MOS6532 {
}
MOS6532() {
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
timer_.value = unsigned((rand() & 0xff) << 10);
}
inline void set_port_did_change(int port) {
@@ -142,7 +142,7 @@ template <class T> class MOS6532 {
}
}
inline bool get_inerrupt_line() {
inline bool get_inerrupt_line() const {
return interrupt_line_;
}
@@ -173,9 +173,11 @@ template <class T> class MOS6532 {
bool interrupt_line_ = false;
// expected to be overridden
uint8_t get_port_input(int port) { return 0xff; }
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
void set_irq_line(bool new_value) {}
void set_port_output([[maybe_unused]] int port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t output_mask) {}
uint8_t get_port_input([[maybe_unused]] int port) {
return 0xff;
}
void set_irq_line(bool) {}
inline void evaluate_interrupts() {
interrupt_line_ =

View File

@@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = {
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
@@ -114,7 +114,7 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
// this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff?
target[c] = static_cast<int16_t>(
target[c] = int16_t(
(shift_registers_[0]&1) +
(shift_registers_[1]&1) +
(shift_registers_[2]&1) +
@@ -133,7 +133,7 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
}
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = static_cast<int16_t>(range / 64);
range_multiplier_ = int16_t(range / 64);
}
#undef shift

View File

@@ -43,7 +43,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
};
struct BusHandler {
void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
void perform_read([[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *pixel_data, [[maybe_unused]] uint8_t *colour_data) {
*pixel_data = 0xff;
*colour_data = 0xff;
}
@@ -81,13 +81,14 @@ template <class BusHandler> class MOS6560 {
}
void set_clock_rate(double clock_rate) {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
speaker_.set_input_rate(float(clock_rate / 4.0));
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
Outputs::Display::DisplayType get_display_type() const { return crt_.get_display_type(); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
@@ -234,7 +235,7 @@ template <class BusHandler> class MOS6560 {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
} else {
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_);
video_matrix_address_counter_++;
if(
(current_character_row_ == 15) ||
@@ -370,7 +371,7 @@ template <class BusHandler> class MOS6560 {
case 0x2:
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
break;
case 0x3:
@@ -379,8 +380,8 @@ template <class BusHandler> class MOS6560 {
break;
case 0x5:
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10);
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
break;
case 0xa:
@@ -419,11 +420,11 @@ template <class BusHandler> class MOS6560 {
/*
Reads from a 6560 register.
*/
uint8_t read(int address) {
uint8_t read(int address) const {
address &= 0xf;
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (raster_value() >> 1) & 0xff;
}
}
@@ -461,11 +462,11 @@ template <class BusHandler> class MOS6560 {
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
const int lines_this_field() {
int lines_this_field() const {
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
}
const int raster_value() {
int raster_value() const {
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
const int line = vertical_counter_ + bonus_line;
const int final_line = lines_this_field();
@@ -480,7 +481,7 @@ template <class BusHandler> class MOS6560 {
}
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
}
bool is_odd_frame() {
bool is_odd_frame() const {
return is_odd_frame_ || !registers_.interlaced;
}

View File

@@ -167,8 +167,8 @@ template <class T> class CRTC6845 {
private:
inline void perform_bus_cycle_phase1() {
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | unsigned(character_is_visible_);
bus_state_.display_enable = (int(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
bus_handler_.perform_bus_cycle_phase1(bus_state_);
}
@@ -240,7 +240,7 @@ template <class T> class CRTC6845 {
inline void do_end_of_frame() {
line_counter_ = 0;
line_is_visible_ = true;
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
line_address_ = uint16_t((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}

View File

@@ -120,7 +120,7 @@ void ACIA::consider_transmission() {
}
}
ClockingHint::Preference ACIA::preferred_clocking() {
ClockingHint::Preference ACIA::preferred_clocking() const {
// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
// is on the receiving end.
if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
@@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;

View File

@@ -86,7 +86,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
Serial::Line request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
ClockingHint::Preference preferred_clocking() const final;
struct InterruptDelegate {
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;

View File

@@ -20,7 +20,7 @@
using namespace Motorola::MFP68901;
ClockingHint::Preference MFP68901::preferred_clocking() {
ClockingHint::Preference MFP68901::preferred_clocking() const {
// Rule applied: if any timer is actively running and permitted to produce an
// interrupt, request real-time running.
return

View File

@@ -76,7 +76,7 @@ class MFP68901: public ClockingHint::Source {
void set_interrupt_delegate(InterruptDelegate *delegate);
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
ClockingHint::Preference preferred_clocking() const final;
private:
// MARK: - Timers

View File

@@ -9,13 +9,15 @@
#ifndef i8255_hpp
#define i8255_hpp
#include <cstdint>
namespace Intel {
namespace i8255 {
class PortHandler {
public:
void set_value(int port, uint8_t value) {}
uint8_t get_value(int port) { return 0xff; }
void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {}
uint8_t get_value([[maybe_unused]] int port) { return 0xff; }
};
// TODO: Modes 1 and 2.

View File

@@ -79,10 +79,14 @@ namespace {
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
Storage::Disk::MFMController(clock_rate),
bus_handler_(bus_handler) {
posit_event(static_cast<int>(Event8272::CommandByte));
posit_event(int(Event8272::CommandByte));
// TODO: implement DMA, etc. I have a vague intention to implement the IBM PC
// one day, that should help to force that stuff.
(void)bus_handler_;
}
ClockingHint::Preference i8272::preferred_clocking() {
ClockingHint::Preference i8272::preferred_clocking() const {
const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
@@ -97,7 +101,7 @@ void i8272::run_for(Cycles cycles) {
if(delay_time_ > 0) {
if(cycles.as_integral() >= delay_time_) {
delay_time_ = 0;
posit_event(static_cast<int>(Event8272::Timer));
posit_event(int(Event8272::Timer));
} else {
delay_time_ -= cycles.as_integral();
}
@@ -114,7 +118,7 @@ void i8272::run_for(Cycles cycles) {
while(steps--) {
// Perform a step.
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position));
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << int(drives_[c].head_position));
select_drive(c);
get_drive().step(Storage::Disk::HeadPosition(direction));
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
@@ -156,7 +160,7 @@ void i8272::run_for(Cycles cycles) {
// check for busy plus ready disabled
if(is_executing_ && !get_drive().get_is_ready()) {
posit_event(static_cast<int>(Event8272::NoLongerReady));
posit_event(int(Event8272::NoLongerReady));
}
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
@@ -177,7 +181,7 @@ void i8272::write(int address, uint8_t value) {
} else {
// accumulate latest byte in the command byte sequence
command_.push_back(value);
posit_event(static_cast<int>(Event8272::CommandByte));
posit_event(int(Event8272::CommandByte));
}
}
@@ -186,7 +190,7 @@ uint8_t i8272::read(int address) {
if(result_stack_.empty()) return 0xff;
uint8_t result = result_stack_.back();
result_stack_.pop_back();
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
if(result_stack_.empty()) posit_event(int(Event8272::ResultEmpty));
return result;
} else {
@@ -198,16 +202,16 @@ uint8_t i8272::read(int address) {
#define END_SECTION() }
#define MS_TO_CYCLES(x) x * 8000
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = int(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
#define PASTE(x, y) x##y
#define CONCAT(x, y) PASTE(x, y)
#define FIND_HEADER() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
if(event_type == int(Event::IndexHole)) { index_hole_limit_--; } \
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
\
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
@@ -215,8 +219,8 @@ uint8_t i8272::read(int address) {
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
if(event_type == static_cast<int>(Event::Token)) { \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
if(event_type == int(Event::Token)) { \
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
}
@@ -264,8 +268,8 @@ uint8_t i8272::read(int address) {
}
void i8272::posit_event(int event_type) {
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
if(event_type == int(Event::IndexHole)) index_hole_count_++;
if(event_type == int(Event8272::NoLongerReady)) {
SetNotReady();
goto abort;
}
@@ -425,12 +429,12 @@ void i8272::posit_event(int event_type) {
// Performs the read data or read deleted data command.
read_data:
LOG(PADHEX(2) << "Read [deleted] data ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << " ... "
<< static_cast<int>(command_[6]) << " "
<< static_cast<int>(command_[8]) << "]");
<< int(command_[2]) << " "
<< int(command_[3]) << " "
<< int(command_[4]) << " "
<< int(command_[5]) << " ... "
<< int(command_[6]) << " "
<< int(command_[8]) << "]");
read_next_data:
goto read_write_find_header;
@@ -439,7 +443,7 @@ void i8272::posit_event(int event_type) {
read_data_found_header:
FIND_DATA();
ClearControlMark();
if(event_type == static_cast<int>(Event::Token)) {
if(event_type == int(Event::Token)) {
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
// Something other than a data mark came next, impliedly an ID or index mark.
SetMissingAddressMark();
@@ -470,24 +474,24 @@ void i8272::posit_event(int event_type) {
//
// TODO: consider DTL.
read_data_get_byte:
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
if(event_type == static_cast<int>(Event::Token)) {
WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
if(event_type == int(Event::Token)) {
result_stack_.push_back(get_latest_token().byte_value);
distance_into_section_++;
SetDataRequest();
SetDataDirectionToProcessor();
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
WAIT_FOR_EVENT(int(Event8272::ResultEmpty) | int(Event::Token) | int(Event::IndexHole));
}
switch(event_type) {
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
case int(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
ResetDataRequest();
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
break;
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
case int(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
SetOverrun();
goto abort;
break;
case static_cast<int>(Event::IndexHole):
case int(Event::IndexHole):
SetEndOfCylinder();
goto abort;
break;
@@ -515,12 +519,12 @@ void i8272::posit_event(int event_type) {
write_data:
LOG(PADHEX(2) << "Write [deleted] data ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << " ... "
<< static_cast<int>(command_[6]) << " "
<< static_cast<int>(command_[8]) << "]");
<< int(command_[2]) << " "
<< int(command_[3]) << " "
<< int(command_[4]) << " "
<< int(command_[5]) << " ... "
<< int(command_[6]) << " "
<< int(command_[8]) << "]");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
@@ -571,7 +575,7 @@ void i8272::posit_event(int event_type) {
// Performs the read ID command.
read_id:
// Establishes the drive and head being addressed, and whether in double density mode.
LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]");
LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << int(command_[1]) << "]");
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
@@ -594,10 +598,10 @@ void i8272::posit_event(int event_type) {
// Performs read track.
read_track:
LOG(PADHEX(2) << "Read track ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << "]");
<< int(command_[2]) << " "
<< int(command_[3]) << " "
<< int(command_[4]) << " "
<< int(command_[5]) << "]");
// Wait for the index hole.
WAIT_FOR_EVENT(Event::IndexHole);
@@ -627,7 +631,7 @@ void i8272::posit_event(int event_type) {
distance_into_section_++;
SetDataRequest();
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
WAIT_FOR_EVENT(int(Event8272::ResultEmpty));
ResetDataRequest();
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
@@ -664,13 +668,13 @@ void i8272::posit_event(int event_type) {
expects_input_ = true;
distance_into_section_ = 0;
format_track_write_header:
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
switch(event_type) {
case static_cast<int>(Event::IndexHole):
case int(Event::IndexHole):
SetOverrun();
goto abort;
break;
case static_cast<int>(Event::DataWritten):
case int(Event::DataWritten):
header_[distance_into_section_] = input_;
write_byte(input_);
has_input_ = false;
@@ -683,10 +687,10 @@ void i8272::posit_event(int event_type) {
}
LOG(PADHEX(2) << "W:"
<< static_cast<int>(header_[0]) << " "
<< static_cast<int>(header_[1]) << " "
<< static_cast<int>(header_[2]) << " "
<< static_cast<int>(header_[3]) << ", "
<< int(header_[0]) << " "
<< int(header_[1]) << " "
<< int(header_[2]) << " "
<< int(header_[3]) << ", "
<< get_crc_generator().get_value());
write_crc();
@@ -706,8 +710,8 @@ void i8272::posit_event(int event_type) {
// Otherwise, pad out to the index hole.
format_track_pad:
write_byte(get_is_double_density() ? 0x4e : 0xff);
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
if(event_type != int(Event::IndexHole)) goto format_track_pad;
end_writing();
@@ -758,7 +762,7 @@ void i8272::posit_event(int event_type) {
// up in run_for understands to mean 'keep going until track 0 is active').
if(command_.size() > 2) {
drives_[drive].target_head_position = command_[2];
LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
LOG(PADHEX(2) << "Seek to " << int(command_[2]));
} else {
drives_[drive].target_head_position = -1;
drives_[drive].head_position = 0;
@@ -789,7 +793,7 @@ void i8272::posit_event(int event_type) {
// If a drive was found, return its results. Otherwise return a single 0x80.
if(found_drive != -1) {
drives_[found_drive].phase = Drive::NotSeeking;
status_[0] = static_cast<uint8_t>(found_drive);
status_[0] = uint8_t(found_drive);
main_status_ &= ~(1 << found_drive);
SetSeekEnd();
@@ -819,7 +823,7 @@ void i8272::posit_event(int event_type) {
int drive = command_[1] & 3;
select_drive(drive);
result_stack_= {
static_cast<uint8_t>(
uint8_t(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
@@ -853,9 +857,9 @@ void i8272::posit_event(int event_type) {
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
// last thing in it will be returned first.
post_result:
LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; ");
for(std::size_t c = 0; c < result_stack_.size(); c++) {
LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c]));
}
LOGNBR(std::endl);
@@ -880,13 +884,13 @@ bool i8272::seek_is_satisfied(int drive) {
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
}
void i8272::set_dma_acknowledge(bool dack) {
void i8272::set_dma_acknowledge(bool) {
}
void i8272::set_terminal_count(bool tc) {
void i8272::set_terminal_count(bool) {
}
void i8272::set_data_input(uint8_t value) {
void i8272::set_data_input(uint8_t) {
}
uint8_t i8272::get_data_output() {

View File

@@ -20,8 +20,9 @@ namespace i8272 {
class BusHandler {
public:
virtual void set_dma_data_request(bool drq) {}
virtual void set_interrupt(bool irq) {}
virtual ~BusHandler() {}
virtual void set_dma_data_request([[maybe_unused]] bool drq) {}
virtual void set_interrupt([[maybe_unused]] bool irq) {}
};
class i8272 : public Storage::Disk::MFMController {
@@ -39,13 +40,13 @@ class i8272 : public Storage::Disk::MFMController {
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
ClockingHint::Preference preferred_clocking() final;
ClockingHint::Preference preferred_clocking() const final;
protected:
virtual void select_drive(int number) = 0;
private:
// The bus handler, for interrupt and DMA-driven usage.
// The bus handler, for interrupt and DMA-driven usage. [TODO]
BusHandler &bus_handler_;
std::unique_ptr<BusHandler> allocated_bus_handler_;
@@ -68,7 +69,7 @@ class i8272 : public Storage::Disk::MFMController {
NoLongerReady = (1 << 6)
};
void posit_event(int type) final;
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
int interesting_event_mask_ = int(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;

View File

@@ -8,6 +8,7 @@
#include "z8530.hpp"
#define LOG_PREFIX "[SCC] "
#include "../../Outputs/Log.hpp"
using namespace Zilog::SCC;
@@ -16,7 +17,7 @@ void z8530::reset() {
// TODO.
}
bool z8530::get_interrupt_line() {
bool z8530::get_interrupt_line() const {
return
(master_interrupt_control_ & 0x8) &&
(
@@ -25,22 +26,30 @@ bool z8530::get_interrupt_line() {
);
}
/*
Per the standard defined in the header file, this implementation follows
an addressing convention of:
A0 = A/B (i.e. channel select)
A1 = C/D (i.e. control or data)
*/
std::uint8_t z8530::read(int address) {
if(address & 2) {
// Read data register for channel
return 0x00;
// Read data register for channel.
return channels_[address & 1].read(true, pointer_);
} else {
// Read control register for channel.
uint8_t result = 0;
switch(pointer_) {
default:
result = channels_[address & 1].read(address & 2, pointer_);
result = channels_[address & 1].read(false, pointer_);
break;
case 2: // Handled non-symmetrically between channels.
if(address & 1) {
LOG("[SCC] Unimplemented: register 2 status bits");
LOG("Unimplemented: register 2 status bits");
} else {
result = interrupt_vector_;
@@ -63,7 +72,11 @@ std::uint8_t z8530::read(int address) {
break;
}
// Cf. the two-step control register selection process in ::write. Since this
// definitely wasn't a *write* to register 0, it follows that the next selected
// control register will be 0.
pointer_ = 0;
update_delegate();
return result;
}
@@ -73,24 +86,31 @@ std::uint8_t z8530::read(int address) {
void z8530::write(int address, std::uint8_t value) {
if(address & 2) {
// Write data register for channel.
// Write data register for channel. This is completely independent
// of whatever is going on over in the control realm.
channels_[address & 1].write(true, pointer_, value);
} else {
// Write control register for channel.
// Write control register for channel; there's a two-step sequence
// here for the programmer. Initially the selected register
// (i.e. `pointer_`) is zero. That register includes a field to
// set the next selected register. After any other register has
// been written to, register 0 is selected again.
// Most registers are per channel, but a couple are shared; sever
// them here.
// Most registers are per channel, but a couple are shared;
// sever them here, send the rest to the appropriate chnanel.
switch(pointer_) {
default:
channels_[address & 1].write(address & 2, pointer_, value);
channels_[address & 1].write(false, pointer_, value);
break;
case 2: // Interrupt vector register; shared between both channels.
case 2: // Interrupt vector register; used only by Channel B.
// So there's only one of these.
interrupt_vector_ = value;
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
LOG("Interrupt vector set to " << PADHEX(2) << int(value));
break;
case 9: // Master interrupt and reset register; also shared between both channels.
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
case 9: // Master interrupt and reset register; there is also only one of these.
LOG("Master interrupt and reset register: " << PADHEX(2) << int(value));
master_interrupt_control_ = value;
break;
}
@@ -105,7 +125,8 @@ void z8530::write(int address, std::uint8_t value) {
pointer_ = value & 7;
// If the command part of the byte is a 'point high', also set the
// top bit of the pointer.
// top bit of the pointer. Channels themselves therefore need not
// (/should not) respond to the point high command.
if(((value >> 3)&7) == 1) {
pointer_ |= 8;
}
@@ -126,16 +147,79 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
if(data) {
return data_;
} else {
LOG("Control read from register " << int(pointer));
// Otherwise, this is a control read...
switch(pointer) {
default:
LOG("[SCC] Unrecognised control read from register " << int(pointer));
return 0x00;
case 0:
case 0x0: // Read Register 0; see p.37 (PDF p.45).
// b0: Rx character available.
// b1: zero count.
// b2: Tx buffer empty.
// b3: DCD.
// b4: sync/hunt.
// b5: CTS.
// b6: Tx underrun/EOM.
// b7: break/abort.
return dcd_ ? 0x8 : 0x0;
case 0xf:
case 0x1: // Read Register 1; see p.37 (PDF p.45).
// b0: all sent.
// b1: residue code 0.
// b2: residue code 1.
// b3: residue code 2.
// b4: parity error.
// b5: Rx overrun error.
// b6: CRC/framing error.
// b7: end of frame (SDLC).
return 0x01;
case 0x2: // Read Register 2; see p.37 (PDF p.45).
// Interrupt vector — modified by status information in B channel.
return 0x00;
case 0x3: // Read Register 3; see p.37 (PDF p.45).
// B channel: all bits are 0.
// A channel:
// b0: Channel B ext/status IP.
// b1: Channel B Tx IP.
// b2: Channel B Rx IP.
// b3: Channel A ext/status IP.
// b4: Channel A Tx IP.
// b5: Channel A Rx IP.
// b6, b7: 0.
return 0x00;
case 0xa: // Read Register 10; see p.37 (PDF p.45).
// b0: 0
// b1: On loop.
// b2: 0
// b3: 0
// b4: Loop sending.
// b5: 0
// b6: Two clocks missing.
// b7: One clock missing.
return 0x00;
case 0xc: // Read Register 12; see p.37 (PDF p.45).
// Lower byte of time constant.
return 0x00;
case 0xd: // Read Register 13; see p.38 (PDF p.46).
// Upper byte of time constant.
return 0x00;
case 0xf: // Read Register 15; see p.38 (PDF p.46).
// External interrupt status:
// b0: 0
// b1: Zero count.
// b2: 0
// b3: DCD.
// b4: Sync/hunt.
// b5: CTS.
// b6: Tx underrun/EOM.
// b7: Break/abort.
return external_interrupt_status_;
}
}
@@ -148,9 +232,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
data_ = value;
return;
} else {
LOG("Control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
switch(pointer) {
default:
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
LOG("Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
break;
case 0x0: // Write register 0 — CRC reset and other functions.
@@ -158,13 +243,13 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
switch(value >> 6) {
default: /* Do nothing. */ break;
case 1:
LOG("[SCC] TODO: reset Rx CRC checker.");
LOG("TODO: reset Rx CRC checker.");
break;
case 2:
LOG("[SCC] TODO: reset Tx CRC checker.");
LOG("TODO: reset Tx CRC checker.");
break;
case 3:
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
LOG("TODO: reset Tx underrun/EOM latch.");
break;
}
@@ -172,32 +257,84 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
switch((value >> 3)&7) {
default: /* Do nothing. */ break;
case 2:
// LOG("[SCC] reset ext/status interrupts.");
// LOG("reset ext/status interrupts.");
external_status_interrupt_ = false;
external_interrupt_status_ = 0;
break;
case 3:
LOG("[SCC] TODO: send abort (SDLC).");
LOG("TODO: send abort (SDLC).");
break;
case 4:
LOG("[SCC] TODO: enable interrupt on next Rx character.");
LOG("TODO: enable interrupt on next Rx character.");
break;
case 5:
LOG("[SCC] TODO: reset Tx interrupt pending.");
LOG("TODO: reset Tx interrupt pending.");
break;
case 6:
LOG("[SCC] TODO: reset error.");
LOG("TODO: reset error.");
break;
case 7:
LOG("[SCC] TODO: reset highest IUS.");
LOG("TODO: reset highest IUS.");
break;
}
break;
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
interrupt_mask_ = value;
/*
b7 = 0 => Wait/Request output is inactive; 1 => output is informative.
b6 = Wait/request output is for...
0 => wait: floating when inactive, low if CPU is attempting to transfer data the SCC isn't yet ready for.
1 => request: high if inactive, low if SCC is ready to transfer data.
b5 = 1 => wait/request is relative to read buffer; 0 => relative to write buffer.
b4/b3:
00 = disable receive interrupt
01 = interrupt on first character or special condition
10 = interrupt on all characters and special conditions
11 = interrupt only upon special conditions.
b2 = 1 => parity error is a special condition; 0 => it isn't.
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
b0 = 1 => external interrupt is enabled; 0 => it isn't.
*/
LOG("Interrupt mask: " << PADHEX(2) << int(value));
break;
case 0x2: // Write register 2 - interrupt vector.
break;
case 0x3: { // Write register 3 — Receive Parameters and Control.
// Get bit count.
int receive_bit_count = 8;
switch(value >> 6) {
default: receive_bit_count = 5; break;
case 1: receive_bit_count = 7; break;
case 2: receive_bit_count = 6; break;
case 3: receive_bit_count = 8; break;
}
LOG("Receive bit count: " << receive_bit_count);
(void)receive_bit_count;
/*
b7,b6:
00 = 5 receive bits per character
01 = 7 bits
10 = 6 bits
11 = 8 bits
b5 = 1 => DCD and CTS outputs are set automatically; 0 => they're inputs to read register 0.
(DCD is ignored in local loopback; CTS is ignored in both auto echo and local loopback).
b4: enter hunt mode (if set to 1, presumably?)
b3 = 1 => enable receiver CRC generation; 0 => don't.
b2: address search mode (SDLC)
b1: sync character load inhibit.
b0: Rx enable.
*/
} break;
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
// Bits 0 and 1 select parity mode.
if(!(value&1)) {
@@ -236,6 +373,23 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
}
break;
case 0x5:
// b7: DTR
// b6/b5:
// 00 = Tx 5 bits (or less) per character
// 01 = Tx 7 bits per character
// 10 = Tx 6 bits per character
// 11 = Tx 8 bits per character
// b4: send break.
// b3: Tx enable.
// b2: SDLC (if 0) / CRC-16 (if 1)
// b1: RTS
// b0: Tx CRC enable.
break;
case 0x6:
break;
case 0xf: // Write register 15 — External/Status Interrupt Control.
external_interrupt_mask_ = value;
break;
@@ -253,12 +407,17 @@ void z8530::Channel::set_dcd(bool level) {
}
}
bool z8530::Channel::get_interrupt_line() {
bool z8530::Channel::get_interrupt_line() const {
return
(interrupt_mask_ & 1) && external_status_interrupt_;
// TODO: other potential causes of an interrupt.
}
/*!
Evaluates the new level of the interrupt line and notifies the delegate if
both: (i) there is one; and (ii) the interrupt line has changed since last
the delegate was notified.
*/
void z8530::update_delegate() {
const bool interrupt_line = get_interrupt_line();
if(interrupt_line != previous_interrupt_line_) {

View File

@@ -30,16 +30,33 @@ class z8530 {
A/B = A0
C/D = A1
*/
/// Performs a read from the SCC; see above for conventions as to 'address'.
std::uint8_t read(int address);
/// Performs a write to the SCC; see above for conventions as to 'address'.
void write(int address, std::uint8_t value);
/// Resets the SCC.
void reset();
bool get_interrupt_line();
/// @returns The current value of the status output: @c true for active; @c false for inactive.
bool get_interrupt_line() const;
struct Delegate {
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
/*!
Communicates that @c scc now has the interrupt line status @c new_status.
*/
virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0;
};
/*!
Sets the delegate for this SCC. If this is a new delegate it is sent
an immediate did_change_interrupt_status message, to get it
up to speed.
*/
void set_delegate(Delegate *delegate) {
if(delegate_ == delegate) return;
delegate_ = delegate;
delegate_->did_change_interrupt_status(this, get_interrupt_line());
}
/*
@@ -53,7 +70,7 @@ class z8530 {
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line();
bool get_interrupt_line() const;
private:
uint8_t data_ = 0xff;

View File

@@ -33,7 +33,7 @@ struct ReverseTable {
ReverseTable() {
for(int c = 0; c < 256; ++c) {
map[c] = static_cast<uint8_t>(
map[c] = uint8_t(
((c & 0x80) >> 7) |
((c & 0x40) >> 5) |
((c & 0x20) >> 3) |
@@ -129,6 +129,10 @@ void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType TMS9918::get_display_type() const {
return crt_.get_display_type();
}
void Base::LineBuffer::reset_sprite_collection() {
sprites_stopped = false;
active_sprite_slot = 0;
@@ -140,7 +144,7 @@ void Base::LineBuffer::reset_sprite_collection() {
void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
if(!(status_ & StatusSpriteOverflow)) {
status_ = static_cast<uint8_t>((status_ & ~0x1f) | (sprite_number & 0x1f));
status_ = uint8_t((status_ & ~0x1f) | (sprite_number & 0x1f));
}
if(buffer.sprites_stopped)
return;
@@ -527,7 +531,7 @@ void TMS9918::write(int address, uint8_t value) {
// The RAM pointer is always set on a second write, regardless of
// whether the caller is intending to enqueue a VDP operation.
ram_pointer_ = (ram_pointer_ & 0x00ff) | static_cast<uint16_t>(value << 8);
ram_pointer_ = (ram_pointer_ & 0x00ff) | uint16_t(value << 8);
write_phase_ = false;
if(value & 0x80) {
@@ -661,7 +665,7 @@ uint8_t TMS9918::get_current_line() {
}
}
return static_cast<uint8_t>(source_row);
return uint8_t(source_row);
}
uint8_t TMS9918::get_latched_horizontal_counter() {

View File

@@ -50,6 +50,9 @@ class TMS9918: public Base {
/*! Sets the type of display the CRT will request. */
void set_display_type(Outputs::Display::DisplayType);
/*! Gets the type of display the CRT will request. */
Outputs::Display::DisplayType get_display_type() const;
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.

View File

@@ -40,7 +40,7 @@ enum class TVStandard {
class Base {
public:
static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
static uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
uint32_t result = 0;
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
result_ptr[0] = r;
@@ -51,7 +51,7 @@ class Base {
}
protected:
const static int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
static constexpr int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
// The default TMS palette.
const uint32_t palette[16] = {
@@ -352,9 +352,9 @@ class Base {
if(master_system_.cram_is_selected) {
// Adjust the palette.
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
uint8_t(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
uint8_t(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
uint8_t(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
);
// Schedule a CRAM dot; this is scheduled for wherever it should appear
@@ -421,7 +421,8 @@ class Base {
*/
#define slot(n) \
if(use_end && end == n) return;\
if(use_end && end == n) return; \
[[fallthrough]]; \
case n
#define external_slot(n) \
@@ -449,7 +450,7 @@ class Base {
/***********************************************
TMS9918 Fetching Code
TMS9918 Fetching Code
************************************************/
template<bool use_end> void fetch_tms_refresh(int start, int end) {
@@ -518,7 +519,7 @@ class Base {
fetch_columns_4(location+12, column+4);
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
const size_t row_base = pattern_name_address_ & (0x3c00 | size_t(write_pointer_.row >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
switch(start) {
@@ -693,7 +694,7 @@ class Base {
/***********************************************
Master System Fetching Code
Master System Fetching Code
************************************************/
template<bool use_end> void fetch_sms(int start, int end) {
@@ -731,7 +732,7 @@ class Base {
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
line_buffer.names[column].flags = ram_[address+1]; \
line_buffer.names[column].offset = static_cast<size_t>( \
line_buffer.names[column].offset = size_t( \
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
}
@@ -785,7 +786,7 @@ class Base {
};
const RowInfo scrolled_row_info = {
(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
{size_t((scrolled_row & 7) << 2), 28 ^ size_t((scrolled_row & 7) << 2)}
};
RowInfo row_info;
if(master_system_.vertical_scroll_lock) {

View File

@@ -234,7 +234,7 @@ template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
}
}
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() {
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() const {
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
}

View File

@@ -30,7 +30,7 @@ class PortHandler {
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual uint8_t get_port_input(bool port_b) {
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
return 0xff;
}
@@ -40,7 +40,7 @@ class PortHandler {
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
@param value the value now being output.
*/
virtual void set_port_output(bool port_b, uint8_t value) {}
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
};
/*!
@@ -107,7 +107,7 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return is_stereo; }

View File

@@ -23,7 +23,7 @@ void Toggle::set_sample_volume_range(std::int16_t range) {
volume_ = range;
}
void Toggle::skip_samples(const std::size_t number_of_samples) {}
void Toggle::skip_samples(std::size_t) {}
void Toggle::set_output(bool enabled) {
if(is_enabled_ == enabled) return;
@@ -33,6 +33,6 @@ void Toggle::set_output(bool enabled) {
});
}
bool Toggle::get_output() {
bool Toggle::get_output() const {
return is_enabled_;
}

View File

@@ -24,10 +24,9 @@ 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();
bool get_output() const;
private:
// Accessed on the calling thread.

View File

@@ -85,13 +85,13 @@ void DiskII::run_for(const Cycles cycles) {
--flux_duration_;
if(!flux_duration_) inputs_ |= input_flux;
}
state_ = state_machine_[static_cast<std::size_t>(address)];
state_ = state_machine_[size_t(address)];
switch(state_ & 0xf) {
default: shift_register_ = 0; break; // clear
case 0x8: break; // nop
default: shift_register_ = 0; break; // clear
case 0x8: break; // nop
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
case 0x9: shift_register_ = uint8_t(shift_register_ << 1); break; // shift left, bringing in a zero
case 0xd: shift_register_ = uint8_t((shift_register_ << 1) | 1); break; // shift left, bringing in a one
case 0xa: // shift right, bringing in write protected status
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
@@ -105,7 +105,7 @@ void DiskII::run_for(const Cycles cycles) {
return;
}
break;
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
}
// Currently writing?
@@ -191,7 +191,6 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
((source_address&0x02) ? 0x02 : 0x00);
uint8_t source_value = state_machine[source_address];
// Remap into Beneath Apple Pro-DOS value form.
source_value =
((source_value & 0x80) ? 0x10 : 0x0) |
((source_value & 0x40) ? 0x20 : 0x0) |
@@ -219,13 +218,13 @@ void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
}
}
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
void DiskII::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
decide_clocking_preference();
}
ClockingHint::Preference DiskII::preferred_clocking() {
ClockingHint::Preference DiskII::preferred_clocking() const {
return clocking_preference_;
}

View File

@@ -76,7 +76,7 @@ class DiskII :
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() final;
ClockingHint::Preference preferred_clocking() const final;
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);

View File

@@ -307,8 +307,8 @@ void IWM::run_for(const Cycles cycles) {
} else {
shift_register_ = sense();
}
[[fallthrough]];
/* Deliberate fallthrough. */
default:
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
break;

View File

@@ -24,7 +24,7 @@ namespace Apple {
Defines the drive interface used by the IWM, derived from the external pinout as
per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
These are subclasses of Storage::Disk::Drive, so accept any disk the emulator supports,
and provide the usual read/write interface for on-disk data.
*/
struct IWMDrive: public Storage::Disk::Drive {
@@ -82,8 +82,9 @@ class IWM:
uint8_t data_register_ = 0;
uint8_t mode_ = 0;
bool read_write_ready_ = true;
bool write_overran_ = false;
// These related to functionality not-yet implemented.
// bool read_write_ready_ = true;
// bool write_overran_ = false;
int state_ = 0;

View File

@@ -15,7 +15,7 @@ using namespace Konami;
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
task_queue_(task_queue) {}
bool SCC::is_zero_level() {
bool SCC::is_zero_level() const {
return !(channel_enable_ & 0x1f);
}
@@ -87,13 +87,13 @@ void SCC::write(uint16_t address, uint8_t value) {
void SCC::evaluate_output_volume() {
transient_output_level_ =
static_cast<int16_t>(
int16_t(
((
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
(channel_enable_ & 0x01) ? int8_t(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
(channel_enable_ & 0x02) ? int8_t(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
(channel_enable_ & 0x04) ? int8_t(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
(channel_enable_ & 0x08) ? int8_t(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
(channel_enable_ & 0x10) ? int8_t(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
) * master_volume_) / (255*15*5)
// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5.
);

View File

@@ -27,7 +27,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
/// As per ::SampleSource; provides a broadphase test for silence.
bool is_zero_level();
bool is_zero_level() const;
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
@@ -61,7 +61,6 @@ class SCC: public ::Outputs::Speaker::SampleSource {
} waves_[4];
std::uint8_t channel_enable_ = 0;
std::uint8_t test_register_ = 0;
void evaluate_output_volume();

View File

@@ -0,0 +1,264 @@
//
// EnvelopeGenerator.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef EnvelopeGenerator_h
#define EnvelopeGenerator_h
#include <optional>
#include <functional>
#include "LowFrequencyOscillator.hpp"
namespace Yamaha {
namespace OPL {
/*!
Models an OPL-style envelope generator.
Damping is optional; if damping is enabled then if there is a transition to key-on while
attenuation is less than maximum then attenuation will be quickly transitioned to maximum
before the attack phase can begin.
in real hardware damping is used by the envelope generators associated with
carriers, with phases being reset upon the transition from damping to attack.
This code considers application of tremolo to be a function of the envelope generator;
this is largely for logical conformity with the phase generator that necessarily has to
apply vibrato.
TODO: use envelope_precision.
*/
template <int envelope_precision, int period_precision> class EnvelopeGenerator {
public:
/*!
Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
// Apply tremolo, which is fairly easy.
tremolo_ = tremolo_enable_ * oscillator.tremolo << 4;
// Something something something...
const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_;
switch(phase_) {
case Phase::Damp:
update_decay(oscillator, 12 << 2);
if(attenuation_ == 511) {
(*will_attack_)();
phase_ = Phase::Attack;
}
break;
case Phase::Attack:
update_attack(oscillator, attack_rate_ + key_scaling_rate);
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
if(attenuation_ <= 0) {
attenuation_ = 0;
phase_ = Phase::Decay;
}
break;
case Phase::Decay:
update_decay(oscillator, decay_rate_ + key_scaling_rate);
if(attenuation_ >= sustain_level_) {
attenuation_ = sustain_level_;
phase_ = use_sustain_level_ ? Phase::Sustain : Phase::Release;
}
break;
case Phase::Sustain:
// Nothing to do.
break;
case Phase::Release:
update_decay(oscillator, release_rate_ + key_scaling_rate);
break;
}
}
/*!
@returns The current attenuation from this envelope generator. This is independent of the envelope precision.
*/
int attenuation() const {
// TODO: if this envelope is fully released, should tremolo still be able to vocalise it?
return (attenuation_ << 3) + tremolo_;
}
/*!
Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will
use the damping phase when necessary (i.e. when transitioning to key on if attenuation is not already at maximum)
and in any case will call @c will_attack before transitioning from any other state to attack.
@param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode.
*/
void set_should_damp(const std::optional<std::function<void(void)>> &will_attack) {
will_attack_ = will_attack;
}
/*!
Sets the current state of the key-on input.
*/
void set_key_on(bool key_on) {
// Do nothing if this is not a leading or trailing edge.
if(key_on == key_on_) return;
key_on_ = key_on;
// Always transition to release upon a key off.
if(!key_on_) {
phase_ = Phase::Release;
return;
}
// On key on: if this is an envelope generator with damping, and damping is required,
// schedule that. If damping is not required, announce a pending attack now and
// transition to attack.
if(will_attack_) {
if(attenuation_ != 511) {
phase_ = Phase::Damp;
return;
}
(*will_attack_)();
}
phase_ = Phase::Attack;
}
/*!
Sets the attack rate, which should be in the range 015.
*/
void set_attack_rate(int rate) {
attack_rate_ = rate << 2;
}
/*!
Sets the decay rate, which should be in the range 015.
*/
void set_decay_rate(int rate) {
decay_rate_ = rate << 2;
}
/*!
Sets the release rate, which should be in the range 015.
*/
void set_release_rate(int rate) {
release_rate_ = rate << 2;
}
/*!
Sets the sustain level, which should be in the range 015.
*/
void set_sustain_level(int level) {
sustain_level_ = level << 3;
// TODO: verify the shift level here. Especially re: precision.
}
/*!
Enables or disables use of the sustain level. If this is disabled, the envelope proceeds
directly from decay to release.
*/
void set_use_sustain_level(bool use) {
use_sustain_level_ = use;
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_rate_enabled(bool enabled) {
key_scale_rate_shift_ = int(enabled) * 2;
}
/*!
Enables or disables application of the low-frequency oscillator's tremolo.
*/
void set_tremolo_enabled(bool enabled) {
tremolo_enable_ = int(enabled);
}
/*!
Sets the current period associated with the channel that owns this envelope generator;
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(int period, int octave) {
key_scale_rate_ = (octave << 1) | (period >> (period_precision - 1));
}
private:
enum class Phase {
Attack, Decay, Sustain, Release, Damp
} phase_ = Phase::Release;
int attenuation_ = 511, tremolo_ = 0;
bool key_on_ = false;
std::optional<std::function<void(void)>> will_attack_;
int key_scale_rate_ = 0;
int key_scale_rate_shift_ = 0;
int tremolo_enable_ = 0;
int attack_rate_ = 0;
int decay_rate_ = 0;
int release_rate_ = 0;
int sustain_level_ = 0;
bool use_sustain_level_ = false;
static constexpr int dithering_patterns[4][8] = {
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
};
void update_attack(const LowFrequencyOscillator &oscillator, int rate) {
// Special case: no attack.
if(rate < 4) {
return;
}
// Special case: instant attack.
if(rate >= 60) {
attenuation_ = 0;
return;
}
// Work out the number of cycles between each adjustment tick, and stop now
// if not at the next adjustment boundary.
const int shift_size = 13 - (std::min(rate, 52) >> 2);
if(oscillator.counter & ((1 << shift_size) - 1)) {
return;
}
// Apply dithered adjustment.
const int rate_shift = (rate > 55);
const int step = dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7];
attenuation_ -= ((attenuation_ >> (3 - rate_shift)) + 1) * step;
}
void update_decay(const LowFrequencyOscillator &oscillator, int rate) {
// Special case: no decay.
if(rate < 4) {
return;
}
// Work out the number of cycles between each adjustment tick, and stop now
// if not at the next adjustment boundary.
const int shift_size = 13 - (std::min(rate, 52) >> 2);
if(oscillator.counter & ((1 << shift_size) - 1)) {
return;
}
// Apply dithered adjustment and clamp.
const int rate_shift = 1 + (rate > 59) + (rate > 55);
attenuation_ += dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7] * (4 << rate_shift);
attenuation_ = std::min(attenuation_, 511);
}
};
}
}
#endif /* EnvelopeGenerator_h */

View File

@@ -0,0 +1,58 @@
//
// KeyLevelScaler.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef KeyLevelScaler_h
#define KeyLevelScaler_h
namespace Yamaha {
namespace OPL {
template <int frequency_precision> class KeyLevelScaler {
public:
/*!
Sets the current period associated with the channel that owns this envelope generator;
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(int period, int octave) {
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
constexpr int masks[2] = {~0, 0};
// A two's complement assumption is embedded below; the use of masks relies
// on the sign bit to clamp to zero.
level_ = key_level_scales[period >> (frequency_precision - 4)];
level_ -= 16 * (octave ^ 7);
level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1];
}
/*!
Enables or disables key-rate scaling.
*/
void set_key_scaling_level(int level) {
// '7' is just a number large enough to render all possible scaling coefficients as 0.
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
shift_ = key_level_scale_shifts[level];
}
/*!
@returns The current attenuation level due to key-level scaling.
*/
int attenuation() const {
return level_ >> shift_;
}
private:
int level_ = 0;
int shift_ = 0;
};
}
}
#endif /* KeyLevelScaler_h */

View File

@@ -0,0 +1,68 @@
//
// LowFrequencyOscillator.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef LowFrequencyOscillator_hpp
#define LowFrequencyOscillator_hpp
#include "../../../Numeric/LFSR.hpp"
namespace Yamaha {
namespace OPL {
/*!
Models the output of the OPL low-frequency oscillator, which provides a couple of optional fixed-frequency
modifications to an operator: tremolo and vibrato. Also exposes a global time counter, which oscillators use
as part of their ADSR envelope.
*/
class LowFrequencyOscillator {
public:
/// Current attenuation due to tremolo / amplitude modulation, between 0 and 26.
int tremolo = 0;
/// A number between 0 and 7 indicating the current vibrato offset; this should be combined by operators
/// with their frequency number to get the actual vibrato.
int vibrato = 0;
/// A counter of the number of operator update cycles (i.e. input clock / 72) since an arbitrary time.
int counter = 0;
/// Describes the current output of the LFSR; will be either 0 or 1.
int lfsr = 0;
/// Updates the oscillator outputs. Should be called at the (input clock/72) rate.
void update() {
++counter;
// This produces output of:
//
// four instances of 0, four instances of 1... _three_ instances of 26,
// four instances of 25, four instances of 24... _three_ instances of 0.
//
// ... advancing once every 64th update.
const int tremolo_index = (counter >> 6) % 210;
const int tremolo_levels[2] = {tremolo_index >> 2, 52 - ((tremolo_index+1) >> 2)};
tremolo = tremolo_levels[tremolo_index / 107];
// Vibrato is relatively simple: it's just three bits from the counter.
vibrato = (counter >> 10) & 7;
}
/// Updartes the LFSR output. Should be called at the input clock rate.
void update_lfsr() {
lfsr = noise_source_.next();
}
private:
// This is the correct LSFR per forums.submarine.org.uk.
Numeric::LFSR<int, 0x800302> noise_source_;
};
}
}
#endif /* LowFrequencyOscillator_hpp */

View File

@@ -0,0 +1,40 @@
//
// OPLBase.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef OPLBase_h
#define OPLBase_h
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../../Concurrency/AsyncTaskQueue.hpp"
namespace Yamaha {
namespace OPL {
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
public:
void write(uint16_t address, uint8_t value) {
if(address & 1) {
static_cast<Child *>(this)->write_register(selected_register_, value);
} else {
selected_register_ = value;
}
}
protected:
OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
Concurrency::DeferringAsyncTaskQueue &task_queue_;
private:
uint8_t selected_register_ = 0;
};
}
}
#endif /* OPLBase_h */

View File

@@ -0,0 +1,125 @@
//
// PhaseGenerator.h
// Clock Signal
//
// Created by Thomas Harte on 30/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef PhaseGenerator_h
#define PhaseGenerator_h
#include <cassert>
#include "LowFrequencyOscillator.hpp"
#include "Tables.hpp"
namespace Yamaha {
namespace OPL {
/*!
Models an OPL-style phase generator of templated precision; having been told its period ('f-num'), octave ('block') and
multiple, and whether to apply vibrato, this will then appropriately update and return phase.
*/
template <int precision> class PhaseGenerator {
public:
/*!
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
constexpr int vibrato_signs[2] = {1, -1};
// Get just the top three bits of the period_.
const int top_freq = period_ >> (precision - 3);
// Cacluaute applicable vibrato as a function of (i) the top three bits of the
// oscillator period; (ii) the current low-frequency oscillator vibrato output; and
// (iii) whether vibrato is enabled.
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_;
// Apply phase update with vibrato from the low-frequency oscillator.
phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1;
}
/*!
@returns Current phase; real hardware provides only the low ten bits of this result.
*/
int phase() const {
// My table if multipliers is multiplied by two, so shift by one more
// than the stated precision.
return phase_ >> precision_shift;
}
/*!
@returns Current phase, scaled up by (1 << precision).
*/
int scaled_phase() const {
return phase_ >> 1;
}
/*!
Applies feedback based on two historic samples of a total output level,
plus the degree of feedback to apply
*/
void apply_feedback(LogSign first, LogSign second, int level) {
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
}
/*!
Sets the multiple for this phase generator, in the same terms as an OPL programmer,
i.e. a 4-bit number that is used as a lookup into the internal multiples table.
*/
void set_multiple(int multiple) {
// This encodes the MUL -> multiple table given on page 12,
// multiplied by two.
constexpr int multipliers[] = {
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
};
assert(multiple < 16);
multiple_ = multipliers[multiple];
}
/*!
Sets the period of this generator, along with its current octave.
Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave.
*/
void set_period(int period, int octave) {
period_ = period;
octave_ = octave;
assert(octave_ < 8);
assert(period_ < (1 << precision));
}
/*!
Enables or disables vibrato.
*/
void set_vibrato_enabled(bool enabled) {
enable_vibrato_ = int(enabled);
}
/*!
Resets the current phase.
*/
void reset() {
phase_ = 0;
}
private:
static constexpr int precision_shift = 1 + precision;
int phase_ = 0;
int multiple_ = 0;
int period_ = 0;
int octave_ = 0;
int enable_vibrato_ = 0;
};
}
}
#endif /* PhaseGenerator_h */

View File

@@ -0,0 +1,227 @@
//
// Tables.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef Tables_hpp
#define Tables_hpp
namespace Yamaha {
namespace OPL {
/*
These are the OPL's built-in log-sin and exponentiation tables, as recovered by
Matthew Gambrell and Olli Niemitalo in 'OPLx decapsulated'. Despite the formulas
being well known, I've elected not to generate these at runtime because even if I
did, I'd just end up with the proper values laid out in full in a unit test, and
they're very compact.
*/
/*!
Represents both the logarithm of a value and its sign.
It's actually the negative logarithm, in base two, in fixed point.
*/
struct LogSign {
int log;
int sign;
void reset() {
log = 0;
sign = 1;
}
LogSign &operator +=(int attenuation) {
log += attenuation;
return *this;
}
LogSign &operator +=(LogSign log_sign) {
log += log_sign.log;
sign *= log_sign.sign;
return *this;
}
int level(int fractional = 0) const;
};
/*!
@returns Negative log sin of x, assuming a 1024-unit circle.
*/
constexpr LogSign negative_log_sin(int x) {
/// Defines the first quadrant of 1024-unit negative log to the base two of sine (that conveniently misses sin(0)).
///
/// Expected branchless usage for a full 1024 unit output:
///
/// constexpr int multiplier[] = { 1, -1 };
/// constexpr int mask[] = { 0, 255 };
///
/// value = exp( log_sin[angle & 255] ^ mask[(angle >> 8) & 1]) * multitplier[(angle >> 9) & 1]
///
/// ... where exp(x) = 2 ^ -x / 256
constexpr int16_t log_sin[] = {
2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137,
1091, 1050, 1013, 979, 949, 920, 894, 869,
846, 825, 804, 785, 767, 749, 732, 717,
701, 687, 672, 659, 646, 633, 621, 609,
598, 587, 576, 566, 556, 546, 536, 527,
518, 509, 501, 492, 484, 476, 468, 461,
453, 446, 439, 432, 425, 418, 411, 405,
399, 392, 386, 380, 375, 369, 363, 358,
352, 347, 341, 336, 331, 326, 321, 316,
311, 307, 302, 297, 293, 289, 284, 280,
276, 271, 267, 263, 259, 255, 251, 248,
244, 240, 236, 233, 229, 226, 222, 219,
215, 212, 209, 205, 202, 199, 196, 193,
190, 187, 184, 181, 178, 175, 172, 169,
167, 164, 161, 159, 156, 153, 151, 148,
146, 143, 141, 138, 136, 134, 131, 129,
127, 125, 122, 120, 118, 116, 114, 112,
110, 108, 106, 104, 102, 100, 98, 96,
94, 92, 91, 89, 87, 85, 83, 82,
80, 78, 77, 75, 74, 72, 70, 69,
67, 66, 64, 63, 62, 60, 59, 57,
56, 55, 53, 52, 51, 49, 48, 47,
46, 45, 43, 42, 41, 40, 39, 38,
37, 36, 35, 34, 33, 32, 31, 30,
29, 28, 27, 26, 25, 24, 23, 23,
22, 21, 20, 20, 19, 18, 17, 17,
16, 15, 15, 14, 13, 13, 12, 12,
11, 10, 10, 9, 9, 8, 8, 7,
7, 7, 6, 6, 5, 5, 5, 4,
4, 4, 3, 3, 3, 2, 2, 2,
2, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0
};
constexpr int16_t sign[] = { 1, -1 };
constexpr int16_t mask[] = { 0, 255 };
return {
.log = log_sin[(x & 255) ^ mask[(x >> 8) & 1]],
.sign = sign[(x >> 9) & 1]
};
}
/*!
Computes the linear value represented by the log-sign @c ls, shifted left by @c fractional prior
to loss of precision.
*/
constexpr int power_two(LogSign ls, int fractional = 0) {
/// A derivative of the exponent table in a real OPL2; mapped_exp[x] = (source[c ^ 0xff] << 1) | 0x800.
///
/// The ahead-of-time transformation represents fixed work the OPL2 does when reading its table
/// independent on the input.
///
/// The original table is a 0.10 fixed-point representation of 2^x - 1 with bit 10 implicitly set, where x is
/// in 0.8 fixed point.
///
/// Since the log_sin table represents sine in a negative base-2 logarithm, values from it would need
/// to be negatived before being put into the original table. That's haned with the ^ 0xff. The | 0x800 is to
/// set the implicit bit 10 (subject to the shift).
///
/// The shift by 1 is to allow the chip's exploitation of the recursive symmetry of the exponential table to
/// be achieved more easily. Specifically, to convert a logarithmic attenuation to a linear one, just perform:
///
/// result = mapped_exp[x & 0xff] >> (x >> 8)
constexpr int16_t mapped_exp[] = {
4084, 4074, 4062, 4052, 4040, 4030, 4020, 4008,
3998, 3986, 3976, 3966, 3954, 3944, 3932, 3922,
3912, 3902, 3890, 3880, 3870, 3860, 3848, 3838,
3828, 3818, 3808, 3796, 3786, 3776, 3766, 3756,
3746, 3736, 3726, 3716, 3706, 3696, 3686, 3676,
3666, 3656, 3646, 3636, 3626, 3616, 3606, 3596,
3588, 3578, 3568, 3558, 3548, 3538, 3530, 3520,
3510, 3500, 3492, 3482, 3472, 3464, 3454, 3444,
3434, 3426, 3416, 3408, 3398, 3388, 3380, 3370,
3362, 3352, 3344, 3334, 3326, 3316, 3308, 3298,
3290, 3280, 3272, 3262, 3254, 3246, 3236, 3228,
3218, 3210, 3202, 3192, 3184, 3176, 3168, 3158,
3150, 3142, 3132, 3124, 3116, 3108, 3100, 3090,
3082, 3074, 3066, 3058, 3050, 3040, 3032, 3024,
3016, 3008, 3000, 2992, 2984, 2976, 2968, 2960,
2952, 2944, 2936, 2928, 2920, 2912, 2904, 2896,
2888, 2880, 2872, 2866, 2858, 2850, 2842, 2834,
2826, 2818, 2812, 2804, 2796, 2788, 2782, 2774,
2766, 2758, 2752, 2744, 2736, 2728, 2722, 2714,
2706, 2700, 2692, 2684, 2678, 2670, 2664, 2656,
2648, 2642, 2634, 2628, 2620, 2614, 2606, 2600,
2592, 2584, 2578, 2572, 2564, 2558, 2550, 2544,
2536, 2530, 2522, 2516, 2510, 2502, 2496, 2488,
2482, 2476, 2468, 2462, 2456, 2448, 2442, 2436,
2428, 2422, 2416, 2410, 2402, 2396, 2390, 2384,
2376, 2370, 2364, 2358, 2352, 2344, 2338, 2332,
2326, 2320, 2314, 2308, 2300, 2294, 2288, 2282,
2276, 2270, 2264, 2258, 2252, 2246, 2240, 2234,
2228, 2222, 2216, 2210, 2204, 2198, 2192, 2186,
2180, 2174, 2168, 2162, 2156, 2150, 2144, 2138,
2132, 2128, 2122, 2116, 2110, 2104, 2098, 2092,
2088, 2082, 2076, 2070, 2064, 2060, 2054, 2048,
};
return ((mapped_exp[ls.log & 0xff] << fractional) >> (ls.log >> 8)) * ls.sign;
}
/*
Credit for the fixed register lists goes to Nuke.YKT; I found them at:
https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instrument_rom
The arrays below begin with channel 1, then each line is a single channel defined
in exactly the same terms as the OPL's user-defined channel.
*/
constexpr uint8_t opll_patch_set[] = {
0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17,
0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13,
0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x11, 0x23,
0x31, 0x61, 0x0e, 0x07, 0xa8, 0x64, 0x70, 0x27,
0x32, 0x21, 0x1e, 0x06, 0xe0, 0x76, 0x00, 0x28,
0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18,
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x10, 0x07,
0x23, 0x21, 0x2d, 0x14, 0xa2, 0x72, 0x00, 0x07,
0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17,
0x41, 0x61, 0x0b, 0x18, 0x85, 0xf7, 0x71, 0x07,
0x13, 0x01, 0x83, 0x11, 0xfa, 0xe4, 0x10, 0x04,
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
0x61, 0x50, 0x0c, 0x05, 0xc2, 0xf5, 0x20, 0x42,
0x01, 0x01, 0x55, 0x03, 0xc9, 0x95, 0x03, 0x02,
0x61, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0x40, 0x13,
};
constexpr uint8_t vrc7_patch_set[] = {
0x03, 0x21, 0x05, 0x06, 0xe8, 0x81, 0x42, 0x27,
0x13, 0x41, 0x14, 0x0d, 0xd8, 0xf6, 0x23, 0x12,
0x11, 0x11, 0x08, 0x08, 0xfa, 0xb2, 0x20, 0x12,
0x31, 0x61, 0x0c, 0x07, 0xa8, 0x64, 0x61, 0x27,
0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28,
0x02, 0x01, 0x06, 0x00, 0xa3, 0xe2, 0xf4, 0xf4,
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07,
0x23, 0x21, 0x22, 0x17, 0xa2, 0x72, 0x01, 0x17,
0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01,
0xb5, 0x01, 0x0f, 0x0f, 0xa8, 0xa5, 0x51, 0x02,
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16,
0x01, 0x02, 0xd3, 0x05, 0xc9, 0x95, 0x03, 0x02,
0x61, 0x63, 0x0c, 0x00, 0x94, 0xc0, 0x33, 0xf6,
0x21, 0x72, 0x0d, 0x00, 0xc1, 0xd5, 0x56, 0x06,
};
constexpr uint8_t percussion_patch_set[] = {
0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d,
0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x48,
0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55,
};
inline int LogSign::level(int fractional) const {
return power_two(*this, fractional);
}
}
}
#endif /* Tables_hpp */

View File

@@ -0,0 +1,92 @@
//
// WaveformGenerator.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef WaveformGenerator_h
#define WaveformGenerator_h
#include "Tables.hpp"
#include "LowFrequencyOscillator.hpp"
namespace Yamaha {
namespace OPL {
enum class Waveform {
Sine, HalfSine, AbsSine, PulseSine
};
template <int phase_precision> class WaveformGenerator {
public:
/*!
@returns The output of waveform @c form at [integral] phase @c phase.
*/
static constexpr LogSign wave(Waveform form, int phase) {
constexpr int waveforms[4][4] = {
{1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant.
{511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half.
{511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave.
{255, 0, 255, 0}, // PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0.
};
return negative_log_sin(phase & waveforms[int(form)][(phase >> 8) & 3]);
}
/*!
@returns The output of waveform @c form at [scaled] phase @c scaled_phase given the modulation input @c modulation.
*/
static constexpr LogSign wave(Waveform form, int scaled_phase, LogSign modulation) {
const int scaled_phase_offset = modulation.level(phase_precision);
const int phase = (scaled_phase + scaled_phase_offset) >> phase_precision;
return wave(form, phase);
}
/*!
@returns Snare output, calculated from the current LFSR state as captured in @c oscillator and an operator's phase.
*/
static constexpr LogSign snare(const LowFrequencyOscillator &oscillator, int phase) {
// If noise is 0, output is positive.
// If noise is 1, output is negative.
// If (noise ^ sign) is 0, output is 0. Otherwise it is max.
const int sign = phase & 0x200;
const int level = ((phase >> 9) & 1) ^ oscillator.lfsr;
return negative_log_sin(sign + (level << 8));
}
/*!
@returns Cymbal output, calculated from an operator's phase and a modulator's phase.
*/
static constexpr LogSign cymbal(int carrier_phase, int modulator_phase) {
return negative_log_sin(256 + (phase_combination(carrier_phase, modulator_phase) << 9));
}
/*!
@returns High-hat output, calculated from the current LFSR state as captured in @c oscillator, an operator's phase and a modulator's phase.
*/
static constexpr LogSign high_hat(const LowFrequencyOscillator &oscillator, int carrier_phase, int modulator_phase) {
constexpr int angles[] = {0x234, 0xd0, 0x2d0, 0x34};
return negative_log_sin(angles[
phase_combination(carrier_phase, modulator_phase) |
(oscillator.lfsr << 1)
]);
}
private:
/*!
@returns The phase bit used for cymbal and high-hat generation, which is a function of two operators' phases.
*/
static constexpr int phase_combination(int carrier_phase, int modulator_phase) {
return (
((carrier_phase >> 5) ^ (carrier_phase >> 3)) &
((modulator_phase >> 7) ^ (modulator_phase >> 2)) &
((carrier_phase >> 5) ^ (modulator_phase >> 3))
) & 1;
}
};
}
}
#endif /* WaveformGenerator_h */

443
Components/OPx/OPLL.cpp Normal file
View File

@@ -0,0 +1,443 @@
//
// OPLL.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "OPLL.hpp"
#include <cassert>
using namespace Yamaha::OPL;
OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7):
OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
// Due to the way that sound mixing works on the OPLL, the audio divider may not
// be larger than 4.
assert(audio_divider <= 4);
// Setup the rhythm envelope generators.
// Treat the bass exactly as if it were a melodic channel.
rhythm_envelope_generators_[BassCarrier].set_should_damp([this] {
// Propagate attack mode to the modulator, and reset both phases.
rhythm_envelope_generators_[BassModulator].set_key_on(true);
phase_generators_[6 + 0].reset();
phase_generators_[6 + 9].reset();
});
// Set the other drums to damp, but only the TomTom to affect phase.
rhythm_envelope_generators_[TomTom].set_should_damp([this] {
phase_generators_[8 + 9].reset();
});
rhythm_envelope_generators_[Snare].set_should_damp({});
rhythm_envelope_generators_[Cymbal].set_should_damp({});
rhythm_envelope_generators_[HighHat].set_should_damp({});
// Crib the proper rhythm envelope generator settings by installing
// the rhythm instruments and copying them over.
rhythm_mode_enabled_ = true;
install_instrument(6);
install_instrument(7);
install_instrument(8);
rhythm_envelope_generators_[BassCarrier] = envelope_generators_[6];
rhythm_envelope_generators_[BassModulator] = envelope_generators_[6 + 9];
rhythm_envelope_generators_[HighHat] = envelope_generators_[7 + 9];
rhythm_envelope_generators_[Cymbal] = envelope_generators_[8];
rhythm_envelope_generators_[TomTom] = envelope_generators_[8 + 9];
rhythm_envelope_generators_[Snare] = envelope_generators_[7];
// Return to ordinary default mode.
rhythm_mode_enabled_ = false;
// Set up damping for the melodic channels.
for(int c = 0; c < 9; ++c) {
envelope_generators_[c].set_should_damp([this, c] {
// Propagate attack mode to the modulator, and reset both phases.
envelope_generators_[c + 9].set_key_on(true);
phase_generators_[c + 0].reset();
phase_generators_[c + 9].reset();
});
}
// Set default instrument.
for(int c = 0; c < 9; ++c) {
install_instrument(c);
}
}
// MARK: - Machine-facing programmatic input.
void OPLL::write_register(uint8_t address, uint8_t value) {
// The OPLL doesn't have timers or other non-audio functions, so all writes
// go to the audio queue.
task_queue_.defer([this, address, value] {
// The first 8 locations are used to define the custom instrument, and have
// exactly the same format as the patch set arrays at the head of this file.
if(address < 8) {
custom_instrument_[address] = value;
// Update all channels that refer to instrument 0.
for(int c = 0; c < 9; ++c) {
if(!channels_[c].instrument) {
install_instrument(c);
}
}
return;
}
// Register 0xe enables or disables rhythm mode and contains the
// percussion key-on bits.
if(address == 0xe) {
const bool old_rhythm_mode = rhythm_mode_enabled_;
rhythm_mode_enabled_ = value & 0x20;
if(old_rhythm_mode != rhythm_mode_enabled_) {
// Change the instlled instruments for channels 6, 7 and 8
// if this was a transition into or out of rhythm mode.
install_instrument(6);
install_instrument(7);
install_instrument(8);
}
rhythm_envelope_generators_[HighHat].set_key_on(value & 0x01);
rhythm_envelope_generators_[Cymbal].set_key_on(value & 0x02);
rhythm_envelope_generators_[TomTom].set_key_on(value & 0x04);
rhythm_envelope_generators_[Snare].set_key_on(value & 0x08);
if(value & 0x10) {
rhythm_envelope_generators_[BassCarrier].set_key_on(true);
} else {
rhythm_envelope_generators_[BassCarrier].set_key_on(false);
rhythm_envelope_generators_[BassModulator].set_key_on(false);
}
return;
}
// That leaves only per-channel selections, for which the addressing
// is completely orthogonal; check that a valid channel is being requested.
const auto index = address & 0xf;
if(index > 8) return;
switch(address & 0xf0) {
default: break;
// Address 1x sets the low 8 bits of the period for channel x.
case 0x10:
channels_[index].period = (channels_[index].period & ~0xff) | value;
set_channel_period(index);
return;
// Address 2x Sets the octave and a single bit of the frequency, as well
// as setting key on and sustain mode.
case 0x20:
channels_[index].period = (channels_[index].period & 0xff) | ((value & 1) << 8);
channels_[index].octave = (value >> 1) & 7;
set_channel_period(index);
// In this implementation the first 9 envelope generators are for
// channel carriers, and their will_attack callback is used to trigger
// key-on for modulators. But key-off needs to be set to both envelope
// generators now.
if(value & 0x10) {
envelope_generators_[index].set_key_on(true);
} else {
envelope_generators_[index + 0].set_key_on(false);
envelope_generators_[index + 9].set_key_on(false);
}
// Set sustain bit to both the relevant operators.
channels_[index].use_sustain = value & 0x20;
set_use_sustain(index);
return;
// Address 3x selects the instrument and attenuation for a channel;
// in rhythm mode some of the nibbles that ordinarily identify instruments
// instead nominate additional attenuations. This code reads those back
// from the stored instrument values.
case 0x30:
channels_[index].attenuation = value & 0xf;
// Install an instrument only if it's new.
if(channels_[index].instrument != value >> 4) {
channels_[index].instrument = value >> 4;
if(index < 6 || !rhythm_mode_enabled_) {
install_instrument(index);
}
}
return;
}
});
}
void OPLL::set_channel_period(int channel) {
phase_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
phase_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
envelope_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
envelope_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
key_level_scalers_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
key_level_scalers_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
}
const uint8_t *OPLL::instrument_definition(int instrument, int channel) {
// Divert to the appropriate rhythm instrument if in rhythm mode.
if(channel >= 6 && rhythm_mode_enabled_) {
return &percussion_patch_set[(channel - 6) * 8];
}
// Instrument 0 is the custom instrument.
if(!instrument) return custom_instrument_;
// Instruments other than 0 are taken from the fixed set.
const int index = (instrument - 1) * 8;
return is_vrc7_ ? &vrc7_patch_set[index] : &opll_patch_set[index];
}
void OPLL::install_instrument(int channel) {
auto &carrier_envelope = envelope_generators_[channel + 0];
auto &carrier_phase = phase_generators_[channel + 0];
auto &carrier_scaler = key_level_scalers_[channel + 0];
auto &modulator_envelope = envelope_generators_[channel + 9];
auto &modulator_phase = phase_generators_[channel + 9];
auto &modulator_scaler = key_level_scalers_[channel + 9];
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
// Bytes 0 (modulator) and 1 (carrier):
//
// b0-b3: multiplier;
// b4: key-scale rate enable;
// b5: sustain-level enable;
// b6: vibrato enable;
// b7: tremolo enable.
modulator_phase.set_multiple(instrument[0] & 0xf);
channels_[channel].modulator_key_rate_scale_multiplier = (instrument[0] >> 4) & 1;
modulator_phase.set_vibrato_enabled(instrument[0] & 0x40);
modulator_envelope.set_tremolo_enabled(instrument[0] & 0x80);
carrier_phase.set_multiple(instrument[1] & 0xf);
channels_[channel].carrier_key_rate_scale_multiplier = (instrument[1] >> 4) & 1;
carrier_phase.set_vibrato_enabled(instrument[1] & 0x40);
carrier_envelope.set_tremolo_enabled(instrument[1] & 0x80);
// Pass off bit 5.
set_use_sustain(channel);
// Byte 2:
//
// b0b5: modulator attenuation;
// b6b7: modulator key-scale level.
modulator_scaler.set_key_scaling_level(instrument[3] >> 6);
channels_[channel].modulator_attenuation = instrument[2] & 0x3f;
// Byte 3:
//
// b0b2: modulator feedback level;
// b3: modulator waveform selection;
// b4: carrier waveform selection;
// b5: [unused]
// b6b7: carrier key-scale level.
channels_[channel].modulator_feedback = instrument[3] & 7;
channels_[channel].modulator_waveform = Waveform((instrument[3] >> 3) & 1);
channels_[channel].carrier_waveform = Waveform((instrument[3] >> 4) & 1);
carrier_scaler.set_key_scaling_level(instrument[3] >> 6);
// Bytes 4 (modulator) and 5 (carrier):
//
// b0b3: decay rate;
// b4b7: attack rate.
modulator_envelope.set_decay_rate(instrument[4] & 0xf);
modulator_envelope.set_attack_rate(instrument[4] >> 4);
carrier_envelope.set_decay_rate(instrument[5] & 0xf);
carrier_envelope.set_attack_rate(instrument[5] >> 4);
// Bytes 6 (modulator) and 7 (carrier):
//
// b0b3: release rate;
// b4b7: sustain level.
modulator_envelope.set_release_rate(instrument[6] & 0xf);
modulator_envelope.set_sustain_level(instrument[6] >> 4);
carrier_envelope.set_release_rate(instrument[7] & 0xf);
carrier_envelope.set_sustain_level(instrument[7] >> 4);
}
void OPLL::set_use_sustain(int channel) {
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
envelope_generators_[channel + 0].set_use_sustain_level((instrument[1] & 0x20) || channels_[channel].use_sustain);
envelope_generators_[channel + 9].set_use_sustain_level((instrument[0] & 0x20) || channels_[channel].use_sustain);
}
// MARK: - Output generation.
void OPLL::set_sample_volume_range(std::int16_t range) {
total_volume_ = range;
}
void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) {
// Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency;
// unlike the OPL2 the OPLL time-divides the output for 'mixing'.
const int update_period = 72 / audio_divider_;
const int channel_output_period = 4 / audio_divider_;
// TODO: the conditional below is terrible. Fix.
while(number_of_samples--) {
if(!audio_offset_) update_all_channels();
*target = output_levels_[audio_offset_ / channel_output_period];
++target;
audio_offset_ = (audio_offset_ + 1) % update_period;
}
}
void OPLL::update_all_channels() {
oscillator_.update();
// Update all phase generators. That's guaranteed.
for(int c = 0; c < 18; ++c) {
phase_generators_[c].update(oscillator_);
}
// Update the ADSR envelopes that are guaranteed to be melodic.
for(int c = 0; c < 6; ++c) {
envelope_generators_[c + 0].update(oscillator_);
envelope_generators_[c + 9].update(oscillator_);
}
#define VOLUME(x) int16_t(((x) * total_volume_) >> 12)
if(rhythm_mode_enabled_) {
// Advance the rhythm envelope generators.
for(int c = 0; c < 6; ++c) {
rhythm_envelope_generators_[c].update(oscillator_);
}
// Fill in the melodic channels.
output_levels_[3] = VOLUME(melodic_output(0));
output_levels_[4] = VOLUME(melodic_output(1));
output_levels_[5] = VOLUME(melodic_output(2));
output_levels_[9] = VOLUME(melodic_output(3));
output_levels_[10] = VOLUME(melodic_output(4));
output_levels_[11] = VOLUME(melodic_output(5));
// Bass drum, which is a regular FM effect.
output_levels_[2] = output_levels_[15] = VOLUME(bass_drum());
oscillator_.update_lfsr();
// Tom tom, which is a single operator.
output_levels_[1] = output_levels_[14] = VOLUME(tom_tom());
oscillator_.update_lfsr();
// Snare.
output_levels_[6] = output_levels_[16] = VOLUME(snare_drum());
oscillator_.update_lfsr();
// Cymbal.
output_levels_[7] = output_levels_[17] = VOLUME(cymbal());
oscillator_.update_lfsr();
// High-hat.
output_levels_[0] = output_levels_[13] = VOLUME(high_hat());
oscillator_.update_lfsr();
// Unutilised slots.
output_levels_[8] = output_levels_[12] = 0;
oscillator_.update_lfsr();
} else {
for(int c = 6; c < 9; ++c) {
envelope_generators_[c + 0].update(oscillator_);
envelope_generators_[c + 9].update(oscillator_);
}
// All melodic. Fairly easy.
output_levels_[0] = output_levels_[1] = output_levels_[2] =
output_levels_[6] = output_levels_[7] = output_levels_[8] =
output_levels_[12] = output_levels_[13] = output_levels_[14] = 0;
output_levels_[3] = VOLUME(melodic_output(0));
output_levels_[4] = VOLUME(melodic_output(1));
output_levels_[5] = VOLUME(melodic_output(2));
output_levels_[9] = VOLUME(melodic_output(3));
output_levels_[10] = VOLUME(melodic_output(4));
output_levels_[11] = VOLUME(melodic_output(5));
output_levels_[15] = VOLUME(melodic_output(6));
output_levels_[16] = VOLUME(melodic_output(7));
output_levels_[17] = VOLUME(melodic_output(8));
}
#undef VOLUME
// TODO: batch updates of the LFSR.
}
// TODO: verify attenuation scales pervasively below.
#define ATTENUATION(x) ((x) << 7)
int OPLL::melodic_output(int channel) {
// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value.
auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output);
carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
// Get the modulator's new value.
auto modulation = WaveformGenerator<period_precision>::wave(channels_[channel].modulator_waveform, phase_generators_[channel + 9].phase());
modulation += envelope_generators_[channel + 9].attenuation() + (channels_[channel].modulator_attenuation << 5) + key_level_scalers_[channel + 9].attenuation();
// Apply feedback, if any.
phase_generators_[channel + 9].apply_feedback(channels_[channel].modulator_output, modulation, channels_[channel].modulator_feedback);
channels_[channel].modulator_output = modulation;
return carrier.level();
}
int OPLL::bass_drum() {
// Use modulator 6 and carrier 6, attenuated as per the bass-specific envelope generators and the attenuation level for channel 6.
auto modulation = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6 + 9].phase());
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
auto carrier = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6].scaled_phase(), modulation);
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + ATTENUATION(channels_[6].attenuation);
return carrier.level();
}
int OPLL::tom_tom() {
// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation.
auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
tom_tom += ATTENUATION(channels_[8].instrument);
return tom_tom.level();
}
int OPLL::snare_drum() {
// Use modulator 7 and the carrier attenuation level for channel 7.
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
snare += ATTENUATION(channels_[7].attenuation);
return snare.level();
}
int OPLL::cymbal() {
// Use modulator 7, carrier 8 and the attenuation level for channel 8.
LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
cymbal += ATTENUATION(channels_[8].attenuation);
return cymbal.level();
}
int OPLL::high_hat() {
// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation.
LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation();
high_hat += ATTENUATION(channels_[7].instrument);
return high_hat.level();
}
#undef ATTENUATION

131
Components/OPx/OPLL.hpp Normal file
View File

@@ -0,0 +1,131 @@
//
// OPLL.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef OPLL_hpp
#define OPLL_hpp
#include "Implementation/OPLBase.hpp"
#include "Implementation/EnvelopeGenerator.hpp"
#include "Implementation/KeyLevelScaler.hpp"
#include "Implementation/PhaseGenerator.hpp"
#include "Implementation/LowFrequencyOscillator.hpp"
#include "Implementation/WaveformGenerator.hpp"
#include <atomic>
namespace Yamaha {
namespace OPL {
class OPLL: public OPLBase<OPLL> {
public:
/// Creates a new OPLL or VRC7.
OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false);
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
// rhythm mode, but it's correct for melodic output.
double get_average_output_peak() const { return 0.5; }
/// Reads from the OPL.
uint8_t read(uint16_t address);
private:
friend OPLBase<OPLL>;
void write_register(uint8_t address, uint8_t value);
int audio_divider_ = 0;
int audio_offset_ = 0;
std::atomic<int> total_volume_;
int16_t output_levels_[18];
void update_all_channels();
int melodic_output(int channel);
int bass_drum();
int tom_tom();
int snare_drum();
int cymbal();
int high_hat();
static constexpr int period_precision = 9;
static constexpr int envelope_precision = 7;
// Standard melodic phase and envelope generators;
//
// These are assigned as:
//
// [x], 0 <= x < 9 = carrier for channel x;
// [x+9] = modulator for channel x.
//
PhaseGenerator<period_precision> phase_generators_[18];
EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
KeyLevelScaler<period_precision> key_level_scalers_[18];
// Dedicated rhythm envelope generators and attenuations.
EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
enum RhythmIndices {
HighHat = 0,
Cymbal = 1,
TomTom = 2,
Snare = 3,
BassCarrier = 4,
BassModulator = 5
};
// Channel specifications.
struct Channel {
int octave = 0;
int period = 0;
int instrument = 0;
int attenuation = 0;
int modulator_attenuation = 0;
Waveform carrier_waveform = Waveform::Sine;
Waveform modulator_waveform = Waveform::Sine;
int carrier_key_rate_scale_multiplier = 0;
int modulator_key_rate_scale_multiplier = 0;
LogSign modulator_output;
int modulator_feedback = 0;
bool use_sustain = false;
} channels_[9];
// The low-frequency oscillator.
LowFrequencyOscillator oscillator_;
bool rhythm_mode_enabled_ = false;
bool is_vrc7_ = false;
// Contains the current configuration of the custom instrument.
uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
// Helpers to push per-channel information.
/// Pushes the current octave and period to channel @c channel.
void set_channel_period(int channel);
/// Installs the appropriate instrument on channel @c channel.
void install_instrument(int channel);
/// Sets whether the sustain level is used for channel @c channel based on its current instrument
/// and the user's selection.
void set_use_sustain(int channel);
/// @returns The 8-byte definition of instrument @c instrument.
const uint8_t *instrument_definition(int instrument, int channel);
};
}
}
#endif /* OPLL_hpp */

View File

@@ -39,9 +39,9 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &
void SN76489::set_sample_volume_range(std::int16_t range) {
// Build a volume table.
double multiplier = pow(10.0, -0.1);
double volume = static_cast<float>(range) / 4.0f; // As there are four channels.
double volume = float(range) / 4.0f; // As there are four channels.
for(int c = 0; c < 16; ++c) {
volumes_[c] = (int)round(volume);
volumes_[c] = int(round(volume));
volume *= multiplier;
}
volumes_[15] = 0;
@@ -65,7 +65,7 @@ void SN76489::write(uint8_t value) {
if(value & 0x80) {
channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
} else {
channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
channels_[channel].divider = uint16_t((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
}
} else {
// writes to the noise register always reset the shifter
@@ -77,7 +77,7 @@ void SN76489::write(uint8_t value) {
noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
}
channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
channels_[3].divider = uint16_t(0x10 << (value & 3));
// Special case: if these bits are both set, the noise channel should track channel 2,
// which is marked with a divider of 0xffff.
if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
@@ -86,12 +86,12 @@ void SN76489::write(uint8_t value) {
});
}
bool SN76489::is_zero_level() {
bool SN76489::is_zero_level() const {
return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
}
void SN76489::evaluate_output_volume() {
output_volume_ = static_cast<int16_t>(
output_volume_ = int16_t(
channels_[0].level * volumes_[channels_[0].volume] +
channels_[1].level * volumes_[channels_[1].volume] +
channels_[2].level * volumes_[channels_[2].volume] +

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