1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-03 16:16:32 +00:00

Compare commits

...

902 Commits

Author SHA1 Message Date
Thomas Harte
3d2490b774 Merge pull request #869 from TomHarte/OricReads
Flips conditionals to ensure 65802 safety.
2021-02-02 21:03:08 -05:00
Thomas Harte
1e041f1adf Flips conditionals to ensure 65802 safety. 2021-02-02 20:52:34 -05:00
Thomas Harte
4fdf01a1a8 Merge pull request #868 from TomHarte/ElectronSCSI
Adds Electron hard disk support.
2021-02-02 20:43:08 -05:00
Thomas Harte
beb514b231 Adds an additional mapping for copy. 2021-02-02 20:37:15 -05:00
Thomas Harte
f57e897085 Corrects visibility of SCSI output. 2021-02-02 20:24:39 -05:00
Thomas Harte
2a8e8a4982 Slightly increases logging. 2021-02-02 20:24:19 -05:00
Thomas Harte
9f202d4238 Adds SCSI interrupt support. 2021-02-01 17:40:11 -05:00
Thomas Harte
1a40cc048e Niceties: include AP6 ROM for hard-disk users; show SCSI activity indicator. 2021-01-31 21:41:11 -05:00
Thomas Harte
53514c7fdc Ensures non-breakage of Qt interface. 2021-01-31 21:28:55 -05:00
Thomas Harte
274b3c7d24 Handles SCSI changes on-demand. 2021-01-31 21:24:54 -05:00
Thomas Harte
07df7572b3 Switch to preferred Acorn-world extension: DAT. 2021-01-31 21:03:09 -05:00
Thomas Harte
906b6ccdb7 This appears to be sufficient for the Electron to _read_ SCSI.
So that's step one.
2021-01-31 18:36:29 -05:00
Thomas Harte
f1ba040dd8 This is probably how Acorn hard disk images look (?) 2021-01-31 16:00:52 -05:00
Thomas Harte
8db289e229 Adds some notes-to-self on SCSI and a route to using Acorn's ADFS. 2021-01-31 13:12:59 -05:00
Thomas Harte
8142487d57 Merge pull request #867 from TomHarte/ElectronStarCommand
Pause longer for Electron commands that start with a modifier.
2021-01-31 12:34:19 -05:00
Thomas Harte
2860be7068 Permit a longer pause at startup for Electron commands that start with shift, control or func. 2021-01-31 12:25:22 -05:00
Thomas Harte
7e720e754b Merge pull request #866 from TomHarte/ElectronUI
Adds UI for the new Electron configuration options.
2021-01-31 11:44:47 -05:00
Thomas Harte
41a618c957 Adds new Electron configuration options to the Qt UI. 2021-01-31 10:13:32 -05:00
Thomas Harte
3d85e6bb97 Adds Mac UI for new Electron configuration options. 2021-01-31 09:49:51 -05:00
Thomas Harte
d54085c7fd Merge pull request #865 from TomHarte/ADL
Electron: adds support for the ADL file format, and logic for AP6 and sideways RAM selection
2021-01-31 09:37:24 -05:00
Thomas Harte
0bb8bdf938 Switch to O(1) test, which avoids an extra #include. 2021-01-30 23:33:03 -05:00
Thomas Harte
865058b8d6 Adds basic text search to achieve AP6 detection. 2021-01-30 23:32:04 -05:00
Thomas Harte
b6bc0a21fb Adds a TODO on intended logic around the AP6 ROM.
... plus a promise as to intent in the Electron-specific ROM readme.
2021-01-30 23:20:43 -05:00
Thomas Harte
8311ac4a7c Adds parsing of the top-level directory for ADFS images. 2021-01-30 23:10:59 -05:00
Thomas Harte
4636d8dfb7 Adds support for installing the AP6 ROM and/or sideways RAM. 2021-01-30 19:38:19 -05:00
Thomas Harte
ac95e4d758 Adds support for ADL-format disk images. 2021-01-30 18:39:29 -05:00
Thomas Harte
4bd6ffa9e4 Merge pull request #863 from TomHarte/DecodersAhoy
Sketches out the concept of a `Decoder`
2021-01-15 21:14:49 -05:00
Thomas Harte
9c2c918760 Better sorts by function, corrects TEST description. 2021-01-15 21:07:02 -05:00
Thomas Harte
47d20699d8 Completes list, ensures POP acts as documented. 2021-01-15 20:48:31 -05:00
Thomas Harte
e8ce70dccb Chips further away at documentation. 2021-01-15 18:52:59 -05:00
Thomas Harte
fa4938f29c Establishes the reason I'm sort-of documenting these. 2021-01-15 18:27:55 -05:00
Thomas Harte
ddb4bb1421 Better plans project layout. 2021-01-15 18:16:01 -05:00
Thomas Harte
ca94e9038e Introduces 'far' test, fixes parsing. 2021-01-14 22:15:38 -05:00
Thomas Harte
2c72a77a25 Adds byte-by-byte decoder test; corrects divergences. 2021-01-13 21:51:18 -05:00
Thomas Harte
8c0e06e645 Adds a test for 0x83 and fixes sign extension.
ODA doesn't seem to accept 0x82, but testing 0x83 adds some confidence.
2021-01-13 20:42:21 -05:00
Thomas Harte
a24ae727a7 Takes a run at 0x82 and 0x83, completing the set. 2021-01-13 20:29:44 -05:00
Thomas Harte
5058a8b96a Completes the first test stream.
... and improves decoding consistency in conjunction.
2021-01-12 21:49:22 -05:00
Thomas Harte
762ecab3aa Adds operand/displacement capture.
This gets unit test as far as a disagreement over how to handle bad 0xc4 suffixes.
2021-01-10 22:55:25 -05:00
Thomas Harte
9ba5b7c1d4 Adds a few more asserts.
It's still just operands and displacements failing, which is nice.
2021-01-08 23:21:01 -05:00
Thomas Harte
5f807b6e47 Ensures that the operand is the only thing failing in decoding of the first instruction. 2021-01-08 23:02:06 -05:00
Thomas Harte
718f950071 Implements 80 and 81. 2021-01-08 22:50:59 -05:00
Thomas Harte
68fe16a092 Marks intent for operand/displacement. 2021-01-08 22:45:27 -05:00
Thomas Harte
97a64db5e0 Edges closer towards full x86 recording. 2021-01-08 22:38:56 -05:00
Thomas Harte
86577b772b Rethinks size; packs all captured information into an x86 Instruction.
Albeit that operand and displacement are't yet captured. Or extractable.
2021-01-08 22:22:07 -05:00
Thomas Harte
306df7554e Starts trying to find a good packing for X86 instructions.
To consider: do I really need `size` on every instruction?
2021-01-08 21:33:01 -05:00
Thomas Harte
30c2c0f050 Attempts to complete operand recognition. 2021-01-07 21:59:00 -05:00
Thomas Harte
205649cac2 Decodes 8e. 2021-01-07 21:36:05 -05:00
Thomas Harte
fd49b72e31 Simplifies macros, implements d0, d1, d2 and d3. 2021-01-07 21:30:01 -05:00
Thomas Harte
995904993d Fills in 8f, c2, c3, ca and cb.
Also switches to RETN and RETF for near/far RET as this seems idiomatic.
2021-01-06 21:18:24 -05:00
Thomas Harte
17cbba85fc Formalises what's missing in terms of opcodes and fills in some blanks. 2021-01-05 21:47:12 -05:00
Thomas Harte
9d7d45338f Ostensibly gets the instruction stream correct for test case 1.
Subject to operand and displacement currently being absent, and likely inconsistencies in field population, most of which are omitted from the Instruction anyway.
2021-01-05 21:34:35 -05:00
Thomas Harte
3b55d3f158 Nudges up to a need to decode operation from the ModRegRM byte. 2021-01-05 21:25:12 -05:00
Thomas Harte
fda2293d6b Improves constness. 2021-01-04 22:36:39 -05:00
Thomas Harte
d4095b1b3b Merge branch 'master' into DecodersAhoy 2021-01-03 20:56:47 -05:00
Thomas Harte
ed41154338 Merge pull request #862 from MaddTheSane/madds-patch-1
Madd's improvements
2021-01-03 20:53:39 -05:00
Thomas Harte
38bca5f0f0 Finally runs into the wall of trying to merge operands and offsets. 2021-01-03 20:08:13 -05:00
Thomas Harte
a8738b533a Switch for now to block-level decoding.
It's easier to step debug.
2021-01-03 20:07:46 -05:00
Thomas Harte
29cf96c703 Adds decoding of disp16 RETs. 2021-01-03 19:39:28 -05:00
Thomas Harte
782dc3d046 Distinguishes inter- and intra-segment RET. 2021-01-03 19:37:37 -05:00
Thomas Harte
0ae217f51d Improves exposition, adds decoding of the 0xbx patch of MOVs. 2021-01-03 19:33:16 -05:00
Thomas Harte
adcb2e03e8 Attempts to consolidate source/destination ordering. 2021-01-03 17:28:29 -05:00
Thomas Harte
11b6c1d4b5 Proceeds to three instructions correctly decoded. 'Wow'. 2021-01-03 17:03:50 -05:00
Thomas Harte
367cb1789d Starts building an x86 test. 2021-01-03 16:37:35 -05:00
Thomas Harte
adf1484ecc Introduces third test sequence, uneventfully. 2021-01-03 16:21:23 -05:00
Thomas Harte
5401ff6c78 Proactively fixes li sign extension. 2021-01-03 11:14:43 -05:00
Thomas Harte
eb8d0eefd5 Factors out some boilerplate and introduces second sequence. 2021-01-03 11:14:30 -05:00
Thomas Harte
c934e22cee Introduces a first test of PowerPC decoding.
Corrected as a result: the bcx conditional, that stdu is 64-bit only, extraction of the li field.
2021-01-02 22:47:42 -05:00
Thomas Harte
1a3effc692 Modifies contract again. This is why I'm doing this now. 2021-01-02 21:19:45 -05:00
Thomas Harte
32c942d154 Muddles drunkenly towards decoding ModRM. 2021-01-02 21:11:19 -05:00
Thomas Harte
9c5dc0ed29 Deferring ModRM work, proceeds to 0x9f. 2021-01-02 19:29:43 -05:00
Thomas Harte
290972cedf Adds health warning. 2021-01-02 19:16:21 -05:00
Thomas Harte
dc9d370952 Does the easier part of the easier half of 8086 decoding. 2021-01-02 19:16:07 -05:00
Thomas Harte
a41be61f99 Slightly fleshes out models, for a sensible beginning. 2021-01-01 17:36:47 -05:00
Thomas Harte
3d1783ddae Add exposition as to the purpose of decoders. 2021-01-01 17:32:57 -05:00
Thomas Harte
8151c8e409 Rounds out field list. 2021-01-01 16:38:40 -05:00
Thomas Harte
0ef42f93ff Further rounds out decoder. 2021-01-01 11:46:26 -05:00
Thomas Harte
d318ab4e70 Edges further onwards. 2020-12-31 21:12:36 -05:00
Thomas Harte
ebfa35c2c7 Conquers another page of instructions; adds supervisor flag. 2020-12-31 18:14:38 -05:00
Thomas Harte
db50b0fe23 Gets started on 6+10 decoding, places stake as to other fields. 2020-12-31 16:51:31 -05:00
Thomas Harte
233a69a1d8 Decodes operations for the simplest 45. 2020-12-31 16:02:52 -05:00
C.W. Betts
3749b7b776 My improvements:
Use synthesized properties for CSMissingROM.
Remove openGLView from the xib: that will quiet a warning.
Add nullability metadata to CSStaticAnalyser.
2020-12-31 13:23:46 -07:00
Thomas Harte
ed63e7ea75 Starts building out a PowerPC decoder. 2020-12-30 22:55:59 -05:00
Thomas Harte
3d79b11f92 Merge pull request #861 from TomHarte/DiskIIOtherROM
Ensure proper in-memory ordering of the b72a2c70 ROM.
2020-12-29 22:13:24 -05:00
Thomas Harte
dfe4e49110 Ensure proper in-memory ordering of the b72a2c70 ROM. 2020-12-29 22:08:48 -05:00
Thomas Harte
c5c56f9d05 Mention my manual list sorting. 2020-12-23 11:15:57 -04:00
Thomas Harte
9f0129cab8 Merge pull request #859 from MaddTheSane/gcJoystick
Initial GameController joystick support.
2020-12-16 21:39:28 -04:00
C.W. Betts
5a48e50355 Use isEqual: to compare GCController when connecting/disconnecting.
Only remove observers for GCController notifications.
2020-12-14 15:41:11 -07:00
C.W. Betts
86283b1815 Actually write the setup code. 2020-12-14 01:14:40 -07:00
C.W. Betts
a38d964f62 Initial GameController joystick support. 2020-12-13 11:23:33 -07:00
Thomas Harte
2f86d5ebaf Merge pull request #858 from TomHarte/M1ForLife
Corrects Metal buffer sizing on Retina displays.
2020-12-09 19:18:56 -05:00
Thomas Harte
b589d6e3ef Fixes retina-display buffer size. 2020-12-09 18:51:10 -05:00
Thomas Harte
db8b265e80 Enable M1 release builds. 2020-12-09 18:38:14 -05:00
Thomas Harte
8560b38ffa Reduce to less-daunting URL. 2020-12-09 16:38:59 -05:00
Thomas Harte
94eb17db0c Add sponsorship exposition; improve general wording 2020-12-08 16:35:00 -05:00
Thomas Harte
9577c8e27f Experiment with F 2020-12-08 16:08:25 -05:00
Thomas Harte
32ccce3040 Merge pull request #855 from TomHarte/QtNoKeyboardCopy
Qt: don't copy the result of get_keyboard().
2020-11-29 00:05:26 -05:00
Thomas Harte
ab3fcb3ea0 Qt: don't copy the result of get_keyboard(). 2020-11-29 00:01:11 -05:00
Thomas Harte
9610672615 Merge pull request #854 from TomHarte/OpenGLNoColourBurst
Avoids all risk of infinities when there is no colour burst
2020-11-28 23:54:29 -05:00
Thomas Harte
5ee9630624 Use compositeAmplitude in favour of its reciprocal. 2020-11-28 19:53:34 -05:00
Thomas Harte
1b3836eb1c Adds an overt branch for mono/colour composite selection. 2020-11-28 19:47:04 -05:00
Thomas Harte
1302a046e9 Merge branch 'OpenGLNoColourBurst' of github.com:TomHarte/CLK into OpenGLNoColourBurst 2020-11-28 17:19:42 -05:00
Thomas Harte
33dec3c220 Given that lineCompositeAmplitude is not normalised, ups threshold. 2020-11-28 17:19:28 -05:00
Thomas Harte
7c29c3a944 Given that lineCompositeAmplitude is not normalised, ups threshold. 2020-11-28 17:13:18 -05:00
Thomas Harte
c9ca1fc7a0 Merge pull request #853 from TomHarte/AppleIIReset
Improves Apple II keyboard input, especially under SDL.
2020-11-28 12:43:32 -05:00
Thomas Harte
a965c8de9f Resolves intended reset_all_keys. 2020-11-27 21:53:34 -05:00
Thomas Harte
0b4b271e3d Pulls out redundant check. 2020-11-27 21:04:20 -05:00
Thomas Harte
5fc6dd1a4d Regresses macOS deployment target for kiosk mode to avoid OpenGL warning. 2020-11-27 21:02:04 -05:00
Thomas Harte
79ef026b93 Allows machines to declare a preference for logical input.
It's only a preference, and the Apple II does prefer it.
2020-11-27 21:00:48 -05:00
Thomas Harte
a4ab5b0b49 Does a better job of ensuring sensible key mappings. 2020-11-27 20:49:38 -05:00
Thomas Harte
3207183f05 Merge pull request #850 from TomHarte/BigSurAgain
Takes a second stab at resolving Big Sur File -> New...
2020-11-13 20:02:29 -05:00
Thomas Harte
e803f993b7 Increases minimum macOS version to 10.14.
This is lazy, but it means I definitely don't need non-Metal fallback code.
2020-11-13 19:48:45 -05:00
Thomas Harte
5dbc87caf0 Smarter: just ensures any attached panels are closed at close(). 2020-11-13 19:09:30 -05:00
Thomas Harte
4862ccc947 Dismisses ROM requester upon that cancel too. 2020-11-13 19:01:53 -05:00
Thomas Harte
e1ecf66485 Dismisses sheet before closing document. 2020-11-13 19:00:37 -05:00
Thomas Harte
2c71ba0744 Ameliorates against a potential NSRangeException. 2020-11-13 18:29:48 -05:00
Thomas Harte
a7aeb779e9 Disables Apple Silicon binaries until I have some means to test. 2020-11-13 18:07:45 -05:00
Thomas Harte
e72cfbf447 Stop assuming that NSNotification => window.isVisible. 2020-11-13 18:04:31 -05:00
Thomas Harte
5d154e3d0c Merge pull request #849 from TomHarte/BigSur
Slightly tweaks machine picker for macOS Big Sur.
2020-11-13 12:37:36 -05:00
Thomas Harte
86a24cc928 Allows Xcode to bump its versioning on the ROM requester too. 2020-11-13 12:23:48 -05:00
Thomas Harte
e8b52d20e9 Slightly tweaks machine picker for macOS Big Sur. 2020-11-13 12:14:35 -05:00
Thomas Harte
b5fa574686 Merge pull request #847 from TomHarte/MetalTones
Corrects R4G4B4 and R2G2B2 output in Metal.
2020-11-07 23:22:36 -05:00
Thomas Harte
7aea3dc124 Corrects R4G4B4 and R2G2B2 output. 2020-11-07 23:15:48 -05:00
Thomas Harte
3aa47f9c68 Merge pull request #843 from TomHarte/MoreDormann
Introduces a build of Dormann's 65C02 tests that is 65816 compatible.
2020-10-19 21:13:46 -04:00
Thomas Harte
ab07814614 Eliminates now-broken 65816 flow test. 2020-10-19 21:02:46 -04:00
Thomas Harte
1653abdf88 Adds the .lst; otherwise I'll probably just lose it. 2020-10-19 20:58:24 -04:00
Thomas Harte
b3ab9fff9b Imports a custom-built copy of Klaus Dormann's 65C02 test, with only 65816-compatible parts.
Thereby fixes another couple of 65816 issues — BRK(, etc) not clearing the decimal flag, and `TRB d` being mismapped.
2020-10-19 19:27:16 -04:00
Thomas Harte
14718b93a4 Improve commentary. 2020-10-19 09:32:50 -04:00
Thomas Harte
69450e27ad Merge pull request #839 from TomHarte/65816
Adds emulation of the 65816.
2020-10-18 21:49:46 -04:00
Thomas Harte
0cd08aa79d Permits the Oric analyser to check CPC-style DSKs.
Oric Mist seems to use that format, so some of these now exist out in the wild.
2020-10-18 21:44:44 -04:00
Thomas Harte
1fa94e1b08 Adds the 65816 as an in-code option for Oric emulation.
This also means it'll be exposed via the SDL build, but that's okay.
2020-10-18 21:43:08 -04:00
Thomas Harte
76d9893866 Declares address-bus sizes formally.
This allows me to fix the final two implicit conversion warnings, albeit that it would have been nice to find a templatey way just to get the type directly from the declaration of `perform_bus_operation`.
2020-10-18 15:08:21 -04:00
Thomas Harte
c3f8982c62 Resolves all internal implicit type-conversion warnings.
Chasing those down, it looks like flags were wrong for PLB and PLD. So it's official: warnings help.
2020-10-18 14:55:17 -04:00
Thomas Harte
99eba2f8ba Ensures intended 65816 exception behaviour.
i.e. the relevant micro-op sequence exists, and its operation isn't lost. Also sets the 65816 by default to jump straight into power-on, not to execute an instruction first. That shouldn't make a functional difference, but it makes debugging easier because it makes startup fully deterministic.
2020-10-18 14:43:47 -04:00
Thomas Harte
69509f6502 Attempts to bring a little more consistency to my use of Swift in test code. 2020-10-17 22:42:54 -04:00
Thomas Harte
c3187fdbe1 Makes minor formatting improvement. 2020-10-17 22:31:51 -04:00
Thomas Harte
42228ea955 Adds 65C02As6502 test, to round out the set. 2020-10-17 22:31:32 -04:00
Thomas Harte
e5f57ea743 Make isReadOperation more overt. 2020-10-17 22:27:04 -04:00
Thomas Harte
3b398f7a9a Attempts to complete all 65816 bus signalling. 2020-10-16 21:56:20 -04:00
Thomas Harte
096add7551 Exposes non-BusOperation bus outputs. 2020-10-16 21:05:42 -04:00
Thomas Harte
334e0666b7 Reports ::Ready upon a WAI. 2020-10-15 21:37:37 -04:00
Thomas Harte
98c81749c8 Adds the conventional flush. 2020-10-15 21:36:04 -04:00
Thomas Harte
5dcf720bb5 Extends list of BusOperations.
Now to retest, widely.
2020-10-15 21:35:01 -04:00
Thomas Harte
9c0c0255f6 Ensures data/program bank can't accidentally be set to 16-bit values. 2020-10-15 21:10:32 -04:00
Thomas Harte
68c15bd605 Updates Qt project; catches another couple of issues via its compiler. 2020-10-15 21:09:22 -04:00
Thomas Harte
9a2f32795f Revokes stack-local storage non-optimisation. 2020-10-15 21:03:10 -04:00
Thomas Harte
7aa6cf4c6b Tidies up layout very slightly. 2020-10-15 20:51:23 -04:00
Thomas Harte
dfda2adf0d Attempts implementations of both ready and abort.
Which I think concludes the inputs?
2020-10-15 20:46:18 -04:00
Thomas Harte
c0a1c34012 Wraps all registers into a struct, so that I can implement abort.
Makes some preparations for ready too.
2020-10-15 18:42:38 -04:00
Thomas Harte
3c6adc1ff4 Completes 65816 addressing mode tests and corresponding fixes. 2020-10-14 22:00:52 -04:00
Thomas Harte
e511d33a7c Adds test for [d], y; fixes implementation. 2020-10-14 21:42:41 -04:00
Thomas Harte
c35969d677 Adds tests for (d, x) and (d), y. Both amply tested in emulation mode, so no problems.
Five to go, all potentially troublesome.
2020-10-14 21:38:00 -04:00
Thomas Harte
27afb8f0a7 Adds direct indirect long test, and thereby fixes addressing mode.
Nine to go!
2020-10-14 21:26:20 -04:00
Thomas Harte
327ab81436 Fills in direct, x and (direct) tests, fixing implementation of the latter.
10 to go.
2020-10-14 21:17:28 -04:00
Thomas Harte
db7178495f Implements direct and final absolute test.
14 to go.
2020-10-14 20:57:47 -04:00
Thomas Harte
979186e71d Transcribes the English-language versions of the outstanding tests.
Passing these will make me willing to call the 65816 functionality provisionally done, other than making sureI signal VPA, VDA, VPB, etc, correctly.
2020-10-14 13:56:37 -04:00
Thomas Harte
f05e0d956b Adds a TODO list in order to keep an end in sight. 2020-10-13 21:43:42 -04:00
Thomas Harte
b22aa5d699 Starts transcribing the addressing examples I have into tests.
Correspondingly extends the exposed register set and test-machine addressing range.
2020-10-13 21:38:30 -04:00
Thomas Harte
3e6a2adaaf Corrects absolute, x and absolute, y addressing modes. 2020-10-13 20:30:39 -04:00
Thomas Harte
8f5537aaaa Attempts to resolve my direct-indirect addressing stumble. 2020-10-13 20:21:53 -04:00
Thomas Harte
a15d4a156b Starts trying to ensure appropriate address wrapping. 2020-10-12 22:33:43 -04:00
Thomas Harte
6a47571d17 Stops truncating tests by two bytes.
Not that it seems to have been problematic.
2020-10-12 21:53:27 -04:00
Thomas Harte
7479dc74ed Removes printf. It's no longer telling me anything. 2020-10-12 21:52:58 -04:00
Thomas Harte
28da1a724a Introduces Jeek816 test case. 2020-10-12 21:43:44 -04:00
Thomas Harte
f529eadbec Corrects 16-bit read-modify-write.
Subject to the TODO proviso on 'correct'; has my 6502 prejudice pushed me into unrealistic bus signalling?
2020-10-12 18:36:09 -04:00
Thomas Harte
5dc3cd3a2f Starts using Jeek816 for a basic native-mode audit. Fixes absolute long addressing. 2020-10-11 22:02:46 -04:00
Thomas Harte
3039a445f0 Ups the 65816 test machine to a full 16mb RAM. 2020-10-11 21:18:01 -04:00
Thomas Harte
82797fd395 Attempts to do the proper thing for interrupts. 2020-10-11 21:10:44 -04:00
Thomas Harte
a0885ab7d0 Implements STP and WAI.
Albeit still without fully-implemented reactions to exceptions in general.
2020-10-11 17:56:55 -04:00
Thomas Harte
8eaf1303a3 Attempts proactively to ensure proper RTI behaviour on the 65816. 2020-10-11 15:25:13 -04:00
Thomas Harte
20cbe72985 Ties to 8- or 16-bit those instructions that aren't M/X-dependent.
This is technically redundant for PEI, PEA and PER since they have dedicated bus programs anyway, but it's good to be explicit.
2020-10-11 14:38:35 -04:00
Thomas Harte
071ad6b767 I don't think RTL is needed; JML looks like it covers it. 2020-10-10 22:16:35 -04:00
Thomas Harte
0619e49eac Takes a short at TSB and TRB.
Three to go.
2020-10-10 22:00:17 -04:00
Thomas Harte
b8848d8580 Implements TCD, TDC, TCS, TSC. 2020-10-10 21:43:05 -04:00
Thomas Harte
aface1f8be Implements XBA and XCE. 2020-10-10 21:34:22 -04:00
Thomas Harte
ae87728770 Ensures M and X are exposed to the public interface. 2020-10-10 21:33:56 -04:00
Thomas Harte
28c8ba70c1 Implements REP and SEP and exposes the MX flags generally. 2020-10-10 21:23:59 -04:00
Thomas Harte
486324ecab This test isn't actually 65816-compatible. 2020-10-10 18:19:48 -04:00
Thomas Harte
6892ac13e8 Corrects BIT. All 65816-applicable Wolfgang Lorenz tests now pass. 2020-10-10 17:47:33 -04:00
Thomas Harte
340ad093a6 Adds 65816 runs of the final tranche of applicable tests. 2020-10-10 17:26:41 -04:00
Thomas Harte
0fe09cd1e4 Knocks SBC into producing likely results; disables Lorenz testing. 2020-10-10 17:13:16 -04:00
Thomas Harte
da4702851f Fixes ADC. 2020-10-10 16:29:48 -04:00
Thomas Harte
09fba72d58 Adds flag manipulation, ADC and SBC 65816 tests.
The latter two fail.
2020-10-10 11:30:15 -04:00
Thomas Harte
d17c90edf7 Corrects ROL d, x. 2020-10-10 11:25:14 -04:00
Thomas Harte
7966592fae Corrects ROL d. 2020-10-10 11:22:23 -04:00
Thomas Harte
6efe4e1753 Fixes AND, EOR, ORA. Takes an unsuccessful shot at ROL. 2020-10-10 10:53:17 -04:00
Thomas Harte
536c4d45c1 Adds additional 65816 tests, some failing; seeks to improve carry behaviour in ASL and ROL. 2020-10-10 10:11:57 -04:00
Thomas Harte
a02f88fe7c Confirms a couple more of the easy sets. 2020-10-10 09:34:29 -04:00
Thomas Harte
d9be6ab806 Confirms that a few other simple tests work immediately on the 65816. 2020-10-09 23:26:35 -04:00
Thomas Harte
290598429a Applies indirect page zero emulation mode addressing constraint to ix addressing.
Lorenz's LDA tests now pass in emulation mode.
2020-10-09 23:22:48 -04:00
Thomas Harte
92e72959c3 Makes corrections to ix addressing mode and shift/roll flags. 2020-10-09 23:12:20 -04:00
Thomas Harte
776f014dbe Attempts LDA tests against the 65816.
Result: ix is faulty. Which we already knew.
2020-10-09 22:23:54 -04:00
Thomas Harte
c01bc784b9 Slightly reduces branching. 2020-10-09 22:21:55 -04:00
Thomas Harte
abcd86a294 Fixes accumulator instructions. 2020-10-09 22:18:22 -04:00
Thomas Harte
451f83ba51 Corrects emulation-mode read-modify-writes not to empty the data buffer. 2020-10-09 22:14:42 -04:00
Thomas Harte
b439f40fe2 Corrects INC and DEC. 2020-10-09 22:04:25 -04:00
Thomas Harte
968166b06d Resolves incorrectly flow after setting up an absolute address. 2020-10-09 21:48:35 -04:00
Thomas Harte
88293909f4 Enables running of a first test on the 65816. 2020-10-09 21:44:47 -04:00
Thomas Harte
9b6c48631d Removes usage of a JAM instruction to spot end-of-tests. 2020-10-09 21:39:34 -04:00
Thomas Harte
0ed98cbfac Attempts to fix direct indirect indexed; not yet successful I think. 2020-10-08 22:15:19 -04:00
Thomas Harte
7dde7cc743 Implements altered direct indexed addressing in emulation mode. 2020-10-08 22:02:14 -04:00
Thomas Harte
755627f12d Corrects direct addressing. 2020-10-08 20:00:01 -04:00
Thomas Harte
f8004d7096 Implements RTI, corrects TAY. 2020-10-08 18:06:11 -04:00
Thomas Harte
0418f51ef2 Takes a shot at emulation-mode 'exceptions'.
It's just RTI and correct decimal SBC left of the official 6502s now, I think.
2020-10-08 17:52:13 -04:00
Thomas Harte
054e0af071 Corrects RTS behaviour: the return address on the stack is off by one.
Dormann's tests now proceed to a BRK.
2020-10-08 16:55:45 -04:00
Thomas Harte
907c3374c3 Attempts to clean up my JMP/JSR mess.
Also takes a step forwards in decimal SBC, but it's not right yet.
2020-10-08 16:48:46 -04:00
Thomas Harte
b578240993 Adds a further error.
Clearly I've severely overloaded 'JMP' and not fully thought through where it gets its addresses from.
2020-10-07 21:47:58 -04:00
Thomas Harte
f83ee97439 PHP pushes with the BRK flag set in emulation mode. 2020-10-07 21:37:50 -04:00
Thomas Harte
19aea85184 Corrects CMP, CPX, CPY carry flags. 2020-10-07 21:23:29 -04:00
Thomas Harte
1ba0a117e7 Corrects PLB, PLD, PLP. 2020-10-07 20:23:53 -04:00
Thomas Harte
b510b9d337 Adds PHD, PHK and 8-bit PHP and PLP. 2020-10-07 20:13:12 -04:00
Thomas Harte
b608e11965 Realises that not all non-incrementing PC fetches should be thrown away. 2020-10-07 20:06:27 -04:00
Thomas Harte
e68b3a2f32 Corrects JMP program. 2020-10-07 19:59:29 -04:00
Thomas Harte
f7b119ffe1 Moves temporary logging, fixes branch instructions. 2020-10-07 19:57:58 -04:00
Thomas Harte
a4cec95db1 Corrects load and transfer flag oversights. 2020-10-07 19:36:23 -04:00
Thomas Harte
84c4fa197b Corrects DEX mapping, notes new Dormann failure case. 2020-10-07 18:48:03 -04:00
Thomas Harte
eac722cf59 Implements enough of ADC and SBC for the Dormann test definitively to fail. 2020-10-07 18:36:17 -04:00
Thomas Harte
7439a326a6 Implements BIT (in regular and immediate forms). 2020-10-07 18:15:18 -04:00
Thomas Harte
5ca1c0747f Generalises CMP to implement CPX and CPY. 2020-10-07 18:09:56 -04:00
Thomas Harte
466ca38dfa Corrects TXY and TYX; kudos to PatrickvL for the spot! 2020-10-07 18:05:42 -04:00
Thomas Harte
93b0839036 Knocks out some transfer operations.
I'm possibly only seven or eight away from being able to test with complete official-opcode-only 6502 code?
2020-10-06 22:29:34 -04:00
Thomas Harte
e068cbc103 Implements CMP and fixes a zero-flag error on 16-bit operations. 2020-10-06 21:47:26 -04:00
Thomas Harte
5c809e5fbf Implements rolls and shifts. 2020-10-06 21:34:39 -04:00
Thomas Harte
3933bf49cf Implements BRL. 2020-10-06 21:28:54 -04:00
Thomas Harte
7065ba4857 Implements the single-byte branches. 2020-10-06 21:24:43 -04:00
Thomas Harte
ebff83018e Implements the bitwise operators. 2020-10-06 20:17:03 -04:00
Thomas Harte
9ce9167e3c Formalises work left to do. 2020-10-06 19:12:19 -04:00
Thomas Harte
993eff1d3d Starts slowly, with flag manipulation. 2020-10-06 16:25:30 -04:00
Thomas Harte
7be983ec00 Slightly improve exposition. 2020-10-05 22:25:20 -04:00
Thomas Harte
18e8d6ce06 Makes an effort to factor out the 6502's [lazy] flags.
This is preparatory to deciding which instructions, if any, are worth factoring out.
2020-10-05 22:23:33 -04:00
Thomas Harte
b7ba0d4327 Attempts to complete all addressing modes.
So, if bugs didn't exist, it'd just be members of the Operation enum to go.
2020-10-05 17:04:57 -04:00
Thomas Harte
825201f4f2 Adds direct indirect. 2020-10-04 22:11:41 -04:00
Thomas Harte
9a05c68ce7 Attempts direct and direct indexed indirect. 2020-10-04 22:06:25 -04:00
Thomas Harte
d8dccf2500 Attempts a full implementation of MVN and MVP. 2020-10-04 19:21:04 -04:00
Thomas Harte
b416aa640f Slightly tidies up, eliminating some store bugs. 2020-10-04 19:12:04 -04:00
Thomas Harte
4ebf594b3b This should bring me up to absolute, y.
i.e. next is datasheet program 7.
2020-10-04 19:02:47 -04:00
Thomas Harte
8a83024962 Starts a dash towards just completing the addressing modes for now.
This brings me up to the end of absolute long (i.e. 4a on the datasheet).
2020-10-04 18:52:46 -04:00
Thomas Harte
bdc1136b96 Edges towards working short absolute addressing mode. 2020-10-03 21:30:24 -04:00
Thomas Harte
da78dea98f Merge branch 'master' into 65816 2020-10-03 21:00:29 -04:00
Thomas Harte
dcf8cb14e2 Merge pull request #842 from TomHarte/QtMouseEscape
Adds F8+F12 as an alternative mouse-release combo for Qt.
2020-10-02 20:37:46 -04:00
Thomas Harte
38912859e1 Adds F8+F12 as an alternative mouse-release combo for Qt. 2020-10-02 20:31:47 -04:00
Thomas Harte
b83d93abc2 Accepts that whether instructions do 8- or 16-bit bus accesses depends on either M or X depending on the operation. 2020-10-02 17:08:30 -04:00
Thomas Harte
36f843bc6e Ensure std::function is visible to 65816Storage.cpp. 2020-09-29 19:23:38 -04:00
Thomas Harte
15c87e02e9 Ditto for printf. 2020-09-29 18:53:02 -04:00
Thomas Harte
00923eac7c Ensure assert is visible to 65816Implementation.hpp. 2020-09-29 18:52:25 -04:00
Thomas Harte
a72ac8294c Adds 65816 alternates to Klaus Dormann's tests.
While also correcting a couple of misspellings of his name. Apologies, Klaus!
2020-09-29 18:49:58 -04:00
Thomas Harte
4f03bf754d Adds the 65816 to SConstruct. 2020-09-29 18:43:39 -04:00
Thomas Harte
78b3ec4b10 The actual work begins: starts implementing 65816 micro-ops. 2020-09-29 18:42:07 -04:00
Thomas Harte
ef1a514785 Introduces 6502Selector, for picking either a 6502 or a 65816 based on a single template parameter. 2020-09-28 21:35:46 -04:00
Thomas Harte
6635876e7e Performs a bare factoring out of the 6502 bus handler. 2020-09-28 18:43:53 -04:00
Thomas Harte
5645f90abe Takes a minor first step towards actually performing 65816 instructions. 2020-09-27 22:20:58 -04:00
Thomas Harte
b96cd4d18b Resolves another unsafe pointer assumption. 2020-09-27 22:20:13 -04:00
Thomas Harte
ad8a2e2cb9 Corrects a long-standing naming obscurity. 2020-09-27 22:19:42 -04:00
Thomas Harte
fa438e5113 Merge branch 'master' into 65816 2020-09-27 15:10:54 -04:00
Thomas Harte
8641494809 Resolve various test-case warnings. 2020-09-27 15:10:29 -04:00
Thomas Harte
f4a23af5d6 Merge pull request #840 from MaddTheSane/patch-1
Update 6502TimingTests.swift
2020-09-27 15:02:10 -04:00
Thomas Harte
5449e90b34 Edges towards offering the 65816 as another type of 6502 for testing. 2020-09-26 22:31:50 -04:00
Thomas Harte
1cd664ad85 Adds a sanity check. 2020-09-26 21:43:26 -04:00
Thomas Harte
e680022b1f Completes the opcode set.
A million bugs yet to find.
2020-09-26 21:35:31 -04:00
Thomas Harte
67c2ce2174 Takes a run at completing the stack section.
I'm not really sure about BRK though — does it gain a signature on the 65816?
2020-09-26 21:20:01 -04:00
Thomas Harte
596e700b60 Drags myself onto the final page of bus programs.
233 opcodes now complete; six bus programs to go.
2020-09-26 20:57:24 -04:00
Thomas Harte
4a53b6e538 Adds push and pull, reaching 229/256 opcodes. 2020-09-26 20:38:29 -04:00
Thomas Harte
687f4bb3bb Adds relative and relative long bus patterns.
Many of the rest cover only one or two opcodes so this puts me at 216/256 opcodes covered; 35/47 bus programs; just more than 5/7 pages.
2020-09-26 20:24:50 -04:00
Thomas Harte
473799cb62 There's not a lot to STP and WAI from a bus program point of view. 2020-09-26 20:18:30 -04:00
C.W. Betts
ce0536cdfa Update 6502TimingTests.swift 2020-09-26 16:13:27 -06:00
Thomas Harte
3dc22a9fd5 Adds implied and immediate modes.
... for 204/256 opcodes covered.
2020-09-26 17:42:42 -04:00
Thomas Harte
f54b655606 Adds d, x and d, y. 2020-09-26 17:26:17 -04:00
Thomas Harte
d2e868ea2b Adds (d), y; [d], y; and [d].
Now covered: 146/256 opcodes, 4/7 pages, 25/47 bus programs.
2020-09-26 16:55:58 -04:00
Thomas Harte
3fc649359a Transcribes the titles of all remaining bus programs.
Thereby frames the distance yet to travel.
2020-09-25 22:29:19 -04:00
Thomas Harte
1512ac11da Adds (d, x) and (d) modes. Albeit by deferring the hard work.
That's: 122/256 opcodes; 22/47 bus programs, ~3.5/7 pages transcribed. Maybe I'll be able to get to the runtime stuff sooner rather than later?
2020-09-25 22:22:30 -04:00
Thomas Harte
5039cc7bb2 Adds direct page.
... to cover 106 opcodes.
2020-09-25 22:01:36 -04:00
Thomas Harte
5360a7b4ce Adds block moves.
These are fairly specialised, dealing in two data addresses simultaneously.
2020-09-25 21:49:03 -04:00
Thomas Harte
2957a31f40 Adds absolute, x; absolute,y; and accumulator addressing modes.
Now covered: 80/256 opcodes, from 2/6 pages of the data sheet; or 16/47 bus programs.
2020-09-25 21:16:36 -04:00
Thomas Harte
8c11df52bf Adds absolute long, x.
Factors out the commonality of a closing read/write while I'm here.
2020-09-25 19:27:17 -04:00
Thomas Harte
2b7ffcd48f Takes a run at JSL al. 2020-09-25 18:35:00 -04:00
Thomas Harte
7980a9033e Adds two-thirds of absolute long.
Working total: 31 opcodes covered; 10/47ths of bus patterns.

Next is JSL, which I think will require additional operations.
2020-09-25 18:16:49 -04:00
Thomas Harte
125ddfa513 Pays a little attention to runtime storage; completes the first page of bus patterns. 2020-09-25 18:00:02 -04:00
Thomas Harte
636e929607 Adds a check for 8/16-bit redundancy. 2020-09-25 17:42:42 -04:00
Thomas Harte
22c792dc46 Adds enough logic to start serialising instructions to somewhere.
Possibly extraneous for now, but it means I can start stepping and testing.
2020-09-25 17:18:25 -04:00
Thomas Harte
95af1815c8 Completes absolute indexed indirect micro-ops.
For the record: this is just six out of forty-seven codes complete. Or about two-thirds of six pages. Plenty to do even before I start trying to interpret these things.
2020-09-24 22:37:31 -04:00
Thomas Harte
d707c5ac95 Switches to generators with stable pointers; adds 2a. 2020-09-24 22:27:20 -04:00
Thomas Harte
5c9192e5e6 Switches to generators for spitting out micro-ops.
Hopefully with a lot of parts to factor out naturally.
2020-09-24 17:36:11 -04:00
Thomas Harte
72b5584042 Immediately runs afoul of a read/write difference in the specs between 8/16-bit mode that suggests maybe this isn't a good structure.
Perhaps generators of some sort?
2020-09-23 22:28:15 -04:00
Thomas Harte
f9045b5352 Rounds out declaration of the absolutes. 2020-09-23 22:23:23 -04:00
Thomas Harte
f87fe92bc8 Begins a meandering road towards the 65816. 2020-09-23 22:14:42 -04:00
Thomas Harte
669d8e64ab Merge pull request #838 from TomHarte/MTKViewRace
Resolves a minor MTKView main-thread race condition.
2020-09-22 22:18:57 -04:00
Thomas Harte
9447aa38be Removes debugging printf. 2020-09-22 22:13:54 -04:00
Thomas Harte
a781c3eb4d Resolves thread-unsafe access of _view.bounds. 2020-09-22 22:13:37 -04:00
Thomas Harte
c0b1308dfd Merge branch 'Vic20Tests' 2020-09-22 22:08:29 -04:00
Thomas Harte
2d9dd6704a Merge branch 'master' of github.com:TomHarte/CLK 2020-09-22 22:07:47 -04:00
Thomas Harte
94dba70bbe Merge pull request #837 from TomHarte/Vic20Tests
Further improves 6522 emulation.
2020-09-22 22:07:34 -04:00
Thomas Harte
022ec20e75 Tries to add semantic meaning to the various auxiliary control fields.
To consider: decoding at set?
2020-09-22 20:50:39 -04:00
Thomas Harte
41f69405d8 Don't decrement timer 1 from the system clock when in PB6 mode.
TODO: rest of PB6 mode.
2020-09-21 22:39:49 -04:00
Thomas Harte
5741e22e29 Switch back to debug-by-default builds. 2020-09-20 18:22:13 -04:00
Thomas Harte
8e242eea54 Ensures timer-linked PB7 output is actually output. 2020-09-20 15:03:26 -04:00
Thomas Harte
703065a0a5 Takes a run at timer-linked PB7 output behaviour.
Seemingly sufficiently to pass the VICE test (which I've transcribed), though with some guesswork.
2020-09-20 14:51:59 -04:00
Thomas Harte
291aa42fe1 Corrects test target. 2020-09-19 21:20:37 -04:00
Thomas Harte
8fc3496cc9 Merge pull request #836 from TomHarte/Vic20Tests
Corrects a couple of minor VIA timer issues
2020-09-17 21:56:43 -04:00
Thomas Harte
e807a462a1 My new reading is that only a write to the counter should affect the interrupt flag. 2020-09-17 21:31:29 -04:00
Thomas Harte
18790a90ae Ensures timer 2 doesn't use timed behaviour when in pulse mode. 2020-09-17 21:09:32 -04:00
Thomas Harte
21afc70261 Adds formal data-sheet names. 2020-09-17 19:00:46 -04:00
Thomas Harte
7bb74af478 Merge pull request #835 from TomHarte/ErrantScan
Allows for permitted 1/32nd timing error in `time_multiplier_`.
2020-09-17 18:17:37 -04:00
Thomas Harte
894269aa06 Allows for permitted 1/32nd timing error in time_multiplier_. 2020-09-17 18:12:21 -04:00
Thomas Harte
8b16da9695 Merge pull request #834 from TomHarte/FloatingSpeaker
Resolves lowpass-speaker position aliasing
2020-09-16 19:05:54 -04:00
Thomas Harte
f783ec6269 Since input and output are floating point, using an integer Stepper is not appropriate. 2020-09-16 18:53:44 -04:00
Thomas Harte
22c9734874 Merge pull request #832 from TomHarte/MetalScanTarget
Adds a Metal ScanTarget, for macOS.
2020-09-16 18:19:58 -04:00
Thomas Harte
a17d0e428f Protects against some further uninitialised values. 2020-09-16 18:15:57 -04:00
Thomas Harte
bb57f0bcc7 Ensures all 6560 properties have a valid default value. 2020-09-16 17:24:18 -04:00
Thomas Harte
b1aefbfe85 Separates asserts. 2020-09-15 23:24:06 -04:00
Thomas Harte
061288f5a7 Add the Macintosh to the Mac kiosk mode informal test set. 2020-09-15 22:49:00 -04:00
Thomas Harte
5a53474536 Ensure MultiKeyboard deconstructs properly. 2020-09-15 22:48:44 -04:00
Thomas Harte
18d0fff8da Graduates the Atari ST. 2020-09-15 22:46:38 -04:00
Thomas Harte
0ac2145740 Add Metal/OpenGL distinction. 2020-09-15 22:43:39 -04:00
Thomas Harte
bc8787ded6 Improves macro safety. 2020-09-15 22:26:33 -04:00
Thomas Harte
69d21daaa3 Improves commentary. 2020-09-15 22:21:05 -04:00
Thomas Harte
5651ef606d Resolves failure to advance video address when output is blocked. 2020-09-15 22:20:06 -04:00
Thomas Harte
b831b31382 Adds a further sanity check. 2020-09-15 17:04:04 -04:00
Thomas Harte
2fd5cc056c Adds std::atomic_thread_fences, but these seem not to be a magic bullet. 2020-09-15 16:34:34 -04:00
Thomas Harte
82dbdf7dfc Switches to using regular linear interpolation for supersampling. 2020-09-14 22:36:00 -04:00
Thomas Harte
eb9903cd10 Defensively disables allocation of anything outside of visible lines. 2020-09-14 22:29:05 -04:00
Thomas Harte
227e98d6d7 Slightly simplifies control flow. 2020-09-14 22:27:25 -04:00
Thomas Harte
35476063b7 Resolves potential data races. 2020-09-14 21:07:50 -04:00
Thomas Harte
8557bb2136 Adds minor exposition. 2020-09-14 20:39:52 -04:00
Thomas Harte
c0c7818d5d Reintroduces screenshots. 2020-09-14 20:33:05 -04:00
Thomas Harte
ceeadd6a33 Edges up towards reimplementing screenshots. 2020-09-13 22:30:17 -04:00
Thomas Harte
1a2545fdea Excises dangling references to OpenGLView, reinstates display link. 2020-09-13 22:11:51 -04:00
Thomas Harte
c5e9a74c88 Uses DisplayMetrics to disable supersampling when too slow. 2020-09-13 21:07:59 -04:00
Thomas Harte
d7972a7b86 Enforces across-the-board supersampling.
I'm damned if I can figure out how to talk an MTKView, or Metal in general, into supersampling so as a first effort this does it in software.
2020-09-13 19:30:26 -04:00
Thomas Harte
7dd4c67304 Corrects access to data_type_size, adds sanity check on output area return. 2020-09-13 18:59:27 -04:00
Thomas Harte
e113780fd1 Minor: ensures no possibility of a dangling(-ish) pointer within the Mac video. 2020-09-10 22:13:19 -04:00
Thomas Harte
e32ae6c191 Adds UGLY HACKs to workaround uncovered issues in the OpenGL scan target. 2020-09-10 22:10:24 -04:00
Thomas Harte
bcaceff378 Simplifies in-Metal transform logic, loading responsibility for setup onto the CPU.
I think I've also finally excised whatever order-of-operations issue I was having with regard to non-4:3 displays.
2020-09-10 20:32:58 -04:00
Thomas Harte
d7b405c6f8 Ensures direct luminance -> 'RGB' video doesn't go down the composition pipeline. 2020-09-10 13:20:40 -04:00
Thomas Harte
edf8cf4dc6 Completes the set of with/without gamma, and ensures correct alpha selection.
Also culls some other repetitive TODOs.
2020-09-09 19:28:38 -04:00
Thomas Harte
dfcc8e9822 Switches some of the interpolated fields to half precision. 2020-09-09 18:17:05 -04:00
Thomas Harte
016e96e6f8 Extends usage of half. Possibly towards its conclusion. 2020-09-09 15:10:19 -04:00
Thomas Harte
e7ce03c418 Attempts to ensure initial finalised line texture state.
This was an attempt to remove the vertical line on the left of a composite display. Obviously the cause is not as suspected.
2020-09-09 13:15:21 -04:00
Thomas Harte
3d392dd81d Completes conversion of composite & S-Video per-pixel processing to 16-bit floats. 2020-09-09 13:02:04 -04:00
Thomas Harte
42d810db7f Switches per-pixel uniforms to halfs. 2020-09-09 10:53:09 -04:00
Thomas Harte
18571e8351 Also calculates a chroma kernel size, though it isn't used for anything yet. 2020-09-08 20:08:56 -04:00
Thomas Harte
dda1649ab7 Introduces smaller luma kernel functions where useable. 2020-09-08 19:55:37 -04:00
Thomas Harte
c82e0df071 Starts a transition towards half-precision arithmetic. 2020-09-08 19:37:36 -04:00
Thomas Harte
06b7ea5a6e Strips the luma kernel back to 1d. 2020-09-08 19:15:19 -04:00
Thomas Harte
c49fcb9ec9 Based on further play: one box filter to separate luma/chroma, another to filter chroma, plus a FIR sharpen on luma. 2020-09-08 16:35:05 -04:00
Thomas Harte
0e44d6d214 Experiments with an all-box filter. 2020-09-08 16:19:08 -04:00
Thomas Harte
6adad7fbf5 Starts experimenting again with box filters. 2020-09-07 22:47:49 -04:00
Thomas Harte
de6ed7b615 Corrects phase-linked luminance support. 2020-09-07 20:53:28 -04:00
Thomas Harte
07dcb4dbb1 Starts reintroducing brightness, gamma and transparency for composite and S-Video pipelines. 2020-09-07 18:19:13 -04:00
Thomas Harte
e99896eadc At least nominates alpha, gamma and brightness to metal. 2020-09-04 16:07:58 -04:00
Thomas Harte
489701afcb Fixes window resizing. 2020-09-03 21:28:39 -04:00
Thomas Harte
55e576cc57 Ensures unpainted areas in composite displays have a non-asymptotic effect on luminance calculations. 2020-09-03 21:10:30 -04:00
Thomas Harte
6bd8ec9545 Alas, 1.0 seems to be the limit for proper artefact colour. 2020-09-03 20:53:45 -04:00
Thomas Harte
5cd8d86eef Switches to dynamic generation of the 'sharpness' filter, correcting issues with the Apple II (amongst others). 2020-09-03 20:48:44 -04:00
Thomas Harte
74d0acdaec Fixes non-RGB colour composite generation.
The hard-coded sharpen filter proves to be a really bad fit for the Apple II though.
2020-09-03 19:04:17 -04:00
Thomas Harte
0288a1974b Tries: separate filters for chroma and luma, plus a post-separation sharpen filter on the latter. 2020-09-03 13:18:21 -04:00
Thomas Harte
6efd8782fe Tweaks coefficients some more; makes sure that data is never larger than the intermediate buffers. 2020-09-02 20:14:41 -04:00
Thomas Harte
8bab9d5d60 Corrects S-Video and composite generation for RGB[1/2/4] sources.
Also toys with a double luminance filter in order to try to sharpen chrominance. But maybe I should be looking at other convolutions entirely?
2020-09-02 19:13:54 -04:00
Thomas Harte
6ef1dfd8be Sets a more realistic colour subcarrier amplitude. 2020-09-02 15:52:05 -04:00
Thomas Harte
7e58648743 Corrects front-running bug, plays further with colour amplitude. 2020-09-02 15:51:48 -04:00
Thomas Harte
0f0c3e616d Tweaks some numbers.
I'm largely treading water here. Probably time to think about the race.
2020-09-02 08:17:01 -04:00
Thomas Harte
c7ce65ea4c Attempts fully to restore composite video.
Subject to some sort of nasty race condition for the time being.
2020-09-02 08:03:10 -04:00
Thomas Harte
c36247b609 Ensures reuse of offset buffers.
There seems to be some sort of epic race condition as the drawing pipeline lags though. Will need to investigate.
2020-09-01 22:11:48 -04:00
Thomas Harte
15296e43a4 Attempts correctly to set up the CPU side of a composite video pipeline, at least.
So: I think this is really close, but I'm out of time for the day.
2020-09-01 21:58:33 -04:00
Thomas Harte
f2929230a2 [Experimentally] introduces blending between computed S-Video fragments. 2020-09-01 21:37:36 -04:00
Thomas Harte
bf252b8061 Fixes sizing of buffers to the current output. 2020-09-01 21:33:54 -04:00
Thomas Harte
9e2bf2af7e Restricts S-Video processing to updated lines. 2020-09-01 21:27:40 -04:00
Thomas Harte
245f2654f0 Shifts S-Video processing into the compute shader. 2020-09-01 20:37:11 -04:00
Thomas Harte
67ca298a72 Forces a no-op compute shader into the S-Video pipeline.
The intention is to restrict the area acted over, and to do the S-Video filtering in there. Then I'll just need two such stages for composite.
2020-09-01 18:39:52 -04:00
Thomas Harte
67d4dbf91a Starts girding for a third pipeline. 2020-08-31 20:01:59 -04:00
Thomas Harte
b344269140 I think I accept the need for intermediate steps now.
This allocates storage.
2020-08-30 20:21:01 -04:00
Thomas Harte
bb547610f2 Adds commentary, shrinks some intermediate texture sizes. 2020-08-30 12:06:29 -04:00
Thomas Harte
1e1f007bb7 Possibly convinces myself that no-separation chroma/luma isn't practical.
... as appealing as it may be, were filters perfect.
2020-08-29 21:25:49 -04:00
Thomas Harte
c40d858f02 Switches back to angular stuff at input resolution; ensures all S-Video modes work.
Now to roll back onto composite. Fingers crossed!
2020-08-29 20:54:46 -04:00
Thomas Harte
3d564d85fd Proves that per-pixel sine/cos evaluation avoids phase issues.
Even in PAL mode. But I'd rather not _require_ this as it kind of negates directly-sampled input.
2020-08-29 18:53:37 -04:00
Thomas Harte
02cea40ffa Attempts to avoid introducing phase error in scanToComposition.
Also brightens S-Video up to RGB levels.
2020-08-25 22:41:37 -04:00
Thomas Harte
e502d336db Having decided that these things probably need to be separate, starts drilling down on S-Video. 2020-08-25 22:05:19 -04:00
Thomas Harte
807cb99f6d Composite angles are signed. 2020-08-23 21:39:04 -04:00
Thomas Harte
8b6879a782 Brief detour: introduces myself to C++11 multiline string literals.
A full cleaning coming soon, I imagine.
2020-08-23 21:18:38 -04:00
Thomas Harte
7ca0362f23 Treads water.
Difficult current question: why does the Atari 2600's display change colours as the display tries to achieve horizontal lock? Phase should be unchanged. Ergo something is amiss.
2020-08-23 21:03:26 -04:00
Thomas Harte
56c7bd242a Marginally tidies. 2020-08-22 16:38:36 -04:00
Thomas Harte
5c6112415a Sets appropriate clear colour for composition render pass. 2020-08-21 22:41:54 -04:00
Thomas Harte
bf6a0c9fc4 Achieves a return of composite colour for RGB-producing machines. 2020-08-21 22:06:36 -04:00
Thomas Harte
d54b937ab6 Starts trying to do actual composite processing. 2020-08-21 21:11:25 -04:00
Thomas Harte
7c23c32e44 Corrects composition colour phase. 2020-08-20 20:45:45 -04:00
Thomas Harte
4e21d24b5f Corrects composition colour amplitude. 2020-08-20 20:34:37 -04:00
Thomas Harte
ad6fb85fda Corrects use of composition buffer.
Something is still very obviously amiss in colour processing somewhere down the line, but the correct forms are once again visibly in evidence.
2020-08-20 20:21:28 -04:00
Thomas Harte
5dc39a5d24 Adds the composition render pass.
Albeit that something here doesn't work at present.
2020-08-19 21:56:53 -04:00
Thomas Harte
3597f687de Continues sidling towards composite & S-Video handling. 2020-08-19 21:20:06 -04:00
Thomas Harte
8811506adf Starts towards building a compound[/composition?] buffer.
I now need to discover whether I can use natural integer texture coordinates.
2020-08-17 22:10:02 -04:00
Thomas Harte
11dec6fc0f Avoids a redundant clear. 2020-08-17 22:09:15 -04:00
Thomas Harte
59c4c8233f Generalises existing scanToDisplay to add lineToDisplay. 2020-08-17 21:15:19 -04:00
Thomas Harte
9da79d2d81 Clarifies scaling logic. 2020-08-17 20:29:46 -04:00
Thomas Harte
246b474a25 Removes ONE_BIG_LOCK, having effectively neutered it anyway.
Starts work on more explicit API usage validation. Maybe the issue isn't a race condition?
2020-08-16 22:09:25 -04:00
Thomas Harte
27e8a3a1b5 Obeys modals' zoom.
Subject to an attempt at factoring aspect ratio differences.
2020-08-16 21:11:43 -04:00
Thomas Harte
745797b596 Introduces a stencil buffer plus the inter-frame clearing it allows. 2020-08-16 16:42:32 -04:00
Thomas Harte
940e9e037e Adds first_scan to LineMetadata.
Also reorders `Line` fields to match `Scan` fields, just for visual consistency.
2020-08-16 08:59:37 -04:00
Thomas Harte
512c0079a9 Makes thread safe. 2020-08-15 21:52:55 -04:00
Thomas Harte
645c29f853 Adds an intermediate buffer to correct inter-frame smoothing.
Also goes someway back to the old scan output scheduling, albeit presently with limited thread safety.
2020-08-15 21:24:10 -04:00
Thomas Harte
e55945674d Reduces main thread blocking. 2020-08-14 22:16:49 -04:00
Thomas Harte
7ac88536dd Respects machine aspect ratio.
To an extent. Doesn't currently deal with cropping of machines when the window aspect ratio is smaller.
2020-08-14 21:24:25 -04:00
Thomas Harte
230b9fc9e6 Permits multiple simultaneous scan reading ranges.
Also updates the OpenGL scan target as per the latest movements of things.
2020-08-12 22:08:41 -04:00
Thomas Harte
27ca782cac Enables blending; attempts to enable frame preservation.
The latter seems to be evidencing a double buffer at play. More investigation required.

On the plus side, the direct route is still well within GPU budget at 4k on my Core M. So a huge improvement there.
2020-08-12 19:34:07 -04:00
Thomas Harte
a136a00a2f Takes a shot at adding RGB -> S-Video and composite conversion, for all RGB types. 2020-08-11 22:11:50 -04:00
Thomas Harte
637ec35d6a Adds getters for standard colour-space conversion matrices.
These are just more details on the meaning of the colour spaces, so I think they belong here.
2020-08-11 19:58:57 -04:00
Thomas Harte
4b55df1cb4 Starts upon a macro-oriented means of RGB input function generation. 2020-08-10 22:03:39 -04:00
Thomas Harte
b9309268ba Possibly finally succeeds at moving Accelerate.framework to where it should be. 2020-08-10 21:46:11 -04:00
Thomas Harte
8fa89baf54 Slightly cleans up Xcode project; reenables kiosk-for-Mac builds. 2020-08-10 21:43:32 -04:00
Thomas Harte
8374a5e579 Adds superficially correct compositeSampleLuminance8Phase8 function.
Thereby uncovering a minor error in my decoding of colour phase.
2020-08-10 21:33:59 -04:00
Thomas Harte
525233e10b Ensures all input data types are parseable in Metal.
Though now I need to think a bit more about the best way to compose signal-type conversions, and whether output-type calculations (i.e. gamma, brightness) are applied.
2020-08-10 19:47:47 -04:00
Thomas Harte
eadda6a967 Further strips OpenGL from the macOS target. 2020-08-09 22:17:27 -04:00
Thomas Harte
3d6590af89 Throws out a little more OpenGL. 2020-08-09 22:11:31 -04:00
Thomas Harte
28d933d5d6 Does just enough to get 8-bit RGB and 1-bit luminance machines to display.
Assuming an 'RGB' output.
2020-08-09 21:19:07 -04:00
Thomas Harte
c1dc42a094 Add comment on latent design aim.
In the hope that I don't forget why I did this.
2020-08-09 21:18:23 -04:00
Thomas Harte
6384ff3ee7 Add fix for data_type_size_ for owners that don't change texture pointer upon new modals. 2020-08-09 21:17:51 -04:00
Thomas Harte
a118594c8b Hacks to make RGB1 visible (in a fashion). 2020-08-09 20:45:51 -04:00
Thomas Harte
93c6105442 Corrects calculation of dirty texture area. 2020-08-09 20:45:14 -04:00
Thomas Harte
ced4a75a1a Adds note on the buffering scan target's minor adaptation of data_offset. 2020-08-09 20:44:46 -04:00
Thomas Harte
57fecdc09e Ties everything together in an attempt to display RGB scans.
I'm actually just getting a mess of pixels, but it's something!
2020-08-09 18:41:15 -04:00
Thomas Harte
cd491bb6e0 Cleans up project file; macOS 10.13 is definitely the deployment target. 2020-08-09 18:27:57 -04:00
Thomas Harte
f16ad8f71d Takes a shot at submitting texture changes. 2020-08-09 17:59:52 -04:00
Thomas Harte
e340685a99 Seemingly proves that proper geometry is reaching Metal by drawing scans.
No in-buffer accumulation yet, but this is progress. If I can add accumulation and stencil clearing, I'm not doing badly.
2020-08-08 23:11:44 -04:00
Thomas Harte
df89a8771c Makes an attempt to have the emulator fill the actual GPU buffers.
Not that they're drawn from correctly yet. I might first take a run at a new quick-path output route for emulated RGB displays, that just seeks to use the scans directly. No intermediate buffers. Besides probably being a good feature, it'll be a good way to ramp further up with Metal.
2020-08-08 22:49:02 -04:00
Thomas Harte
bdcf266e45 Having learnt a bit more: eliminates Metal attribute tags, switches to more natural expression of structs.
Also thereby eliminates the need for a forced alignas(4) on various structs.
2020-08-08 17:27:32 -04:00
Thomas Harte
edf41b06fd Eliminates the quad buffer.
Vertices can be adduced from vertex ID.
2020-08-08 17:12:49 -04:00
Thomas Harte
38960a08d6 Adds adjustment for display aspect ratio.
While also realising that I appear to be getting away without an MTLVertexDescriptor for Scans. Maybe OpenGL has prejudiced me, and they're actually optional for interleaved data?
2020-08-07 22:29:24 -04:00
Thomas Harte
fbda7aab23 Does just enough to get the correct (aspect ratio aside) output of scan outlines.
So, up next, can I start streaming these things?
2020-08-07 22:20:01 -04:00
Thomas Harte
c575aa0640 Adds a buffer for scans, and posts two test instances. 2020-08-07 22:03:54 -04:00
Thomas Harte
583f6b1ba2 Modifies BufferingScanTarget to allow has-a relationship.
I might switch fully to has-a. Further consideration required.
2020-08-07 22:03:27 -04:00
Thomas Harte
bb55ecc101 Disables --volume for kiosk mode testing. 2020-08-07 21:19:53 -04:00
Thomas Harte
4421acef34 Gets some uniforms in on the action.
With some effort towards scans, but incompletely so.
2020-08-07 21:19:17 -04:00
Thomas Harte
4c9418f59a Guarantees alignof(4) on all GPU-bound structures.
Taken as given: Metal's requirement here is reasonable enough that it'll either be the same as other frameworks, or at least possibly help them down a fast path.
2020-08-07 21:18:08 -04:00
Thomas Harte
219923bd63 Reduces vertex size, draws a quad. 2020-08-05 21:33:25 -04:00
Thomas Harte
7551782a25 Switches to interleaved vertex data.
This more closely relates to what I actually want to do.
2020-08-05 17:27:43 -04:00
Thomas Harte
5c836604c0 Reenable MaserSystem code.
Accidental/poor branch management is evidenced here.
2020-08-04 21:50:54 -04:00
Thomas Harte
eff24a8726 My first baby steps in Metal continue; here's a triangle. 2020-08-04 21:49:01 -04:00
Thomas Harte
72df6e52cd This is possibly at least dispatching an empty command buffer correctly. 2020-08-04 19:44:56 -04:00
Thomas Harte
e235a45abb Breaks all output.
... by switching out NSOpenGLView for MKLView with no drawing infrastructure yet in place.
2020-08-04 18:22:14 -04:00
Thomas Harte
d20c11e401 Merge pull request #831 from TomHarte/MultiKeyboard
Ensures that the MultiKeyboard functions.
2020-07-31 22:08:08 -04:00
Thomas Harte
693b889fdd Ensures that the MultiKeyboard functions. 2020-07-31 21:48:20 -04:00
Thomas Harte
671f48dc10 Merge pull request #830 from TomHarte/MSXCrash
Restores audio to multimachines
2020-07-31 18:31:23 -04:00
Thomas Harte
7b1708f0bc Gets explicit that the delegate_ doesn't need a memory barrier. 2020-07-31 18:21:47 -04:00
Thomas Harte
f34a9b4346 Corrects audio output from the multi-speaker.
Specifically: local duplication of the delegate is unnecessary, and leads to confusion.
2020-07-31 18:18:19 -04:00
Thomas Harte
1e6d03246b Merge pull request #829 from TomHarte/MSXCrash
Ensures proper handover of speaker state when picking in a multimachine.
2020-07-30 23:04:55 -04:00
Thomas Harte
cdde57fcf2 Remove unused code. 2020-07-30 23:02:01 -04:00
Thomas Harte
c0a61ac1ee Ensures proper handover of speaker state when picking in a multimachine. 2020-07-30 22:50:32 -04:00
Thomas Harte
9c97c0a906 Merge pull request #828 from TomHarte/LockFreeQueue
Completes LockFreeQueue branch.
2020-07-30 21:46:56 -04:00
Thomas Harte
8cacab196d Merge branch 'master' into LockFreeQueue 2020-07-30 21:43:25 -04:00
Thomas Harte
b14bedbe29 Merge pull request #817 from TomHarte/LockFreeQueue
Fully splits buffering from drawing for the existing OpenGL scan target.
2020-07-30 21:42:20 -04:00
Thomas Harte
6bc66d8b96 Tidies, ensures ::will_change_owner acquires the producer mutex. 2020-07-29 23:18:03 -04:00
Thomas Harte
23f381f381 Fixes frame_is_complete_, gets rid of active_line_, explains ONE_BIG_LOCK in set_write_area. 2020-07-29 23:03:38 -04:00
Thomas Harte
51ad423eca Resolves off-by-one error in line writing, adds diagnostic one-big-lock option. 2020-07-29 22:45:13 -04:00
Thomas Harte
72a8fef989 Switches to much more straightforward Line/LineMetadata storage.
Spoiler: covering this whole segment behind producer_mutex_ seems to resolve all output issues, so clearly the existing logic isn't functioning correctly. Making it simpler seems like a pretty obvious way to get to the bottom of that.
2020-07-29 21:49:17 -04:00
Thomas Harte
02f41ee513 This has become the general producer mutex, might as well name it as such. 2020-07-29 21:34:07 -04:00
Thomas Harte
9410594486 Merge branch 'master' into LockFreeQueue 2020-07-29 21:22:19 -04:00
Thomas Harte
1c6223cc11 Merge pull request #825 from TomHarte/Microdisc
Gives Qt disk controllers independent ROM/RAM selection logic.
2020-07-29 21:21:29 -04:00
Thomas Harte
82d6a5387f Gives Qt disk controllers independent ROM/RAM selection logic.
In particular, this fixes the Microdisc.
2020-07-29 21:06:41 -04:00
Thomas Harte
5165e65021 Reduces scan_buffer_ to a saner size.
Albeit still probably overspecified.
2020-07-28 22:36:57 -04:00
Thomas Harte
1942742d73 Resolves thread data race on Macintosh audio output. 2020-07-28 22:21:52 -04:00
Thomas Harte
b7760bb052 Reorders code, gets explicit about memory ordering. 2020-07-28 22:02:22 -04:00
Thomas Harte
2470055d90 Hides the modals. 2020-07-27 23:33:39 -04:00
Thomas Harte
62be2a2eec Merge branch 'master' into LockFreeQueue 2020-07-27 23:18:45 -04:00
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
77c5b86acc Moves ownership of the scan and line buffers out of the BufferingScanTarget. 2020-07-26 22:46:03 -04:00
Thomas Harte
bc6426313e Localises three of the four macros. 2020-07-26 17:54:33 -04:00
Thomas Harte
8bef7ff4c5 Makes all three PointerSets and is_updating_ private. 2020-07-26 17:27:19 -04:00
Thomas Harte
a2db6ddea5 Add link to Snap releases. 2020-07-25 23:00:29 -04:00
Thomas Harte
f9f500c194 Merge branch 'master' into LockFreeQueue 2020-07-24 22:29:45 -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
89acb70091 Slightly reorganise. 2020-07-24 16:20:20 -04:00
Thomas Harte
66165a6dea Add missing include files. 2020-07-23 23:24:24 -04:00
Thomas Harte
84dcf9925b Updates Scons and Qt projects to include new files. 2020-07-23 23:14:10 -04:00
Thomas Harte
ee1d7eb61f Makes more buffer-specific stuff private. 2020-07-23 23:06:14 -04:00
Thomas Harte
e260f92988 Privatises write_pointers_mutex_ and write_pointers_.
Also gives subclasses control over write-area texture space allocation.
2020-07-23 22:54:40 -04:00
Thomas Harte
74788ccf8e Pulls the BufferingScanTarget into a separate file. 2020-07-22 22:16:47 -04:00
Thomas Harte
0da5c07942 Starts splitting ring-buffer stuff from OpenGL stuff.
Initially via two very codependent classes.
2020-07-21 22:49:46 -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
468 changed files with 30377 additions and 5861 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

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

@@ -10,12 +10,12 @@
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &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);
}
keyboard_ = std::make_unique<MultiKeyboard>(machines_);
}
void MultiKeyboardMachine::clear_all_keys() {
@@ -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);
@@ -45,10 +45,10 @@ bool MultiKeyboardMachine::can_type(char c) {
}
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return 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,25 +24,25 @@ 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;
};
MultiKeyboard keyboard_;
std::unique_ptr<MultiKeyboard> keyboard_;
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
@@ -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,34 +54,39 @@ bool MultiSpeaker::get_is_stereo() {
return false;
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
void MultiSpeaker::set_output_volume(float volume) {
for(const auto &speaker: speakers_) {
speaker->set_output_volume(volume);
}
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
if(!delegate_) return;
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
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_);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
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);
delegate->speaker_did_change_input_clock(this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
std::lock_guard lock_guard(front_speaker_mutex_);
front_speaker_ = machine->audio_producer()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(delegate) {
delegate->speaker_did_change_input_clock(this);
}
}

View File

@@ -40,8 +40,8 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_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;
@@ -50,7 +50,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
std::mutex front_speaker_mutex_;
bool stereo_output_ = false;

View File

@@ -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_)) {
@@ -107,9 +89,28 @@ void MultiMachine::multi_crt_did_run_machines() {
void MultiMachine::pick_first() {
has_picked_ = true;
// Ensure output rate specifics are properly copied; these may be set only once by the owner,
// but rather than being propagated directly by the MultiSpeaker only the derived computed
// output rate is propagated. So this ensures that if a new derivation is made, it's made correctly.
if(machines_[0]->audio_producer()) {
auto multi_speaker = audio_producer_.get_speaker();
auto specific_speaker = machines_[0]->audio_producer()->get_speaker();
if(specific_speaker && multi_speaker) {
specific_speaker->copy_output_rate(*multi_speaker);
}
}
// TODO: because it is not invalid for a caller to keep a reference to anything previously returned,
// this erase can be added only once the Multi machines that take static copies of the machines list
// are updated.
//
// Example failing use case otherwise: a caller still has reference to the MultiJoystickMachine, and
// it has dangling references to the various JoystickMachines.
//
// This gets into particularly long grass with the MultiConfigurable and its MultiStruct.
// machines_.erase(machines_.begin() + 1, machines_.end());
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
// request a close_output while the context is active.
}
void *MultiMachine::raw_pointer() {

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,21 @@ 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));
if(names->samples[0][file_offset + 7] & 0x80) {
// File is locked; it may not be altered or deleted.
new_file.flags |= File::Flags::Locked;
}
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);
@@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_back(new_file);
if(!data_length) catalogue->files.push_back(std::move(new_file));
}
return catalogue;
}
/*
Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
http://mdfs.net/Docs/Comp/Disk/Format/ADFS
*/
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(true, disk);
@@ -84,7 +92,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());
}
@@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
// Parse the root directory, at least.
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
// Obtain the name, which will be at most ten characters long, and will
// be terminated by either a NULL character or a \r.
char name[11];
std::size_t c = 0;
for(; c < 10; c++) {
const char next = root_directory[file_offset + c] & 0x7f;
name[c] = next;
if(next == '\0' || next == '\r') break;
}
name[c] = '\0';
// Skip if the name is empty.
if(name[0] == '\0') continue;
// Populate a file then.
File new_file;
new_file.name = name;
new_file.flags =
(root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) |
(root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) |
(root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) |
(root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) |
(root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) |
(root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) |
(root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) |
(root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) |
(root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0);
new_file.load_address =
(uint32_t(root_directory[file_offset + 0x0a]) << 0) |
(uint32_t(root_directory[file_offset + 0x0b]) << 8) |
(uint32_t(root_directory[file_offset + 0x0c]) << 16) |
(uint32_t(root_directory[file_offset + 0x0d]) << 24);
new_file.execution_address =
(uint32_t(root_directory[file_offset + 0x0e]) << 0) |
(uint32_t(root_directory[file_offset + 0x0f]) << 8) |
(uint32_t(root_directory[file_offset + 0x10]) << 16) |
(uint32_t(root_directory[file_offset + 0x11]) << 24);
new_file.sequence_number = root_directory[file_offset + 0x19];
const uint32_t size =
(uint32_t(root_directory[file_offset + 0x12]) << 0) |
(uint32_t(root_directory[file_offset + 0x13]) << 8) |
(uint32_t(root_directory[file_offset + 0x14]) << 16) |
(uint32_t(root_directory[file_offset + 0x15]) << 24);
uint32_t start_sector =
(uint32_t(root_directory[file_offset + 0x16]) << 0) |
(uint32_t(root_directory[file_offset + 0x17]) << 8) |
(uint32_t(root_directory[file_offset + 0x18]) << 16);
new_file.data.reserve(size);
while(new_file.data.size() < size) {
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
if(!sector) break;
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
++start_sector;
}
catalogue->files.push_back(std::move(new_file));
}
return catalogue;
}

View File

@@ -19,19 +19,38 @@ namespace Acorn {
struct File {
std::string name;
uint32_t load_address;
uint32_t execution_address;
bool is_protected;
uint32_t load_address = 0;
uint32_t execution_address = 0;
enum Flags: uint16_t {
Readable = 1 << 0,
Writable = 1 << 1,
Locked = 1 << 2,
IsDirectory = 1 << 3,
ExecuteOnly = 1 << 4,
PubliclyReadable = 1 << 5,
PubliclyWritable = 1 << 6,
PubliclyExecuteOnly = 1 << 7,
IsPrivate = 1 << 8,
};
uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable;
uint8_t sequence_number = 0;
std::vector<uint8_t> data;
/// Describes a single chunk of file data; these relate to the tape and ROM filing system.
/// The File-level fields contain a 'definitive' version of the load and execution addresses,
/// but both of those filing systems also store them per chunk.
///
/// Similarly, the file-level data will contain the aggregate data of all chunks.
struct Chunk {
std::string name;
uint32_t load_address;
uint32_t execution_address;
uint16_t block_number;
uint16_t block_length;
uint8_t block_flag;
uint32_t next_address;
uint32_t load_address = 0;
uint32_t execution_address = 0;
uint16_t block_number = 0;
uint16_t block_length = 0;
uint32_t next_address = 0;
uint8_t block_flag = 0;
bool header_crc_matched;
bool data_crc_matched;

View File

@@ -12,6 +12,8 @@
#include "Tape.hpp"
#include "Target.hpp"
#include <algorithm>
using namespace Analyser::Static::Acorn;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
@@ -29,7 +31,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,13 +59,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;
target->should_shift_restart = false;
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
@@ -78,14 +75,14 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
if(!files.empty()) {
bool is_basic = true;
// protected files are always for *RUNning only
if(files.front().is_protected) is_basic = false;
// If a file is execute-only, that means *RUN.
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
// 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;
@@ -109,15 +106,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target->media.disks = media.disks;
target->has_dfs = !!dfs_catalogue;
target->has_adfs = !!adfs_catalogue;
target->has_dfs = bool(dfs_catalogue);
target->has_pres_adfs = bool(adfs_catalogue);
// Check whether a simple shift+break will do for loading this disk.
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None)
if(bootOption != Catalogue::BootOption::None) {
target->should_shift_restart = true;
else
} else {
target->loading_command = "*CAT\n";
}
// Check whether adding the AP6 ROM is justified.
// For now this is an incredibly dense text search;
// if any of the commands that aren't usually present
// on a stock Electron are here, add the AP6 ROM and
// some sideways RAM such that the SR commands are useful.
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
for(const auto &command: {
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
"VERIFY", "ZERO"
}) {
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
target->has_ap6_rom = true;
target->has_sideways_ram = true;
}
}
}
}
}
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
target->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target->has_ap6_rom = true;
target->has_sideways_ram = true;
target->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target->should_shift_restart = true;
} else {
target->loading_command = "*CAT\n";
}
}

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;
@@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
file->name = file->chunks.front().name;
file->load_address = file->chunks.front().load_address;
file->execution_address = file->chunks.front().execution_address;
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
// I think the final chunk's flags are the ones that count; TODO: check.
if(file->chunks.back().block_flag & 0x01) {
// File is locked, which in more generalised terms means it is
// for execution only.
file->flags |= File::Flags::ExecuteOnly;
}
// copy all data into a single big block
for(File::Chunk chunk : file->chunks) {

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,24 @@ namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target {
bool has_adfs = false;
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false;
bool has_ap6_rom = false;
bool has_sideways_ram = false;
bool should_shift_restart = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
DeclareField(has_dfs);
DeclareField(has_ap6_rom);
DeclareField(has_sideways_ram);
}
}
};
}

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,42 @@ 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
);
ReflectableEnum(Processor,
MOS6502,
WDC65816
);
ROM rom = ROM::BASIC11;
DiskInterface disk_interface = DiskInterface::None;
Processor processor = Processor::MOS6502;
std::string loading_command;
bool should_start_jasmin = false;
Target(): Analyser::Static::Target(Machine::Oric) {
if(needs_declare()) {
DeclareField(rom);
DeclareField(disk_interface);
DeclareField(processor);
AnnounceEnum(ROM);
AnnounceEnum(DiskInterface);
AnnounceEnum(Processor);
}
}
};
}

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

@@ -51,6 +51,7 @@
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
@@ -98,16 +99,21 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format( "dsk",
result.disks,
Disk::DiskImageHolder<Storage::Disk::CPCDSK>,
TargetPlatform::AmstradCPC | TargetPlatform::Oric) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)

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();
@@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage {
void access(int address);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
inline void reevaluate_interrupts();
/// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler.
void set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output();
void evaluate_port_b_output();
};
}

View File

@@ -8,6 +8,10 @@
#include "../../../Outputs/Log.hpp"
// As-yet unimplemented (incomplete list):
//
// PB6 count-down mode for timer 2.
namespace MOS {
namespace MOS6522 {
@@ -34,18 +38,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
case 0x0: // Write Port B.
case 0x0: // Write Port B. ('ORB')
// Store locally and communicate outwards.
registers_.output[1] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
evaluate_port_b_output();
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1: // Write Port A.
case 0x1: // Write Port A. ('ORA')
registers_.output[0] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
@@ -59,36 +63,52 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
reevaluate_interrupts();
break;
case 0x2: // Port B direction.
case 0x2: // Port B direction ('DDRB').
registers_.data_direction[1] = value;
break;
case 0x3: // Port A direction.
case 0x3: // Port A direction ('DDRA').
registers_.data_direction[0] = value;
break;
// 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_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
case 0x6: case 0x4: // ('T1L-L' and 'T1C-L')
registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;
break;
case 0x7: // Timer 1 latch, high ('T1L-H').
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
break;
case 0x5: // Timer 1 counter, high ('T1C-H').
// Fill latch.
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
// Restart timer.
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
// If PB7 output mode is active, set it low.
if(timer1_is_controlling_pb7()) {
registers_.timer_port_b_output &= 0x7f;
evaluate_port_b_output();
}
// Clear existing interrupt flag.
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
case 0x8: // ('T2C-L')
registers_.timer_latch[1] = value;
break;
case 0x9: // ('T2C-H')
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;
// Shift
case 0xa:
case 0xa: // ('SR')
registers_.shift = value;
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
@@ -96,11 +116,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
break;
// Control
case 0xb:
case 0xb: // Auxiliary control ('ACR').
registers_.auxiliary_control = value;
evaluate_cb2_output();
// This is a bit of a guess: reset the timer-based PB7 output to its default high level
// any timer that timer-linked PB7 output is disabled.
if(!timer1_is_controlling_pb7()) {
registers_.timer_port_b_output |= 0x80;
}
evaluate_port_b_output();
break;
case 0xc: {
case 0xc: { // Peripheral control ('PCR').
// const auto old_peripheral_control = registers_.peripheral_control;
registers_.peripheral_control = value;
@@ -141,11 +168,11 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
} break;
// Interrupt control
case 0xd:
case 0xd: // Interrupt flag regiser ('IFR').
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
case 0xe: // Interrupt enable register ('IER').
if(value&0x80)
registers_.interrupt_enable |= value;
else
@@ -159,54 +186,55 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
address &= 0xf;
access(address);
switch(address) {
case 0x0:
case 0x0: // Read Port B ('IRB').
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80);
case 0xf:
case 0x1:
case 0x1: // Read Port A ('IRA').
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
case 0x2: return registers_.data_direction[1]; // Port B direction ('DDRB').
case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA').
// Timer 1
case 0x4:
case 0x4: // Timer 1 low-order latches ('T1L-L').
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
case 0x5: return registers_.timer[0] >> 8; // Timer 1 high-order counter ('T1C-H')
case 0x6: return registers_.timer_latch[0] & 0x00ff; // Timer 1 low-order latches ('T1L-L').
case 0x7: return registers_.timer_latch[0] >> 8; // Timer 1 high-order latches ('T1L-H').
// Timer 2
case 0x8:
case 0x8: // Timer 2 low-order counter ('T2C-L').
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0x9: return registers_.timer[1] >> 8; // Timer 2 high-order counter ('T2C-H').
case 0xa:
case 0xa: // Shift register ('SR').
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
reevaluate_interrupts();
return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xb: return registers_.auxiliary_control; // Auxiliary control ('ACR').
case 0xc: return registers_.peripheral_control; // Peripheral control ('PCR').
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); // Interrupt flag register ('IFR').
case 0xe: return registers_.interrupt_enable | 0x80; // Interrupt enable register ('IER').
}
return 0xff;
}
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port);
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
return (input & ~output_mask) | (output & output_mask);
}
@@ -276,16 +304,19 @@ template <typename T> void MOS6522<T>::do_phase2() {
registers_.timer_needs_reload = false;
registers_.timer[0] = registers_.timer_latch[0];
} else {
registers_.timer[0] --;
-- registers_.timer[0];
}
registers_.timer[1] --;
// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear).
registers_.timer[1] -= timer2_clock_decrement();
// TODO: can eliminate conditional branches here.
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;
}
@@ -330,20 +361,29 @@ template <typename T> void MOS6522<T>::do_phase1() {
reevaluate_interrupts();
// Determine whether to reload.
if(registers_.auxiliary_control&0x40)
if(timer1_is_continuous())
registers_.timer_needs_reload = true;
else
timer_is_running_[0] = false;
// Determine whether to toggle PB7.
if(registers_.auxiliary_control&0x80) {
registers_.output[1] ^= 0x80;
if(timer1_is_controlling_pb7()) {
registers_.timer_port_b_output ^= 0x80;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
evaluate_port_b_output();
}
}
}
template <typename T> void MOS6522<T>::evaluate_port_b_output() {
// Apply current timer-linked PB7 output if any atop the stated output.
const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80;
bus_handler_.set_port_output(
Port::B,
(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit,
registers_.data_direction[1] | timer_control_bit);
}
/*! Runs for a specified number of half cycles. */
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
auto number_of_half_cycles = half_cycles.as_integral();
@@ -383,9 +423,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() {
@@ -438,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() {
}
template <typename T> void MOS6522<T>::shift_out() {
// When shifting out, the shift register rotates rather than strictly shifts.
// TODO: is that true for all modes?
if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning;
if(is_free_running || shift_bits_remaining_) {
// Recirculate bits only if in free-running mode (?)
const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running;
registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit;
evaluate_cb2_output();
--shift_bits_remaining_;

View File

@@ -34,7 +34,9 @@ class MOS6522Storage {
uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0;
bool timer_needs_reload = false;
uint8_t timer_port_b_output = 0xff;
} registers_;
// Control state.
@@ -79,12 +81,30 @@ class MOS6522Storage {
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
bool timer1_is_controlling_pb7() const {
return registers_.auxiliary_control & 0x80;
}
bool timer1_is_continuous() const {
return registers_.auxiliary_control & 0x40;
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
int timer2_clock_decrement() const {
return 1 ^ ((registers_.auxiliary_control >> 5)&1);
}
int timer2_pb6_decrement() const {
return (registers_.auxiliary_control >> 5)&1;
}
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool portb_is_latched() const {
return registers_.auxiliary_control & 0x02;
}
bool port1_is_latched() const {
return registers_.auxiliary_control & 0x01;
}
};
}

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;
}
}
@@ -444,28 +445,28 @@ template <class BusHandler> class MOS6560 {
// register state
struct {
bool interlaced = false, tall_characters = false;
uint8_t first_column_location, first_row_location;
uint8_t number_of_columns, number_of_rows;
uint16_t character_cell_start_address, video_matrix_start_address;
uint16_t backgroundColour, borderColour, auxiliary_colour;
uint8_t first_column_location = 0, first_row_location = 0;
uint8_t number_of_columns = 0, number_of_rows = 0;
uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
uint16_t backgroundColour = 0, borderColour = 0, auxiliary_colour = 0;
bool invertedCells = false;
uint8_t direct_values[16];
uint8_t direct_values[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
} registers_;
// output state
enum State {
Sync, ColourBurst, Border, Pixels
} this_state_, output_state_;
int cycles_in_state_;
} this_state_ = State::Sync, output_state_ = State::Sync;
int cycles_in_state_ = 0;
// 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,29 +481,29 @@ 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;
}
// latches dictating start and length of drawing
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_, columns_this_line_;
int rows_this_field_ = 0, columns_this_line_ = 0;
// current drawing position counter
int pixel_line_cycle_, column_counter_;
int current_row_;
uint16_t current_character_row_;
uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
int pixel_line_cycle_ = 0, column_counter_ = 0;
int current_row_ = 0;
uint16_t current_character_row_ = 0;
uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
// data latched from the bus
uint8_t character_code_, character_colour_, character_value_;
uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
bool is_odd_frame_ = false, is_odd_line_ = false;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint16_t colours_[16];
uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint16_t *pixel_pointer;
uint16_t *pixel_pointer = nullptr;
void output_border(int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
@@ -510,13 +511,13 @@ template <class BusHandler> class MOS6560 {
}
struct {
int cycles_per_line;
int line_counter_increment_offset;
int final_line_increment_position;
int lines_per_progressive_field;
bool supports_interlacing;
int cycles_per_line = 0;
int line_counter_increment_offset = 0;
int final_line_increment_position = 0;
int lines_per_progressive_field = 0;
bool supports_interlacing = 0;
} timing_;
OutputMode output_mode_;
OutputMode output_mode_ = OutputMode::NTSC;
};
}

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;
@@ -186,7 +190,9 @@ void TMS9918::run_for(const HalfCycles cycles) {
int read_cycles_pool = int_cycles;
while(write_cycles_pool || read_cycles_pool) {
#ifndef NDEBUG
LineBufferPointer backup = read_pointer_;
#endif
if(write_cycles_pool) {
// Determine how much writing to do.
@@ -325,8 +331,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
#ifndef NDEBUG
assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
backup = write_pointer_;
#endif
if(read_cycles_pool) {
@@ -527,7 +535,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 +669,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?
@@ -180,30 +180,40 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
if(state_machine[0] != 0x18) {
for(size_t source_address = 0; source_address < 256; ++source_address) {
// Remap into Beneath Apple Pro-DOS address form.
size_t destination_address =
((source_address&0x80) ? 0x10 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
const size_t destination_address =
((source_address&0x20) ? 0x80 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x80) ? 0x10 : 0x00) |
((source_address&0x08) ? 0x08 : 0x00) |
((source_address&0x04) ? 0x04 : 0x00) |
((source_address&0x02) ? 0x02 : 0x00);
uint8_t source_value = state_machine[source_address];
((source_address&0x02) ? 0x02 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00);
// Remap into Beneath Apple Pro-DOS value form.
source_value =
// Store.
const uint8_t source_value = state_machine[source_address];
state_machine_[destination_address] =
((source_value & 0x80) ? 0x10 : 0x0) |
((source_value & 0x40) ? 0x20 : 0x0) |
((source_value & 0x20) ? 0x40 : 0x0) |
((source_value & 0x10) ? 0x80 : 0x0) |
(source_value & 0x0f);
// Store.
state_machine_[destination_address] = source_value;
}
} else {
memcpy(&state_machine_[0], &state_machine[0], 128);
for(size_t source_address = 0; source_address < 256; ++source_address) {
// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
const size_t destination_address =
((source_address&0x80) ? 0x80 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x20) ? 0x10 : 0x00) |
((source_address&0x08) ? 0x08 : 0x00) |
((source_address&0x04) ? 0x04 : 0x00) |
((source_address&0x02) ? 0x02 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00);
state_machine_[destination_address] = state_machine[source_address];
}
}
}
@@ -219,13 +229,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 */

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