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

Compare commits

...

321 Commits

Author SHA1 Message Date
Thomas Harte
ab02f82470 Merge pull request #543 from TomHarte/CFBundleTypeOSTypes
Removes `LSItemContentTypes` so as not to reject files.
2018-09-09 17:49:16 -04:00
Thomas Harte
1e3318816c Removes LSItemContentTypes so as not to reject files. 2018-09-09 17:47:03 -04:00
Thomas Harte
3a3dec92c7 Merge pull request #540 from MaddTheSane/plistFix
Remove LSItemContentTypes
2018-09-09 10:07:19 -04:00
Thomas Harte
5a5fc1ae1a Merge pull request #541 from TomHarte/Annunciator3
Implements the two undocumented annunciator 3 graphics modes
2018-09-09 10:06:52 -04:00
Thomas Harte
8d79a1e381 Corrected fat low-res implementation.
As per comment of awanderin that "the odd addresses don't get their pixels auto-shifted by the hardware as with normal lo-res".
2018-09-09 10:06:21 -04:00
Thomas Harte
d70f5da94e Attempts an implementation of the undocumented low res + annunciator 3 graphics mode. 2018-09-08 20:51:15 -04:00
C.W. Betts
05d4274019 Remove LSItemContentTypes: they should be unique identifiers, not generic types like public.item or public.data.
This can result in strange icons showing up in the wrong places.

Also added a category type.
2018-09-07 16:39:52 -06:00
Thomas Harte
afeec09902 Gets explicit about DHIRES being annunciator 3; implements four-colour high res mode. 2018-09-06 23:23:19 -04:00
Thomas Harte
0526ac2ee2 Slightly increases const correctness.
The converters from source data to output pixels do not modify the source data. It's a shame there's no `restrict` in C++.
2018-09-05 11:36:40 -04:00
Thomas Harte
6725ee2190 Merge pull request #539 from TomHarte/40ColumnTextCorruption
Corrects 40-column alternative text mode corruption
2018-09-05 10:27:09 -04:00
Thomas Harte
8b661fb90f Introduces an extra level of indirection for text mapping. 2018-09-05 10:26:08 -04:00
Thomas Harte
dab7d3db1b Merge branch 'master' into 40ColumnTextCorruption 2018-08-30 20:24:47 -04:00
Thomas Harte
1cba3d48d9 Merge pull request #538 from TomHarte/AppleDecodingAgain
Correction: 0xc011 et al get the keyboard value in bits 0 to 6...
2018-08-30 20:19:48 -04:00
Thomas Harte
d53b38ec7e Correction: 0xc011 et al get the keyboard value in bits 0 to 6 and the switch value in bit 7. 2018-08-30 20:18:36 -04:00
Thomas Harte
5d0f47eda2 Merge pull request #536 from TomHarte/AppleDecoding
Adds mirrors for keyboard input and the audio toggle.
2018-08-27 21:14:48 -04:00
Thomas Harte
2e04c4442c Adds mirrors for keyboard input and the audio toggle. 2018-08-27 21:14:21 -04:00
Thomas Harte
f639cdc8ad Merge pull request #535 from TomHarte/DSKFixes
Corrects Apple DSK track length, inter-track skew, and Pro-DOS volume number.
2018-08-27 21:07:11 -04:00
Thomas Harte
71ec7624ca Corrects Apple DSK track length, inter-track skew, and Pro-DOS volume number. 2018-08-27 20:56:25 -04:00
Thomas Harte
0599d9602e Ensures no out-of-bounds accesses to inverses on a IIe. 2018-08-26 23:02:31 -04:00
Thomas Harte
234bef2a88 Adds default to make it explicit that fetch_address is initialised. 2018-08-24 22:26:03 -04:00
Thomas Harte
adb574e1cd Merge pull request #529 from TomHarte/AppleDelay
Corrects Apple II video defects
2018-08-24 22:11:41 -04:00
Thomas Harte
1f491e764e Nudges visible area slightly to the right. 2018-08-24 22:08:11 -04:00
Thomas Harte
114a43a662 Corrects improper indexing for byte shift. 2018-08-24 21:58:43 -04:00
Thomas Harte
5547c39c91 Corrects documentation. 2018-08-24 20:06:40 -04:00
Thomas Harte
97a89aaf4d Factors out the stuff of deferred action interleaving, as I suspect it'll come in handy. 2018-08-24 20:04:26 -04:00
Thomas Harte
61e46399dc About face! There should be no delay on serialisation, but a delay on interpretation-affecting soft switches. 2018-08-22 21:56:45 -04:00
Thomas Harte
e802f6ecc2 Rearranges draw loop around a fixed-size 568-sample line buffer. 2018-08-19 22:31:04 -04:00
Thomas Harte
4209f0e044 Moves memory collection into a separate loop. 2018-08-18 21:54:24 -04:00
Thomas Harte
33576aa2c4 Uses const to ensure output_* are properly constrained. 2018-08-18 21:36:48 -04:00
Thomas Harte
17bf1a64bf Moves the stuff of generating pixels out of the main loop. 2018-08-18 18:44:31 -04:00
Thomas Harte
f8d46f8f3d Merge branch 'master' into AppleDelay 2018-08-18 14:11:21 -04:00
Thomas Harte
8787d85e64 Eliminates #undefs as being (i) unnecessary, now this is a source file; and (ii) incomplete in any case. 2018-08-17 22:24:42 -04:00
Thomas Harte
7f0f17f435 Merge pull request #523 from TomHarte/Further65C02
Further corrects 65C02 behaviour
2018-08-17 21:58:38 -04:00
Thomas Harte
0e7f54f375 Implements STP and WAI, and ensures all unimplemented 65C02 instructions are NOP for all 65C02s. 2018-08-17 21:49:06 -04:00
Thomas Harte
b3bdfa9f46 Corrected: it's three-cycle 65C02 branches that ignore interrupts, not two. 2018-08-16 20:47:49 -04:00
Thomas Harte
592ec69d36 Causes the 65C02 not to accept interrupts immediately after untaken branches. 2018-08-15 22:42:04 -04:00
Thomas Harte
60e00ddd02 Correction: the test for not skipping an operand fetch requires a 65C02. 2018-08-15 22:07:17 -04:00
Thomas Harte
6806193dc2 Ensures that "Read/Modify/Write instructions absolute indexed in same page" take only six cycles on a 65C02. 2018-08-15 19:17:37 -04:00
Thomas Harte
c35dca783f Ensures that page-crossing indexing no longer causes an extra read of an invalid address on the 65C02.
It rereads the last byte of the instruction stream instead.
2018-08-15 18:47:53 -04:00
Thomas Harte
901e0d65b9 Documents all 6502 micro-operations.
Also makes sure 1-cycle NOPs really, definitely are one cycle only on a 65C02 and eliminates OperationCopyOperandFromA as a redundant copy of OperationSTA.
2018-08-14 22:17:53 -04:00
Thomas Harte
ddf45a0010 Ensures NMI and RST reset D on 65C02s. 2018-08-14 19:49:14 -04:00
Thomas Harte
1eca4463b3 Ensures NMI can no longer usurp BRK on 65C02s. 2018-08-14 19:33:48 -04:00
Thomas Harte
be01203cc1 Starts to expand the range of supported 6502s.
This fully implements the NES 6502 because, well, it's virtually no extra work, and ensures that RDY takes effect on write cycles on 65C02s.
2018-08-13 22:17:22 -04:00
Thomas Harte
4d1d19a464 Introduces an intermediate buffer for Apple II video data. 2018-08-12 20:36:08 -04:00
Thomas Harte
760817eb3b Merge pull request #521 from TomHarte/AppleVideo
Fixes Apple II double low resolution graphics
2018-08-11 23:20:40 -04:00
Thomas Harte
cb47575860 Eliminates stdout chatter. 2018-08-11 22:57:54 -04:00
Thomas Harte
434d184503 Corrects deserialisation order in double low res mode. 2018-08-11 22:53:06 -04:00
Thomas Harte
7374c665e8 Corrects regression in video flushing. 2018-08-11 19:57:39 -04:00
Thomas Harte
10c930a59d Merge pull request #520 from TomHarte/EnhancedIIe
Adds Enhanced IIe emulation.
2018-08-11 19:42:47 -04:00
Thomas Harte
60ab6f0c2a Entrusts IIe-esque character logic fully to the ROM. 2018-08-11 18:45:39 -04:00
Thomas Harte
a13eb351da Implements the Enhanced IIe, other than some text selection errors. 2018-08-11 10:26:30 -04:00
Thomas Harte
4b91910fab Removes erroneous addition. 2018-08-10 23:27:09 -04:00
Thomas Harte
f46d52364c Merge pull request #519 from TomHarte/65C02
Makes an initial pass at 65C02 emulation
2018-08-10 23:21:45 -04:00
Thomas Harte
878c63dcd2 Ensures ADC and SBC decimal take an extra cycle on the 65C02. 2018-08-10 22:52:55 -04:00
Thomas Harte
261fb3d4f8 Implements proper test for ADC/SBC 65C02 NZ, though not yet the proper timing.
This gets Klaus Dorman's test to pass.
2018-08-10 22:42:35 -04:00
Thomas Harte
b63e0cff72 Improves has-completed test. 2018-08-10 22:27:01 -04:00
Thomas Harte
5d6e479338 Implements RMB and SMB, and fixes SBC (zero). 2018-08-10 22:13:51 -04:00
Thomas Harte
90094529a5 Implements TSB and TRB, and adds the extra BIT instructions. 2018-08-10 22:04:45 -04:00
Thomas Harte
aed4c0539e Implements STZ. 2018-08-10 21:17:02 -04:00
Thomas Harte
8b50ab2593 Corrects (zero) behaviour. 2018-08-10 21:12:55 -04:00
Thomas Harte
95164b79c9 Attempted implementation of (zp) addressing mode. 2018-08-09 21:51:14 -04:00
Thomas Harte
6f838fe190 Implements INA and DEA. 2018-08-08 22:30:19 -04:00
Thomas Harte
bb680b40d8 Implements the 65C02's JMPs. 2018-08-08 22:26:57 -04:00
Thomas Harte
e3f6da6994 Implements the 65C02 NOPs. 2018-08-08 20:00:14 -04:00
Thomas Harte
e46bde35f5 Implements BBS and BBR. 2018-08-07 21:52:17 -04:00
Thomas Harte
32338bea4d Implements BRA. 2018-08-06 22:37:30 -04:00
Thomas Harte
5c881bd19d Implements PLX, PLY, PHX and PHY. 2018-08-06 22:00:23 -04:00
Thomas Harte
1a44ef0469 Introduces Klaus Dorman's 65C02 tests. All failing. 2018-08-06 21:48:43 -04:00
Thomas Harte
ebce9a2e51 Fixes test target. 2018-08-06 21:15:13 -04:00
Thomas Harte
633af4d404 The operations table is now per-instance. 2018-08-06 20:47:14 -04:00
Thomas Harte
76a73c835c Forces 6502 consumers to declare which model — the original, 65C02 or 65SC02.
All present machines use a regular 6502.
2018-08-06 20:06:07 -04:00
Thomas Harte
c1d1c451ef Merge pull request #518 from TomHarte/MacInsertDisplay
Tweaks the Mac UI
2018-08-06 19:12:17 -04:00
Thomas Harte
3be30d8c71 Tries once again to introduce file type icons. 2018-08-06 19:08:27 -04:00
Thomas Harte
d4c1244485 Adds a hint for users. 2018-08-06 18:56:59 -04:00
Thomas Harte
c61b9dca17 Ensures the Mac doesn't show the 'Insert...' option for machines that can't accept an insertion. 2018-08-06 18:52:42 -04:00
Thomas Harte
39bf682016 Adds mentions of the IIe. 2018-08-06 12:03:54 -04:00
Thomas Harte
60ac9b49ea Merge pull request #517 from TomHarte/MacInsertUI
Completes Mac UI 'Insert...' change
2018-08-05 22:58:16 -04:00
Thomas Harte
a8bb18e2cf Merge branch 'master' into MacInsertUI 2018-08-05 22:57:28 -04:00
Thomas Harte
1852786609 Merge pull request #516 from TomHarte/MacInsertUI
Adds an 'Insert...' menu option.
2018-08-05 22:51:00 -04:00
Thomas Harte
31df8c7e91 Corrects improper NSWindowController sheet stack manipulation.
As a result, 'Insert...' now seems to work properly.
2018-08-05 22:47:51 -04:00
Thomas Harte
832939f5b7 Merge branch 'master' into MacInsertUI 2018-08-05 22:39:00 -04:00
Thomas Harte
c2d9e1ec81 Merge pull request #515 from TomHarte/POPImage
Adds an Apple II screenshot to the readme.
2018-08-05 17:58:53 -04:00
Thomas Harte
673b915ee8 Reduces post-table image size a little. 2018-08-05 17:57:37 -04:00
Thomas Harte
032a62dfff Adds An Apple II screenshot to the mix. 2018-08-05 17:56:10 -04:00
Thomas Harte
f2d78182a3 Merge pull request #514 from TomHarte/VideoFixes
Fixes various IIe video deficiencies.
2018-08-05 17:49:13 -04:00
Thomas Harte
de68e70246 Fixes various IIe video deficiencies.
Specifically:
* the double-high resolution switches should be read/write; and
* the other IIe-specific switches should cause a video update for real-time effect.
2018-08-05 17:47:23 -04:00
Thomas Harte
e07447eb9a Merge pull request #513 from TomHarte/JoystickRange
Significant improves Apple II joystick compatibility
2018-08-05 17:37:38 -04:00
Thomas Harte
5cdeb58571 Makes digital to analogue conversion more extreme. 2018-08-05 17:36:20 -04:00
Thomas Harte
ce14cc8677 Flips meaning of analogue input bits, correcting most joystick titles.
Mysteriously, some functioned correctly before this. But they continue to do so.
2018-08-05 17:36:01 -04:00
Thomas Harte
bcd0479074 This in principle completes the insert action.
With the caveat that 'New...' machines seem to have blocked the window's panel queue somehow.
2018-08-05 13:12:02 -04:00
Thomas Harte
d72dd8c4ff Merge branch 'master' into macInsertUI 2018-08-05 11:54:26 -04:00
Thomas Harte
f7ce86fef8 Merge pull request #512 from TomHarte/80Text
Extends correct text handling to 80-column mode.
2018-08-04 22:28:59 -04:00
Thomas Harte
55f2fccf5e Extends correct text handling to 80-column mode. 2018-08-04 22:25:29 -04:00
Thomas Harte
c939a274be Makes first attempt to connect up an in-machine open panel. 2018-08-04 22:21:23 -04:00
Thomas Harte
101fb5d7bf Merge pull request #511 from TomHarte/ColecoSizeCheck
Relaxes ColecoVision cartridge image size check
2018-08-04 21:47:30 -04:00
Thomas Harte
3c51e335c3 Makes extra sure not to try to read from an empty characters list. 2018-08-04 21:40:26 -04:00
Thomas Harte
33ea90678c Relaxes ColecoVision cartridge size test. 2018-08-04 21:40:02 -04:00
Thomas Harte
11ae2c64ba Merge pull request #502 from TomHarte/IIe
Extends Apple II emulation to include the IIe
2018-08-04 21:02:45 -04:00
Thomas Harte
26624d7652 Fixes vertical blank signal; it should be the other way around. 2018-08-04 20:57:02 -04:00
Thomas Harte
85fb4773b0 Tweaks Apple key mapping and implements reset_all_keys. 2018-08-04 20:31:37 -04:00
Thomas Harte
099d66804e Makes colour burst phase explicit. 2018-08-04 19:29:34 -04:00
Thomas Harte
086596c28e Adds reading of vertical blank and implements the full IIe keyboard logic.
i.e. there are now two Apple keys, and shift isn't assumed.
2018-08-04 19:17:04 -04:00
Thomas Harte
3aeb4213fe Implements the C010 read value. 2018-08-04 17:57:02 -04:00
Thomas Harte
558b96bc05 Corrects IIe text display. 2018-08-04 16:52:29 -04:00
Thomas Harte
e97cc40a2c Corrects typo in Cx-page ROM paging. 2018-08-04 12:44:58 -04:00
Thomas Harte
94503ed771 Disables the macOS Apple II options panel, since it now has no options. 2018-08-04 12:37:55 -04:00
Thomas Harte
c4f86cc324 The Disk II now being its proper speed, withdraws the quickload option. 2018-08-03 21:20:21 -04:00
Thomas Harte
70c4d6b9b3 Adds a one second delay between controller and drive motor off. 2018-08-03 21:13:18 -04:00
Thomas Harte
78c7137427 Avoids observer communication if motor status hasn't changed. 2018-08-03 21:11:22 -04:00
Thomas Harte
74a2f717b3 Turns down the composite signal amplitude a little, to help colour distinctness. 2018-08-01 18:52:42 -04:00
Thomas Harte
98bb5bd9f1 Ensures flux bits are observable for two cycles rather than one; it should be 1us. 2018-07-31 23:01:11 -04:00
Thomas Harte
c91eaaf8da Takes a stab at double low-res graphics. 2018-07-31 21:45:09 -04:00
Thomas Harte
a36f37d240 Introduces a 1/14th delay in output of double high res. 2018-07-31 21:29:51 -04:00
Thomas Harte
c773d3501a Implements the INTC8ROM switch.
Finally causing the Zellyn tests to pass! Is this nightmare behind me?
2018-07-31 19:00:46 -04:00
Thomas Harte
5810f9b3f9 Fixes high resolution address range and switching logic. 2018-07-30 23:23:18 -04:00
Thomas Harte
3f56683342 Fixes order of deserialisation between auxiliary and base RAM. 2018-07-30 23:08:45 -04:00
Thomas Harte
16ccbdefd6 Of course, | has higher precedence than ?. Classic! 2018-07-30 23:08:22 -04:00
Thomas Harte
a533d09fe7 Sets the IIe as the default model. 2018-07-30 23:07:34 -04:00
Thomas Harte
e9aaa5bbdf Factors out the page-mapping function.
For one less potential source of failure.
2018-07-30 22:23:48 -04:00
Thomas Harte
ecb26e3281 Corrections: slot_C3_rom_ works the other way around; 80STORE doesn't affect most of RAM but does always affect the text screen.
Also factored out `set_zero_page_paging` for consistency.
2018-07-30 19:54:25 -04:00
Thomas Harte
5aa0b17720 Improves IIe paging further. 2018-07-29 23:02:27 -04:00
Thomas Harte
632b37ecec Attempts an implementation of auxiliary memory. 2018-07-29 10:41:12 -04:00
Thomas Harte
c905de2e40 Restores IIe ROM-over-card paging. 2018-07-28 13:31:25 -04:00
Thomas Harte
bc2afe69e1 Accepting that memory mapping on a IIe is more complicated than I anticiapted, introduces mapping for all pages.
Also picks a name for the Unenhanced Apple IIe ROM.
2018-07-28 13:02:49 -04:00
Thomas Harte
894998b163 Merge branch 'master' into IIe 2018-07-28 10:54:04 -04:00
Thomas Harte
51192d8397 Merge pull request #508 from TomHarte/Whitespace
Eliminates various blank lines.
2018-07-28 10:53:17 -04:00
Thomas Harte
3c33ccd730 Eliminates various blank lines. 2018-07-28 10:52:34 -04:00
Thomas Harte
3e35109d63 Merge pull request #507 from TomHarte/BetterBMPDestination
Use `xdg-user-dir PICTURES` instead of $HOME for screenshots
2018-07-28 10:48:28 -04:00
Thomas Harte
99c770eab4 Ensure that the output of xdg-user-dir is properly filtered. 2018-07-28 10:45:50 -04:00
Thomas Harte
34aa78b7ce Attempts to use xdg-user-dir PICTURES in preference to $HOME for pictures. 2018-07-28 09:14:18 -04:00
Thomas Harte
8cca9c2055 Merge branch 'master' into IIe 2018-07-27 23:52:39 -04:00
Thomas Harte
85ce21c79f Merge pull request #505 from TomHarte/MacScreenshots
Attempts to introduce screenshot capture for macOS.
2018-07-27 23:43:13 -04:00
Thomas Harte
d19d949b9c Removes unnecessary import. 2018-07-27 23:41:55 -04:00
Thomas Harte
1cb3713b84 Attempts to introduce screenshot capture for macOS. 2018-07-27 23:37:24 -04:00
Thomas Harte
689850d698 Merge pull request #504 from TomHarte/SDLBMPByteOrder
Ensures SDL is properly informed of buffer byte order.
2018-07-27 18:53:16 -04:00
Thomas Harte
c572a52049 Ensures SDL is properly informed of buffer byte order. 2018-07-27 18:51:38 -04:00
Thomas Harte
41765e00c4 Merge branch 'master' into IIe 2018-07-26 21:24:46 -04:00
Thomas Harte
080aa0acc5 Merge pull request #503 from TomHarte/SDLScreenshots
Adds screenshot saving upon ctrl+shift+d.
2018-07-26 20:58:35 -04:00
Thomas Harte
5e7c46a72a Adds screenshot saving upon ctrl+shift+d. 2018-07-26 20:53:12 -04:00
Thomas Harte
5f2b9b2d5a Implements the alternative zero page soft switch. 2018-07-25 22:10:21 -04:00
Thomas Harte
5c4506a9db Talks the IIe into proceeding to a beep and an improperly-formed logo. 2018-07-25 21:43:12 -04:00
Thomas Harte
55a6431fb3 Puts in enough logic to be able to launch a non-functional IIe. 2018-07-25 18:58:34 -04:00
Thomas Harte
ede2696a77 Edges further towards implementing the IIe video subsystem.
All video-specific switches are in place, and mostly honoured, and a IIe machine configuration is advertised at least.
2018-07-24 22:15:42 -04:00
Thomas Harte
59b9e39022 Starts the process of supporting the Apple IIe graphics modes.
Albeit that I'm not yet even up on the proper soft switches.
2018-07-23 22:14:41 -04:00
Thomas Harte
6b2970f2f2 Ensures no-hat input doesn't override analogue axes. 2018-07-22 17:29:37 -04:00
Thomas Harte
6a73fe7d65 Merge pull request #500 from TomHarte/MacJoysticks
Implements initial joystick support for the Mac
2018-07-22 16:56:40 -04:00
Thomas Harte
1362906f94 Wires joystick support all the way through to machines.
Ensures there's only one joystick manager, which is shared by all machines, with input going only to the key window.
2018-07-22 16:55:47 -04:00
Thomas Harte
8f4042c4bb Permits joysticks to be queried for number of fire buttons. 2018-07-22 16:52:58 -04:00
Thomas Harte
c05b6397b0 Attempts a full implementation of the joystick manager.
So it currently vends a list of existing joysticks plus their states. More work will be required for a UI — e.g. there is no way to identify one joystick from another — but this'll do for now.
2018-07-22 15:23:26 -04:00
Thomas Harte
8d18808efe Walks a few steps further along device inspection. 2018-07-20 23:33:04 -04:00
Thomas Harte
09950d9414 Gamely starts to create a HID input manager for joysticks/pads/etc. 2018-07-19 22:43:01 -04:00
Thomas Harte
badbbdf155 Merge pull request #498 from TomHarte/DisplayBorder
Resolves border issues in fullscreen mode
2018-07-16 22:01:08 -04:00
Thomas Harte
2832792fed Corrects improper use of doubles. 2018-07-16 21:55:19 -04:00
Thomas Harte
efa45b9504 Adds a right gutter to clip persistence errors.
Also uncovers and corrects a long-standing centring error.
2018-07-16 21:52:31 -04:00
Thomas Harte
523749edf8 Merge branch 'master' into DisplayBorder 2018-07-16 20:00:52 -04:00
Thomas Harte
5a0499e8a7 Merge pull request #499 from TomHarte/EditorConfig
Adds a .editorconfig to aid Github display.
2018-07-16 20:00:27 -04:00
Thomas Harte
258c8b5900 Adds a .editorconfig to aid Github display. 2018-07-16 19:59:03 -04:00
Thomas Harte
24b861f056 Eliminates make_unique as this is presently a C++11 project. 2018-07-15 22:52:36 -04:00
Thomas Harte
29f7f4d432 Adds missing #include. 2018-07-15 22:47:50 -04:00
Thomas Harte
21080a1149 Merge branch 'master' into DisplayBorder 2018-07-15 22:31:33 -04:00
Thomas Harte
1d068fd09b Merge pull request #497 from TomHarte/RobocopSprites
Ensures only the first 8px of sprites is output in 8x8 mode.
2018-07-15 22:30:42 -04:00
Thomas Harte
92065813ef Ensures only the first 8px of sprites is output in 8x8 mode.
Also adds a little extra documentation.
2018-07-15 22:21:29 -04:00
Thomas Harte
3e9ef6b8cb Adds indicator lights for the SDL port.
To complete #426
2018-07-15 20:19:06 -04:00
Thomas Harte
c9451a5382 Introduces an object for drawing OpenGL rectangles. 2018-07-14 17:42:23 -04:00
Thomas Harte
2be3b027db Merge branch 'master' into DisplayBorder 2018-07-14 13:13:29 -04:00
Thomas Harte
e339d169c5 Ensures the joystick doesn't obstruct tape input. 2018-07-12 22:10:05 -04:00
Thomas Harte
87001f86ee Merge pull request #495 from TomHarte/MSXJoysticks
Adds joystick support for the MSX.
2018-07-12 21:44:26 -04:00
Thomas Harte
58484e8f37 Adds joystick support for the MSX. 2018-07-12 21:42:47 -04:00
Thomas Harte
94f68f9d55 Merge pull request #494 from TomHarte/CustomInfoBlock
Corrects TZX custom info block parsing.
2018-07-11 22:22:49 -04:00
Thomas Harte
3f6944de54 Corrects custom info block parsing. 2018-07-11 22:21:35 -04:00
Thomas Harte
00cb4d26b3 Corrects typo. 2018-07-11 19:52:55 -04:00
Thomas Harte
774d8668bf Merge pull request #493 from TomHarte/SpecificROMs
Clarifies startup procedure for machines
2018-07-10 22:15:40 -04:00
Thomas Harte
8503589828 Corrects failure to retain OS. 2018-07-10 22:05:50 -04:00
Thomas Harte
0f95ef2059 Merge branch 'SpecificROMs' of github.com:TomHarte/CLK into SpecificROMs 2018-07-10 21:54:45 -04:00
Thomas Harte
efd812cf22 Ensures no buffer overrun when installing the OS ROM. 2018-07-10 21:54:36 -04:00
Thomas Harte
736e14c83e Ensures no buffer overrun when installing the OS ROM. 2018-07-10 21:49:38 -04:00
Thomas Harte
57f161e64c Corrects documentation of the media target. 2018-07-10 21:42:09 -04:00
Thomas Harte
0897210969 Neither cartridge machine should be a media target; their media can't be changed at runtime. 2018-07-10 21:40:13 -04:00
Thomas Harte
7e58a44771 Renames ConfigurationTarget to MediaTarget as per its newly-reduced interface. 2018-07-10 21:32:28 -04:00
Thomas Harte
e8f847d288 Fixes CRC generator used to verify Acorn programs. 2018-07-10 20:01:31 -04:00
Thomas Harte
a0f817108e Minor style fix. 2018-07-10 20:01:11 -04:00
Thomas Harte
3862fdb44c Simplifies initialisation procedure for all machines.
With the side effect of allowing every machine to try to load only the ROMs that it needs.
2018-07-10 20:00:46 -04:00
Thomas Harte
3e2d271566 Merge pull request #491 from TomHarte/CPCClip
[Re-]recalibrates CRT retrace period and affected view windows.
2018-07-05 22:38:20 -04:00
Thomas Harte
c97c5fa03a [Re-]recalibrates CRT retrace period and affected view windows.
In the hope of moving the CPC closer to the real CTM visible area.
2018-07-05 22:07:18 -04:00
Thomas Harte
fa63f7ffc3 Merge pull request #490 from TomHarte/NIBTails
Ensures NIB tracks aren't truncated
2018-07-03 21:38:31 -04:00
Thomas Harte
bfccadd356 Corrects comment typo. 2018-07-03 21:38:04 -04:00
Thomas Harte
5b3512f1df Attempts to pick an intelligent place to pad out tracks. 2018-07-03 20:10:22 -04:00
Thomas Harte
6e34e60f8a Ensures no data is dropped in transcribing a NIB to real track data. 2018-07-03 20:01:07 -04:00
Thomas Harte
a391d0f4ae Merge pull request #489 from TomHarte/OricDiskII
Simplifies disk track storage and writing implementation
2018-07-02 22:09:58 -04:00
Thomas Harte
abc5c50b2e Added some additional exposition. 2018-07-02 21:51:53 -04:00
Thomas Harte
1fcb461c42 Ensures that segments are written in a properly-circular fashion. 2018-07-02 19:35:49 -04:00
Thomas Harte
abca38a548 Makes an initial removal of PCMPatchedTrack. Farewell, old friend. 2018-07-01 22:49:57 -04:00
Thomas Harte
b4be2cd063 Implements PCMTrack::add_segment. Thereby completes PCMTrack::resampled_clone. 2018-07-01 18:28:25 -04:00
Thomas Harte
2d83eeb9c4 Further minor style improvements. 2018-07-01 17:59:43 -04:00
Thomas Harte
4d9e897cc3 Corrects addressing for deserialisation of bytes. 2018-07-01 15:58:56 -04:00
Thomas Harte
be664b5695 Ensures that start positions are properly related to sectors. 2018-07-01 15:53:48 -04:00
Thomas Harte
c3751066b7 Ensures segments are properly sized. 2018-07-01 15:43:31 -04:00
Thomas Harte
77feee8197 Applies minor style improvements. 2018-07-01 15:38:42 -04:00
Thomas Harte
f75af3b45e Adds some extra exposition. 2018-07-01 14:41:17 -04:00
Thomas Harte
1471a35bb8 Reserves a more appropriate amount of data. 2018-07-01 14:40:48 -04:00
Thomas Harte
555c2a4377 Makes a first sweep at converting the storage underlying PCMSegment to vector<bool>.
This is to remove another pain point, in preparation for the work immediately forthcoming but also work as-yet unknown.
2018-07-01 12:05:41 -04:00
Thomas Harte
16bef0dcd5 Starts the movement towards a world without PCMPatchedTrack. 2018-06-30 20:03:18 -04:00
Thomas Harte
cd464fc7de Corrects status logging. 2018-06-26 20:53:08 -04:00
Thomas Harte
5b88207477 Merge pull request #488 from TomHarte/AYClarification
Removes unused AY state and implements AND output readback.
2018-06-26 19:31:50 -04:00
Thomas Harte
df8c896193 Removes unused state and implements AND output readback. 2018-06-26 19:31:16 -04:00
Thomas Harte
5d3e1f7084 Merge pull request #487 from TomHarte/SpuriousKeyboard
Corrects a misreported value when reading the AY if not in reading mode
2018-06-25 20:49:32 -04:00
Thomas Harte
59f8eeb05a Ensures the AY goes high impedance when not in read mode. 2018-06-25 20:48:24 -04:00
Thomas Harte
0b14850467 Corrects some comments. 2018-06-24 23:02:36 -04:00
Thomas Harte
f72e260915 Merge pull request #486 from TomHarte/AppleIIEqualisation
Ensures the Apple II retains horizontal sync for its entire display
2018-06-24 11:27:57 -04:00
Thomas Harte
640a84d456 Shift the h-within-v pulse to eliminate a curved top line. 2018-06-24 11:27:18 -04:00
Thomas Harte
04f6cb1750 Merge branch 'master' into AppleIIEqualisation 2018-06-23 23:10:29 -04:00
Thomas Harte
87d688b7e3 Merge pull request #485 from TomHarte/FurtherTweaks
Various tweaks: Mac UI and CPC window size
2018-06-23 23:10:07 -04:00
Thomas Harte
26141a59b0 Moves all default Mac window positions up by 50px. 2018-06-23 23:09:34 -04:00
Thomas Harte
a93f8103ad Zooms out the CPC a little more.
To fix the maximum amount of content that I can, at least for now.
2018-06-23 22:15:34 -04:00
Thomas Harte
4a3d7c338a Moves the activity window down to start at approximately the same top as the options window. 2018-06-23 22:14:44 -04:00
Thomas Harte
55ab305dbf Introduces equalisation pulses for the Apple II. 2018-06-23 22:11:39 -04:00
Thomas Harte
e48ba89721 Merge pull request #484 from TomHarte/MachinePickerLocation
Cleans up the Mac UI
2018-06-23 19:45:49 -04:00
Thomas Harte
9bb55b6b61 Ensures that 'Activity' view has minimum acceptable height. 2018-06-23 19:44:35 -04:00
Thomas Harte
c33308bdc5 Attempts to improve relative default window positions. 2018-06-23 18:59:19 -04:00
Thomas Harte
44a33941bf Undoes Xcode's folder renaming. 2018-06-23 18:55:17 -04:00
Thomas Harte
cc34cd2133 Merge pull request #481 from TomHarte/CPCJoysticks
Introduces joystick support for the CPC.
2018-06-23 17:09:32 -04:00
Thomas Harte
52c9f9e89e Merge branch 'master' into CPCJoysticks 2018-06-23 16:43:49 -04:00
Thomas Harte
2363deb19c Merge pull request #483 from TomHarte/BetterClip
Picks more appropriate cropping now that I'm obeying HSYNC-as-blank.
2018-06-23 16:42:44 -04:00
Thomas Harte
1c6af279b2 Picks more appropriate cropping now that I'm obeying HSYNC-as-blank. 2018-06-23 16:40:17 -04:00
Thomas Harte
6e96275e1c Merge pull request #482 from TomHarte/PixelCapture
Ensures the pixel collection test is inline with other decisions.
2018-06-23 16:19:04 -04:00
Thomas Harte
9968342a11 Ensures the pixel collection test is inline with other decisions. 2018-06-23 16:18:33 -04:00
Thomas Harte
c248ecde48 Introduces joystick support for the CPC. 2018-06-21 22:46:10 -04:00
Thomas Harte
370952ab33 Merge pull request #480 from TomHarte/Blank
Corrects left-border handling on the CPC
2018-06-21 20:08:09 -04:00
Thomas Harte
154c89e041 Introduces a missing separator. 2018-06-21 20:01:04 -04:00
Thomas Harte
d45f1a793d Introduces composite/RGB selection for the Amstrad CPC. 2018-06-21 20:00:49 -04:00
Thomas Harte
9800951f18 Merge branch 'master' into Blank 2018-06-21 19:41:04 -04:00
Thomas Harte
17251997c2 Merge pull request #479 from TomHarte/8272Logging
Returns sanity to 8272 logging.
2018-06-21 19:40:42 -04:00
Thomas Harte
5ab4cfee84 Factors out repeated hex-size setting. 2018-06-21 19:27:54 -04:00
Thomas Harte
a9eb0d02c6 Returns sanity to 8272 logging. 2018-06-20 23:02:32 -04:00
Thomas Harte
1f8b69a5b0 Attempts to honour the full CRTC 'sync' period, placing blank and the colour burst. 2018-06-20 22:38:54 -04:00
Thomas Harte
8b83f58d7a Merge pull request #478 from TomHarte/CPCTimingTests
Differentiates reasons for a read to be four cycles.
2018-06-20 21:39:12 -04:00
Thomas Harte
9a91ae38c1 Differentiates reasons for a read to be four cycles.
Specifically, puts the enforced wait either before or after checking the wait line. More research may be required; it feels more likely to me that a forced post wait should complete the read then wait, but would that still count as a single machine cycle?
2018-06-20 21:34:21 -04:00
Thomas Harte
ad57caed5e Merge pull request #476 from TomHarte/LongLines
Increases permissible error in scanline length.
2018-06-19 22:25:37 -04:00
Thomas Harte
283ed8dbae Increases permissible error in scanline length. 2018-06-19 22:24:11 -04:00
Thomas Harte
acb74185d5 Revokes test logging. 2018-06-19 19:39:09 -04:00
Thomas Harte
7a5d16ccf8 Merge pull request #475 from TomHarte/VisibleActivity
Shows activity indicators on the Mac
2018-06-18 22:49:33 -04:00
Thomas Harte
adca862166 Finally makes an initial pass at logging macros. 2018-06-18 22:37:19 -04:00
Thomas Harte
1bdc718527 Ensures the MSX reports the proper number of drives. 2018-06-18 22:15:52 -04:00
Thomas Harte
685a80f95b Ensures the Electron Plus 3 properly announces drives to an activity observer.
Does away with lazy allocation as not all that helpful, and liable to cause complexity.
2018-06-18 21:49:57 -04:00
Thomas Harte
62eef8cb40 Reinstates proper ready behaviour. 2018-06-18 21:35:39 -04:00
Thomas Harte
6ed3a49fe1 Made failed attempt to apply height constraint. 2018-06-18 21:35:22 -04:00
Thomas Harte
17702bfb89 Causes GUI LEDs to reflect their underlying activity. 2018-06-18 21:22:51 -04:00
Thomas Harte
292e02702a Progresses very slightly to being able to show up to four activity indicator names.
Blinking to come.
2018-06-17 22:52:17 -04:00
Thomas Harte
5a56d8a5d0 Exposes a list of machine LEDs to Swift.
Also gets explicit about nullability on the Objective-C side.
2018-06-17 18:53:56 -04:00
Thomas Harte
3da1d5700c Merge pull request #472 from TomHarte/SDLJoystick
Connects SDL joystick input to joystick machines.
2018-06-16 22:26:19 -04:00
Thomas Harte
d437e06e15 Adds support for digital hat input as an alternative to analogue sticks. 2018-06-16 22:25:46 -04:00
Thomas Harte
6a3702a5c7 Reduces space for floating point accuracy errors. 2018-06-16 22:22:40 -04:00
Thomas Harte
83a654540a Fixes threshold for positive movement. 2018-06-16 22:22:14 -04:00
Thomas Harte
678bd93c52 Connects SDL joystick input to joystick machines. 2018-06-14 22:37:44 -04:00
Thomas Harte
1bf0c1891a Merge pull request #471 from TomHarte/FadeAwayNotRadiate
Hides persistent low-part colour channel errors.
2018-06-14 20:45:41 -04:00
Thomas Harte
b899a22c7d Hides persistent low-part colour channel errors. 2018-06-14 20:40:27 -04:00
Thomas Harte
1bd6bbca8d Merge pull request #468 from TomHarte/PALColour
Tweaks blending back to appease interlaced video
2018-06-14 18:28:06 -04:00
Thomas Harte
14a2e470e4 Corrects overbrightness issue with autogeneration of PAL composite from an RGB source. 2018-06-14 18:25:48 -04:00
Thomas Harte
41dcf1de42 Increases blur again just a little more, after consideration of interlaced output. 2018-06-14 18:25:04 -04:00
Thomas Harte
0c65385c82 Undoes older interpretation of alternating phase.
I now understand, hopefully, that it's only the phase of the second colour component that alternates. That has the pointwise effect of reversing the colour signal. Hence the effect of phase errors cancelling themselves out up on successive lines up to a point.
2018-06-14 18:24:32 -04:00
Thomas Harte
4aaf43150a Merge pull request #467 from TomHarte/Sharpness
Decreases inter-frame blur.
2018-06-14 17:43:16 -04:00
Thomas Harte
f05ee525cb Tweaks blurriness downward. 2018-06-14 17:41:17 -04:00
Thomas Harte
1172c4fd97 Merge pull request #466 from TomHarte/CPCKeyboard
Improves key state handling under SDL when switching to/from full screen
2018-06-14 17:26:11 -04:00
Thomas Harte
15deef50c8 Adds a key reset upon screen mode changes in SDL. 2018-06-14 17:24:16 -04:00
Thomas Harte
7728adfc5a Eliminates repetition of the 10 constant. 2018-06-14 17:23:47 -04:00
Thomas Harte
eff67f2250 Merge pull request #465 from TomHarte/SDLSwitch
Changes SDL full screen/window toggle and allows ROM path to be specified
2018-06-13 21:36:36 -04:00
Thomas Harte
64e3cf5de2 Ensured all usage messages reflect latest usage. 2018-06-13 21:31:13 -04:00
Thomas Harte
31a6d620e8 Revokes make_unique; I had forgotten that's a C++14 feature. 2018-06-13 21:24:12 -04:00
Thomas Harte
dfd37e7dec Switches full-screen command and adds user-specifiable ROM paths. 2018-06-13 21:21:52 -04:00
Thomas Harte
8d8f244bf5 Merge pull request #461 from TomHarte/Joystick
Introduces Joystick support for the Apple II
2018-06-13 19:34:26 -04:00
Thomas Harte
037b4802db Eliminates unused usings. 2018-06-13 19:26:59 -04:00
Thomas Harte
51da21b844 Formally introduces keyboard-as-joystick for the Mac, and fixes discovered joystick issues.
Specifically:
* digital to analogue conversion not returning to centre;
* Apple II axes being the wrong way around; and
* Apple II buttons using improper selection logic.
2018-06-13 19:22:34 -04:00
Thomas Harte
0be19d8de7 Ensures analogue channels which are already charging don't abide by c070. 2018-06-13 18:16:02 -04:00
Thomas Harte
f26e4734b3 Adds use of joystick input in the Apple II. 2018-06-12 22:21:43 -04:00
Thomas Harte
f1b430338e Makes the Apple II a joystick machine.
Albeit that the values supplied to its joysticks do not currently make it into the emulated state.
2018-06-11 22:16:32 -04:00
Thomas Harte
2954373115 Introduces an intermediary for digital <-> analogue conversion. 2018-06-11 21:35:03 -04:00
Thomas Harte
42d21ea3a9 Merge branch 'master' into Joystick 2018-06-10 21:08:59 -04:00
Thomas Harte
7d761f145f Corrects typo that mapped Apple II options to the Electron. 2018-06-10 21:05:14 -04:00
Thomas Harte
27657fcde0 Adds necessary header for assert. 2018-06-10 21:02:19 -04:00
Thomas Harte
3ea2a4ccb8 Moves the joystick class towards accepting analogue inputs. 2018-06-10 20:45:52 -04:00
Thomas Harte
a1c60152d4 Merge pull request #460 from TomHarte/RWTSAcceleration
Introduces optional quick loading of Apple DOS 3.3 programs
2018-06-10 18:41:23 -04:00
Thomas Harte
69da00fcfb Modifies test slightly for usual RWTS16 location.
Also eliminates messy print logging.
2018-06-10 18:41:09 -04:00
Thomas Harte
c4108efc5c Adds a more accurate option description for the Apple II. 2018-06-10 18:32:22 -04:00
Thomas Harte
d576ff1172 Exposes DOS 3.3 acceleration as an option.
Albeit with an unhelpful label in the macOS GUI for now.
2018-06-10 18:28:29 -04:00
Thomas Harte
af54666c23 Implements RWTS acceleration. 2018-06-10 17:58:16 -04:00
Thomas Harte
e0b75b6e3d Corrects logic for avoiding overwrite. 2018-06-09 21:47:51 -04:00
Thomas Harte
12c59ede09 Adds writeback of track location. 2018-06-09 20:34:12 -04:00
Thomas Harte
b4d0d4fff6 Starts building out some fast-loading infrastructure for DOS 3.3. 2018-06-09 17:29:14 -04:00
Thomas Harte
a694844190 Moves gap 1 into proper ownership. 2018-06-09 17:28:08 -04:00
Thomas Harte
28f2d331a8 Switches to more realistic gaps. 2018-06-09 13:06:45 -04:00
Thomas Harte
dde9b73a22 Creates the through-path that will be necessary for RWTS acceleration. 2018-06-09 12:51:53 -04:00
Thomas Harte
fb4bb21bf6 Ensures an objective copy of the bus address is kept, and forwarded to cards. 2018-06-08 20:12:15 -04:00
Thomas Harte
744c35b617 Caps the number of sync bytes inserted at five. 2018-06-06 21:52:26 -04:00
Thomas Harte
9ac21a4e71 Switches to ignoring the byte count, trusting the bit count entirely. 2018-06-06 21:51:55 -04:00
Thomas Harte
94359e9c75 Merge pull request #458 from TomHarte/ApplePhase
Corrects NTSC Q phase
2018-06-03 08:11:43 -04:00
Thomas Harte
076fa55651 Corrects: flux set is no-flux incoming.
This restores good sleeping behaviour.
2018-06-03 08:11:17 -04:00
Thomas Harte
d380595ad4 Unrolls the loops for slightly fewer conditionals. 2018-06-03 07:27:03 -04:00
Thomas Harte
d84b8700a3 Switches the Apple II to one byte per pixel.
Just trying to get it right for now; optimisation to come.
2018-06-02 22:03:45 -04:00
Thomas Harte
80b281d9f1 Switches back to whole bytes per pixel, owing to persistent precision problems at 1bpp.
Also fixes the inaccurately-named `cycles_since_update_`.
2018-06-02 18:25:00 -04:00
Thomas Harte
69dc3cc4d8 Switches to using the same varying for byte and subpixel selection. 2018-06-01 22:52:29 -04:00
Thomas Harte
1a9cea050e Minor: ensure AY registers *read* as 0 from reset, as well as being 0. 2018-06-01 19:48:42 -04:00
Thomas Harte
0833412df9 Corrects port for ZON-X reads. 2018-06-01 19:45:37 -04:00
Thomas Harte
35e84ff1a8 Corrects NTSC quadrature phase. 2018-05-31 21:40:46 -04:00
Thomas Harte
8dd7c6ef23 Eliminates 'reversed_c' as I no longer believe low-resolution colour numbers are reversed.
Also gets explicit about phase.
2018-05-29 22:30:45 -04:00
Thomas Harte
a26ab7086d Merge pull request #456 from TomHarte/TristateSleeper
Commutes `Sleeper` to `ClockingHint::Source`
2018-05-28 18:25:21 -04:00
Thomas Harte
b2464598d0 Forces the Apple II bus handler call inline. 2018-05-28 18:21:01 -04:00
Thomas Harte
6812a001d8 Teaches the Oric to apply a lighter Disk II touch when possible. 2018-05-28 18:20:43 -04:00
Thomas Harte
6c16754a6b Strips further improper constexprs. 2018-05-28 17:48:55 -04:00
Thomas Harte
75f9e3caeb Resolves incorrect bracketing. 2018-05-28 17:48:35 -04:00
Thomas Harte
ad5afe21ee Removes constexpr from things which are not const. Duh. 2018-05-28 17:28:57 -04:00
Thomas Harte
8a566cc1dd Experimentally goes to town on constexpr. 2018-05-28 17:20:11 -04:00
Thomas Harte
928aab13dc Introduces more granular clocking announcements to the Disk II.
As well as making it accept the clock rate it'll actually receive, to supply to the drives, so that they spin at the proper speed.
2018-05-28 17:19:29 -04:00
Thomas Harte
f3fe711542 Attempts to reduce FDC costs. 2018-05-27 23:55:04 -04:00
Thomas Harte
db8d8d8404 Commutes Sleeper to ClockingHint::Source, making state more granular. 2018-05-27 23:17:06 -04:00
Thomas Harte
6220ccb5d3 Merge pull request #455 from TomHarte/HumptyDumpty
Relaxes .p validation even further
2018-05-27 17:01:07 -04:00
Thomas Harte
20843305dd Removes unused calculation of vars. 2018-05-27 13:31:30 -04:00
Thomas Harte
8f6c0f6a8d Eliminates vars test.
At least Humpty Dumpty is a working .p that doesn't satisfy the test.
2018-05-26 19:05:35 -04:00
Thomas Harte
ede2df7e70 Merge pull request #452 from TomHarte/NIBWriting
Adds write support for NIBs
2018-05-25 18:40:35 -04:00
Thomas Harte
d45231c1a8 Introduces an additional validation test.
Thereby satisfying the TODO.
2018-05-25 18:40:15 -04:00
Thomas Harte
772812b35f Corrects improper textual reference to interface names. 2018-05-25 18:31:20 -04:00
Thomas Harte
f443fd44b5 Introduces support for writing NIBs. 2018-05-25 18:30:55 -04:00
172 changed files with 6526 additions and 3342 deletions

6
.editorconfig Normal file
View File

@@ -0,0 +1,6 @@
[*]
charset = utf-8
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true

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) = 0;
virtual void register_led(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) = 0;
virtual void register_drive(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) = 0;
virtual void set_led_status(const std::string &name, bool lit) {}
enum class DriveEvent {
StepNormal,
@@ -39,11 +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) = 0;
virtual void announce_drive_event(const std::string &name, 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) = 0;
virtual void set_drive_motor_status(const std::string &name, bool is_on) {}
};
}

View File

@@ -1,29 +0,0 @@
//
// MultiConfigurationTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiConfigurationTarget.hpp"
using namespace Analyser::Dynamic;
MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
if(configuration_target) targets_.push_back(configuration_target);
}
}
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
}
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);
}
return inserted;
}

View File

@@ -1,42 +0,0 @@
//
// MultiConfigurationTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiConfigurationTarget_hpp
#define MultiConfigurationTarget_hpp
#include "../../../../Machines/ConfigurationTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configuration target interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
public:
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
void configure_as_target(const Analyser::Static::Target *target) override;
bool insert_media(const Analyser::Static::Media &media) override;
private:
std::vector<ConfigurationTarget::Machine *> targets_;
};
}
}
#endif /* MultiConfigurationTarget_hpp */

View File

@@ -25,14 +25,14 @@ class MultiJoystick: public Inputs::Joystick {
}
}
std::vector<DigitalInput> get_inputs() override {
std::vector<DigitalInput> inputs;
for(const auto &joystick: joysticks_) {
std::vector<DigitalInput> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
std::vector<Input> &get_inputs() override {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
}
}
}
}
@@ -40,11 +40,18 @@ class MultiJoystick: public Inputs::Joystick {
return inputs;
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
void set_input(const Input &digital_input, bool is_active) override {
for(const auto &joystick: joysticks_) {
joystick->set_digital_input(digital_input, is_active);
joystick->set_input(digital_input, is_active);
}
}
void set_input(const Input &digital_input, float value) override {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, value);
}
}
void reset_all_inputs() override {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
@@ -52,6 +59,7 @@ class MultiJoystick: public Inputs::Joystick {
}
private:
std::vector<Input> inputs;
std::vector<Inputs::Joystick *> joysticks_;
};

View File

@@ -0,0 +1,26 @@
//
// MultiMediaTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiMediaTarget.hpp"
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();
if(media_target) targets_.push_back(media_target);
}
}
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);
}
return inserted;
}

View File

@@ -0,0 +1,41 @@
//
// MultiMediaTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiMediaTarget_hpp
#define MultiMediaTarget_hpp
#include "../../../../Machines/MediaTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the media target interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiMediaTarget: public MediaTarget::Machine {
public:
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard MediaTarget::Machine interface; see there for documentation.
bool insert_media(const Analyser::Static::Media &media) override;
private:
std::vector<MediaTarget::Machine *> targets_;
};
}
}
#endif /* MultiMediaTarget_hpp */

View File

@@ -15,10 +15,10 @@ using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
machines_(std::move(machines)),
configurable_(machines_),
configuration_target_(machines_),
crt_machine_(machines_, machines_mutex_),
joystick_machine_(machines),
keyboard_machine_(machines_) {
keyboard_machine_(machines_),
media_target_(machines_) {
crt_machine_.set_delegate(this);
}
@@ -26,11 +26,11 @@ Activity::Source *MultiMachine::activity_source() {
return nullptr; // TODO
}
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
MediaTarget::Machine *MultiMachine::media_target() {
if(has_picked_) {
return machines_.front()->configuration_target();
return machines_.front()->media_target();
} else {
return &configuration_target_;
return &media_target_;
}
}

View File

@@ -12,10 +12,10 @@
#include "../../../Machines/DynamicMachine.hpp"
#include "Implementation/MultiConfigurable.hpp"
#include "Implementation/MultiConfigurationTarget.hpp"
#include "Implementation/MultiCRTMachine.hpp"
#include "Implementation/MultiJoystickMachine.hpp"
#include "Implementation/MultiKeyboardMachine.hpp"
#include "Implementation/MultiMediaTarget.hpp"
#include <memory>
#include <mutex>
@@ -51,11 +51,11 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
Activity::Source *activity_source() override;
ConfigurationTarget::Machine *configuration_target() override;
Configurable::Device *configurable_device() override;
CRTMachine::Machine *crt_machine() override;
JoystickMachine::Machine *joystick_machine() override;
KeyboardMachine::Machine *keyboard_machine() override;
Configurable::Device *configurable_device() override;
MediaTarget::Machine *media_target() override;
void *raw_pointer() override;
private:
@@ -65,10 +65,10 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
std::mutex machines_mutex_;
MultiConfigurable configurable_;
MultiConfigurationTarget configuration_target_;
MultiCRTMachine crt_machine_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
MultiMediaTarget media_target_;
void pick_first();
bool has_picked_ = false;

View File

@@ -69,13 +69,13 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// if there are any tapes, attempt to get data from the first
if(media.tapes.size() > 0) {
if(!media.tapes.empty()) {
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
std::vector<File> files = GetFiles(tape);
tape->reset();
// continue if there are any files
if(files.size()) {
if(!files.empty()) {
bool is_basic = true;
// protected files are always for *RUNning only
@@ -103,7 +103,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
}
}
if(media.disks.size() > 0) {
if(!media.disks.empty()) {
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
dfs_catalogue = GetDFSCatalogue(disk);

View File

@@ -18,7 +18,9 @@ namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
II,
IIplus
IIplus,
IIe,
EnhancedIIe
};
enum class DiskController {
None,
@@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target {
ThirteenSector
};
Model model = Model::IIplus;
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
};

View File

@@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 8, 12, 16, 24 or 32 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
const std::size_t data_size = segment.data.size();
const std::size_t overflow = data_size&8191;
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
if(data_size < 8192) continue;
// the two bytes that will be first must be 0xaa and 0x55, either way around
auto *start = &segment.data[0];
@@ -34,19 +29,24 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
if(start[0] == start[1]) continue;
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
if(!overflow) {
coleco_cartridges.push_back(cartridge);
// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if
// this image is within a short distance of 32kb.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
size_t target_size;
if(data_size >= 32*1024 && data_size < 32*1024 + 512) {
target_size = 32 * 1024;
} else {
// Size down to a multiple of 8kb and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
target_size = data_size + ((8192 - (data_size & 8191)) & 8191);
}
std::vector<uint8_t> truncated_data;
truncated_data = segment.data;
truncated_data.resize(target_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
}
return coleco_cartridges;

View File

@@ -111,7 +111,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
for(auto &tape : media.tapes) {
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(tape_files.size()) {
if(!tape_files.empty()) {
for(const auto &file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};

View File

@@ -0,0 +1,81 @@
//
// ClockDeferrer.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ClockDeferrer_h
#define ClockDeferrer_h
#include <vector>
/*!
A ClockDeferrer maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
*/
template <typename TimeUnit> class ClockDeferrer {
public:
/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Schedules @c action to occur in @c delay units of time.
Actions must be scheduled in the order they will occur. It is undefined behaviour
to schedule them out of order.
*/
void defer(TimeUnit delay, const std::function<void(void)> &action) {
pending_actions_.emplace_back(delay, action);
}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
// If there are no pending actions, just run for the entire length.
// This should be the normal branch.
if(pending_actions_.empty()) {
target_(length);
return;
}
// Divide the time to run according to the pending actions.
while(length > TimeUnit(0)) {
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
target_(next_period);
length -= next_period;
off_t performances = 0;
for(auto &action: pending_actions_) {
action.delay -= next_period;
if(!action.delay) {
action.action();
++performances;
}
}
if(performances) {
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
}
}
}
private:
std::function<void(TimeUnit)> target_;
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
std::function<void(void)> action;
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
};
std::vector<DeferredAction> pending_actions_;
};
#endif /* ClockDeferrer_h */

View File

@@ -52,79 +52,79 @@
*/
template <class T> class WrappedInt {
public:
inline WrappedInt(int l) : length_(l) {}
inline WrappedInt() : length_(0) {}
constexpr WrappedInt(int l) : length_(l) {}
constexpr WrappedInt() : length_(0) {}
inline T &operator =(const T &rhs) {
T &operator =(const T &rhs) {
length_ = rhs.length_;
return *this;
}
inline T &operator +=(const T &rhs) {
T &operator +=(const T &rhs) {
length_ += rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator -=(const T &rhs) {
T &operator -=(const T &rhs) {
length_ -= rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator ++() {
T &operator ++() {
++ length_;
return *static_cast<T *>(this);
}
inline T &operator ++(int) {
T &operator ++(int) {
length_ ++;
return *static_cast<T *>(this);
}
inline T &operator --() {
T &operator --() {
-- length_;
return *static_cast<T *>(this);
}
inline T &operator --(int) {
T &operator --(int) {
length_ --;
return *static_cast<T *>(this);
}
inline T &operator %=(const T &rhs) {
T &operator %=(const T &rhs) {
length_ %= rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator &=(const T &rhs) {
T &operator &=(const T &rhs) {
length_ &= rhs.length_;
return *static_cast<T *>(this);
}
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
inline T operator -() const { return T(- length_); }
constexpr T operator -() const { return T(- length_); }
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
inline bool operator !() const { return !length_; }
constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
inline int as_int() const { return length_; }
constexpr int as_int() const { return length_; }
/*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/
inline T divide(const T &divisor) {
T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
@@ -134,7 +134,7 @@ template <class T> class WrappedInt {
Flushes the value in @c this. The current value is returned, and the internal value
is reset to zero.
*/
inline T flush() {
T flush() {
T result(length_);
length_ = 0;
return result;
@@ -150,34 +150,34 @@ template <class T> class WrappedInt {
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
inline Cycles() : WrappedInt<Cycles>() {}
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
constexpr Cycles() : WrappedInt<Cycles>() {}
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
};
/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
inline HalfCycles() : WrappedInt<HalfCycles>() {}
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
inline Cycles cycles() {
constexpr Cycles cycles() const {
return Cycles(length_ >> 1);
}
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
inline Cycles flush_cycles() {
Cycles flush_cycles() {
Cycles result(length_ >> 1);
length_ &= 1;
return result;
}
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
inline HalfCycles flush() {
HalfCycles flush() {
HalfCycles result(length_);
length_ = 0;
return result;
@@ -187,7 +187,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/
inline Cycles divide_cycles(const Cycles &divisor) {
Cycles divide_cycles(const Cycles &divisor) {
HalfCycles half_divisor = HalfCycles(divisor);
Cycles result(length_ / half_divisor.length_);
length_ %= half_divisor.length_;
@@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T {
public:
using T::T;
using T::run_for;
inline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush_cycles());

View File

@@ -0,0 +1,88 @@
//
// ClockingHintSource.h
// Clock Signal
//
// Created by Thomas Harte on 20/08/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#ifndef ClockingHintSource_hpp
#define ClockingHintSource_hpp
namespace ClockingHint {
enum class Preference {
/// The component doesn't currently require a clock signal.
None,
/// The component can be clocked only immediate prior to (explicit) accesses.
JustInTime,
/// The component require real-time clocking.
RealTime
};
class Source;
struct Observer {
/// Called to inform an observer that the component @c component has changed its clocking requirements.
virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0;
};
/*!
An clocking hint source is any component that can provide hints as to the type of
clocking required for accurate emulation. A disk controller is an archetypal example.
Types of clocking are:
- none:
a component that acts and reacts to direct contact but does not have a state that autonomously evolves.
E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command.
- just-in-time:
a component that has an evolving state but can receive clock updates only immediately before a
direct contact. This is possibly the most common kind of component.
- real-time:
a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example
so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of
the machine rather than waiting to be called upon, or because there's some other non-obvious relationship
at play.
A clocking hint source can signal changes in preferred clocking to an observer.
This is intended to allow for performance improvements to machines with components that can be messaged selectively.
The observer callout is virtual so the intended use case is that a machine holds a component that might go through
periods of different clocking requirements.
Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that
the saved or deferred ::run_fors add up to a substantial amount.
The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency.
*/
class Source {
public:
/// Registers @c observer as the new clocking observer.
void set_clocking_hint_observer(Observer *observer) {
observer_ = observer;
update_clocking_observer();
}
/// @returns the current preferred clocking strategy.
virtual Preference preferred_clocking() = 0;
private:
Observer *observer_ = nullptr;
protected:
/*!
Provided for subclasses; call this whenever the clocking preference might have changed.
This will notify the observer if there is one.
*/
void update_clocking_observer() {
if(!observer_) return;
observer_->set_component_prefers_clocking(this, preferred_clocking());
}
};
}
#endif /* ClockingHintSource_h */

View File

@@ -1,60 +0,0 @@
//
// Sleeper.h
// Clock Signal
//
// Created by Thomas Harte on 20/08/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#ifndef Sleeper_hpp
#define Sleeper_hpp
/*!
A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
A sleeper will signal sleeps and wakes to an observer.
This is intended to allow for performance improvements to machines with components that can sleep. The observer
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
the saved ::run_fors add up to a substantial amount.
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
not a command.
*/
class Sleeper {
public:
Sleeper() : sleep_observer_(nullptr) {}
class SleepObserver {
public:
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0;
};
/// Registers @c observer as the new sleep observer;
void set_sleep_observer(SleepObserver *observer) {
sleep_observer_ = observer;
}
/// @returns @c true if the component is currently sleeping; @c false otherwise.
virtual bool is_sleeping() = 0;
protected:
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
SleepObserver *sleep_observer_;
/*!
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
if one exists.
@c is_sleeping will be called only if there is an observer.
*/
void update_sleep_observer() {
if(!sleep_observer_) return;
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
}
};
#endif /* Sleeper_h */

View File

@@ -7,7 +7,9 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#include "../../Outputs/Log.hpp"
using namespace WD;
@@ -25,10 +27,10 @@ void WD1770::set_register(int address, uint8_t value) {
if((value&0xf0) == 0xd0) {
if(value == 0xd0) {
// Force interrupt **immediately**.
printf("Force interrupt immediately\n");
LOG("Force interrupt immediately");
posit_event(static_cast<int>(Event1770::ForceInterrupt));
} else {
printf("!!!TODO: force interrupt!!!\n");
ERROR("!!!TODO: force interrupt!!!");
update_status([] (Status &status) {
status.type = Status::One;
});
@@ -193,7 +195,7 @@ void WD1770::posit_event(int new_event_type) {
// Wait for a new command, branch to the appropriate handler.
case 0:
wait_for_command:
printf("Idle...\n");
LOG("Idle...");
set_data_mode(DataMode::Scanning);
index_hole_count_ = 0;
@@ -209,7 +211,7 @@ void WD1770::posit_event(int new_event_type) {
status.interrupt_request = false;
});
printf("Starting %02x\n", command_);
LOG("Starting " << std::hex << command_ << std::endl);
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
@@ -327,7 +329,7 @@ void WD1770::posit_event(int new_event_type) {
}
if(header_[0] == track_) {
printf("Reached track %d\n", track_);
LOG("Reached track " << std::dec << track_);
update_status([] (Status &status) {
status.crc_error = false;
});
@@ -396,20 +398,20 @@ void WD1770::posit_event(int new_event_type) {
READ_ID();
if(index_hole_count_ == 5) {
printf("Failed to find sector %d\n", sector_);
LOG("Failed to find sector " << std::dec << sector_);
update_status([] (Status &status) {
status.record_not_found = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7) {
printf("Considering %d/%d\n", header_[0], header_[2]);
LOG("Considering " << std::dec << header_[0] << "/" << header_[2]);
set_data_mode(DataMode::Scanning);
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
printf("Found %d/%d\n", header_[0], header_[2]);
LOG("Found " << std::dec << header_[0] << "/" << header_[2]);
if(get_crc_generator().get_value()) {
printf("CRC error; back to searching\n");
LOG("CRC error; back to searching");
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -465,7 +467,7 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_++;
if(distance_into_section_ == 2) {
if(get_crc_generator().get_value()) {
printf("CRC error; terminating\n");
LOG("CRC error; terminating");
update_status([this] (Status &status) {
status.crc_error = true;
});
@@ -476,7 +478,7 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
printf("Finished reading sector %d\n", sector_);
LOG("Finished reading sector " << std::dec << sector_);
goto wait_for_command;
}
goto type2_check_crc;
@@ -558,7 +560,7 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
printf("Wrote sector %d\n", sector_);
LOG("Wrote sector " << std::dec << sector_);
goto wait_for_command;

View File

@@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController {
/// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles);
using Storage::Disk::Controller::run_for;
enum Flag: uint8_t {
NotReady = 0x80,

View File

@@ -69,7 +69,7 @@ template <class BusHandler> class MOS6560 {
speaker_(audio_generator_)
{
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
@@ -125,10 +125,10 @@ template <class BusHandler> class MOS6560 {
19, 86, 123, 59,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 7, 71,
25, 86, 48, 112,
0, 119, 7, 71,
25, 86, 48, 112,
255, 255, 121, 57,
103, 42, 80, 16,
0, 9, 121, 57,
103, 42, 80, 16,
};
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;

View File

@@ -7,9 +7,8 @@
//
#include "i8272.hpp"
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
#include <cstdio>
#include "../../Outputs/Log.hpp"
using namespace Intel::i8272;
@@ -83,8 +82,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
posit_event(static_cast<int>(Event8272::CommandByte));
}
bool i8272::is_sleeping() {
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
ClockingHint::Preference i8272::preferred_clocking() {
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;
}
void i8272::run_for(Cycles cycles) {
@@ -113,7 +114,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;
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<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;
@@ -159,7 +160,7 @@ void i8272::run_for(Cycles cycles) {
}
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
if(is_sleeping_) update_sleep_observer();
if(is_sleeping_) update_clocking_observer();
}
void i8272::set_register(int address, uint8_t value) {
@@ -198,7 +199,7 @@ uint8_t i8272::get_register(int address) {
#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_sleep_observer(); case __LINE__: if(delay_time_) return;
#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 PASTE(x, y) x##y
#define CONCAT(x, y) PASTE(x, y)
@@ -257,7 +258,7 @@ uint8_t i8272::get_register(int address) {
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
head_timers_running_++; \
is_sleeping_ = false; \
update_sleep_observer(); \
update_clocking_observer(); \
} \
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
}
@@ -384,17 +385,17 @@ void i8272::posit_event(int event_type) {
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
// values in the internal registers.
index_hole_limit_ = 2;
// printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_);
// LOG("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
find_next_sector:
FIND_HEADER();
if(!index_hole_limit_) {
// Two index holes have passed wihout finding the header sought.
// printf("Not found\n");
// LOG("Not found");
SetNoData();
goto abort;
}
index_hole_count_ = 0;
// printf("Header\n");
// LOG("Header");
READ_HEADER();
if(index_hole_count_) {
// This implies an index hole was sighted within the header. Error out.
@@ -405,11 +406,11 @@ void i8272::posit_event(int event_type) {
// This implies a CRC error in the header; mark as such but continue.
SetDataError();
}
// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
// LOG("Considering << PADHEX(2) << header_[0] << " " << header_[1] << " " << header_[2] << " " << header_[3] << " [" << get_crc_generator().get_value() << "]");
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
// Branch to whatever is supposed to happen next
// printf("Proceeding\n");
// LOG("Proceeding");
switch(command_[0] & 0x1f) {
case CommandReadData:
case CommandReadDeletedData:
@@ -423,7 +424,13 @@ void i8272::posit_event(int event_type) {
// Performs the read data or read deleted data command.
read_data:
printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
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]) << "]");
read_next_data:
goto read_write_find_header;
@@ -507,7 +514,13 @@ void i8272::posit_event(int event_type) {
goto post_st012chrn;
write_data:
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
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]) << "]");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
@@ -542,7 +555,7 @@ void i8272::posit_event(int event_type) {
goto write_loop;
}
printf("Wrote %d bytes\n", distance_into_section_);
LOG("Wrote " << PADDEC(0) << distance_into_section_ << " bytes");
write_crc();
expects_input_ = false;
WAIT_FOR_EVENT(Event::DataWritten);
@@ -558,7 +571,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.
printf("Read ID [%02x %02x]\n", command_[0], command_[1]);
LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<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.
@@ -580,7 +593,11 @@ void i8272::posit_event(int event_type) {
// Performs read track.
read_track:
printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
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]) << "]");
// Wait for the index hole.
WAIT_FOR_EVENT(Event::IndexHole);
@@ -621,7 +638,7 @@ void i8272::posit_event(int event_type) {
// Performs format [/write] track.
format_track:
printf("Format track\n");
LOG("Format track");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
@@ -665,7 +682,12 @@ void i8272::posit_event(int event_type) {
break;
}
printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
LOG(PADHEX(2) << "W:"
<< static_cast<int>(header_[0]) << " "
<< static_cast<int>(header_[1]) << " "
<< static_cast<int>(header_[2]) << " "
<< static_cast<int>(header_[3]) << ", "
<< get_crc_generator().get_value());
write_crc();
// Write the sector body.
@@ -697,15 +719,15 @@ void i8272::posit_event(int event_type) {
goto post_st012chrn;
scan_low:
printf("Scan low unimplemented!!\n");
ERROR("Scan low unimplemented!!");
goto wait_for_command;
scan_low_or_equal:
printf("Scan low or equal unimplemented!!\n");
ERROR("Scan low or equal unimplemented!!");
goto wait_for_command;
scan_high_or_equal:
printf("Scan high or equal unimplemented!!\n");
ERROR("Scan high or equal unimplemented!!");
goto wait_for_command;
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
@@ -720,7 +742,7 @@ void i8272::posit_event(int event_type) {
if(drives_[drive].phase != Drive::Seeking) {
drives_seeking_++;
is_sleeping_ = false;
update_sleep_observer();
update_clocking_observer();
}
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
@@ -736,11 +758,11 @@ 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];
printf("Seek to %02x\n", command_[2]);
LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
} else {
drives_[drive].target_head_position = -1;
drives_[drive].head_position = 0;
printf("Recalibrate\n");
LOG("Recalibrate");
}
// Check whether any steps are even needed; if not then mark as completed already.
@@ -753,7 +775,7 @@ void i8272::posit_event(int event_type) {
// Performs sense interrupt status.
sense_interrupt_status:
printf("Sense interrupt status\n");
LOG("Sense interrupt status");
{
// Find the first drive that is in the CompletedSeeking state.
int found_drive = -1;
@@ -781,7 +803,7 @@ void i8272::posit_event(int event_type) {
// Performs specify.
specify:
// Just store the values, and terminate the command.
printf("Specify\n");
LOG("Specify");
step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
@@ -792,7 +814,7 @@ void i8272::posit_event(int event_type) {
goto wait_for_command;
sense_drive_status:
printf("Sense drive status\n");
LOG("Sense drive status");
{
int drive = command_[1] & 3;
select_drive(drive);
@@ -831,11 +853,11 @@ 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:
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
for(std::size_t c = 0; c < result_stack_.size(); c++) {
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
}
printf("\n");
LOGNBR(std::endl);
// Set ready to send data to the processor, no longer in non-DMA execution phase.
is_executing_ = false;

View File

@@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
bool is_sleeping();
ClockingHint::Preference preferred_clocking() override;
protected:
virtual void select_drive(int number) = 0;
@@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController {
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type);
void posit_event(int type) override;
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;

View File

@@ -496,17 +496,25 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
}
// Paint sprites and check for collisions.
// Paint sprites and check for collisions, but only if at least one sprite is active
// on this line.
if(sprite_set.active_sprite_slot) {
int sprite_pixels_left = pixels_left;
const int shift_advance = sprites_magnified_ ? 1 : 2;
const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
while(sprite_pixels_left--) {
// sprite_colour is the colour that's going to reach the display after sprite logic has been
// applied; by default assume that nothing is going to be drawn.
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
// The sprite_mask is used to keep track of whether two sprites have both sought to output
// a pixel at the same location, and to feed that into the status register's sprite
// collision bit.
int sprite_mask = 0;
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
@@ -517,15 +525,24 @@ void TMS9918::run_for(const HalfCycles cycles) {
} else if(sprite.shift_position < 32) {
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
mask = (mask >> 7) & 1;
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
sprite_mask |= mask;
sprite.shift_position += shift_advance;
mask &= colour_masks[sprite.info[3]&15];
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
// Ignore the right half of whatever was collected if sprites are not in 16x16 mode.
if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) {
// If any previous sprite has been painted in this column and this sprite
// has this pixel set, set the sprite collision status bit.
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
sprite_mask |= mask;
// Check that the sprite colour is not transparent
mask &= colour_masks[sprite.info[3]&15];
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
}
sprite.shift_position += shift_advance;
}
}
// Output whichever sprite colour was on top.
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
output_column_++;
}

View File

@@ -225,14 +225,10 @@ uint8_t AY38910::get_register_value() {
};
if(selected_register_ > 15) return 0xff;
switch(selected_register_) {
default: return registers_[selected_register_] & register_masks[selected_register_];
case 14: return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0];
case 15: return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1];
}
return registers_[selected_register_] & register_masks[selected_register_];
}
// MARK: - Port handling
// MARK: - Port querying
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
@@ -250,11 +246,16 @@ void AY38910::set_data_input(uint8_t r) {
}
uint8_t AY38910::get_data_output() {
if(control_state_ == Read && selected_register_ >= 14) {
if(port_handler_) {
return port_handler_->get_port_input(selected_register_ == 15);
} else {
return 0xff;
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
// value returned to the CPU when reading it is the and of the output value and any input.
// If it's defined as input then you just get the input.
const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff;
switch(selected_register_) {
default: break;
case 14: return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff);
case 15: return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff);
}
}
return data_output_;
@@ -276,8 +277,10 @@ void AY38910::set_control_lines(ControlLines control_lines) {
}
void AY38910::update_bus() {
// Assume no output, unless this turns out to be a read.
data_output_ = 0xff;
switch(control_state_) {
default: break;
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;

View File

@@ -92,9 +92,8 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
Concurrency::DeferringAsyncTaskQueue &task_queue_;
int selected_register_ = 0;
uint8_t registers_[16];
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t port_inputs_[2];
int master_divider_ = 0;

View File

@@ -19,12 +19,13 @@ namespace {
const uint8_t input_flux = 0x1;
}
DiskII::DiskII() :
DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command),
drives_{{2045454, 300, 1}, {2045454, 300, 1}}
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
{
drives_[0].set_sleep_observer(this);
drives_[1].set_sleep_observer(this);
drives_[0].set_clocking_hint_observer(this);
drives_[1].set_clocking_hint_observer(this);
drives_[active_drive_].set_event_delegate(this);
}
@@ -72,72 +73,90 @@ void DiskII::select_drive(int drive) {
drives_[active_drive_].set_motor_on(motor_is_enabled_);
}
// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.
void DiskII::run_for(const Cycles cycles) {
if(is_sleeping()) return;
if(preferred_clocking() == ClockingHint::Preference::None) return;
if(!controller_can_sleep_) {
int integer_cycles = cycles.as_int();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
inputs_ |= input_flux;
state_ = state_machine_[static_cast<std::size_t>(address)];
switch(state_ & 0xf) {
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 0xa: // shift right, bringing in write protected status
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
// If the controller is in the sense write protect loop but the register will never change,
// short circuit further work and return now.
if(shift_register_ == is_write_protected() ? 0xff : 0x00) {
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
set_controller_can_sleep();
return;
}
break;
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
}
// Currently writing?
if(inputs_&input_mode) {
// state_ & 0x80 should be the current level sent to the disk;
// therefore transitions in that bit should become flux transitions
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
}
// TODO: surely there's a less heavyweight solution than this?
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
int integer_cycles = cycles.as_int();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
if(flux_duration_) {
--flux_duration_;
if(!flux_duration_) inputs_ |= input_flux;
}
} else {
if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles);
if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles);
state_ = state_machine_[static_cast<std::size_t>(address)];
switch(state_ & 0xf) {
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 0xa: // shift right, bringing in write protected status
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
// If the controller is in the sense write protect loop but the register will never change,
// short circuit further work and return now.
if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
decide_clocking_preference();
return;
}
break;
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
}
// Currently writing?
if(inputs_&input_mode) {
// state_ & 0x80 should be the current level sent to the disk;
// therefore transitions in that bit should become flux transitions
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
}
// TODO: surely there's a less heavyweight solution than inline updates?
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
}
set_controller_can_sleep();
// Per comp.sys.apple2.programmer there is a delay between the controller
// motor switch being flipped and the drive motor actually switching off.
// This models that, accepting overrun as a risk.
if(motor_off_time_ >= 0) {
motor_off_time_ -= cycles.as_int();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
}
decide_clocking_preference();
}
void DiskII::set_controller_can_sleep() {
// Permit the controller to sleep if it's in sense write protect mode, and the shift register
// has already filled with the result of shifting eight times.
bool controller_could_sleep = controller_can_sleep_;
controller_can_sleep_ =
(
(inputs_ == input_flux) &&
!motor_is_enabled_ &&
!shift_register_
) ||
(
(inputs_ == (input_command | input_flux)) &&
(shift_register_ == (is_write_protected() ? 0xff : 0x00))
);
if(controller_could_sleep != controller_can_sleep_)
update_sleep_observer();
void DiskII::decide_clocking_preference() {
ClockingHint::Preference prior_preference = clocking_preference_;
// If in read mode, clocking is either:
//
// just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
// none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
if(!(inputs_ & ~input_flux)) {
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
}
// If in writing mode, clocking is real time.
if(inputs_ & input_mode) {
clocking_preference_ = ClockingHint::Preference::RealTime;
}
// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that
// corresponds to the current write protect status. Otherwise it is none.
if((inputs_ & ~input_flux) == input_command) {
clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
}
// Announce a change if there was one.
if(prior_preference != clocking_preference_)
update_clocking_observer();
}
bool DiskII::is_write_protected() {
@@ -195,18 +214,19 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
inputs_ &= ~input_flux;
set_controller_can_sleep();
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
decide_clocking_preference();
}
}
void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
drive_is_sleeping_[0] = drives_[0].is_sleeping();
drive_is_sleeping_[1] = drives_[1].is_sleeping();
update_sleep_observer();
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference 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();
}
bool DiskII::is_sleeping() {
return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1];
ClockingHint::Preference DiskII::preferred_clocking() {
return clocking_preference_;
}
void DiskII::set_data_input(uint8_t input) {
@@ -227,9 +247,12 @@ int DiskII::read_address(int address) {
case 0x8:
shift_register_ = 0;
set_control(Control::Motor, false);
motor_off_time_ = clock_rate_;
break;
case 0x9:
set_control(Control::Motor, true);
motor_off_time_ = -1;
break;
case 0x9: set_control(Control::Motor, true); break;
case 0xa: select_drive(0); break;
case 0xb: select_drive(1); break;
@@ -243,11 +266,11 @@ int DiskII::read_address(int address) {
break;
case 0xf:
if(!(inputs_ & input_mode))
drives_[active_drive_].begin_writing(Storage::Time(1, 2045454), false);
drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
inputs_ |= input_mode;
break;
}
set_controller_can_sleep();
decide_clocking_preference();
return (address & 1) ? 0xff : shift_register_;
}
@@ -255,3 +278,7 @@ void DiskII::set_activity_observer(Activity::Observer *observer) {
drives_[0].set_activity_observer(observer, "Drive 1", true);
drives_[1].set_activity_observer(observer, "Drive 2", true);
}
Storage::Disk::Drive &DiskII::get_drive(int index) {
return drives_[index];
}

View File

@@ -10,7 +10,7 @@
#define DiskII_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/Sleeper.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Disk/Drive.hpp"
@@ -28,10 +28,10 @@ namespace Apple {
*/
class DiskII:
public Storage::Disk::Drive::EventDelegate,
public Sleeper::SleepObserver,
public Sleeper {
public ClockingHint::Source,
public ClockingHint::Observer {
public:
DiskII();
DiskII(int clock_rate);
/// Sets the current external value of the data bus.
void set_data_input(uint8_t input);
@@ -76,11 +76,15 @@ class DiskII:
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper.
bool is_sleeping() override;
ClockingHint::Preference preferred_clocking() override;
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);
// Returns the Storage::Disk::Drive in use for drive @c index.
// *NOT FOR HARDWARE EMULATION USAGE*.
Storage::Disk::Drive &get_drive(int index);
private:
enum class Control {
P0, P1, P2, P3,
@@ -95,7 +99,9 @@ class DiskII:
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Track::Event &event) override;
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
const int clock_rate_ = 0;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
@@ -103,18 +109,20 @@ class DiskII:
int stepper_mask_ = 0;
int stepper_position_ = 0;
int motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;
Storage::Disk::Drive drives_[2];
bool drive_is_sleeping_[2];
bool controller_can_sleep_ = false;
int active_drive_ = 0;
bool motor_is_enabled_ = false;
void set_controller_can_sleep();
void decide_clocking_preference();
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
uint8_t data_input_ = 0;
int flux_duration_ = 0;
};
}

View File

@@ -69,7 +69,7 @@ struct BooleanSelection: public Selection {
struct ListSelection: public Selection {
std::string value;
ListSelection *list_selection();
BooleanSelection *boolean_selection();
ListSelection(const std::string value) : value(value) {}

View File

@@ -21,28 +21,73 @@ class Joystick {
public:
virtual ~Joystick() {}
struct DigitalInput {
/*!
Defines a single input, any individually-measured thing — a fire button or
other digital control, an analogue axis, or a button with a symbol on it.
*/
struct Input {
/// Defines the broad type of the input.
enum Type {
Up, Down, Left, Right, Fire,
// Half-axis inputs.
Up, Down, Left, Right,
// Full-axis inputs.
Horizontal, Vertical,
// Fire buttons.
Fire,
// Other labelled keys.
Key
} type;
union {
};
const Type type;
bool is_digital_axis() const {
return type < Type::Horizontal;
}
bool is_analogue_axis() const {
return type >= Type::Horizontal && type < Type::Fire;
}
bool is_axis() const {
return type < Type::Fire;
}
bool is_button() const {
return type >= Type::Fire;
}
enum Precision {
Analogue, Digital
};
Precision precision() const {
return is_analogue_axis() ? Precision::Analogue : Precision::Digital;
}
/*!
Holds extra information pertaining to the input.
@c Type::Key inputs declare the symbol printed on them.
All other types of input have an associated index, indicating whether they
are the zeroth, first, second, third, etc of those things. E.g. a joystick
may have two fire buttons, which will be buttons 0 and 1.
*/
union Info {
struct {
int index;
size_t index;
} control;
struct {
wchar_t symbol;
} key;
} info;
};
Info info;
// TODO: Find a way to make the above safely const; may mean not using a union.
DigitalInput(Type type, int index = 0) : type(type) {
Input(Type type, size_t index = 0) :
type(type) {
info.control.index = index;
}
DigitalInput(wchar_t symbol) : type(Key) {
Input(wchar_t symbol) : type(Key) {
info.key.symbol = symbol;
}
bool operator == (const DigitalInput &rhs) {
bool operator == (const Input &rhs) {
if(rhs.type != type) return false;
if(rhs.type == Key) {
return rhs.info.key.symbol == info.key.symbol;
@@ -52,15 +97,139 @@ class Joystick {
}
};
virtual std::vector<DigitalInput> get_inputs() = 0;
/// @returns The list of all inputs defined on this joystick.
virtual std::vector<Input> &get_inputs() = 0;
// Host interface.
virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0;
/*!
Sets the digital value of @c input. This may have direct effect or
influence an analogue value; e.g. if the caller declares that ::Left is
active but this joystick has only an analogue horizontal axis, this will
cause a change to that analogue value.
*/
virtual void set_input(const Input &input, bool is_active) = 0;
/*!
Sets the analogue value of @c input. If the input is actually digital,
or if there is a digital input with a corresponding meaning (e.g. ::Left
versus the horizontal axis), this may cause a digital input to be set.
@c value should be in the range [0.0, 1.0].
*/
virtual void set_input(const Input &input, float value) = 0;
/*!
Sets all inputs to their resting state.
*/
virtual void reset_all_inputs() {
for(const auto &input: get_inputs()) {
set_digital_input(input, false);
if(input.precision() == Input::Precision::Digital)
set_input(input, false);
else
set_input(input, 0.5f);
}
}
/*!
Gets the number of input fire buttons.
This is cached by default, but it's virtual so overridable.
*/
virtual int get_number_of_fire_buttons() {
if(number_of_buttons_ >= 0) return number_of_buttons_;
number_of_buttons_ = 0;
for(const auto &input: get_inputs()) {
if(input.type == Input::Type::Fire) ++number_of_buttons_;
}
return number_of_buttons_;
}
private:
int number_of_buttons_ = -1;
};
/*!
ConcreteJoystick is the class that it's expected most machines will actually subclass;
it accepts a set of Inputs at construction and thereby is able to provide the
promised analogue <-> digital mapping of Joystick.
*/
class ConcreteJoystick: public Joystick {
public:
ConcreteJoystick(const std::vector<Input> &inputs) : inputs_(inputs) {
// Size and populate stick_types_, which is used for digital <-> analogue conversion.
for(const auto &input: inputs_) {
const bool is_digital_axis = input.is_digital_axis();
const bool is_analogue_axis = input.is_analogue_axis();
if(is_digital_axis || is_analogue_axis) {
const size_t required_size = static_cast<size_t>(input.info.control.index+1);
if(stick_types_.size() < required_size) {
stick_types_.resize(required_size);
}
stick_types_[static_cast<size_t>(input.info.control.index)] = is_digital_axis ? StickType::Digital : StickType::Analogue;
}
}
}
std::vector<Input> &get_inputs() override final {
return inputs_;
}
void set_input(const Input &input, bool is_active) override final {
// If this is a digital setting to a digital property, just pass it along.
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
did_set_input(input, is_active);
return;
}
// Otherwise this is logically to an analogue axis; for now just use some
// convenient hard-coded values. TODO: make these a function of time.
using Type = Joystick::Input::Type;
switch(input.type) {
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break;
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break;
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break;
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f); break;
}
}
void set_input(const Input &input, float value) override final {
// If this is an analogue setting to an analogue property, just pass it along.
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
did_set_input(input, value);
return;
}
// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
using Type = Joystick::Input::Type;
switch(input.type) {
default: did_set_input(input, value > 0.5f); break;
case Type::Horizontal:
did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);
break;
case Type::Vertical:
did_set_input(Input(Type::Up, input.info.control.index), value <= 0.25f);
did_set_input(Input(Type::Down, input.info.control.index), value >= 0.75f);
break;
}
}
protected:
virtual void did_set_input(const Input &input, float value) {
}
virtual void did_set_input(const Input &input, bool value) {
}
private:
std::vector<Input> inputs_;
enum class StickType {
Digital,
Analogue
};
std::vector<StickType> stick_types_;
};
}

View File

@@ -21,8 +21,9 @@
#include "../Utility/Typer.hpp"
#include "../../Activity/Source.hpp"
#include "../ConfigurationTarget.hpp"
#include "../MediaTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Storage/Tape/Tape.hpp"
@@ -37,12 +38,11 @@
namespace AmstradCPC {
enum ROMType: int {
OS464 = 0, BASIC464,
OS664, BASIC664,
OS6128, BASIC6128,
AMSDOS
};
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite)
);
}
/*!
Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
@@ -192,36 +192,51 @@ class CRTCBusHandler {
}
bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6);
bool is_colour_burst = (cycles_into_hsync_ >= 7 && cycles_into_hsync_ < 11);
// Sync is taken to override pixels, and is combined as a simple OR.
bool is_sync = is_hsync || state.vsync;
bool is_blank = !is_sync && state.hsync;
OutputMode output_mode;
if(is_sync) {
output_mode = OutputMode::Sync;
} else if(is_colour_burst) {
output_mode = OutputMode::ColourBurst;
} else if(is_blank) {
output_mode = OutputMode::Blank;
} else if(state.display_enable) {
output_mode = OutputMode::Pixels;
} else {
output_mode = OutputMode::Border;
}
// If a transition between sync/border/pixels just occurred, flush whatever was
// in progress to the CRT and reset counting.
if(state.display_enable != was_enabled_ || is_sync != was_sync_) {
if(was_sync_) {
crt_->output_sync(cycles_ * 16);
} else {
if(was_enabled_) {
if(cycles_) {
if(output_mode != previous_output_mode_) {
if(cycles_) {
switch(previous_output_mode_) {
default:
case OutputMode::Blank: crt_->output_blank(cycles_ * 16); break;
case OutputMode::Sync: crt_->output_sync(cycles_ * 16); break;
case OutputMode::Border: output_border(cycles_); break;
case OutputMode::ColourBurst: crt_->output_default_colour_burst(cycles_ * 16); break;
case OutputMode::Pixels:
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
pixel_pointer_ = pixel_data_ = nullptr;
}
} else {
output_border(cycles_);
break;
}
}
cycles_ = 0;
was_sync_ = is_sync;
was_enabled_ = state.display_enable;
previous_output_mode_ = output_mode;
}
// increment cycles since state changed
cycles_++;
// collect some more pixels if output is ongoing
if(!is_sync && state.display_enable) {
if(previous_output_mode_ == OutputMode::Pixels) {
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
}
@@ -317,7 +332,7 @@ class CRTCBusHandler {
"uint sample = texture(texID, coordinate).r;"
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}");
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
}
@@ -349,7 +364,7 @@ class CRTCBusHandler {
if(pen_ & 16) {
// If border is[/was] currently being output, flush what should have been
// drawn in the old colour.
if(!was_sync_ && !was_enabled_) {
if(previous_output_mode_ == OutputMode::Border) {
output_border(cycles_);
cycles_ = 0;
}
@@ -506,9 +521,16 @@ class CRTCBusHandler {
return mapping[colour];
}
enum class OutputMode {
Sync,
Blank,
ColourBurst,
Border,
Pixels
} previous_output_mode_ = OutputMode::Sync;
unsigned int cycles_ = 0;
bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
bool was_hsync_ = false, was_vsync_ = false;
int cycles_into_hsync_ = 0;
std::unique_ptr<Outputs::CRT::CRT> crt_;
@@ -537,24 +559,28 @@ class CRTCBusHandler {
/*!
Holds and vends the current keyboard state, acting as the AY's port handler.
Also owns the joysticks.
*/
class KeyboardState: public GI::AY38910::PortHandler {
public:
KeyboardState() : rows_{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} {}
KeyboardState() {
joysticks_.emplace_back(new Joystick(rows_[9]));
joysticks_.emplace_back(new Joystick(joy2_state_));
}
/*!
Sets the row currently being reported to the AY.
*/
void set_row(int row) {
row_ = row;
row_ = static_cast<size_t>(row);
}
/*!
Reports the state of the currently-selected row as Port A to the AY.
*/
uint8_t get_port_input(bool port_b) {
if(!port_b && row_ < 10) {
return rows_[row_];
if(!port_b && row_ < sizeof(rows_)) {
return (row_ == 6) ? rows_[row_] & joy2_state_ : rows_[row_];
}
return 0xff;
@@ -565,7 +591,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
*/
void set_is_pressed(bool is_pressed, int line, int key) {
int mask = 1 << key;
assert(line < 10);
assert(static_cast<size_t>(line) < sizeof(rows_));
if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask;
}
@@ -573,12 +599,52 @@ class KeyboardState: public GI::AY38910::PortHandler {
Sets all keys as currently unpressed.
*/
void clear_all_keys() {
memset(rows_, 0xff, 10);
memset(rows_, 0xff, sizeof(rows_));
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
private:
uint8_t rows_[10];
int row_;
uint8_t joy2_state_ = 0xff;
uint8_t rows_[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
size_t row_ = 0;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick(uint8_t &state) :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire, 0),
Input(Input::Fire, 1),
}),
state_(state) {}
void did_set_input(const Input &input, bool is_active) override {
uint8_t mask = 0;
switch(input.type) {
default: return;
case Input::Up: mask = 0x01; break;
case Input::Down: mask = 0x02; break;
case Input::Left: mask = 0x04; break;
case Input::Right: mask = 0x08; break;
case Input::Fire:
if(input.info.control.index >= 2) return;
mask = input.info.control.index ? 0x20 : 0x10;
break;
}
if(is_active) state_ &= ~mask; else state_ |= mask;
}
private:
uint8_t &state_;
};
};
/*!
@@ -688,17 +754,19 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
/*!
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
*/
class ConcreteMachine:
template <bool has_fdc> class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public Sleeper::SleepObserver,
public ClockingHint::Observer,
public Configurable::Device,
public JoystickMachine::Machine,
public Machine,
public Activity::Source {
public:
ConcreteMachine() :
ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this),
crtc_bus_handler_(ram_, interrupt_timer_),
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
@@ -714,13 +782,62 @@ class ConcreteMachine:
Memory::Fuzz(ram_, sizeof(ram_));
// register this class as the sleep observer for the FDC and tape
fdc_.set_sleep_observer(this);
fdc_is_sleeping_ = fdc_.is_sleeping();
tape_player_.set_sleep_observer(this);
tape_player_is_sleeping_ = tape_player_.is_sleeping();
fdc_.set_clocking_hint_observer(this);
tape_player_.set_clocking_hint_observer(this);
// install the keyboard state class as the AY port handler
ay_.ay().set_port_handler(&key_state_);
// construct the list of necessary ROMs
std::vector<std::string> required_roms = {"amsdos.rom"};
std::string model_number;
switch(target.model) {
default:
model_number = "6128";
has_128k_ = true;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
model_number = "464";
has_128k_ = false;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
model_number = "664";
has_128k_ = false;
break;
}
required_roms.push_back("os" + model_number + ".rom");
required_roms.push_back("basic" + model_number + ".rom");
// fetch and verify the ROMs
const auto roms = rom_fetcher("AmstradCPC", required_roms);
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) throw ROMMachine::Error::MissingROMs;
roms_[static_cast<int>(index)] = std::move(*data);
roms_[static_cast<int>(index)].resize(16384);
}
// Establish default memory map
upper_rom_is_paged_ = true;
upper_rom_ = ROMType::BASIC;
write_pointers_[0] = &ram_[0];
write_pointers_[1] = &ram_[16384];
write_pointers_[2] = &ram_[32768];
write_pointers_[3] = &ram_[49152];
read_pointers_[0] = roms_[ROMType::OS].data();
read_pointers_[1] = write_pointers_[1];
read_pointers_[2] = write_pointers_[2];
read_pointers_[3] = roms_[upper_rom_].data();
// Type whatever is required.
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
insert_media(target.media);
}
/// The entry point for performing a partial Z80 machine cycle.
@@ -749,7 +866,7 @@ class ConcreteMachine:
ay_.run_for(cycle.length);
// Clock the FDC, if connected, using a lazy scale by two
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int()));
time_since_fdc_update_ += cycle.length;
// Update typing activity
if(typer_) typer_->run_for(cycle.length);
@@ -775,8 +892,8 @@ class ConcreteMachine:
}
// Check for an upper ROM selection
if(has_fdc_ && !(address&0x2000)) {
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : rom_model_ + 1;
if(has_fdc && !(address&0x2000)) {
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
}
@@ -795,12 +912,14 @@ class ConcreteMachine:
}
// Check for an FDC access
if(has_fdc_ && (address & 0x580) == 0x100) {
if(has_fdc && (address & 0x580) == 0x100) {
flush_fdc();
fdc_.set_register(address & 1, *cycle.value);
}
// Check for a disk motor access
if(has_fdc_ && !(address & 0x580)) {
if(has_fdc && !(address & 0x580)) {
flush_fdc();
fdc_.set_motor_on(!!(*cycle.value));
}
break;
@@ -814,7 +933,8 @@ class ConcreteMachine:
}
// Check for an FDC access
if(has_fdc_ && (address & 0x580) == 0x100) {
if(has_fdc && (address & 0x580) == 0x100) {
flush_fdc();
*cycle.value &= fdc_.get_register(address & 1);
}
@@ -858,6 +978,7 @@ class ConcreteMachine:
// Just flush the AY.
ay_.update();
ay_.flush();
flush_fdc();
}
/// A CRTMachine function; indicates that outputs should be created now.
@@ -885,50 +1006,6 @@ class ConcreteMachine:
z80_.run_for(cycles);
}
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target);
switch(cpc_target->model) {
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
rom_model_ = ROMType::OS464;
has_128k_ = false;
has_fdc_ = false;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
rom_model_ = ROMType::OS664;
has_128k_ = false;
has_fdc_ = true;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC6128:
rom_model_ = ROMType::OS6128;
has_128k_ = true;
has_fdc_ = true;
break;
}
// Establish default memory map
upper_rom_is_paged_ = true;
upper_rom_ = rom_model_ + 1;
write_pointers_[0] = &ram_[0];
write_pointers_[1] = &ram_[16384];
write_pointers_[2] = &ram_[32768];
write_pointers_[3] = &ram_[49152];
read_pointers_[0] = roms_[rom_model_].data();
read_pointers_[1] = write_pointers_[1];
read_pointers_[2] = write_pointers_[2];
read_pointers_[3] = roms_[upper_rom_].data();
// Type whatever is required.
if(!cpc_target->loading_command.empty()) {
type_string(cpc_target->loading_command);
}
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
@@ -943,37 +1020,15 @@ class ConcreteMachine:
if(c == 4) break;
}
return !media.tapes.empty() || (!media.disks.empty() && has_fdc_);
return !media.tapes.empty() || (!media.disks.empty() && has_fdc);
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"AmstradCPC",
{
"os464.rom", "basic464.rom",
"os664.rom", "basic664.rom",
"os6128.rom", "basic6128.rom",
"amsdos.rom"
});
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) return false;
roms_[static_cast<int>(index)] = std::move(*data);
roms_[static_cast<int>(index)].resize(16384);
}
return true;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
}
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
fdc_is_sleeping_ = fdc_.is_sleeping();
tape_player_is_sleeping_ = tape_player_.is_sleeping();
}
// MARK: - Keyboard
// MARK: - Keyboard
void type_string(const std::string &string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::add_typer(string, std::move(mapper));
@@ -1003,9 +1058,37 @@ class ConcreteMachine:
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
if(has_fdc_) fdc_.set_activity_observer(observer);
if(has_fdc) fdc_.set_activity_observer(observer);
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return AmstradCPC::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
// MARK: - Joysticks
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return key_state_.get_joysticks();
}
private:
inline void write_to_gate_array(uint8_t value) {
@@ -1014,7 +1097,7 @@ class ConcreteMachine:
case 1: crtc_bus_handler_.set_colour(value & 0x1f); break;
case 2:
// Perform ROM paging.
read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data();
read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[ROMType::OS].data();
upper_rom_is_paged_ = !(value & 8);
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3];
@@ -1063,6 +1146,14 @@ class ConcreteMachine:
Intel::i8255::i8255<i8255PortHandler> i8255_;
FDC fdc_;
HalfCycles time_since_fdc_update_;
void flush_fdc() {
// Clock the FDC, if connected, using a lazy scale by two
if(has_fdc && !fdc_is_sleeping_) {
fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
}
time_since_fdc_update_ = HalfCycles(0);
}
InterruptTimer interrupt_timer_;
Storage::Tape::BinaryTapePlayer tape_player_;
@@ -1073,13 +1164,16 @@ class ConcreteMachine:
uint8_t ram_[128 * 1024];
std::vector<uint8_t> roms_[7];
int rom_model_;
bool has_fdc_, fdc_is_sleeping_;
bool fdc_is_sleeping_;
bool tape_player_is_sleeping_;
bool has_128k_;
enum ROMType: int {
AMSDOS = 0, OS = 1, BASIC = 2
};
std::vector<uint8_t> roms_[3];
bool upper_rom_is_paged_;
int upper_rom_;
ROMType upper_rom_;
uint8_t *ram_pages_[4];
uint8_t *read_pointers_[4];
@@ -1094,8 +1188,13 @@ class ConcreteMachine:
using namespace AmstradCPC;
// See header; constructs and returns an instance of the Amstrad CPC.
Machine *Machine::AmstradCPC() {
return new AmstradCPC::ConcreteMachine;
Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AmstradCPC::Target;
const Target *const cpc_target = dynamic_cast<const Target *>(target);
switch(cpc_target->model) {
default: return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher);
case Target::Model::CPC464: return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@@ -9,8 +9,18 @@
#ifndef AmstradCPC_hpp
#define AmstradCPC_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
#include <memory>
#include <vector>
namespace AmstradCPC {
/// @returns The options available for an Amstrad CPC.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
/*!
Models an Amstrad CPC.
*/
@@ -19,7 +29,7 @@ class Machine {
virtual ~Machine();
/// Creates and returns an Amstrad CPC.
static Machine *AmstradCPC();
static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}

View File

@@ -9,8 +9,9 @@
#include "AppleII.hpp"
#include "../../Activity/Source.hpp"
#include "../ConfigurationTarget.hpp"
#include "../MediaTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/StringSerialiser.hpp"
@@ -19,12 +20,14 @@
#include "../../Components/AudioToggle/AudioToggle.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Log.hpp"
#include "Card.hpp"
#include "DiskIICard.hpp"
#include "Video.hpp"
#include "../../Analyser/Static/AppleII/Target.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include <algorithm>
#include <array>
@@ -32,31 +35,35 @@
namespace {
class ConcreteMachine:
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public AppleII::Machine,
public Activity::Source,
public JoystickMachine::Machine,
public AppleII::Card::Delegate {
private:
struct VideoBusHandler : public AppleII::Video::BusHandler {
public:
VideoBusHandler(uint8_t *ram) : ram_(ram) {}
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
uint8_t perform_read(uint16_t address) {
return ram_[address];
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
memcpy(base_target, &ram_[address], count);
memcpy(auxiliary_target, &aux_ram_[address], count);
}
private:
uint8_t *ram_;
uint8_t *ram_, *aux_ram_;
};
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
VideoBusHandler video_bus_handler_;
std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_;
std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie()>> video_;
int cycles_into_current_line_ = 0;
Cycles cycles_since_video_update_;
@@ -76,17 +83,24 @@ class ConcreteMachine:
}
uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> apple2_rom_, apple2plus_rom_, rom_;
std::vector<uint8_t> rom_;
std::vector<uint8_t> character_rom_;
uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false;
uint8_t get_keyboard_input() {
if(string_serialiser_) {
return string_serialiser_->head() | 0x80;
} else {
return keyboard_input_;
}
}
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
Cycles cycles_since_audio_update_;
ROMMachine::ROMFetcher rom_fetcher_;
// MARK: - Cards
std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
Cycles cycles_since_card_update_;
@@ -121,11 +135,48 @@ class ConcreteMachine:
pick_card_messaging_group(card);
}
// MARK: - Memory Map
struct MemoryBlock {
uint8_t *read_pointer = nullptr;
uint8_t *write_pointer = nullptr;
} memory_blocks_[4]; // The IO page isn't included.
AppleII::DiskIICard *diskii_card() {
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
}
// MARK: - Memory Map.
/*
The Apple II's paging mechanisms are byzantine to say the least. Painful is
another appropriate adjective.
On a II and II+ there are five distinct zones of memory:
0000 to c000 : the main block of RAM
c000 to d000 : the IO area, including card ROMs
d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it
0400 to 0800 : the text screen, can be configured to write to auxiliary RAM
2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM
c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area
c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest
c800 to d000 : can contain ROM separately from the region below c800
If dealt with as individual blocks in the inner loop, that would therefore imply mapping
an address to one of 13 potential pageable zones. So I've gone reductive and surrendered
to paging every 6502 page of memory independently. It makes the paging events more expensive,
but hopefully more clear.
*/
uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write.
void page(int start, int end, uint8_t *read, uint8_t *write) {
for(int position = start; position < end; ++position) {
read_pages_[position] = read;
if(read) read += 256;
write_pages_[position] = write;
if(write) write += 256;
}
}
// MARK: - The language card.
struct {
@@ -136,29 +187,128 @@ class ConcreteMachine:
} language_card_;
bool has_language_card_ = true;
void set_language_card_paging() {
if(has_language_card_ && !language_card_.write) {
memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
memory_blocks_[3].write_pointer = &ram_[56*1024];
} else {
memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr;
uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
page(0xd0, 0xe0,
language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom,
language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_card_.read ? &ram[0xe000] : &rom[0x1000],
language_card_.write ? nullptr : &ram[0xe000]);
}
// MARK - The IIe's ROM controls.
bool internal_CX_rom_ = false;
bool slot_C3_rom_ = false;
bool internal_c8_rom_ = false;
void set_card_paging() {
page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr);
if(!internal_CX_rom_) {
if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100];
}
if(has_language_card_ && language_card_.read) {
memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
memory_blocks_[3].read_pointer = &ram_[56*1024];
page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
// MARK - The IIe's auxiliary RAM controls.
bool alternative_zero_page_ = false;
void set_zero_page_paging() {
if(alternative_zero_page_) {
read_pages_[0] = aux_ram_;
} else {
memory_blocks_[2].read_pointer = rom_.data();
memory_blocks_[3].read_pointer = rom_.data() + 0x1000;
read_pages_[0] = ram_;
}
read_pages_[1] = read_pages_[0] + 256;
write_pages_[0] = read_pages_[0];
write_pages_[1] = read_pages_[1];
}
bool read_auxiliary_memory_ = false;
bool write_auxiliary_memory_ = false;
void set_main_paging() {
page(0x02, 0xc0,
read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
if(video_ && video_->get_80_store()) {
bool use_aux_ram = video_->get_page2();
page(0x04, 0x08,
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
if(video_->get_high_resolution()) {
page(0x20, 0x40,
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
}
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK - joysticks
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) override {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) override {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
};
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool analogue_channel_is_discharged(size_t channel) {
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
}
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed_ = false;
bool closed_apple_is_pressed_ = false;
public:
ConcreteMachine():
m6502_(*this),
video_bus_handler_(ram_),
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
video_bus_handler_(ram_, aux_ram_),
audio_toggle_(audio_queue_),
speaker_(audio_toggle_) {
// The system's master clock rate.
@@ -180,6 +330,66 @@ class ConcreteMachine:
// Also, start with randomised memory contents.
Memory::Fuzz(ram_, sizeof(ram_));
Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
std::vector<std::string> rom_names;
size_t rom_size = 12*1024;
switch(target.model) {
default:
rom_names.push_back("apple2-character.rom");
rom_names.push_back("apple2o.rom");
break;
case Target::Model::IIplus:
rom_names.push_back("apple2-character.rom");
rom_names.push_back("apple2.rom");
break;
case Target::Model::IIe:
rom_size += 3840;
rom_names.push_back("apple2eu-character.rom");
rom_names.push_back("apple2eu.rom");
break;
case Target::Model::EnhancedIIe:
rom_size += 3840;
rom_names.push_back("apple2e-character.rom");
rom_names.push_back("apple2e.rom");
break;
}
const auto roms = rom_fetcher("AppleII", rom_names);
if(!roms[0] || !roms[1]) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(*roms[1]);
if(rom_.size() > rom_size) {
rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
}
character_rom_ = std::move(*roms[0]);
if(target.disk_controller != Target::DiskController::None) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
}
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
set_main_paging();
set_zero_page_paging();
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);
// Set proper values for the language card/ROM area.
set_language_card_paging();
insert_media(target.media);
}
~ConcreteMachine() {
@@ -187,7 +397,7 @@ class ConcreteMachine:
}
void setup_output(float aspect_ratio) override {
video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_));
video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie()>(video_bus_handler_));
video_->set_character_rom(character_rom_);
}
@@ -203,7 +413,7 @@ class ConcreteMachine:
return &speaker_;
}
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) {
++ cycles_since_video_update_;
++ cycles_since_card_update_;
cycles_since_audio_update_ += Cycles(7);
@@ -221,30 +431,23 @@ class ConcreteMachine:
++ stretched_cycles_since_card_update_;
}
/*
There are five distinct zones of memory on an Apple II:
0000 to 0200 : the zero and stack pages, which can be paged independently on a IIe
0200 to c000 : the main block of RAM, which can be paged on a IIe
c000 to d000 : the IO area, including card ROMs
d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
*/
MemoryBlock *block = nullptr;
if(address < 0x200) block = &memory_blocks_[0];
else if(address < 0xc000) {
if(address < 0x6000 && !isReadOperation(operation)) update_video();
block = &memory_blocks_[1];
address -= 0x200;
}
else if(address < 0xd000) block = nullptr;
else if(address < 0xe000) {block = &memory_blocks_[2]; address -= 0xd000; }
else { block = &memory_blocks_[3]; address -= 0xe000; }
bool has_updated_cards = false;
if(block) {
if(isReadOperation(operation)) *value = block->read_pointer[address];
else if(block->write_pointer) block->write_pointer[address] = *value;
if(read_pages_[address >> 8]) {
if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff];
else {
if(address >= 0x200 && address < 0x6000) update_video();
if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value;
}
if(is_iie() && address >= 0xc300 && address < 0xd000) {
bool internal_c8_rom = internal_c8_rom_;
internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_;
internal_c8_rom &= (address != 0xcfff);
if(internal_c8_rom != internal_c8_rom_) {
internal_c8_rom_ = internal_c8_rom;
set_card_paging();
}
}
} else {
// Assume a vapour read unless it turns out otherwise; this is a little
// wasteful but works for now.
@@ -271,27 +474,171 @@ class ConcreteMachine:
default: break;
case 0xc000:
if(string_serialiser_) {
*value = string_serialiser_->head() | 0x80;
} else {
*value = keyboard_input_;
*value = get_keyboard_input();
break;
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
break;
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] ||
(is_iie() && open_apple_is_pressed_)
)
*value |= 0x80;
break;
case 0xc062: // Switch input 1.
*value &= 0x7f;
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] ||
(is_iie() && closed_apple_is_pressed_)
)
*value |= 0x80;
break;
case 0xc063: // Switch input 2.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[2] || static_cast<Joystick *>(joysticks_[1].get())->buttons[0])
*value |= 0x80;
break;
case 0xc064: // Analogue input 0.
case 0xc065: // Analogue input 1.
case 0xc066: // Analogue input 2.
case 0xc067: { // Analogue input 3.
const size_t input = address - 0xc064;
*value &= 0x7f;
if(!analogue_channel_is_discharged(input)) {
*value |= 0x80;
}
} break;
// The IIe-only state reads follow...
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
case 0xc011: IIeSwitchRead(language_card_.bank1); break;
case 0xc012: IIeSwitchRead(language_card_.read); break;
case 0xc013: IIeSwitchRead(read_auxiliary_memory_); break;
case 0xc014: IIeSwitchRead(write_auxiliary_memory_); break;
case 0xc015: IIeSwitchRead(internal_CX_rom_); break;
case 0xc016: IIeSwitchRead(alternative_zero_page_); break;
case 0xc017: IIeSwitchRead(slot_C3_rom_); break;
case 0xc018: IIeSwitchRead(video_->get_80_store()); break;
case 0xc019: IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_)); break;
case 0xc01a: IIeSwitchRead(video_->get_text()); break;
case 0xc01b: IIeSwitchRead(video_->get_mixed()); break;
case 0xc01c: IIeSwitchRead(video_->get_page2()); break;
case 0xc01d: IIeSwitchRead(video_->get_high_resolution()); break;
case 0xc01e: IIeSwitchRead(video_->get_alternative_character_set()); break;
case 0xc01f: IIeSwitchRead(video_->get_80_columns()); break;
#undef IIeSwitchRead
case 0xc07f:
if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00);
break;
}
} else {
// Write-only switches.
// Write-only switches. All IIe as currently implemented.
if(is_iie()) {
switch(address) {
default: break;
case 0xc000:
case 0xc001:
update_video();
video_->set_80_store(!!(address&1));
set_main_paging();
break;
case 0xc002:
case 0xc003:
read_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc004:
case 0xc005:
write_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc006:
case 0xc007:
internal_CX_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc008:
case 0xc009:
// The alternative zero page setting affects both bank 0 and any RAM
// that's paged as though it were on a language card.
alternative_zero_page_ = !!(address&1);
set_zero_page_paging();
set_language_card_paging();
break;
case 0xc00a:
case 0xc00b:
slot_C3_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc00c:
case 0xc00d:
update_video();
video_->set_80_columns(!!(address&1));
break;
case 0xc00e:
case 0xc00f:
update_video();
video_->set_alternative_character_set(!!(address&1));
break;
}
}
}
break;
/* Read-write switches. */
case 0xc050: update_video(); video_->set_graphics_mode(); break;
case 0xc051: update_video(); video_->set_text_mode(); break;
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
case 0xc054: update_video(); video_->set_video_page(0); break;
case 0xc055: update_video(); video_->set_video_page(1); break;
case 0xc056: update_video(); video_->set_low_resolution(); break;
case 0xc057: update_video(); video_->set_high_resolution(); break;
case 0xc070: { // Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
}
}
analogue_charge_ = 0.0f;
} break;
/* Switches triggered by reading or writing. */
case 0xc050:
case 0xc051:
update_video();
video_->set_text(!!(address&1));
break;
case 0xc052: update_video(); video_->set_mixed(false); break;
case 0xc053: update_video(); video_->set_mixed(true); break;
case 0xc054:
case 0xc055:
update_video();
video_->set_page2(!!(address&1));
set_main_paging();
break;
case 0xc056:
case 0xc057:
update_video();
video_->set_high_resolution(!!(address&1));
set_main_paging();
break;
case 0xc05e:
case 0xc05f:
if(is_iie()) {
update_video();
video_->set_annunciator_3(!(address&1));
}
break;
case 0xc010:
keyboard_input_ &= 0x7f;
@@ -299,9 +646,15 @@ class ConcreteMachine:
if(!string_serialiser_->advance())
string_serialiser_.reset();
}
// On the IIe, reading C010 returns additional key info.
if(is_iie() && isReadOperation(operation)) {
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
}
break;
case 0xc030:
case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037:
case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f:
update_audio();
audio_toggle_.set_output(!audio_toggle_.get_output());
break;
@@ -330,6 +683,7 @@ class ConcreteMachine:
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
set_language_card_paging();
break;
}
@@ -338,7 +692,7 @@ class ConcreteMachine:
Communication with cards follows.
*/
if(address >= 0xc090 && address < 0xc800) {
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) {
// If this is a card access, figure out which card is at play before determining
// the totality of who needs messaging.
size_t card_number = 0;
@@ -363,7 +717,7 @@ class ConcreteMachine:
// If the selected card is a just-in-time card, update the just-in-time cards,
// and then message it specifically.
const bool is_read = isReadOperation(operation);
AppleII::Card *const target = cards_[card_number].get();
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
if(target && !is_every_cycle_card(target)) {
update_just_in_time_cards();
target->perform_bus_operation(select, is_read, address, value);
@@ -390,6 +744,9 @@ class ConcreteMachine:
}
}
// Update analogue charge level.
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
return Cycles(1);
}
@@ -400,49 +757,50 @@ class ConcreteMachine:
audio_queue_.perform();
}
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"AppleII",
{
"apple2o.rom",
"apple2.rom",
"apple2-character.rom"
});
if(!roms[0] || !roms[1] || !roms[2]) return false;
apple2_rom_ = std::move(*roms[0]);
apple2plus_rom_ = std::move(*roms[1]);
character_rom_ = std::move(*roms[2]);
rom_fetcher_ = roms_with_names;
return true;
}
void run_for(const Cycles cycles) override {
m6502_.run_for(cycles);
}
void reset_all_keys() override {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
if(key == Key::F12) {
m6502_.set_reset_line(is_pressed);
switch(key) {
default: break;
case Key::F12:
m6502_.set_reset_line(is_pressed);
return;
case Key::LeftOption:
open_apple_is_pressed_ = is_pressed;
return;
case Key::RightOption:
closed_apple_is_pressed_ = is_pressed;
return;
}
if(is_pressed) {
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 8; break;
case Key::Right: value = 21; break;
case Key::Down: value = 10; break;
default: break;
}
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::BackSpace: value = 0x7f; break;
default: return;
}
}
keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80);
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = static_cast<char>(toupper(value));
if(is_pressed) {
keyboard_input_ = static_cast<uint8_t>(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
}
@@ -454,32 +812,11 @@ class ConcreteMachine:
string_serialiser_.reset(new Utility::StringSerialiser(string, true));
}
// MARK: ConfigurationTarget
void configure_as_target(const Analyser::Static::Target *target) override {
using Target = Analyser::Static::AppleII::Target;
auto *const apple_target = dynamic_cast<const Target *>(target);
if(apple_target->disk_controller != Target::DiskController::None) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new AppleII::DiskIICard(rom_fetcher_, apple_target->disk_controller == Target::DiskController::SixteenSector));
}
rom_ = (apple_target->model == Target::Model::II) ? apple2_rom_ : apple2plus_rom_;
if(rom_.size() > 12*1024) {
rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024);
}
// Set up the default memory blocks.
memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_;
memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200];
set_language_card_paging();
insert_media(apple_target->media);
}
// MARK: MediaTarget
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.disks.empty() && cards_[5]) {
dynamic_cast<AppleII::DiskIICard *>(cards_[5].get())->set_disk(media.disks[0], 0);
if(!media.disks.empty()) {
auto diskii = diskii_card();
if(diskii) diskii->set_disk(media.disks[0], 0);
}
return true;
}
@@ -490,14 +827,27 @@ class ConcreteMachine:
if(card) card->set_activity_observer(observer);
}
}
// MARK: JoystickMachine
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
};
}
using namespace AppleII;
Machine *Machine::AppleII() {
return new ConcreteMachine;
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AppleII::Target;
const Target *const appleii_target = dynamic_cast<const Target *>(target);
switch(appleii_target->model) {
default: return nullptr;
case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher);
case Target::Model::IIplus: return new ConcreteMachine<Target::Model::IIplus>(*appleii_target, rom_fetcher);
case Target::Model::IIe: return new ConcreteMachine<Target::Model::IIe>(*appleii_target, rom_fetcher);
case Target::Model::EnhancedIIe: return new ConcreteMachine<Target::Model::EnhancedIIe>(*appleii_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@@ -9,6 +9,13 @@
#ifndef AppleII_hpp
#define AppleII_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
#include <memory>
#include <vector>
namespace AppleII {
class Machine {
@@ -16,7 +23,7 @@ class Machine {
virtual ~Machine();
/// Creates and returns an AppleII.
static Machine *AppleII();
static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
};

View File

@@ -10,8 +10,8 @@
using namespace AppleII;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) {
auto roms = rom_fetcher(
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
const auto roms = rom_fetcher(
"DiskII",
{
is_16_sector ? "boot-16.rom" : "boot-13.rom",
@@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec
boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]);
set_select_constraints(None);
diskii_.set_sleep_observer(this);
diskii_.set_clocking_hint_observer(this);
}
void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) {
@@ -41,7 +41,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
}
void DiskIICard::run_for(Cycles cycles, int stretches) {
if(diskii_is_sleeping_) return;
if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
diskii_.run_for(Cycles(cycles.as_int() * 2));
}
@@ -53,7 +53,11 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
diskii_.set_activity_observer(observer);
}
void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
diskii_is_sleeping_ = is_sleeping;
set_select_constraints(is_sleeping ? (IO | Device) : 0);
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
diskii_clocking_preference_ = preference;
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
}
Storage::Disk::Drive &DiskIICard::get_drive(int drive) {
return diskii_.get_drive(drive);
}

View File

@@ -14,7 +14,7 @@
#include "../../Components/DiskII/DiskII.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../ClockReceiver/Sleeper.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include <cstdint>
#include <memory>
@@ -22,7 +22,7 @@
namespace AppleII {
class DiskIICard: public Card, public Sleeper::SleepObserver {
class DiskIICard: public Card, public ClockingHint::Observer {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
@@ -32,12 +32,13 @@ class DiskIICard: public Card, public Sleeper::SleepObserver {
void set_activity_observer(Activity::Observer *observer) override;
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
Storage::Disk::Drive &get_drive(int drive);
private:
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
std::vector<uint8_t> boot_;
Apple::DiskII diskii_;
bool diskii_is_sleeping_ = false;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
};
}

View File

@@ -10,122 +10,330 @@
using namespace AppleII::Video;
namespace {
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)),
is_iie_(is_iie),
deferrer_(std::move(target)) {
struct ScaledByteFiller {
ScaledByteFiller() {
VideoBase::setup_tables();
}
} throwaway;
}
VideoBase::VideoBase() :
crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) {
// Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte.
// Set a composite sampling function that assumes one byte per pixel input, and
// accepts any non-zero value as being fully on, zero being fully off.
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"texValue >>= int(icoordinate.x) % 7;"
"return float(texValue & 1u);"
"return clamp(texture(sampler, coordinate).r, 0.0, 0.7);"
"}");
crt_->set_integer_coordinate_multiplier(7.0f);
// Show only the centre 75% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f));
crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f));
crt_->set_immediate_default_phase(0.0f);
character_zones[0].xor_mask = 0;
character_zones[0].address_mask = 0x3f;
character_zones[1].xor_mask = 0;
character_zones[1].address_mask = 0x3f;
character_zones[2].xor_mask = 0;
character_zones[2].address_mask = 0x3f;
character_zones[3].xor_mask = 0;
character_zones[3].address_mask = 0x3f;
if(is_iie) {
character_zones[0].xor_mask =
character_zones[2].xor_mask =
character_zones[3].xor_mask = 0xff;
character_zones[2].address_mask =
character_zones[3].address_mask = 0xff;
}
}
Outputs::CRT::CRT *VideoBase::get_crt() {
return crt_.get();
}
uint16_t VideoBase::scaled_byte[256];
uint16_t VideoBase::low_resolution_patterns[2][16];
void VideoBase::setup_tables() {
for(int c = 0; c < 128; ++c) {
const uint16_t value =
((c & 0x01) ? 0x0003 : 0x0000) |
((c & 0x02) ? 0x000c : 0x0000) |
((c & 0x04) ? 0x0030 : 0x0000) |
((c & 0x08) ? 0x0140 : 0x0000) |
((c & 0x10) ? 0x0600 : 0x0000) |
((c & 0x20) ? 0x1800 : 0x0000) |
((c & 0x40) ? 0x6000 : 0x0000);
uint8_t *const table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
table_entry[0] = static_cast<uint8_t>(value & 0xff);
table_entry[1] = static_cast<uint8_t>(value >> 8);
}
for(int c = 128; c < 256; ++c) {
uint8_t *const source_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c & 0x7f]);
uint8_t *const destination_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
destination_table_entry[0] = static_cast<uint8_t>(source_table_entry[0] << 1);
destination_table_entry[1] = static_cast<uint8_t>((source_table_entry[1] << 1) | (source_table_entry[0] >> 6));
}
for(int c = 0; c < 16; ++c) {
// Produce the whole 28-bit pattern that would cover two columns.
const int reversed_c = ((c&0x1) ? 0x8 : 0x0) | ((c&0x2) ? 0x4 : 0x0) | ((c&0x4) ? 0x2 : 0x0) | ((c&0x8) ? 0x1 : 0x0);
int pattern = 0;
for(int l = 0; l < 7; ++l) {
pattern <<= 4;
pattern |= reversed_c;
/*
Rote setters and getters.
*/
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
set_alternative_character_set_ = alternative_character_set;
deferrer_.defer(Cycles(2), [=] {
alternative_character_set_ = alternative_character_set;
if(alternative_character_set) {
character_zones[1].address_mask = 0xff;
character_zones[1].xor_mask = 0;
} else {
character_zones[1].address_mask = 0x3f;
character_zones[1].xor_mask = flash_mask();
}
// Pack that 28-bit pattern into the appropriate look-up tables.
uint8_t *const left_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[0][c]);
uint8_t *const right_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[1][c]);
left_entry[0] = static_cast<uint8_t>(pattern);;
left_entry[1] = static_cast<uint8_t>(pattern >> 7);
right_entry[0] = static_cast<uint8_t>(pattern >> 14);
right_entry[1] = static_cast<uint8_t>(pattern >> 21);
}
});
}
void VideoBase::set_graphics_mode() {
use_graphics_mode_ = true;
bool VideoBase::get_alternative_character_set() {
return set_alternative_character_set_;
}
void VideoBase::set_text_mode() {
use_graphics_mode_ = false;
void VideoBase::set_80_columns(bool columns_80) {
set_columns_80_ = columns_80;
deferrer_.defer(Cycles(2), [=] {
columns_80_ = columns_80;
});
}
void VideoBase::set_mixed_mode(bool mixed_mode) {
mixed_mode_ = mixed_mode;
bool VideoBase::get_80_columns() {
return set_columns_80_;
}
void VideoBase::set_video_page(int page) {
video_page_ = page;
void VideoBase::set_80_store(bool store_80) {
set_store_80_ = store_80_ = store_80;
}
void VideoBase::set_low_resolution() {
graphics_mode_ = GraphicsMode::LowRes;
bool VideoBase::get_80_store() {
return set_store_80_;
}
void VideoBase::set_high_resolution() {
graphics_mode_ = GraphicsMode::HighRes;
void VideoBase::set_page2(bool page2) {
set_page2_ = page2_ = page2;
}
bool VideoBase::get_page2() {
return set_page2_;
}
void VideoBase::set_text(bool text) {
set_text_ = text;
deferrer_.defer(Cycles(2), [=] {
text_ = text;
});
}
bool VideoBase::get_text() {
return set_text_;
}
void VideoBase::set_mixed(bool mixed) {
set_mixed_ = mixed;
deferrer_.defer(Cycles(2), [=] {
mixed_ = mixed;
});
}
bool VideoBase::get_mixed() {
return set_mixed_;
}
void VideoBase::set_high_resolution(bool high_resolution) {
set_high_resolution_ = high_resolution;
deferrer_.defer(Cycles(2), [=] {
high_resolution_ = high_resolution;
});
}
bool VideoBase::get_high_resolution() {
return set_high_resolution_;
}
void VideoBase::set_annunciator_3(bool annunciator_3) {
set_annunciator_3_ = annunciator_3;
deferrer_.defer(Cycles(2), [=] {
annunciator_3_ = annunciator_3;
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
});
}
bool VideoBase::get_annunciator_3() {
return set_annunciator_3_;
}
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
character_rom_ = character_rom;
// Bytes in the character ROM are stored in reverse bit order. Reverse them
// ahead of time so as to be able to use the same scaling table as for
// high-resolution graphics.
for(auto &byte : character_rom_) {
byte =
((byte & 0x40) ? 0x01 : 0x00) |
((byte & 0x20) ? 0x02 : 0x00) |
((byte & 0x10) ? 0x04 : 0x00) |
((byte & 0x08) ? 0x08 : 0x00) |
((byte & 0x04) ? 0x10 : 0x00) |
((byte & 0x02) ? 0x20 : 0x00) |
((byte & 0x01) ? 0x40 : 0x00) |
(byte & 0x80);
// Flip all character contents based on the second line of the $ graphic.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}
void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const {
for(size_t c = 0; c < length; ++c) {
const int character = source[c] & character_zones[source[c] >> 6].address_mask;
const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask;
const std::size_t character_address = static_cast<std::size_t>(character << 3) + pixel_row;
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
// The character ROM is output MSB to LSB rather than LSB to MSB.
target[0] = target[1] = character_pattern & 0x40;
target[2] = target[3] = character_pattern & 0x20;
target[4] = target[5] = character_pattern & 0x10;
target[6] = target[7] = character_pattern & 0x08;
target[8] = target[9] = character_pattern & 0x04;
target[10] = target[11] = character_pattern & 0x02;
target[12] = target[13] = character_pattern & 0x01;
graphics_carry_ = character_pattern & 0x01;
target += 14;
}
}
void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, size_t pixel_row) const {
for(size_t c = 0; c < length; ++c) {
const std::size_t character_addresses[2] = {
static_cast<std::size_t>(
(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3
) + pixel_row,
static_cast<std::size_t>(
(source[c] & character_zones[source[c] >> 6].address_mask) << 3
) + pixel_row
};
const uint8_t character_patterns[2] = {
static_cast<uint8_t>(
character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask
),
static_cast<uint8_t>(
character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask
)
};
// The character ROM is output MSB to LSB rather than LSB to MSB.
target[0] = character_patterns[0] & 0x40;
target[1] = character_patterns[0] & 0x20;
target[2] = character_patterns[0] & 0x10;
target[3] = character_patterns[0] & 0x08;
target[4] = character_patterns[0] & 0x04;
target[5] = character_patterns[0] & 0x02;
target[6] = character_patterns[0] & 0x01;
target[7] = character_patterns[1] & 0x40;
target[8] = character_patterns[1] & 0x20;
target[9] = character_patterns[1] & 0x10;
target[10] = character_patterns[1] & 0x08;
target[11] = character_patterns[1] & 0x04;
target[12] = character_patterns[1] & 0x02;
target[13] = character_patterns[1] & 0x01;
graphics_carry_ = character_patterns[1] & 0x01;
target += 14;
}
}
void VideoBase::output_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
if((column + static_cast<int>(c))&1) {
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4;
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8;
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1;
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2;
graphics_carry_ = (source[c] >> row_shift) & 8;
} else {
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1;
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2;
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4;
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8;
graphics_carry_ = (source[c] >> row_shift) & 2;
}
target += 14;
}
}
void VideoBase::output_fat_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
// Fat low-resolution mode appears not to do anything to try to make odd and
// even columns compatible.
target[0] = target[1] = target[8] = target[9] = (source[c] >> row_shift) & 1;
target[2] = target[3] = target[10] = target[11] = (source[c] >> row_shift) & 2;
target[4] = target[5] = target[12] = target[13] = (source[c] >> row_shift) & 4;
target[6] = target[7] = (source[c] >> row_shift) & 8;
graphics_carry_ = (source[c] >> row_shift) & 4;
target += 14;
}
}
void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, int column, int row) const {
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
if((column + static_cast<int>(c))&1) {
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8;
target[3] = (auxiliary_source[c] >> row_shift) & 1;
target[8] = target[12] = (source[c] >> row_shift) & 4;
target[9] = target[13] = (source[c] >> row_shift) & 8;
target[10] = (source[c] >> row_shift) & 1;
target[7] = target[11] = (source[c] >> row_shift) & 2;
graphics_carry_ = (source[c] >> row_shift) & 8;
} else {
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2;
target[3] = (auxiliary_source[c] >> row_shift) & 4;
target[8] = target[12] = (source[c] >> row_shift) & 1;
target[9] = target[13] = (source[c] >> row_shift) & 2;
target[10] = (source[c] >> row_shift) & 4;
target[7] = target[11] = (source[c] >> row_shift) & 8;
graphics_carry_ = (source[c] >> row_shift) & 2;
}
target += 14;
}
}
void VideoBase::output_high_resolution(uint8_t *target, const uint8_t *const source, size_t length) const {
for(size_t c = 0; c < length; ++c) {
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
// If there is a delay, the previous output level is held to bridge the gap.
// Delays may be ignored on a IIe if Annunciator 3 is set; that's the state that
// high_resolution_mask_ models.
if(source[c] & high_resolution_mask_ & 0x80) {
target[0] = graphics_carry_;
target[1] = target[2] = source[c] & 0x01;
target[3] = target[4] = source[c] & 0x02;
target[5] = target[6] = source[c] & 0x04;
target[7] = target[8] = source[c] & 0x08;
target[9] = target[10] = source[c] & 0x10;
target[11] = target[12] = source[c] & 0x20;
target[13] = source[c] & 0x40;
} else {
target[0] = target[1] = source[c] & 0x01;
target[2] = target[3] = source[c] & 0x02;
target[4] = target[5] = source[c] & 0x04;
target[6] = target[7] = source[c] & 0x08;
target[8] = target[9] = source[c] & 0x10;
target[10] = target[11] = source[c] & 0x20;
target[12] = target[13] = source[c] & 0x40;
}
graphics_carry_ = source[c] & 0x40;
target += 14;
}
}
void VideoBase::output_double_high_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length) const {
for(size_t c = 0; c < length; ++c) {
target[0] = auxiliary_source[c] & 0x01;
target[1] = auxiliary_source[c] & 0x02;
target[2] = auxiliary_source[c] & 0x04;
target[3] = auxiliary_source[c] & 0x08;
target[4] = auxiliary_source[c] & 0x10;
target[5] = auxiliary_source[c] & 0x20;
target[6] = auxiliary_source[c] & 0x40;
target[7] = source[c] & 0x01;
target[8] = source[c] & 0x02;
target[9] = source[c] & 0x04;
target[10] = source[c] & 0x08;
target[11] = source[c] & 0x10;
target[12] = source[c] & 0x20;
target[13] = source[c] & 0x40;
graphics_carry_ = auxiliary_source[c] & 0x40;
target += 14;
}
}

View File

@@ -11,7 +11,9 @@
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockDeferrer.hpp"
#include <array>
#include <vector>
namespace AppleII {
@@ -19,26 +21,126 @@ namespace Video {
class BusHandler {
public:
uint8_t perform_read(uint16_t address) {
return 0xff;
/*!
Requests fetching of the @c count bytes starting from @c address.
The handler should write the values from base memory to @c base_target, and those
from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory,
it needn't write anything to auxiliary_target.
*/
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
}
};
class VideoBase {
public:
VideoBase();
static void setup_tables();
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
/// @returns The CRT this video feed is feeding.
Outputs::CRT::CRT *get_crt();
// Inputs for the various soft switches.
void set_graphics_mode();
void set_text_mode();
void set_mixed_mode(bool);
void set_video_page(int);
void set_low_resolution();
void set_high_resolution();
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
Those registers which don't exist on a II/II+ are marked.
*/
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
* Off: display text using primary character set.
* On: display text using alternate character set.
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool);
bool get_alternative_character_set();
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool);
bool get_80_columns();
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool);
bool get_80_store();
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool);
bool get_page2();
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool);
bool get_text();
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool);
bool get_mixed();
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool);
bool get_high_resolution();
/*!
Setter for annunciator 3.
* On: turn on annunciator 3.
* Off: turn off annunciator 3.
This exists on both the II/II+ and the IIe, but has no effect on
video on the older machines. It's intended to be used on the IIe
to confirm double-high resolution mode but has side effects in
selecting mixed mode output and discarding high-resolution
delay bits.
*/
void set_annunciator_3(bool);
bool get_annunciator_3();
// Setup for text mode.
void set_character_rom(const std::vector<uint8_t> &);
@@ -46,167 +148,121 @@ class VideoBase {
protected:
std::unique_ptr<Outputs::CRT::CRT> crt_;
int video_page_ = 0;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;
// State affecting logical state.
int row_ = 0, column_ = 0, flash_ = 0;
uint16_t *pixel_pointer_ = nullptr;
uint8_t flash_mask() {
return static_cast<uint8_t>((flash_ / flash_length) * 0xff);
}
// Enumerates all Apple II and IIe display modes.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
FatLowRes
};
bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; }
bool is_double_mode(GraphicsMode m) { return !!(static_cast<int>(m)&1); }
// Various soft-switch values.
bool alternative_character_set_ = false, set_alternative_character_set_ = false;
bool columns_80_ = false, set_columns_80_ = false;
bool store_80_ = false, set_store_80_ = false;
bool page2_ = false, set_page2_ = false;
bool text_ = true, set_text_ = true;
bool mixed_ = false, set_mixed_ = false;
bool high_resolution_ = false, set_high_resolution_ = false;
bool annunciator_3_ = false, set_annunciator_3_ = false;
// Graphics carry is the final level output in a fetch window;
// it carries on into the next if it's high resolution with
// the delay bit set.
mutable uint8_t graphics_carry_ = 0;
bool was_double_ = false;
uint8_t high_resolution_mask_ = 0xff;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
enum class GraphicsMode {
LowRes,
HighRes,
Text
} graphics_mode_ = GraphicsMode::LowRes;
bool use_graphics_mode_ = false;
bool mixed_mode_ = false;
uint16_t graphics_carry_ = 0;
// Memory is fetched ahead of time into this array;
// this permits the correct delay between fetching
// without having to worry about a rolling buffer.
std::array<uint8_t, 40> base_stream_;
std::array<uint8_t, 40> auxiliary_stream_;
static uint16_t scaled_byte[256];
static uint16_t low_resolution_patterns[2][16];
bool is_iie_ = false;
static const int flash_length = 8406;
// Describes the current text mode mapping from in-memory character index
// to output character.
struct CharacterMapping {
uint8_t address_mask;
uint8_t xor_mask;
};
CharacterMapping character_zones[4];
/*!
Outputs 40-column text to @c target, using @c length bytes from @c source.
*/
void output_text(uint8_t *target, const uint8_t *source, size_t length, size_t pixel_row) const;
/*!
Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source.
*/
void output_double_text(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, size_t pixel_row) const;
/*!
Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source.
*/
void output_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
/*!
Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source.
*/
void output_double_low_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, int column, int row) const;
/*!
Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source.
*/
void output_high_resolution(uint8_t *target, const uint8_t *source, size_t length) const;
/*!
Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source.
*/
void output_double_high_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length) const;
/*!
Outputs 40-column "fat low resolution" graphics to @c target, drawing @c length columns from @c source.
Fat low-resolution mode is like regular low-resolution mode except that data is shifted out on the 7M
clock rather than the 14M.
*/
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
// Maintain a ClockDeferrer for delayed mode switches.
ClockDeferrer<Cycles> deferrer_;
};
template <class BusHandler> class Video: public VideoBase {
template <class BusHandler, bool is_iie> class Video: public VideoBase {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(),
VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
/*!
Advances time by @c cycles; expects to be fed by the CPU clock.
Implicitly adds an extra half a colour clock at the end of every
line.
Runs video for @c cycles.
*/
void run_for(const Cycles cycles) {
/*
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
row 0 is the first row with pixels in it.
A frame is oriented around 65 cycles across, 262 lines down.
*/
const int first_sync_line = 220; // A complete guess. Information needed.
const int first_sync_column = 49; // Also a guess.
int int_cycles = cycles.as_int();
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 7);
} else {
const int ending_column = column_ + cycles_this_line;
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
// The first 40 columns are submitted to the CRT only upon completion;
// they'll be either graphics or blank, depending on which side we are
// of line 192.
if(column_ < 40) {
if(row_ < 192) {
if(!column_) {
pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2));
graphics_carry_ = 0;
}
const int pixel_end = std::min(40, ending_column);
const int character_row = row_ >> 3;
const int pixel_row = row_ & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
const int row_shift = (row_&4);
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
switch(pixel_mode) {
case GraphicsMode::Text: {
const uint8_t inverses[] = {
0xff,
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
0x00,
0x00
};
for(int c = column_; c < pixel_end; ++c) {
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
pixel_pointer_[c] = scaled_byte[character_pattern & 0x7f];
}
} break;
case GraphicsMode::LowRes:
for(int c = column_; c < pixel_end; ++c) {
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
pixel_pointer_[c] = low_resolution_patterns[c&1][(character >> row_shift)&0xf];
}
break;
case GraphicsMode::HighRes:
for(int c = column_; c < pixel_end; ++c) {
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
pixel_pointer_[c] = scaled_byte[graphic];
if(graphic & 0x80) {
reinterpret_cast<uint8_t *>(&pixel_pointer_[c])[0] |= graphics_carry_;
}
graphics_carry_ = (graphic >> 6) & 1;
}
break;
}
if(ending_column >= 40) {
crt_->output_data(280, 80);
}
} else {
if(ending_column >= 40) {
crt_->output_blank(280);
}
}
}
/*
The left border, sync, right border pattern doesn't depend on whether
there were pixels this row and is output as soon as it is known.
*/
const int first_blank_start = std::max(40, column_);
const int first_blank_end = std::min(first_sync_column, ending_column);
if(first_blank_end > first_blank_start) {
crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 7);
}
const int sync_start = std::max(first_sync_column, column_);
const int sync_end = std::min(first_sync_column + 4, ending_column);
if(sync_end > sync_start) {
crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 7);
}
int second_blank_start;
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
const int colour_burst_start = std::max(first_sync_column + 4, column_);
const int colour_burst_end = std::min(first_sync_column + 7, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 7);
}
second_blank_start = std::max(first_sync_column + 7, column_);
} else {
second_blank_start = std::max(first_sync_column + 4, column_);
}
if(ending_column > second_blank_start) {
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 7);
}
}
int_cycles -= cycles_this_line;
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
flash_ = (flash_ + 1) % (2 * flash_length);
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised.
crt_->output_blank(1);
}
}
void run_for(Cycles cycles) {
deferrer_.run_for(cycles);
}
/*!
@@ -244,22 +300,294 @@ template <class BusHandler> class Video: public VideoBase {
// Calculate the address and return the value.
uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25);
return bus_handler_.perform_read(read_address);
uint8_t value, aux_value;
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
return value;
}
/*!
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
*/
bool get_is_vertical_blank(Cycles offset) {
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
int mapped_column = column_ + offset.as_int();
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter and test it for location.
int mapped_row = row_ + (mapped_column / 65);
return (mapped_row % 262) >= 192;
}
private:
/*!
Advances time by @c cycles; expects to be fed by the CPU clock.
Implicitly adds an extra half a colour clock at the end of
line.
*/
void advance(Cycles cycles) {
/*
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
row 0 is the first row with pixels in it.
A frame is oriented around 65 cycles across, 262 lines down.
*/
static const int first_sync_line = 220; // A complete guess. Information needed.
static const int first_sync_column = 49; // Also a guess.
static const int sync_length = 4; // One of the two likely candidates.
int int_cycles = cycles.as_int();
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
// pulses (and hencce keep hsync approximately where it should be during vsync).
const int blank_start = std::max(first_sync_column - sync_length, column_);
const int blank_end = std::min(first_sync_column, ending_column);
if(blank_end > blank_start) {
if(blank_start > column_) {
crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
}
crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
if(blank_end < ending_column) {
crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
}
} else {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
}
} else {
const GraphicsMode line_mode = graphics_mode(row_);
// Determine whether there's any fetching to do. Fetching occurs during the first
// 40 columns of rows prior to 192.
if(row_ < 192 && column_ < 40) {
const int character_row = row_ >> 3;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
// Grab the memory contents that'll be needed momentarily.
const int fetch_end = std::min(40, ending_column);
uint16_t fetch_address;
switch(line_mode) {
default:
case GraphicsMode::Text:
case GraphicsMode::DoubleText:
case GraphicsMode::LowRes:
case GraphicsMode::FatLowRes:
case GraphicsMode::DoubleLowRes: {
const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
fetch_address = static_cast<uint16_t>(text_address + column_);
} break;
case GraphicsMode::HighRes:
case GraphicsMode::DoubleHighRes:
fetch_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
break;
}
bus_handler_.perform_read(
fetch_address,
static_cast<size_t>(fetch_end - column_),
&base_stream_[static_cast<size_t>(column_)],
&auxiliary_stream_[static_cast<size_t>(column_)]);
// TODO: should character modes be mapped to character pixel outputs here?
}
if(row_ < 192) {
// The pixel area is the first 40.5 columns; base contents
// remain where they would naturally be but auxiliary
// graphics appear to the left of that.
if(!column_) {
pixel_pointer_ = crt_->allocate_write_area(568);
graphics_carry_ = 0;
was_double_ = true;
}
if(column_ < 40) {
const int pixel_start = std::max(0, column_);
const int pixel_end = std::min(40, ending_column);
const int pixel_row = row_ & 7;
const bool is_double = Video::is_double_mode(line_mode);
if(!is_double && was_double_) {
pixel_pointer_[pixel_start*14 + 0] =
pixel_pointer_[pixel_start*14 + 1] =
pixel_pointer_[pixel_start*14 + 2] =
pixel_pointer_[pixel_start*14 + 3] =
pixel_pointer_[pixel_start*14 + 4] =
pixel_pointer_[pixel_start*14 + 5] =
pixel_pointer_[pixel_start*14 + 6] = 0;
}
was_double_ = is_double;
switch(line_mode) {
case GraphicsMode::Text:
output_text(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
static_cast<size_t>(pixel_row));
break;
case GraphicsMode::DoubleText:
output_double_text(
&pixel_pointer_[pixel_start * 14],
&base_stream_[static_cast<size_t>(pixel_start)],
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
static_cast<size_t>(pixel_row));
break;
case GraphicsMode::LowRes:
output_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::FatLowRes:
output_fat_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::DoubleLowRes:
output_double_low_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[static_cast<size_t>(pixel_start)],
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::HighRes:
output_high_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start));
break;
case GraphicsMode::DoubleHighRes:
output_double_high_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[static_cast<size_t>(pixel_start)],
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start));
break;
default: break;
}
if(pixel_end == 40) {
if(was_double_) {
pixel_pointer_[563] =
pixel_pointer_[564] =
pixel_pointer_[565] =
pixel_pointer_[566] =
pixel_pointer_[567] = 0;
} else {
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
pixel_pointer_[567] = graphics_carry_;
else
pixel_pointer_[567] = 0;
}
crt_->output_data(568, 568);
pixel_pointer_ = nullptr;
}
}
} else {
if(column_ < 40 && ending_column >= 40) {
crt_->output_blank(568);
}
}
/*
The left border, sync, right border pattern doesn't depend on whether
there were pixels this row and is output as soon as it is known.
*/
if(column_ < first_sync_column && ending_column >= first_sync_column) {
crt_->output_blank((first_sync_column - 41)*14 - 1);
}
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
crt_->output_sync(sync_length*14);
}
int second_blank_start;
if(!is_text_mode(graphics_mode(row_+1))) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192);
}
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
} else {
second_blank_start = std::max(first_sync_column + sync_length, column_);
}
if(ending_column > second_blank_start) {
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
}
}
int_cycles -= cycles_this_line;
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
flash_ = (flash_ + 1) % (2 * flash_length);
if(!alternative_character_set_) {
character_zones[1].xor_mask = flash_mask();
}
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised.
crt_->output_blank(2);
}
}
}
GraphicsMode graphics_mode(int row) {
if(
text_ ||
(mixed_ && row >= 160 && row < 192)
) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(high_resolution_) {
return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
if(columns_80_) return GraphicsMode::DoubleLowRes;
if(annunciator_3_) return GraphicsMode::FatLowRes;
return GraphicsMode::LowRes;
}
}
int video_page() {
return (store_80_ || !page2_) ? 0 : 1;
}
uint16_t get_row_address(int row) {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text;
return (pixel_mode == GraphicsMode::HighRes) ?
static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
}
const int flash_length = 8406;
BusHandler &bus_handler_;
};

View File

@@ -11,7 +11,6 @@
#include <algorithm>
#include <cstdio>
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
@@ -37,30 +36,27 @@ namespace {
namespace Atari2600 {
class Joystick: public Inputs::Joystick {
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}),
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire)
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) override {
switch(digital_input.type) {
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
case DigitalInput::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
// TODO: latching
case DigitalInput::Fire:
case Input::Fire:
if(is_active)
bus_->tia_input_value_[fire_tia_input_] &= ~0x80;
else
@@ -79,24 +75,16 @@ class Joystick: public Inputs::Joystick {
class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine,
public Outputs::CRT::Delegate {
public:
ConcreteMachine() {
ConcreteMachine(const Analyser::Static::Atari::Target &target) {
set_clock_rate(NTSC_clock_rate);
}
~ConcreteMachine() {
close_output();
}
void configure_as_target(const Analyser::Static::Target *target) override {
auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target);
const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data;
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
using PagingModel = Analyser::Static::Atari::Target::PagingModel;
switch(atari_target->paging_model) {
switch(target.paging_model) {
case PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
@@ -108,21 +96,21 @@ class ConcreteMachine:
case PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case PagingModel::Atari8k:
if(atari_target->uses_superchip) {
if(target.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
}
break;
case PagingModel::Atari16k:
if(atari_target->uses_superchip) {
if(target.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
}
break;
case PagingModel::Atari32k:
if(atari_target->uses_superchip) {
if(target.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
@@ -134,8 +122,8 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
}
bool insert_media(const Analyser::Static::Media &media) override {
return false;
~ConcreteMachine() {
close_output();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
@@ -257,8 +245,10 @@ class ConcreteMachine:
using namespace Atari2600;
Machine *Machine::Atari2600() {
return new Atari2600::ConcreteMachine;
Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Atari::Target;
const Target *const atari_target = dynamic_cast<const Target *>(target);
return new Atari2600::ConcreteMachine(*atari_target);
}
Machine::~Machine() {}

View File

@@ -9,6 +9,10 @@
#ifndef Atari2600_cpp
#define Atari2600_cpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
@@ -21,7 +25,7 @@ class Machine {
virtual ~Machine();
/// Creates and returns an Atari 2600 on the heap.
static Machine *Atari2600();
static Machine *Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
/// Sets the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;

View File

@@ -204,7 +204,7 @@ template<class T> class Cartridge:
}
protected:
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
private:

View File

@@ -124,19 +124,19 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
if(output_mode == OutputMode::NTSC) {
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset));"
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));"
"}");
display_type = Outputs::CRT::DisplayType::NTSC60;
} else {
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"

View File

@@ -26,7 +26,7 @@ namespace CRTMachine {
that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate
should that clock rate change.
*/
class Machine: public ROMMachine::Machine {
class Machine {
public:
/*!
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees

View File

@@ -14,7 +14,6 @@
#include "../../Components/AY38910/AY38910.hpp" // For the Super Game Module.
#include "../../Components/SN76489/SN76489.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
@@ -32,30 +31,29 @@ const int sn76489_divider = 2;
namespace Coleco {
namespace Vision {
class Joystick: public Inputs::Joystick {
class Joystick: public Inputs::ConcreteJoystick {
public:
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
DigitalInput(DigitalInput::Fire, 0),
DigitalInput(DigitalInput::Fire, 1),
Input(Input::Fire, 0),
Input(Input::Fire, 1),
DigitalInput('0'), DigitalInput('1'), DigitalInput('2'),
DigitalInput('3'), DigitalInput('4'), DigitalInput('5'),
DigitalInput('6'), DigitalInput('7'), DigitalInput('8'),
DigitalInput('9'), DigitalInput('*'), DigitalInput('#'),
};
}
Input('0'), Input('1'), Input('2'),
Input('3'), Input('4'), Input('5'),
Input('6'), Input('7'), Input('8'),
Input('9'), Input('*'), Input('#'),
}) {}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) override {
switch(digital_input.type) {
default: return;
case DigitalInput::Key:
case Input::Key:
if(!is_active) keypad_ |= 0xf;
else {
uint8_t mask = 0xf;
@@ -78,11 +76,11 @@ class Joystick: public Inputs::Joystick {
}
break;
case DigitalInput::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case DigitalInput::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case DigitalInput::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case DigitalInput::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
case DigitalInput::Fire:
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
case Input::Fire:
switch(digital_input.info.control.index) {
default: break;
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
@@ -109,11 +107,10 @@ class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine {
public:
ConcreteMachine() :
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
ay_(audio_queue_),
@@ -123,6 +120,36 @@ class ConcreteMachine:
set_clock_rate(3579545);
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
const auto roms = rom_fetcher(
"ColecoVision",
{ "coleco.rom" });
if(!roms[0]) {
throw ROMMachine::Error::MissingROMs;
}
bios_ = *roms[0];
bios_.resize(8192);
if(!target.media.cartridges.empty()) {
const auto &segment = target.media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
if(cartridge_.size() >= 32768)
cartridge_address_limit_ = 0xffff;
else
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
if(cartridge_.size() > 32768) {
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
cartridge_pages_[1] = cartridge_.data();
is_megacart_ = true;
} else {
cartridge_pages_[0] = cartridge_.data();
cartridge_pages_[1] = cartridge_.data() + 16384;
is_megacart_ = false;
}
}
}
~ConcreteMachine() {
@@ -154,48 +181,6 @@ class ConcreteMachine:
z80_.run_for(cycles);
}
void configure_as_target(const Analyser::Static::Target *target) override {
// Insert the media.
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
if(cartridge_.size() >= 32768)
cartridge_address_limit_ = 0xffff;
else
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
if(cartridge_.size() > 32768) {
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
cartridge_pages_[1] = cartridge_.data();
is_megacart_ = true;
} else {
cartridge_pages_[0] = cartridge_.data();
cartridge_pages_[1] = cartridge_.data() + 16384;
is_megacart_ = false;
}
}
return true;
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"ColecoVision",
{ "coleco.rom" });
if(!roms[0]) return false;
bios_ = *roms[0];
bios_.resize(8192);
return true;
}
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// The SN76489 will use its ready line to trigger the Z80's wait for three
@@ -409,8 +394,8 @@ class ConcreteMachine:
using namespace Coleco::Vision;
Machine *Machine::ColecoVision() {
return new ConcreteMachine;
Machine *Machine::ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
return new ConcreteMachine(*target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -9,13 +9,16 @@
#ifndef ColecoVision_hpp
#define ColecoVision_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
namespace Coleco {
namespace Vision {
class Machine {
public:
virtual ~Machine();
static Machine *ColecoVision();
static Machine *ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}

View File

@@ -9,6 +9,25 @@
#ifndef Commodore1540_hpp
#define Commodore1540_hpp
namespace Commodore {
namespace C1540 {
/// Defines the type of drive this 1540 hardware is configured as.
enum class Personality {
C1540,
C1541
};
/*
Implementation note: this is defined up here so that it precedes
C1540Base.hpp below. The alternative option was to factor it out,
but the whole point of the C1540.hpp/C1540Base.hpp split is supposed
to be to create a single file of public interface.
*/
}
}
#include "../SerialBus.hpp"
#include "../../ROMMachine.hpp"
#include "../../../Storage/Disk/Disk.hpp"
@@ -20,18 +39,9 @@ namespace C1540 {
/*!
Provides an emulation of the C1540.
*/
class Machine: public MachineBase, public ROMMachine::Machine {
class Machine: public MachineBase {
public:
enum Personality {
C1540,
C1541
};
Machine(Personality p);
/*!
Sets the source for this drive's ROM image.
*/
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names);
Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
/*!
Sets the serial bus to which this drive should attach itself.
@@ -43,9 +53,6 @@ class Machine: public MachineBase, public ROMMachine::Machine {
/// Inserts @c disk into the drive.
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
private:
Personality personality_;
};
}

View File

@@ -16,7 +16,7 @@
using namespace Commodore::C1540;
MachineBase::MachineBase() :
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
Storage::Disk::Controller(1000000),
m6502_(*this),
drive_(new Storage::Disk::Drive(1000000, 300, 2)),
@@ -38,9 +38,22 @@ MachineBase::MachineBase() :
// attach the only drive there is
set_drive(drive_);
std::string rom_name;
switch(personality) {
case Personality::C1540: rom_name = "1540.bin"; break;
case Personality::C1541: rom_name = "1541.bin"; break;
}
auto roms = rom_fetcher("Commodore1540", {rom_name});
if(!roms[0]) {
throw ROMMachine::Error::MissingROMs;
}
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
}
Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {}
Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
MachineBase(personality, rom_fetcher) {}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
@@ -82,19 +95,6 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
return Cycles(1);
}
bool Machine::set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) {
std::string rom_name;
switch(personality_) {
case Personality::C1540: rom_name = "1540.bin"; break;
case Personality::C1541: rom_name = "1541.bin"; break;
}
auto roms = roms_with_names("Commodore1540", {rom_name});
if(!roms[0]) return false;
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
return true;
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
drive_->set_disk(disk);
}

View File

@@ -19,6 +19,8 @@
#include "../../../../Storage/Disk/Controller/DiskController.hpp"
#include "../C1540.hpp"
namespace Commodore {
namespace C1540 {
@@ -125,7 +127,7 @@ class MachineBase:
public Storage::Disk::Controller {
public:
MachineBase();
MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
@@ -141,7 +143,7 @@ class MachineBase:
void set_activity_observer(Activity::Observer *observer);
protected:
CPU::MOS6502::Processor<MachineBase, false> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
std::shared_ptr<Storage::Disk::Drive> drive_;
uint8_t ram_[0x800];

View File

@@ -11,7 +11,7 @@
#include "Keyboard.hpp"
#include "../../../Activity/Source.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../MediaTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
@@ -62,18 +62,6 @@ enum JoystickInput {
Fire = 0x20
};
enum ROM {
CharactersDanish = 0,
CharactersEnglish,
CharactersJapanese,
CharactersSwedish,
KernelDanish,
KernelJapanese,
KernelNTSC,
KernelPAL,
KernelSwedish
};
/*!
Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder;
sensing the presence or absence of a tape and controlling the tape motor; and reading the current
@@ -257,31 +245,28 @@ class Vic6560BusHandler {
/*!
Interfaces a joystick to the two VIAs.
*/
class Joystick: public Inputs::Joystick {
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}),
user_port_via_port_handler_(user_port_via_port_handler),
keyboard_via_port_handler_(keyboard_via_port_handler) {}
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire)
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
void did_set_input(const Input &digital_input, bool is_active) override {
JoystickInput mapped_input;
switch(digital_input.type) {
default: return;
case DigitalInput::Up: mapped_input = Up; break;
case DigitalInput::Down: mapped_input = Down; break;
case DigitalInput::Left: mapped_input = Left; break;
case DigitalInput::Right: mapped_input = Right; break;
case DigitalInput::Fire: mapped_input = Fire; break;
case Input::Up: mapped_input = Up; break;
case Input::Down: mapped_input = Down; break;
case Input::Left: mapped_input = Left; break;
case Input::Right: mapped_input = Right; break;
case Input::Fire: mapped_input = Fire; break;
}
user_port_via_port_handler_.set_joystick_state(mapped_input, is_active);
@@ -295,7 +280,7 @@ class Joystick: public Inputs::Joystick {
class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine,
public Configurable::Device,
@@ -304,10 +289,10 @@ class ConcreteMachine:
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Machine,
public Sleeper::SleepObserver,
public ClockingHint::Observer,
public Activity::Source {
public:
ConcreteMachine() :
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
user_port_via_port_handler_(new UserPortVIA),
keyboard_via_port_handler_(new KeyboardVIA),
@@ -331,131 +316,72 @@ class ConcreteMachine:
user_port_via_port_handler_->set_interrupt_delegate(this);
keyboard_via_port_handler_->set_interrupt_delegate(this);
tape_->set_delegate(this);
tape_->set_sleep_observer(this);
tape_->set_clocking_hint_observer(this);
// install a joystick
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
rom_fetcher_ = roms_with_names;
auto roms = roms_with_names(
"Vic20",
{
"characters-danish.bin",
"characters-english.bin",
"characters-japanese.bin",
"characters-swedish.bin",
"kernel-danish.bin",
"kernel-japanese.bin",
"kernel-ntsc.bin",
"kernel-pal.bin",
"kernel-swedish.bin",
"basic.bin"
});
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) return false;
if(index < 9) roms_[index] = std::move(*data); else basic_rom_ = std::move(*data);
std::vector<std::string> rom_names = { "basic.bin" };
switch(target.region) {
default:
rom_names.push_back("characters-english.bin");
rom_names.push_back("kernel-pal.bin");
break;
case Analyser::Static::Commodore::Target::Region::American:
rom_names.push_back("characters-english.bin");
rom_names.push_back("kernel-ntsc.bin");
break;
case Analyser::Static::Commodore::Target::Region::Danish:
rom_names.push_back("characters-danish.bin");
rom_names.push_back("kernel-danish.bin");
break;
case Analyser::Static::Commodore::Target::Region::Japanese:
rom_names.push_back("characters-japanese.bin");
rom_names.push_back("kernel-japanese.bin");
break;
case Analyser::Static::Commodore::Target::Region::Swedish:
rom_names.push_back("characters-swedish.bin");
rom_names.push_back("kernel-japanese.bin");
break;
}
const auto roms = rom_fetcher("Vic20", rom_names);
for(const auto &rom: roms) {
if(!rom) {
throw ROMMachine::Error::MissingROMs;
}
}
basic_rom_ = std::move(*roms[0]);
character_rom_ = std::move(*roms[1]);
kernel_rom_ = std::move(*roms[2]);
// Characters ROMs should be 4kb.
for(std::size_t index = 0; index < 4; ++index) roms_[index].resize(4096);
character_rom_.resize(4096);
// Kernel ROMs and the BASIC ROM should be 8kb.
for(std::size_t index = 4; index < roms.size(); ++index) roms_[index].resize(8192);
kernel_rom_.resize(8192);
return true;
}
void configure_as_target(const Analyser::Static::Target *target) override final {
commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target);
if(!commodore_target_.loading_command.empty()) {
type_string(commodore_target_.loading_command);
}
if(commodore_target_.has_c1540) {
if(target.has_c1540) {
// construct the 1540
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Personality::C1540, rom_fetcher));
// attach it to the serial bus
c1540_->set_serial_bus(serial_bus_);
// give it a means to obtain its ROM
c1540_->set_rom_fetcher(rom_fetcher_);
// give it a little warm up
c1540_->run_for(Cycles(2000000));
}
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_->set_tape(media.tapes.front());
}
if(!media.disks.empty() && c1540_) {
c1540_->set_disk(media.disks.front());
}
if(!media.cartridges.empty()) {
rom_address_ = 0xa000;
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
rom_length_ = static_cast<uint16_t>(rom_image.size());
rom_ = rom_image;
rom_.resize(0x2000);
}
set_use_fast_tape();
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
}
void set_key_state(uint16_t key, bool is_pressed) override final {
if(key != KeyRestore)
keyboard_via_port_handler_->set_key_state(key, is_pressed);
else
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
}
void clear_all_keys() override final {
keyboard_via_port_handler_->clear_all_keys();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
void set_ntsc_6560() {
set_clock_rate(1022727);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560::OutputMode::NTSC);
mos6560_->set_clock_rate(1022727);
}
}
void set_pal_6560() {
set_clock_rate(1108404);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560::OutputMode::PAL);
mos6560_->set_clock_rate(1108404);
}
}
void set_memory_map(Analyser::Static::Commodore::Target::MemoryModel memory_model, Analyser::Static::Commodore::Target::Region region) {
// Determine PAL/NTSC
if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) {
if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) {
// NTSC
set_ntsc_6560();
set_clock_rate(1022727);
output_mode_ = MOS::MOS6560::OutputMode::NTSC;
} else {
// PAL
set_pal_6560();
set_clock_rate(1108404);
output_mode_ = MOS::MOS6560::OutputMode::PAL;
}
// Initialise the memory maps as all pointing to nothing
@@ -468,7 +394,7 @@ class ConcreteMachine:
write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length);
// Add 6502-visible RAM as requested
switch(memory_model) {
switch(target.memory_model) {
case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded:
// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000.
set_ram(0x0000, 0x0400);
@@ -515,39 +441,53 @@ class ConcreteMachine:
write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size()));
// install the system ROM
ROM character_rom;
ROM kernel_rom;
switch(region) {
default:
character_rom = CharactersEnglish;
kernel_rom = KernelPAL;
break;
case Analyser::Static::Commodore::Target::Region::American:
character_rom = CharactersEnglish;
kernel_rom = KernelNTSC;
break;
case Analyser::Static::Commodore::Target::Region::Danish:
character_rom = CharactersDanish;
kernel_rom = KernelDanish;
break;
case Analyser::Static::Commodore::Target::Region::Japanese:
character_rom = CharactersJapanese;
kernel_rom = KernelJapanese;
break;
case Analyser::Static::Commodore::Target::Region::Swedish:
character_rom = CharactersSwedish;
kernel_rom = KernelSwedish;
break;
write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, static_cast<uint16_t>(character_rom_.size()));
write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, static_cast<uint16_t>(character_rom_.size()));
write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, static_cast<uint16_t>(kernel_rom_.size()));
insert_media(target.media);
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
}
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_->set_tape(media.tapes.front());
}
write_to_map(processor_read_memory_map_, roms_[character_rom].data(), 0x8000, static_cast<uint16_t>(roms_[character_rom].size()));
write_to_map(mos6560_bus_handler_.video_memory_map, roms_[character_rom].data(), 0x0000, static_cast<uint16_t>(roms_[character_rom].size()));
write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size()));
if(!media.disks.empty() && c1540_) {
c1540_->set_disk(media.disks.front());
}
// install the inserted ROM if there is one
if(!rom_.empty()) {
if(!media.cartridges.empty()) {
rom_address_ = 0xa000;
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
rom_length_ = static_cast<uint16_t>(rom_image.size());
rom_ = rom_image;
rom_.resize(0x2000);
write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_);
}
set_use_fast_tape();
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
}
void set_key_state(uint16_t key, bool is_pressed) override final {
if(key != KeyRestore)
keyboard_via_port_handler_->set_key_state(key, is_pressed);
else
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
}
void clear_all_keys() override final {
keyboard_via_port_handler_->clear_all_keys();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
// to satisfy CPU::MOS6502::Processor
@@ -683,8 +623,8 @@ class ConcreteMachine:
void setup_output(float aspect_ratio) override final {
mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_));
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
set_memory_map(commodore_target_.memory_model, commodore_target_.region);
mos6560_->set_output_mode(output_mode_);
mos6560_->set_clock_rate(get_clock_rate());
}
void close_output() override final {
@@ -749,8 +689,8 @@ class ConcreteMachine:
return selection_set;
}
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
tape_is_sleeping_ = is_sleeping;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
set_use_fast_tape();
}
@@ -763,11 +703,7 @@ class ConcreteMachine:
void update_video() {
mos6560_->run_for(cycles_since_mos6560_update_.flush());
}
Analyser::Static::Commodore::Target commodore_target_;
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
std::vector<uint8_t> roms_[9];
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
std::vector<uint8_t> character_rom_;
std::vector<uint8_t> basic_rom_;
@@ -778,8 +714,6 @@ class ConcreteMachine:
uint8_t ram_[0x8000];
uint8_t colour_ram_[0x0400];
std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_;
uint8_t *processor_read_memory_map_[64];
uint8_t *processor_write_memory_map_[64];
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
@@ -797,6 +731,7 @@ class ConcreteMachine:
Cycles cycles_since_mos6560_update_;
Vic6560BusHandler mos6560_bus_handler_;
MOS::MOS6560::OutputMode output_mode_;
std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
@@ -825,8 +760,10 @@ class ConcreteMachine:
using namespace Commodore::Vic20;
Machine *Machine::Vic20() {
return new Vic20::ConcreteMachine;
Machine *Machine::Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Commodore::Target;
const Target *const commodore_target = dynamic_cast<const Target *>(target);
return new Vic20::ConcreteMachine(*commodore_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -10,6 +10,11 @@
#define Vic20_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
#include <memory>
#include <vector>
namespace Commodore {
namespace Vic20 {
@@ -22,7 +27,7 @@ class Machine {
virtual ~Machine();
/// Creates and returns a Vic-20.
static Machine *Vic20();
static Machine *Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}

View File

@@ -1,38 +0,0 @@
//
// ConfigurationTarget.h
// Clock Signal
//
// Created by Thomas Harte on 08/09/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#ifndef ConfigurationTarget_hpp
#define ConfigurationTarget_hpp
#include "../Analyser/Static/StaticAnalyser.hpp"
#include "../Configurable/Configurable.hpp"
#include <string>
namespace ConfigurationTarget {
/*!
A ConfigurationTarget::Machine is anything that can accept a Analyser::Static::Target
and configure itself appropriately, or accept a list of media subsequently to insert.
*/
class Machine {
public:
/// Instructs the machine to configure itself as described by @c target and insert the included media.
virtual void configure_as_target(const Analyser::Static::Target *target) = 0;
/*!
Requests that the machine insert @c media as a modification to current state
@returns @c true if any media was inserted; @c false otherwise.
*/
virtual bool insert_media(const Analyser::Static::Media &media) = 0;
};
}
#endif /* ConfigurationTarget_h */

View File

@@ -11,7 +11,7 @@
#include "../Configurable/Configurable.hpp"
#include "../Activity/Source.hpp"
#include "ConfigurationTarget.hpp"
#include "MediaTarget.hpp"
#include "CRTMachine.hpp"
#include "JoystickMachine.hpp"
#include "KeyboardMachine.hpp"
@@ -27,11 +27,11 @@ struct DynamicMachine {
virtual ~DynamicMachine() {}
virtual Activity::Source *activity_source() = 0;
virtual ConfigurationTarget::Machine *configuration_target() = 0;
virtual Configurable::Device *configurable_device() = 0;
virtual CRTMachine::Machine *crt_machine() = 0;
virtual JoystickMachine::Machine *joystick_machine() = 0;
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
virtual Configurable::Device *configurable_device() = 0;
virtual MediaTarget::Machine *media_target() = 0;
/*!
Provides a raw pointer to the underlying machine if and only if this dynamic machine really is

View File

@@ -9,7 +9,7 @@
#include "Electron.hpp"
#include "../../Activity/Source.hpp"
#include "../ConfigurationTarget.hpp"
#include "../MediaTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
@@ -41,7 +41,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
@@ -49,10 +49,10 @@ class ConcreteMachine:
public Utility::TypeRecipient,
public Activity::Source {
public:
ConcreteMachine() :
m6502_(*this),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
memset(key_states_, 0, sizeof(key_states_));
for(int c = 0; c < 16; c++)
memset(roms_[c], 0xff, 16384);
@@ -61,60 +61,53 @@ class ConcreteMachine:
set_clock_rate(2000000);
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
std::vector<std::string> rom_names = {"basic.rom", "os.rom"};
if(target.has_adfs) {
rom_names.push_back("ADFS-E00_1.rom");
rom_names.push_back("ADFS-E00_2.rom");
}
const size_t dfs_rom_position = rom_names.size();
if(target.has_dfs) {
rom_names.push_back("DFS-1770-2.20.rom");
}
const auto roms = rom_fetcher("Electron", rom_names);
for(const auto &rom: roms) {
if(!rom) {
throw ROMMachine::Error::MissingROMs;
}
}
set_rom(ROM::BASIC, *roms[0], false);
set_rom(ROM::OS, *roms[1], false);
if(target.has_dfs || target.has_adfs) {
plus3_.reset(new Plus3);
if(target.has_dfs) {
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
}
if(target.has_adfs) {
set_rom(ROM::Slot4, *roms[2], true);
set_rom(ROM::Slot5, *roms[3], true);
}
}
insert_media(target.media);
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
if(target.should_shift_restart) {
shift_restart_counter_ = 1000000;
}
}
~ConcreteMachine() {
audio_queue_.flush();
}
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final {
uint8_t *target = nullptr;
switch(slot) {
case ROMSlotDFS: dfs_ = data; return;
case ROMSlotADFS1: adfs1_ = data; return;
case ROMSlotADFS2: adfs2_ = data; return;
case ROMSlotOS: target = os_; break;
default:
target = roms_[slot];
rom_write_masks_[slot] = is_writeable;
break;
}
// Copy in, with mirroring.
std::size_t rom_ptr = 0;
while(rom_ptr < 16384) {
std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size());
std::memcpy(&target[rom_ptr], data.data(), size_to_copy);
rom_ptr += size_to_copy;
}
rom_inserted_[slot] = true;
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"Electron",
{
"DFS-1770-2.20.rom",
"ADFS-E00_1.rom", "ADFS-E00_2.rom",
"basic.rom", "os.rom"
});
ROMSlot slots[] = {
ROMSlotDFS,
ROMSlotADFS1, ROMSlotADFS2,
ROMSlotBASIC, ROMSlotOS
};
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) return false;
set_rom(slots[index], *data, false);
}
return true;
}
void set_key_state(uint16_t key, bool isPressed) override final {
if(key == KeyBreak) {
m6502_.set_reset_line(isPressed);
@@ -131,32 +124,6 @@ class ConcreteMachine:
if(is_holding_shift_) set_key_state(KeyShift, true);
}
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target);
if(!acorn_target->loading_command.empty()) {
type_string(acorn_target->loading_command);
}
if(acorn_target->should_shift_restart) {
shift_restart_counter_ = 1000000;
}
if(acorn_target->has_dfs || acorn_target->has_adfs) {
plus3_.reset(new Plus3);
if(acorn_target->has_dfs) {
set_rom(ROMSlot0, dfs_, true);
}
if(acorn_target->has_adfs) {
set_rom(ROMSlot4, adfs1_, true);
set_rom(ROMSlot5, adfs2_, true);
}
}
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_.set_tape(media.tapes.front());
@@ -167,11 +134,11 @@ class ConcreteMachine:
plus3_->set_disk(media.disks.front(), 0);
}
ROMSlot slot = ROMSlot12;
ROM slot = ROM::Slot12;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
const ROMSlot first_slot_tried = slot;
while(rom_inserted_[slot]) {
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
const ROM first_slot_tried = slot;
while(rom_inserted_[static_cast<int>(slot)]) {
slot = static_cast<ROM>((static_cast<int>(slot) + 1) & 15);
if(slot == first_slot_tried) return false;
}
set_rom(slot, cartridge->get_segments().front().data, false);
@@ -258,7 +225,7 @@ class ConcreteMachine:
}
// latch the paged ROM in case external hardware is being emulated
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
active_rom_ = *value & 0xf;
// apply the ULA's test
if(*value & 0x08) {
@@ -363,7 +330,7 @@ class ConcreteMachine:
}
}
if(basic_is_active_) {
*value &= roms_[ROMSlotBASIC][address & 16383];
*value &= roms_[static_cast<int>(ROM::BASIC)][address & 16383];
}
} else if(rom_write_masks_[active_rom_]) {
roms_[active_rom_][address & 16383] = *value;
@@ -480,15 +447,64 @@ class ConcreteMachine:
return selection_set;
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
activity_observer_ = observer;
if(activity_observer_) {
activity_observer_->register_led(caps_led);
activity_observer_->set_led_status(caps_led, caps_led_state_);
if(plus3_) {
plus3_->set_activity_observer(observer);
}
}
}
private:
enum class ROM {
Slot0 = 0,
Slot1, Slot2, Slot3,
Slot4, Slot5, Slot6, Slot7,
Keyboard = 8, Slot9,
BASIC = 10, Slot11,
Slot12, Slot13, Slot14, Slot15,
OS, DFS,
ADFS1, ADFS2
};
/*!
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
*/
void set_rom(ROM slot, const std::vector<uint8_t> &data, bool is_writeable) {
uint8_t *target = nullptr;
switch(slot) {
case ROM::DFS: dfs_ = data; return;
case ROM::ADFS1: adfs1_ = data; return;
case ROM::ADFS2: adfs2_ = data; return;
case ROM::OS: target = os_; break;
default:
target = roms_[static_cast<int>(slot)];
rom_write_masks_[static_cast<int>(slot)] = is_writeable;
break;
}
// Copy in, with mirroring.
std::size_t rom_ptr = 0;
while(rom_ptr < 16384) {
std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size());
std::memcpy(&target[rom_ptr], data.data(), size_to_copy);
rom_ptr += size_to_copy;
}
if(static_cast<int>(slot) < 16)
rom_inserted_[static_cast<int>(slot)] = true;
}
// MARK: - Work deferral updates.
inline void update_display() {
if(cycles_since_display_update_ > 0) {
@@ -525,7 +541,7 @@ class ConcreteMachine:
m6502_.set_irq_line(interrupt_status_ & 1);
}
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
// Things that directly constitute the memory map.
uint8_t roms_[16][16384];
@@ -535,7 +551,7 @@ class ConcreteMachine:
std::vector<uint8_t> dfs_, adfs1_, adfs2_;
// Paging
ROMSlot active_rom_ = ROMSlot::ROMSlot0;
int active_rom_ = static_cast<int>(ROM::Slot0);
bool keyboard_is_active_ = false;
bool basic_is_active_ = false;
@@ -585,8 +601,10 @@ class ConcreteMachine:
using namespace Electron;
Machine *Machine::Electron() {
return new Electron::ConcreteMachine;
Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Acorn::Target;
const Target *const acorn_target = dynamic_cast<const Target *>(target);
return new Electron::ConcreteMachine(*acorn_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -10,26 +10,15 @@
#define Electron_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace Electron {
enum ROMSlot: uint8_t {
ROMSlot0 = 0,
ROMSlot1, ROMSlot2, ROMSlot3,
ROMSlot4, ROMSlot5, ROMSlot6, ROMSlot7,
ROMSlotKeyboard = 8, ROMSlot9,
ROMSlotBASIC = 10, ROMSlot11,
ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15,
ROMSlotOS, ROMSlotDFS,
ROMSlotADFS1, ROMSlotADFS2
};
/// @returns The options available for an Electron.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
@@ -44,13 +33,7 @@ class Machine {
virtual ~Machine();
/// Creates and returns an Electron.
static Machine *Electron();
/*!
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
*/
virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0;
static Machine *Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}

View File

@@ -11,14 +11,12 @@
using namespace Electron;
Plus3::Plus3() : WD1770(P1770) {
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
set_control_register(last_control_, 0xff);
}
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
drives_[drive]->set_disk(disk);
}
@@ -42,8 +40,8 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) {
}
}
if(changes & 0x04) {
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
drives_[0]->set_head((control & 0x04) ? 1 : 0);
drives_[1]->set_head((control & 0x04) ? 1 : 0);
}
if(changes & 0x08) set_is_double_density(!(control & 0x08));
}
@@ -55,6 +53,9 @@ void Plus3::set_motor_on(bool on) {
}
void Plus3::set_activity_observer(Activity::Observer *observer) {
drives_[0]->set_activity_observer(observer, "Drive 1", true);
drives_[1]->set_activity_observer(observer, "Drive 2", true);
size_t index = 0;
for(const auto &drive: drives_) {
drive->set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
++index;
}
}

View File

@@ -18,17 +18,18 @@ class Plus3 : public WD::WD1770 {
public:
Plus3();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_control_register(uint8_t control);
void set_activity_observer(Activity::Observer *observer);
private:
void set_control_register(uint8_t control, uint8_t changes);
std::shared_ptr<Storage::Disk::Drive> drives_[2];
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
int selected_drive_ = 0;
uint8_t last_control_ = 0;
void set_motor_on(bool on);
std::string drive_name(size_t drive);
};
}

View File

@@ -62,7 +62,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender);
crt_->set_bookender(std::move(bookender));
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
}
// MARK: - CRT getter
@@ -404,7 +404,7 @@ unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
if(current_line >= output_position_line) {
// Get the number of lines since then if still in the same frame.
int lines_since_output_position = current_line - output_position_line;
// Therefore get the character row at the proposed time, modulo 10.
implied_row = (current_character_row_ + lines_since_output_position) % 10;
} else {

View File

@@ -13,6 +13,8 @@ using namespace MSX;
DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
WD1770(P1793),
rom_(rom) {
drives_[0].reset(new Storage::Disk::Drive(8000000, 300, 2));
drives_[1].reset(new Storage::Disk::Drive(8000000, 300, 2));
set_is_double_density(true);
}
@@ -23,16 +25,16 @@ void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) {
break;
case 0x7ffc:
selected_head_ = value & 1;
if(drives_[0]) drives_[0]->set_head(selected_head_);
if(drives_[1]) drives_[1]->set_head(selected_head_);
drives_[0]->set_head(selected_head_);
drives_[1]->set_head(selected_head_);
break;
case 0x7ffd: {
selected_drive_ = value & 1;
set_drive(drives_[selected_drive_]);
bool drive_motor = !!(value & 0x80);
if(drives_[0]) drives_[0]->set_motor_on(drive_motor);
if(drives_[1]) drives_[1]->set_motor_on(drive_motor);
drives_[0]->set_motor_on(drive_motor);
drives_[1]->set_motor_on(drive_motor);
} break;
}
}
@@ -57,12 +59,6 @@ void DiskROM::run_for(HalfCycles half_cycles) {
}
void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
drives_[drive]->set_head(selected_head_);
if(drive == selected_drive_) set_drive(drives_[drive]);
drives_[drive]->set_activity_observer(observer_, drive_name(drive), true);
}
drives_[drive]->set_disk(disk);
}
@@ -72,14 +68,9 @@ void DiskROM::set_head_load_request(bool head_load) {
}
void DiskROM::set_activity_observer(Activity::Observer *observer) {
size_t c = 0;
observer_ = observer;
size_t c = 1;
for(auto &drive: drives_) {
if(drive) drive->set_activity_observer(observer, drive_name(c), true);
drive->set_activity_observer(observer, "Drive " + std::to_string(c), true);
++c;
}
}
std::string DiskROM::drive_name(size_t index) {
return "Drive " + std::to_string(index);
}

View File

@@ -41,8 +41,6 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
std::array<std::shared_ptr<Storage::Disk::Drive>, 2> drives_;
void set_head_load_request(bool head_load) override;
std::string drive_name(size_t index);
Activity::Observer *observer_ = nullptr;
};
}

View File

@@ -32,7 +32,8 @@
#include "../../Activity/Source.hpp"
#include "../CRTMachine.hpp"
#include "../ConfigurationTarget.hpp"
#include "../JoystickMachine.hpp"
#include "../MediaTarget.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
@@ -54,13 +55,19 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
class AYPortHandler: public GI::AY38910::PortHandler {
public:
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {}
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
void set_port_output(bool port_b, uint8_t value) {
if(port_b) {
// Bits 0-3: touchpad handshaking (?)
// Bit 4-5: monostable timer pulses
// Bit 6: joystick select
selected_joystick_ = (value >> 6) & 1;
// Bit 7: code LED, if any
}
}
@@ -69,29 +76,75 @@ class AYPortHandler: public GI::AY38910::PortHandler {
if(!port_b) {
// Bits 0-5: Joystick (up, down, left, right, A, B)
// Bit 6: keyboard switch (not universal)
// Bit 7: tape input
return 0x7f | (tape_player_.get_input() ? 0x00 : 0x80);
return
(static_cast<Joystick *>(joysticks_[selected_joystick_].get())->get_state() & 0x3f) |
0x40 |
(tape_player_.get_input() ? 0x00 : 0x80);
}
return 0xff;
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
private:
Storage::Tape::BinaryTapePlayer &tape_player_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
size_t selected_joystick_ = 0;
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire, 0),
Input(Input::Fire, 1),
}) {}
void did_set_input(const Input &input, bool is_active) override {
uint8_t mask = 0;
switch(input.type) {
default: return;
case Input::Up: mask = 0x01; break;
case Input::Down: mask = 0x02; break;
case Input::Left: mask = 0x04; break;
case Input::Right: mask = 0x08; break;
case Input::Fire:
if(input.info.control.index >= 2) return;
mask = input.info.control.index ? 0x20 : 0x10;
break;
}
if(is_active) state_ &= ~mask; else state_ |= mask;
}
uint8_t get_state() {
return state_;
}
private:
uint8_t state_ = 0xff;
};
};
class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public JoystickMachine::Machine,
public MemoryMap,
public Sleeper::SleepObserver,
public ClockingHint::Observer,
public Activity::Source {
public:
ConcreteMachine():
ConcreteMachine(const Analyser::Static::MSX::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
z80_(*this),
i8255_(i8255_port_handler_),
ay_(audio_queue_),
@@ -108,10 +161,55 @@ class ConcreteMachine:
ay_.set_port_handler(&ay_port_handler_);
speaker_.set_input_rate(3579545.0f / 2.0f);
tape_player_.set_sleep_observer(this);
tape_player_.set_clocking_hint_observer(this);
// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC.
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
// Fetch the necessary ROMs.
std::vector<std::string> rom_names = {"msx.rom"};
if(target.has_disk_drive) {
rom_names.push_back("disk.rom");
}
const auto roms = rom_fetcher("MSX", rom_names);
if(!roms[0] || (target.has_disk_drive && !roms[1])) {
throw ROMMachine::Error::MissingROMs;
}
memory_slots_[0].source = std::move(*roms[0]);
memory_slots_[0].source.resize(32768);
for(size_t c = 0; c < 8; ++c) {
for(size_t slot = 0; slot < 3; ++slot) {
memory_slots_[slot].read_pointers[c] = unpopulated_;
memory_slots_[slot].write_pointers[c] = scratch_;
}
memory_slots_[3].read_pointers[c] =
memory_slots_[3].write_pointers[c] = &ram_[c * 8192];
}
map(0, 0, 0, 32768);
page_memory(0);
// Add a disk cartridge if any disks were supplied.
if(target.has_disk_drive) {
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
memory_slots_[2].source = std::move(*roms[1]);
memory_slots_[2].source.resize(16384);
map(2, 0, 0x4000, 0x2000);
unmap(2, 0x6000, 0x2000);
}
// Insert the media.
insert_media(target.media);
// Type whatever has been requested.
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
}
~ConcreteMachine() {
@@ -152,25 +250,6 @@ class ConcreteMachine:
}
}
void configure_as_target(const Analyser::Static::Target *target) override {
auto *const msx_target = dynamic_cast<const Analyser::Static::MSX::Target *>(target);
// Add a disk cartridge if any disks were supplied.
if(msx_target->has_disk_drive) {
map(2, 0, 0x4000, 0x2000);
unmap(2, 0x6000, 0x2000);
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
}
// Insert the media.
insert_media(target->media);
// Type whatever has been requested.
if(!msx_target->loading_command.empty()) {
type_string(msx_target->loading_command);
}
}
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
@@ -467,39 +546,6 @@ class ConcreteMachine:
audio_queue_.perform();
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
auto roms = roms_with_names(
"MSX",
{
"msx.rom",
"disk.rom"
});
if(!roms[0] || !roms[1]) return false;
memory_slots_[0].source = std::move(*roms[0]);
memory_slots_[0].source.resize(32768);
memory_slots_[2].source = std::move(*roms[1]);
memory_slots_[2].source.resize(16384);
for(size_t c = 0; c < 8; ++c) {
for(size_t slot = 0; slot < 3; ++slot) {
memory_slots_[slot].read_pointers[c] = unpopulated_;
memory_slots_[slot].write_pointers[c] = scratch_;
}
memory_slots_[3].read_pointers[c] =
memory_slots_[3].write_pointers[c] = &ram_[c * 8192];
}
map(0, 0, 0, 32768);
page_memory(0);
return true;
}
void set_keyboard_line(int line) {
selected_key_line_ = line;
}
@@ -555,8 +601,8 @@ class ConcreteMachine:
}
// MARK: - Sleeper
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
tape_player_is_sleeping_ = tape_player_.is_sleeping();
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
set_use_fast_tape();
}
@@ -568,6 +614,11 @@ class ConcreteMachine:
}
}
// MARK: - Joysticks
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return ay_port_handler_.get_joysticks();
}
private:
DiskROM *get_disk_rom() {
return dynamic_cast<DiskROM *>(memory_slots_[2].handler.get());
@@ -683,8 +734,10 @@ class ConcreteMachine:
using namespace MSX;
Machine *Machine::MSX() {
return new ConcreteMachine;
Machine *Machine::MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::MSX::Target;
const Target *const msx_target = dynamic_cast<const Target *>(target);
return new ConcreteMachine(*msx_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -10,17 +10,22 @@
#define MSX_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
#include <memory>
#include <vector>
namespace MSX {
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine {
public:
virtual ~Machine();
static Machine *MSX();
static Machine *MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
std::vector<std::unique_ptr<Configurable::Option>> get_options();
}
#endif /* MSX_hpp */

34
Machines/MediaTarget.hpp Normal file
View File

@@ -0,0 +1,34 @@
//
// MediaTarget.h
// Clock Signal
//
// Created by Thomas Harte on 08/09/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#ifndef MediaTarget_hpp
#define MediaTarget_hpp
#include "../Analyser/Static/StaticAnalyser.hpp"
#include "../Configurable/Configurable.hpp"
#include <string>
namespace MediaTarget {
/*!
A MediaTarget::Machine is anything that can accept new media while running.
*/
class Machine {
public:
/*!
Requests that the machine insert @c media as a modification to current state
@returns @c true if any media was inserted; @c false otherwise.
*/
virtual bool insert_media(const Analyser::Static::Media &media) = 0;
};
}
#endif /* MediaTarget_hpp */

View File

@@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 {
bool get_interrupt_request_line();
void run_for(const Cycles cycles);
using WD::WD1770::run_for;
enum PagingFlags {
/// Indicates that the BASIC ROM should be disabled; if this is set then either
@@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 {
private:
void set_control_register(uint8_t control, uint8_t changes);
void set_head_load_request(bool head_load);
void set_head_load_request(bool head_load) override;
bool get_drive_is_ready();
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
size_t selected_drive_;
bool irq_enable_ = false;

View File

@@ -13,7 +13,7 @@
#include "Video.hpp"
#include "../../Activity/Source.hpp"
#include "../ConfigurationTarget.hpp"
#include "../MediaTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
@@ -193,7 +193,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
@@ -201,36 +201,29 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
public Sleeper::SleepObserver,
public ClockingHint::Observer,
public Activity::Source,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Oric::Target *target) :
ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
rom_type_(target ? target->rom : Analyser::Static::Oric::Target::ROM::BASIC10),
ay8910_(audio_queue_),
speaker_(ay8910_),
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
via_(via_port_handler_) {
via_(via_port_handler_),
diskii_(2000000) {
set_clock_rate(1000000);
via_port_handler_.set_interrupt_delegate(this);
tape_player_.set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) {
diskii_.set_sleep_observer(this);
diskii_.set_clocking_hint_observer(this);
}
}
~ConcreteMachine() {
audio_queue_.flush();
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
std::vector<std::string> rom_names = {"colour.rom"};
switch(rom_type_) {
switch(target.rom) {
case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom"); break;
case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom"); break;
case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom"); break;
@@ -238,15 +231,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
switch(disk_interface) {
default: break;
case Analyser::Static::Oric::Target::DiskInterface::Microdisc: rom_names.push_back("microdisc.rom"); break;
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: rom_names.push_back("8dos.rom"); break;
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: rom_names.push_back("8dos.rom"); break;
}
auto roms = roms_with_names("Oric", rom_names);
const auto roms = rom_fetcher("Oric", rom_names);
for(std::size_t index = 0; index < roms.size(); ++index) {
if(!roms[index]) return false;
if(!roms[index]) {
throw ROMMachine::Error::MissingROMs;
}
}
colour_rom_ = std::move(*roms[0]);
rom_ = std::move(*roms[1]);
@@ -260,8 +255,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
pravetz_rom_ = std::move(*roms[2]);
pravetz_rom_.resize(512);
auto state_machine_rom = roms_with_names("DiskII", {"state-machine-16.rom"});
if(!state_machine_rom[0]) return false;
auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"});
if(!state_machine_rom[0]) {
throw ROMMachine::Error::MissingROMs;
}
diskii_.set_state_machine(*state_machine_rom[0]);
} break;
}
@@ -270,9 +267,35 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
rom_.resize(16384);
paged_rom_ = rom_.data();
if(video_output_) video_output_->set_colour_rom(colour_rom_);
switch(target.disk_interface) {
default: break;
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
break;
}
return true;
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
switch(target.rom) {
case Analyser::Static::Oric::Target::ROM::BASIC10:
tape_get_byte_address_ = 0xe630;
tape_speed_address_ = 0x67;
break;
case Analyser::Static::Oric::Target::ROM::BASIC11:
case Analyser::Static::Oric::Target::ROM::Pravetz:
tape_get_byte_address_ = 0xe6c9;
tape_speed_address_ = 0x024d;
break;
}
insert_media(target.media);
}
~ConcreteMachine() {
audio_queue_.flush();
}
void set_key_state(uint16_t key, bool is_pressed) override final {
@@ -291,41 +314,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
use_fast_tape_hack_ = activate;
}
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target);
switch(oric_target->disk_interface) {
default: break;
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
break;
}
if(!oric_target->loading_command.empty()) {
type_string(oric_target->loading_command);
}
switch(rom_type_) {
case Analyser::Static::Oric::Target::ROM::BASIC10:
tape_get_byte_address_ = 0xe630;
tape_speed_address_ = 0x67;
break;
case Analyser::Static::Oric::Target::ROM::BASIC11:
case Analyser::Static::Oric::Target::ROM::Pravetz:
tape_get_byte_address_ = 0xe6c9;
tape_speed_address_ = 0x024d;
break;
}
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool inserted = false;
if(media.tapes.size()) {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
inserted = true;
}
@@ -410,6 +402,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
}
} else {
flush_diskii();
const int disk_value = diskii_.read_address(address);
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value);
}
@@ -444,9 +437,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
microdisc_.run_for(Cycles(8));
break;
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
if(!diskii_is_sleeping_) {
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
diskii_.set_data_input(*value);
diskii_.run_for(Cycles(2));
} else {
cycles_since_diskii_update_ += Cycles(2);
}
break;
}
@@ -457,6 +452,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
forceinline void flush() {
update_video();
via_port_handler_.flush();
flush_diskii();
}
// to satisfy CRTMachine::Machine
@@ -571,18 +567,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
}
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
diskii_is_sleeping_ = diskii_.is_sleeping();
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
diskii_clocking_preference_ = diskii_.preferred_clocking();
}
private:
const uint16_t basic_invisible_ram_top_ = 0xffff;
const uint16_t basic_visible_ram_top_ = 0xbfff;
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
// RAM and ROM
Analyser::Static::Oric::Target::ROM rom_type_;
std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536];
Cycles cycles_since_video_update_;
@@ -617,9 +612,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
// the Pravetz/Disk II, if in use
Apple::DiskII diskii_;
Cycles cycles_since_diskii_update_;
void flush_diskii() {
diskii_.run_for(cycles_since_diskii_update_.flush());
}
std::vector<uint8_t> pravetz_rom_;
std::size_t pravetz_rom_base_pointer_ = 0;
bool diskii_is_sleeping_ = false;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
// Overlay RAM
uint16_t ram_top_ = basic_visible_ram_top_;
@@ -641,13 +640,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
using namespace Oric;
Machine *Machine::Oric(const Analyser::Static::Target *target_hint) {
Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMachine::ROMFetcher &rom_fetcher) {
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint);
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
switch(oric_target->disk_interface) {
default: return new ConcreteMachine<DiskInterface::None>(oric_target);
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(oric_target);
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(oric_target);
default: return new ConcreteMachine<DiskInterface::None>(*oric_target, rom_fetcher);
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
}
}

View File

@@ -11,6 +11,7 @@
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
namespace Oric {
@@ -25,7 +26,7 @@ class Machine {
virtual ~Machine();
/// Creates and returns an Oric.
static Machine *Oric(const Analyser::Static::Target *target_hint);
static Machine *Oric(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}

View File

@@ -42,7 +42,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
crt_->set_visible_area(crt_->get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) {

View File

@@ -16,13 +16,18 @@
namespace ROMMachine {
/*!
Defines the signature for a function that must be supplied by the host environment in order to give machines
a route for fetching any system ROMs they might need.
The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects
to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the
ROM from @c names that corresponds by index, or else are the nullptr
*/
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher;
struct Machine {
/*!
Provides the machine with a way to obtain such ROMs as it needs.
*/
virtual bool set_rom_fetcher(const ROMFetcher &rom_with_name) { return true; }
enum class Error {
MissingROMs
};
}

View File

@@ -25,36 +25,36 @@ namespace {
::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) {
error = Machine::Error::None;
::Machine::DynamicMachine *machine = nullptr;
switch(target->machine) {
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
case Analyser::Machine::AppleII: machine = new Machine::TypedDynamicMachine<AppleII::Machine>(AppleII::Machine::AppleII()); break;
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break;
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); break;
case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric(target)); break;
case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20()); break;
case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target)); break;
default:
error = Machine::Error::UnknownMachine;
return nullptr;
}
Machine::DynamicMachine *machine = nullptr;
try {
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<name::Machine>(name::Machine::m(target, rom_fetcher)); break;
#define Bind(m) BindD(m, m)
switch(target->machine) {
Bind(AmstradCPC)
Bind(AppleII)
Bind(Atari2600)
BindD(Coleco::Vision, ColecoVision)
Bind(Electron)
Bind(MSX)
Bind(Oric)
BindD(Commodore::Vic20, Vic20)
Bind(ZX8081)
// TODO: this shouldn't depend on CRT machine's inclusion of ROM machine.
CRTMachine::Machine *crt_machine = machine->crt_machine();
if(crt_machine) {
if(!machine->crt_machine()->set_rom_fetcher(rom_fetcher)) {
delete machine;
error = Machine::Error::MissingROM;
default:
error = Machine::Error::UnknownMachine;
return nullptr;
}
}
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
if(configuration_target) {
machine->configuration_target()->configure_as_target(target);
#undef Bind
} catch(ROMMachine::Error construction_error) {
switch(construction_error) {
case ROMMachine::Error::MissingROMs:
error = Machine::Error::MissingROM;
break;
default:
error = Machine::Error::UnknownError;
break;
}
}
return machine;
@@ -129,6 +129,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() {
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));

View File

@@ -21,6 +21,7 @@ namespace Machine {
enum class Error {
None,
UnknownError,
UnknownMachine,
MissingROM,
NoTargets

View File

@@ -29,8 +29,8 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
return get<Activity::Source>();
}
ConfigurationTarget::Machine *configuration_target() override {
return get<ConfigurationTarget::Machine>();
MediaTarget::Machine *media_target() override {
return get<MediaTarget::Machine>();
}
CRTMachine::Machine *crt_machine() override {

View File

@@ -16,25 +16,20 @@ namespace {
The number of bytes of PCM data to allocate at once; if/when more are required,
the class will simply allocate another batch.
*/
const std::size_t StandardAllocationSize = 40;
/// The amount of time a byte takes to output.
const std::size_t HalfCyclesPerByte = 8;
const std::size_t StandardAllocationSize = 320;
}
Video::Video() :
crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) {
// Set a composite sampling function that assumes 1bpp input.
// Set a composite sampling function that assumes two-level input; either a byte is 0, which is black,
// or it is non-zero, which is white.
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"texValue <<= int(icoordinate.x) & 7;"
"return float(texValue & 128u);"
"return texture(sampler, coordinate).r;"
"}");
crt_->set_integer_coordinate_multiplier(8.0f);
// Show only the centre 80% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
@@ -43,7 +38,7 @@ Video::Video() :
void Video::run_for(const HalfCycles half_cycles) {
// Just keep a running total of the amount of time that remains owed to the CRT.
cycles_since_update_ += static_cast<unsigned int>(half_cycles.as_int());
time_since_update_ += half_cycles;
}
void Video::flush() {
@@ -53,29 +48,29 @@ void Video::flush() {
void Video::flush(bool next_sync) {
if(sync_) {
// If in sync, that takes priority. Output the proper amount of sync.
crt_->output_sync(cycles_since_update_);
crt_->output_sync(static_cast<unsigned int>(time_since_update_.as_int()));
} else {
// If not presently in sync, then...
if(line_data_) {
// If there is output data queued, output it either if it's being interrupted by
// sync, or if we're past its end anyway. Otherwise let it be.
unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_) * HalfCyclesPerByte;
if(data_length < cycles_since_update_ || next_sync) {
unsigned int output_length = std::min(data_length, cycles_since_update_);
crt_->output_data(output_length, output_length / HalfCyclesPerByte);
int data_length = static_cast<int>(line_data_pointer_ - line_data_);
if(data_length < time_since_update_.as_int() || next_sync) {
auto output_length = std::min(data_length, time_since_update_.as_int());
crt_->output_data(static_cast<unsigned int>(output_length), static_cast<unsigned int>(output_length));
line_data_pointer_ = line_data_ = nullptr;
cycles_since_update_ -= output_length;
time_since_update_ -= HalfCycles(output_length);
} else return;
}
// Any pending pixels being dealt with, pad with the white level.
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = 0xff;
crt_->output_level(cycles_since_update_);
crt_->output_level(static_cast<unsigned int>(time_since_update_.as_int()));
}
cycles_since_update_ = 0;
time_since_update_ = 0;
}
void Video::set_sync(bool sync) {
@@ -101,14 +96,19 @@ void Video::output_byte(uint8_t byte) {
if(line_data_) {
// If the buffer is full, output it now and obtain a new one
if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
crt_->output_data(StandardAllocationSize * HalfCyclesPerByte, StandardAllocationSize);
cycles_since_update_ -= StandardAllocationSize * HalfCyclesPerByte;
crt_->output_data(StandardAllocationSize, StandardAllocationSize);
time_since_update_ -= StandardAllocationSize;
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
if(!line_data_) return;
}
line_data_pointer_[0] = byte;
line_data_pointer_ ++;
// Convert to one-byte-per-pixel where any non-zero value will act as white.
uint8_t mask = 0x80;
for(int c = 0; c < 8; c++) {
line_data_pointer_[c] = byte & mask;
mask >>= 1;
}
line_data_pointer_ += 8;
}
}

View File

@@ -45,7 +45,7 @@ class Video {
bool sync_ = false;
uint8_t *line_data_ = nullptr;
uint8_t *line_data_pointer_ = nullptr;
unsigned int cycles_since_update_ = 0;
HalfCycles time_since_update_ = 0;
std::unique_ptr<Outputs::CRT::CRT> crt_;
void flush(bool next_sync);

View File

@@ -8,7 +8,7 @@
#include "ZX8081.hpp"
#include "../ConfigurationTarget.hpp"
#include "../MediaTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
@@ -59,14 +59,14 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
template<bool is_zx81> class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public Machine {
public:
ConcreteMachine() :
ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this),
tape_player_(ZX8081ClockRate),
ay_(audio_queue_),
@@ -74,6 +74,55 @@ template<bool is_zx81> class ConcreteMachine:
set_clock_rate(ZX8081ClockRate);
speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f);
clear_all_keys();
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" });
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
rom_ = std::move(*roms[0]);
rom_.resize(use_zx81_rom ? 8192 : 4096);
if(is_zx81) {
tape_trap_address_ = 0x37c;
tape_return_address_ = 0x380;
vsync_start_ = HalfCycles(32);
vsync_end_ = HalfCycles(64);
automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3;
} else {
tape_trap_address_ = 0x220;
tape_return_address_ = 0x248;
vsync_start_ = HalfCycles(26);
vsync_end_ = HalfCycles(66);
automatic_tape_motor_start_address_ = 0x0206;
automatic_tape_motor_end_address_ = 0x024d;
}
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
switch(target.memory_model) {
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
break;
}
Memory::Fuzz(ram_);
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
insert_media(target.media);
}
~ConcreteMachine() {
@@ -105,7 +154,7 @@ template<bool is_zx81> class ConcreteMachine:
video_->run_for(cycle.length);
}
if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207));
if(is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207));
if(!tape_advance_delay_) {
tape_player_.run_for(cycle.length);
} else {
@@ -129,7 +178,7 @@ template<bool is_zx81> class ConcreteMachine:
set_vsync(false);
}
if(!(address & 2)) nmi_is_enabled_ = false;
if(!(address & 1)) nmi_is_enabled_ = is_zx81_;
if(!(address & 1)) nmi_is_enabled_ = is_zx81;
// The below emulates the ZonX AY expansion device.
if(is_zx81) {
@@ -157,7 +206,7 @@ template<bool is_zx81> class ConcreteMachine:
// The below emulates the ZonX AY expansion device.
if(is_zx81) {
if((address&0xef) == 0x0f) {
if((address&0xef) == 0xcf) {
value &= ay_read_data();
}
}
@@ -281,54 +330,6 @@ template<bool is_zx81> class ConcreteMachine:
z80_.run_for(cycles);
}
void configure_as_target(const Analyser::Static::Target *target) override final {
auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
is_zx81_ = zx8081_target->is_ZX81;
if(is_zx81_) {
rom_ = zx81_rom_;
tape_trap_address_ = 0x37c;
tape_return_address_ = 0x380;
vsync_start_ = HalfCycles(32);
vsync_end_ = HalfCycles(64);
automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3;
} else {
rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_;
tape_trap_address_ = 0x220;
tape_return_address_ = 0x248;
vsync_start_ = HalfCycles(26);
vsync_end_ = HalfCycles(66);
automatic_tape_motor_start_address_ = 0x0206;
automatic_tape_motor_end_address_ = 0x024d;
}
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
switch(zx8081_target->memory_model) {
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
break;
}
Memory::Fuzz(ram_);
if(!zx8081_target->loading_command.empty()) {
type_string(zx8081_target->loading_command);
}
insert_media(target->media);
}
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
@@ -339,30 +340,10 @@ template<bool is_zx81> class ConcreteMachine:
}
void type_string(const std::string &string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_));
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81));
Utility::TypeRecipient::add_typer(string, std::move(mapper));
}
// Obtains the system ROMs.
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
const auto roms = roms_with_names(
"ZX8081",
{
"zx80.rom", "zx81.rom",
});
for(std::size_t index = 0; index < roms.size(); ++index) {
if(!roms[index]) return false;
}
zx80_rom_ = std::move(*roms[0]);
zx81_rom_ = std::move(*roms[1]);
zx80_rom_.resize(4096);
zx81_rom_.resize(8192);
return true;
}
// MARK: - Keyboard
void set_key_state(uint16_t key, bool is_pressed) override final {
if(is_pressed)
@@ -436,7 +417,6 @@ template<bool is_zx81> class ConcreteMachine:
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
std::unique_ptr<Video> video_;
std::vector<uint8_t> zx81_rom_, zx80_rom_;
uint16_t tape_trap_address_, tape_return_address_;
uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_;
@@ -456,7 +436,6 @@ template<bool is_zx81> class ConcreteMachine:
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
Storage::Tape::ZX8081::Parser parser_;
bool is_zx81_;
bool nmi_is_enabled_ = false;
HalfCycles vsync_start_, vsync_end_;
@@ -522,14 +501,12 @@ template<bool is_zx81> class ConcreteMachine:
using namespace ZX8081;
// See header; constructs and returns an instance of the ZX80 or 81.
Machine *Machine::ZX8081(const Analyser::Static::Target *target_hint) {
const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint);
Machine *Machine::ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
const Analyser::Static::ZX8081::Target *const zx_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
// Instantiate the correct type of machine.
if(hint->is_ZX81)
return new ZX8081::ConcreteMachine<true>();
else
return new ZX8081::ConcreteMachine<false>();
if(zx_target->is_ZX81) return new ZX8081::ConcreteMachine<true>(*zx_target, rom_fetcher);
else return new ZX8081::ConcreteMachine<false>(*zx_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -11,6 +11,7 @@
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
namespace ZX8081 {
@@ -21,7 +22,7 @@ class Machine {
public:
virtual ~Machine();
static Machine *ZX8081(const Analyser::Static::Target *target_hint);
static Machine *ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
virtual void set_tape_is_playing(bool is_playing) = 0;
virtual bool get_tape_is_playing() = 0;

View File

@@ -95,14 +95,14 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
those used by the FM and MFM disk encodings.
*/
struct CCITT: public Generator<uint16_t, 0xffff, 0x0000, false, false> {
CCITT() : Generator(0x1021) {}
CCITT(): Generator(0x1021) {}
};
/*!
Provides a generator of "standard 32-bit" CRCs.
*/
struct CRC32: public Generator<uint32_t, 0xffffffff, 0xffffffff, true, true> {
CRC32() : Generator(0x04c11db7) {}
CRC32(): Generator(0x04c11db7) {}
};
}

View File

@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
@@ -43,7 +44,6 @@
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; };
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; };
4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
@@ -115,6 +115,7 @@
4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; };
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B08A56720D72BEF0016CE5A /* Activity.xib */; };
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
@@ -124,7 +125,6 @@
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; };
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; };
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
@@ -185,7 +185,6 @@
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; };
4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; };
4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; };
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; };
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
@@ -214,6 +213,8 @@
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; };
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */; };
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B55DD8120DF06680043F2E5 /* MachinePicker.xib */; };
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; };
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
@@ -309,8 +310,6 @@
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */; };
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */; };
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
@@ -597,8 +596,8 @@
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; };
4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; };
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; };
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; };
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
@@ -607,14 +606,18 @@
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; };
4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; };
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; };
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
@@ -644,6 +647,7 @@
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
@@ -687,6 +691,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; };
@@ -704,6 +709,7 @@
4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
4B08A2791EE3957B008B7065 /* TestMachine+ForSubclassEyesOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestMachine+ForSubclassEyesOnly.h"; sourceTree = "<group>"; };
4B08A56820D72BEF0016CE5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Activity.xib"; sourceTree = SOURCE_ROOT; };
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
@@ -716,7 +722,6 @@
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = "<group>"; };
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; };
4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; };
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; };
@@ -823,8 +828,6 @@
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
4B4518731F75E91800926311 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; };
4B4518741F75E91800926311 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; };
4B4518751F75E91800926311 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
@@ -883,6 +886,8 @@
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; };
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; };
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
@@ -1002,6 +1007,7 @@
4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; };
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = "<group>"; };
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; };
@@ -1024,12 +1030,10 @@
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; };
4BA141BC2072E8A400A31EC9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = "<group>"; };
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; };
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; };
4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; };
@@ -1037,7 +1041,7 @@
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; };
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; };
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; };
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
@@ -1326,8 +1330,8 @@
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; };
4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiConfigurationTarget.hpp; sourceTree = "<group>"; };
4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiConfigurationTarget.cpp; sourceTree = "<group>"; };
4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; };
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; };
4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; };
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; };
@@ -1345,6 +1349,8 @@
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; };
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; };
4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; };
@@ -1353,10 +1359,13 @@
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; };
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Rectangle.cpp; sourceTree = "<group>"; };
4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Rectangle.hpp; sourceTree = "<group>"; };
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; };
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; };
4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; };
@@ -1377,6 +1386,7 @@
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; };
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
@@ -1425,6 +1435,7 @@
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
4BEE0A6E1D72496600532C7B /* PRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PRG.hpp; sourceTree = "<group>"; };
4BEEE6BC20DC72EA003723BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/CompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
@@ -1537,14 +1548,15 @@
4B1414631B588A1100E04248 /* Test Binaries */ = {
isa = PBXGroup;
children = (
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */,
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */,
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */,
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
4BE9A6B21EDE294200CBCB47 /* Zexall */,
4BBF49B41ED2881600AB3669 /* FUSE */,
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */,
4BE9A6B21EDE294200CBCB47 /* Zexall */,
);
name = "Test Binaries";
sourceTree = "<group>";
@@ -1770,6 +1782,7 @@
children = (
4B0CCC411C62D0B3001CAC5F /* CRT */,
4BD060A41FE49D3C006E14BE /* Speaker */,
4BD601A920D89F2A00CBCE57 /* Log.hpp */,
);
name = Outputs;
sourceTree = "<group>";
@@ -1841,12 +1854,10 @@
4B4518701F75E91800926311 /* Track */ = {
isa = PBXGroup;
children = (
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */,
4B4518731F75E91800926311 /* PCMSegment.cpp */,
4B4518751F75E91800926311 /* PCMTrack.cpp */,
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */,
4B4518771F75E91800926311 /* UnformattedTrack.cpp */,
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */,
4B4518741F75E91800926311 /* PCMSegment.hpp */,
4B4518761F75E91800926311 /* PCMTrack.hpp */,
4B4518881F75ECB100926311 /* Track.hpp */,
@@ -1986,7 +1997,10 @@
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
4B08A56720D72BEF0016CE5A /* Activity.xib */,
4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */,
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
@@ -2005,6 +2019,15 @@
path = Views;
sourceTree = "<group>";
};
4B55DD7F20DF06680043F2E5 /* MachinePicker */ = {
isa = PBXGroup;
children = (
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */,
4B55DD8120DF06680043F2E5 /* MachinePicker.xib */,
);
path = MachinePicker;
sourceTree = "<group>";
};
4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = {
isa = PBXGroup;
children = (
@@ -2384,16 +2407,6 @@
path = Implementation;
sourceTree = "<group>";
};
4BA141C02072E8B300A31EC9 /* MachinePicker */ = {
isa = PBXGroup;
children = (
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */,
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */,
);
name = MachinePicker;
path = "New Group";
sourceTree = "<group>";
};
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
isa = PBXGroup;
children = (
@@ -2754,6 +2767,7 @@
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
isa = PBXGroup;
children = (
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
4BB73EAD1B587A5100552FC2 /* Info.plist */,
@@ -2763,7 +2777,7 @@
4B643F3D1D77B88000D431D6 /* Document Controller */,
4B55CE551C3B7D360093A61B /* Documents */,
4B2A53921D117D36003C6002 /* Machine */,
4BA141C02072E8B300A31EC9 /* MachinePicker */,
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BD5F1961D1352A000631CD1 /* Updater */,
@@ -2779,7 +2793,6 @@
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
@@ -2819,7 +2832,7 @@
isa = PBXGroup;
children = (
4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */,
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */,
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */,
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */,
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */,
4B7041271F92C26900735E45 /* JoystickMachine.hpp */,
@@ -2857,16 +2870,16 @@
isa = PBXGroup;
children = (
4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */,
4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */,
4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */,
4B1B88C6202E469300B67DFF /* MultiJoystickMachine.cpp */,
4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */,
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */,
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */,
4B1B88BF202E3DB200B67DFF /* MultiConfigurable.hpp */,
4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */,
4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */,
4B1B88C7202E469300B67DFF /* MultiJoystickMachine.hpp */,
4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */,
4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */,
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */,
);
path = Implementation;
@@ -2895,6 +2908,7 @@
children = (
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */,
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */,
4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */,
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */,
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */,
@@ -2902,6 +2916,7 @@
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */,
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */,
4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */,
4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */,
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */,
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */,
4BC3B74C1CD194CC00F86E85 /* Shaders */,
@@ -2909,6 +2924,15 @@
path = Internals;
sourceTree = "<group>";
};
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */ = {
isa = PBXGroup;
children = (
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */,
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */,
);
path = "Joystick Manager";
sourceTree = "<group>";
};
4BC3B74C1CD194CC00F86E85 /* Shaders */ = {
isa = PBXGroup;
children = (
@@ -3124,8 +3148,9 @@
children = (
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
@@ -3277,9 +3302,11 @@
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */,
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */,
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */,
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */,
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */,
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
@@ -3288,6 +3315,7 @@
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */,
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -3307,6 +3335,7 @@
4BB2998A1B587D8400A49093 /* lseix in Resources */,
4BB2994E1B587D8400A49093 /* dexn in Resources */,
4BB299971B587D8400A49093 /* nopa in Resources */,
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */,
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */,
4BB299521B587D8400A49093 /* eoray in Resources */,
4BB299411B587D8400A49093 /* cpyb in Resources */,
@@ -3610,6 +3639,7 @@
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */,
4B894539201967B4007DE474 /* Tape.cpp in Sources */,
4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */,
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
@@ -3626,7 +3656,6 @@
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */,
4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */,
4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */,
4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */,
4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */,
4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */,
4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
@@ -3688,7 +3717,7 @@
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */,
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */,
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */,
4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
@@ -3799,7 +3828,6 @@
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
4B894532201967B4007DE474 /* 6502.cpp in Sources */,
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */,
4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */,
4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */,
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
@@ -3815,6 +3843,7 @@
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */,
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */,
4B89451C201967B4007DE474 /* Disk.cpp in Sources */,
4B302184208A550100773308 /* DiskII.cpp in Sources */,
@@ -3834,11 +3863,11 @@
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */,
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */,
4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
@@ -3855,7 +3884,7 @@
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
@@ -3900,6 +3929,7 @@
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */,
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
@@ -3951,7 +3981,6 @@
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */,
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */,
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */,
4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -3980,6 +4009,14 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
4B08A56720D72BEF0016CE5A /* Activity.xib */ = {
isa = PBXVariantGroup;
children = (
4B08A56820D72BEF0016CE5A /* Base */,
);
name = Activity.xib;
sourceTree = "<group>";
};
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -3996,6 +4033,14 @@
name = OricOptions.xib;
sourceTree = "<group>";
};
4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = {
isa = PBXVariantGroup;
children = (
4B55DD8220DF06680043F2E5 /* Base */,
);
name = MachinePicker.xib;
sourceTree = "<group>";
};
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -4020,14 +4065,6 @@
name = QuickLoadCompositeOptions.xib;
sourceTree = "<group>";
};
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = {
isa = PBXVariantGroup;
children = (
4BA141BC2072E8A400A31EC9 /* Base */,
);
name = MachinePicker.xib;
sourceTree = "<group>";
};
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -4036,6 +4073,14 @@
name = MainMenu.xib;
sourceTree = "<group>";
};
4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4BC5FC2F20CDDDEE00410AA0 /* Base */,
);
name = AppleIIOptions.xib;
sourceTree = "<group>";
};
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -4044,6 +4089,14 @@
name = QuickLoadOptions.xib;
sourceTree = "<group>";
};
4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4BEEE6BC20DC72EA003723BF /* Base */,
);
name = CompositeOptions.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="activityPanel" destination="ZW7-Bw-4RP" id="GRG-Q6-RQU"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Activity" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="300" y="76" width="200" height="131"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="131"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ySY-ir-hzb" userLabel="First indicator">
<rect key="frame" x="20" y="95" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="ySY-ir-hzb" secondAttribute="height" multiplier="1:1" id="UX0-hT-7Td"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="DhQ-Di-tRT"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tah-UQ-vdf">
<rect key="frame" x="44" y="94" width="59" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 1" id="a5P-Ci-RzC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncQ-wN-C61" userLabel="Second indicator">
<rect key="frame" x="20" y="70" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="ncQ-wN-C61" secondAttribute="height" multiplier="1:1" id="176-v3-mVW"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="jlb-bk-FPd"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="14O-Lq-Npx">
<rect key="frame" x="44" y="69" width="61" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 2" id="NE1-CO-pGI">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0rV-Th-Zwt" userLabel="Third indicator">
<rect key="frame" x="20" y="45" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="0rV-Th-Zwt" secondAttribute="height" multiplier="1:1" id="Ai8-b3-Nn5"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="CJy-Jn-eCL"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acy-tT-OFH">
<rect key="frame" x="44" y="44" width="61" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 3" id="FSR-y6-7WE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bvH-EJ-TYb" userLabel="Fourth indicator">
<rect key="frame" x="20" y="20" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="bvH-EJ-TYb" secondAttribute="height" multiplier="1:1" id="cKc-q1-2Q4"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="eoN-hl-30l"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R0g-Oa-VB5">
<rect key="frame" x="44" y="19" width="62" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 4" id="aGr-cd-jC0">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="14O-Lq-Npx" firstAttribute="centerY" secondItem="ncQ-wN-C61" secondAttribute="centerY" id="0Ht-U2-sPg"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="top" secondItem="0rV-Th-Zwt" secondAttribute="bottom" constant="9" id="0xw-qA-6vP"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="14O-Lq-Npx" secondAttribute="trailing" constant="20" id="5eo-XI-a3W"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="centerY" secondItem="ySY-ir-hzb" secondAttribute="centerY" id="6Hn-ts-mTi"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="leading" secondItem="bvH-EJ-TYb" secondAttribute="trailing" constant="10" id="Dgy-JI-nA1"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="centerY" secondItem="bvH-EJ-TYb" secondAttribute="centerY" id="Gfq-mB-Y1z"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="centerY" secondItem="0rV-Th-Zwt" secondAttribute="centerY" id="ImF-rK-oOr"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="leading" secondItem="0rV-Th-Zwt" secondAttribute="trailing" constant="10" id="JSU-pZ-l9Q"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="KMh-EO-rxE"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="top" secondItem="ncQ-wN-C61" secondAttribute="bottom" constant="9" id="Q2g-yM-nlJ"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="QUI-Hc-Bcl"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="bKh-4L-mqj"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="cPA-Ls-fLj"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Tah-UQ-vdf" secondAttribute="trailing" constant="20" id="igX-7U-TeE"/>
<constraint firstItem="14O-Lq-Npx" firstAttribute="leading" secondItem="ncQ-wN-C61" secondAttribute="trailing" constant="10" id="jjP-qH-Pqg"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="leading" secondItem="ySY-ir-hzb" secondAttribute="trailing" constant="10" id="lux-Nz-K7E"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Acy-tT-OFH" secondAttribute="trailing" constant="20" id="mEe-VT-dNr"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="top" secondItem="ySY-ir-hzb" secondAttribute="bottom" constant="9" id="mSc-jj-amw"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="R0g-Oa-VB5" secondAttribute="trailing" constant="20" id="sR8-Ph-suC"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="wbj-48-DYq"/>
</constraints>
</view>
<point key="canvasLocation" x="84" y="115"/>
</window>
</objects>
</document>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="80" y="150" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Accelerate DOS 3.3" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
</constraints>
</view>
<connections>
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
</connections>
<point key="canvasLocation" x="175" y="30"/>
</window>
</objects>
</document>

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
@@ -14,8 +16,8 @@
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="121"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<rect key="contentRect" x="80" y="150" width="200" height="121"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="aQh-Pm-DEo">
<rect key="frame" x="0.0" y="0.0" width="200" height="121"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="80" y="150" width="200" height="61"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n">
<rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" title="RGB Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="L06-TO-EF0">
<items>
<menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/>
<menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/>
<menuItem title="Television" tag="1" id="fFm-fS-rWG"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setDisplayType:" target="ZW7-Bw-4RP" id="PAH-CZ-zlk"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="rh8-km-57n" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="B6L-VS-2cN"/>
<constraint firstItem="rh8-km-57n" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="VRo-6R-IKd"/>
<constraint firstAttribute="bottom" secondItem="rh8-km-57n" secondAttribute="bottom" constant="20" id="jHA-lf-e7V"/>
<constraint firstAttribute="trailing" secondItem="rh8-km-57n" secondAttribute="trailing" constant="20" id="urO-Ac-aqK"/>
</constraints>
</view>
<connections>
<outlet property="displayTypeButton" destination="rh8-km-57n" id="FB2-Zg-VKq"/>
</connections>
<point key="canvasLocation" x="175" y="33.5"/>
</window>
</objects>
</document>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -18,8 +18,8 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="133" y="235" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<rect key="contentRect" x="80" y="250" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<value key="minSize" type="size" width="228" height="171"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -111,6 +111,17 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Save Screenshot" keyEquivalent="D" id="BVJ-oQ-hUp">
<connections>
<action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/>
</connections>
</menuItem>
<menuItem title="Insert..." keyEquivalent="O" id="qQa-kh-4nz">
<connections>
<action selector="insertMedia:" target="-1" id="9Hs-9J-dlY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="rXU-KX-GkZ"/>
<menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
@@ -129,22 +140,6 @@
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
@@ -155,192 +150,6 @@
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" enabled="NO" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" enabled="NO" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
@@ -348,10 +157,33 @@
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Options" keyEquivalent="o" id="WCd-6R-baV">
<menuItem title="Show Activity" keyEquivalent="a" id="WCd-6R-baV">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="showOptions:" target="-1" id="ge3-Qg-kb5"/>
<action selector="showActivity:" target="-1" id="oeF-uJ-cOS"/>
</connections>
</menuItem>
<menuItem title="Show Options" keyEquivalent="o" id="GtG-CV-Uro">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="showOptions:" target="-1" id="M6T-DE-Duo"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Input" id="5bL-VY-cxd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Input" id="6yv-Cf-E9r">
<items>
<menuItem title="Use Keyboard as Keyboard" state="on" tag="100" keyEquivalent="k" id="TfX-0B-j4U">
<connections>
<action selector="useKeyboardAsKeyboard:" target="-1" id="6fl-fS-Oe9"/>
</connections>
</menuItem>
<menuItem title="Use Keyboard as Joystick" tag="101" enabled="NO" keyEquivalent="j" id="5mn-ch-Xv6">
<connections>
<action selector="useKeyboardAsJoystick:" target="-1" id="Yz7-CL-f0y"/>
</connections>
</menuItem>
</items>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -16,8 +16,8 @@
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<rect key="contentRect" x="80" y="150" width="200" height="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -16,8 +16,8 @@
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<rect key="contentRect" x="80" y="150" width="200" height="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -16,8 +16,8 @@
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<rect key="contentRect" x="80" y="150" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -14,9 +15,9 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="261" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" topStrut="YES"/>
<rect key="contentRect" x="80" y="150" width="261" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="7Pv-WL-2Rq">
<rect key="frame" x="0.0" y="0.0" width="261" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@@ -4,6 +4,12 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.pictures.read-write</key>
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>

View File

@@ -12,6 +12,8 @@
#import "CSOpenGLView.h"
#import "CSAudioQueue.h"
#import "CSBestEffortUpdater.h"
#import "CSJoystickManager.h"
#include "KeyCodes.h"

View File

@@ -9,4 +9,5 @@
import Cocoa
class DocumentController: NSDocumentController {
let joystickManager = CSJoystickManager()
}

View File

@@ -6,8 +6,8 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
import Cocoa
import AudioToolbox
import Cocoa
class MachineDocument:
NSDocument,
@@ -40,6 +40,11 @@ class MachineDocument:
optionsPanel?.setIsVisible(true)
}
@IBOutlet var activityPanel: NSPanel!
@IBAction func showActivity(_ sender: AnyObject!) {
activityPanel.setIsVisible(true)
}
fileprivate var audioQueue: CSAudioQueue! = nil
fileprivate var bestEffortUpdater: CSBestEffortUpdater?
@@ -51,7 +56,6 @@ class MachineDocument:
super.windowControllerDidLoadNib(aController)
aController.window?.contentAspectRatio = self.aspectRatio()
setupMachineOutput()
}
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
@@ -102,7 +106,7 @@ class MachineDocument:
}
}
func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) {
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
setupAudioQueueClockRate()
}
@@ -119,6 +123,9 @@ class MachineDocument:
}
override func close() {
activityPanel?.setIsVisible(false)
activityPanel = nil
optionsPanel?.setIsVisible(false)
optionsPanel = nil
@@ -147,6 +154,7 @@ class MachineDocument:
self.machine = machine
self.optionsPanelNibName = analysis.optionsPanelNibName
setupMachineOutput()
setupActivityDisplay()
}
}
@@ -204,6 +212,7 @@ class MachineDocument:
}
}
// MARK: Runtime media insertion.
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
let mediaSet = CSMediaSet(fileAt: URL)
if let mediaSet = mediaSet {
@@ -211,6 +220,21 @@ class MachineDocument:
}
}
@IBAction final func insertMedia(_ sender: AnyObject!) {
let openPanel = NSOpenPanel()
openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window."
openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in
if response == .OK {
for url in openPanel.urls {
let mediaSet = CSMediaSet(fileAt: url)
if let mediaSet = mediaSet {
mediaSet.apply(to: self.machine)
}
}
}
}
}
// MARK: NSDocument overrides
override func data(ofType typeName: String) throws -> Data {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
@@ -220,6 +244,13 @@ class MachineDocument:
func windowDidResignKey(_ notification: Notification) {
if let machine = self.machine {
machine.clearAllKeys()
machine.joystickManager = nil
}
}
func windowDidBecomeKey(_ notification: Notification) {
if let machine = self.machine {
machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager
}
}
@@ -250,10 +281,165 @@ class MachineDocument:
@IBAction func createMachine(_ sender: NSButton?) {
self.configureAs(machinePicker!.selectedMachine())
machinePicker = nil
sender?.window?.close()
self.windowControllers[0].window?.endSheet(self.machinePickerPanel!)
}
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
close()
}
// MARK: Joystick-via-the-keyboard selection
@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) {
machine.inputMode = .keyboard
}
@IBAction func useKeyboardAsJoystick(_ sender: NSMenuItem?) {
machine.inputMode = .joystick
}
override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
if let menuItem = item as? NSMenuItem {
switch item.action {
case #selector(self.useKeyboardAsKeyboard):
if machine == nil || !machine.hasKeyboard {
menuItem.state = .off
return false
}
menuItem.state = machine.inputMode == .keyboard ? .on : .off
return true
case #selector(self.useKeyboardAsJoystick):
if machine == nil || !machine.hasJoystick {
menuItem.state = .off
return false
}
menuItem.state = machine.inputMode == .joystick ? .on : .off
return true
case #selector(self.showActivity(_:)):
return self.activityPanel != nil
case #selector(self.insertMedia(_:)):
return self.machine != nil && self.machine.canInsertMedia
default: break
}
}
return super.validateUserInterfaceItem(item)
}
// Screenshot capture.
@IBAction func saveScreenshot(_ sender: AnyObject!) {
// Grab a date formatter and form a file name.
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .long
let filename = ("Clock Signal Screen Shot " + dateFormatter.string(from: Date()) + ".png").replacingOccurrences(of: "/", with: "-")
.replacingOccurrences(of: ":", with: ".")
let pictursURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
let url = pictursURL.appendingPathComponent(filename)
// Obtain the machine's current display.
var imageRepresentation: NSBitmapImageRep? = nil
self.openGLView.perform {
imageRepresentation = self.machine.imageRepresentation
}
// Encode as a PNG and save.
let pngData = imageRepresentation!.representation(using: .png, properties: [:])
try! pngData?.write(to: url)
}
// MARK: Activity display.
class LED {
let levelIndicator: NSLevelIndicator
init(levelIndicator: NSLevelIndicator) {
self.levelIndicator = levelIndicator
}
var isLit = false
var isBlinking = false
}
fileprivate var leds: [String: LED] = [:]
func setupActivityDisplay() {
var leds = machine.leds
if leds.count > 0 {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil)
showActivity(nil)
// Inspect the activity panel for indicators.
var activityIndicators: [NSLevelIndicator] = []
var textFields: [NSTextField] = []
if let contentView = self.activityPanel.contentView {
for view in contentView.subviews {
if let levelIndicator = view as? NSLevelIndicator {
activityIndicators.append(levelIndicator)
}
if let textField = view as? NSTextField {
textFields.append(textField)
}
}
}
// If there are fewer level indicators than LEDs, trim that list.
if activityIndicators.count < leds.count {
leds.removeSubrange(activityIndicators.count ..< leds.count)
}
// Remove unused views.
for c in leds.count ..< activityIndicators.count {
textFields[c].removeFromSuperview()
activityIndicators[c].removeFromSuperview()
}
// Apply labels and create leds entries.
for c in 0 ..< leds.count {
textFields[c].stringValue = leds[c]
self.leds[leds[c]] = LED(levelIndicator: activityIndicators[c])
}
// Add a constraints to minimise window height.
let heightConstraint = NSLayoutConstraint(
item: self.activityPanel.contentView!,
attribute: .bottom,
relatedBy: .equal,
toItem: activityIndicators[leds.count-1],
attribute: .bottom,
multiplier: 1.0,
constant: 20.0)
self.activityPanel.contentView?.addConstraint(heightConstraint)
}
}
func machine(_ machine: CSMachine, ledShouldBlink ledName: String) {
// If there is such an LED, switch it off for 0.03 of a second; if it's meant
// to be off at the end of that, leave it off. Don't allow the blinks to
// pile up allow there to be only one in flight at a time.
if let led = leds[ledName] {
DispatchQueue.main.async {
if !led.isBlinking {
led.levelIndicator.floatValue = 0.0
led.isBlinking = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) {
led.levelIndicator.floatValue = led.isLit ? 1.0 : 0.0
led.isBlinking = false
}
}
}
}
}
func machine(_ machine: CSMachine, led ledName: String, didChangeToLit isLit: Bool) {
// If there is such an LED, switch it appropriately.
if let led = leds[ledName] {
DispatchQueue.main.async {
led.levelIndicator.floatValue = isLit ? 1.0 : 0.0
led.isLit = isLit
}
}
}
}

View File

@@ -13,39 +13,43 @@
<string>bin</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge</string>
<string>cartridge.png</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeName</key>
<string>Atari 2600 Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>rom</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>chip</string>
<string>chip.png</string>
<key>CFBundleTypeName</key>
<string>ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -53,100 +57,110 @@
<string>uef</string>
<string>uef.gz</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC UEF Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>prg</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Commodore Program</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tap</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>g64</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Commodore Disk</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>d64</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Commodore 1540/1 Disk</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -157,40 +171,44 @@
<string>adl</string>
<string>adm</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dsk</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -198,20 +216,22 @@
<string>o</string>
<string>80</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>ZX80 Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -220,178 +240,196 @@
<string>81</string>
<string>p81</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>ZX81 Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>csw</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tzx</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cdt</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Amstrad CPC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>hfe</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>HxC Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cas</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>MSX Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dmk</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tsx</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>MSX Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>col</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge</string>
<string>cartridge.png</string>
<key>CFBundleTypeName</key>
<string>ColecoVision Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -401,20 +439,22 @@
<string>do</string>
<string>po</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Apple II Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>CFBundleExecutable</key>
@@ -436,7 +476,7 @@
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string></string>
<string>public.app-category.entertainment</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>

View File

@@ -0,0 +1,80 @@
//
// CSJoystickManager.h
// Clock Signal
//
// Created by Thomas Harte on 19/07/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
Models a single joystick button.
Buttons have an index and are either currently pressed, or not.
*/
@interface CSJoystickButton: NSObject
/// The button index. By convention the USB spec defines the first button as number 1.
@property(nonatomic, readonly) NSInteger index;
@property(nonatomic, readonly) bool isPressed;
@end
typedef NS_ENUM(NSInteger, CSJoystickAxisType) {
CSJoystickAxisTypeX,
CSJoystickAxisTypeY,
CSJoystickAxisTypeZ,
};
/*!
Models a joystick axis.
Axes have a nominated type and a continuous value between 0 and 1.
*/
@interface CSJoystickAxis: NSObject
@property(nonatomic, readonly) CSJoystickAxisType type;
/// The current position of this axis in the range [0, 1].
@property(nonatomic, readonly) float position;
@end
typedef NS_OPTIONS(NSInteger, CSJoystickHatDirection) {
CSJoystickHatDirectionUp = 1 << 0,
CSJoystickHatDirectionDown = 1 << 1,
CSJoystickHatDirectionLeft = 1 << 2,
CSJoystickHatDirectionRight = 1 << 3,
};
/*!
Models a joystick hat.
A hat is a digital directional input, so e.g. this is how thumbpads are represented.
*/
@interface CSJoystickHat: NSObject
@property(nonatomic, readonly) CSJoystickHatDirection direction;
@end
/*!
Models a joystick.
A joystick is a collection of buttons, axes and hats, each of which holds a current
state. The holder must use @c update to cause this joystick to read a fresh copy
of its state.
*/
@interface CSJoystick: NSObject
@property(nonatomic, readonly) NSArray<CSJoystickButton *> *buttons;
@property(nonatomic, readonly) NSArray<CSJoystickAxis *> *axes;
@property(nonatomic, readonly) NSArray<CSJoystickHat *> *hats;
- (void)update;
@end
/*!
The joystick manager watches for joystick connections and disconnections and
offers a list of joysticks currently attached.
Be warned: this means using Apple's IOKit directly to watch for Bluetooth and
USB HID devices. So to use this code, make sure you have USB and Bluetooth
enabled for the app's sandbox.
*/
@interface CSJoystickManager : NSObject
@property(nonatomic, readonly) NSArray<CSJoystick *> *joysticks;
/// Updates all joysticks.
- (void)update;
@end

View File

@@ -0,0 +1,333 @@
//
// CSJoystickManager.m
// Clock Signal
//
// Created by Thomas Harte on 19/07/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#import "CSJoystickManager.h"
@import IOKit;
#include <IOKit/hid/IOHIDLib.h>
#pragma mark - CSJoystickButton
@implementation CSJoystickButton {
IOHIDElementRef _element;
}
- (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index {
self = [super init];
if(self) {
_index = index;
_element = (IOHIDElementRef)CFRetain(element);
}
return self;
}
- (void)dealloc {
CFRelease(_element);
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
}
- (IOHIDElementRef)element {
return _element;
}
- (void)setIsPressed:(bool)isPressed {
_isPressed = isPressed;
}
@end
#pragma mark - CSJoystickAxis
@implementation CSJoystickAxis {
IOHIDElementRef _element;
}
- (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type {
self = [super init];
if(self) {
_element = (IOHIDElementRef)CFRetain(element);
_type = type;
_position = 0.5f;
}
return self;
}
- (void)dealloc {
CFRelease(_element);
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
}
- (IOHIDElementRef)element {
return _element;
}
- (void)setPosition:(float)position {
_position = position;
}
@end
#pragma mark - CSJoystickHat
@implementation CSJoystickHat {
IOHIDElementRef _element;
}
- (instancetype)initWithElement:(IOHIDElementRef)element {
self = [super init];
if(self) {
_element = (IOHIDElementRef)CFRetain(element);
}
return self;
}
- (void)dealloc {
CFRelease(_element);
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction];
}
- (IOHIDElementRef)element {
return _element;
}
- (void)setDirection:(CSJoystickHatDirection)direction {
_direction = direction;
}
@end
#pragma mark - CSJoystick
@implementation CSJoystick {
IOHIDDeviceRef _device;
}
- (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons
axes:(NSArray<CSJoystickAxis *> *)axes
hats:(NSArray<CSJoystickHat *> *)hats
device:(IOHIDDeviceRef)device {
self = [super init];
if(self) {
// Sort buttons by index.
_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]];
// Sort axes by enum value.
_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]];
// Hats have no guaranteed ordering.
_hats = hats;
// Keep hold of the device.
_device = (IOHIDDeviceRef)CFRetain(device);
}
return self;
}
- (void)dealloc {
CFRelease(_device);
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
}
- (void)update {
// Update buttons.
for(CSJoystickButton *button in _buttons) {
IOHIDValueRef value;
if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) {
// Some pressure-sensitive buttons return values greater than 1 for hard presses,
// but this class rationalised everything to Boolean.
button.isPressed = !!IOHIDValueGetIntegerValue(value);
}
}
// Update hats.
for(CSJoystickHat *hat in _hats) {
IOHIDValueRef value;
if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) {
// Hats report a direction, which is either one of eight or one of four.
CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(hat.element);
const CFIndex range = 1 + IOHIDElementGetLogicalMax(hat.element) - IOHIDElementGetLogicalMin(hat.element);
integerValue *= 8 / range;
// Map from the HID direction to the bit field.
switch(integerValue) {
default: hat.direction = 0; break;
case 0: hat.direction = CSJoystickHatDirectionUp; break;
case 1: hat.direction = CSJoystickHatDirectionUp | CSJoystickHatDirectionRight; break;
case 2: hat.direction = CSJoystickHatDirectionRight; break;
case 3: hat.direction = CSJoystickHatDirectionRight | CSJoystickHatDirectionDown; break;
case 4: hat.direction = CSJoystickHatDirectionDown; break;
case 5: hat.direction = CSJoystickHatDirectionDown | CSJoystickHatDirectionLeft; break;
case 6: hat.direction = CSJoystickHatDirectionLeft; break;
case 7: hat.direction = CSJoystickHatDirectionLeft | CSJoystickHatDirectionUp; break;
}
}
}
// Update axes.
for(CSJoystickAxis *axis in _axes) {
IOHIDValueRef value;
if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) {
const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element);
const CFIndex range = 1 + IOHIDElementGetLogicalMax(axis.element) - IOHIDElementGetLogicalMin(axis.element);
axis.position = (float)integerValue / (float)range;
}
}
}
- (IOHIDDeviceRef)device {
return _device;
}
@end
#pragma mark - CSJoystickManager
@interface CSJoystickManager ()
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
@end
static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
[(__bridge CSJoystickManager *)context deviceMatched:device result:result sender:sender];
}
static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
[(__bridge CSJoystickManager *)context deviceRemoved:device result:result sender:sender];
}
@implementation CSJoystickManager {
IOHIDManagerRef _hidManager;
NSMutableArray<CSJoystick *> *_joysticks;
}
- (instancetype)init {
self = [super init];
if(self) {
_joysticks = [[NSMutableArray alloc] init];
_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if(!_hidManager) return nil;
NSArray<NSDictionary<NSString *, NSNumber *> *> *const multiple = @[
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick) },
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_GamePad) },
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_MultiAxisController) },
];
IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)multiple);
IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self);
IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self);
IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
NSLog(@"Failed to open HID manager");
// something
return nil;
}
}
return self;
}
- (void)dealloc {
IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone);
CFRelease(_hidManager);
}
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
// Double check this joystick isn't already known.
for(CSJoystick *joystick in _joysticks) {
if(joystick.device == device) return;
}
// Prepare to collate a list of buttons, axes and hats for the new device.
NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init];
NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init];
NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init];
// Inspect all elements for those that are comprehensible to this code.
const CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) {
const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index);
// Check that this element is either on the generic desktop page or else is a button.
const uint32_t usagePage = IOHIDElementGetUsagePage(element);
if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue;
// Then inspect the type.
switch(IOHIDElementGetType(element)) {
default: break;
case kIOHIDElementTypeInput_Button: {
// Add a button; pretty easy stuff. 'Usage' provides a button index.
const uint32_t usage = IOHIDElementGetUsage(element);
[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]];
} break;
case kIOHIDElementTypeInput_Misc:
case kIOHIDElementTypeInput_Axis: {
CSJoystickAxisType axisType;
switch(IOHIDElementGetUsage(element)) {
default: continue;
// Three analogue axes are implemented here; there are another three sets
// of these that could be parsed in the future if interesting.
case kHIDUsage_GD_X: axisType = CSJoystickAxisTypeX; break;
case kHIDUsage_GD_Y: axisType = CSJoystickAxisTypeY; break;
case kHIDUsage_GD_Z: axisType = CSJoystickAxisTypeZ; break;
// A hatswitch is a multi-directional control all of its own.
case kHIDUsage_GD_Hatswitch:
[hats addObject:[[CSJoystickHat alloc] initWithElement:element]];
continue;
}
// Add the axis; if it was a hat switch or unrecognised then the code doesn't
// reach here.
[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]];
} break;
}
}
CFRelease(elements);
// Add this joystick to the list.
[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
}
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
// If this joystick was recorded, remove it.
for(CSJoystick *joystick in [_joysticks copy]) {
if(joystick.device == device) {
[_joysticks removeObject:joystick];
return;
}
}
}
- (void)update {
[self.joysticks makeObjectsPerformSelector:@selector(update)];
}
- (NSArray<CSJoystick *> *)joysticks {
return [_joysticks copy];
}
@end

View File

@@ -12,10 +12,13 @@
#import "CSFastLoading.h"
#import "CSOpenGLView.h"
#import "CSStaticAnalyser.h"
#import "CSJoystickManager.h"
@class CSMachine;
@protocol CSMachineDelegate
- (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine;
- (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine;
- (void)machine:(nonnull CSMachine *)machine led:(nonnull NSString *)led didChangeToLit:(BOOL)isLit;
- (void)machine:(nonnull CSMachine *)machine ledShouldBlink:(nonnull NSString *)led;
@end
typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
@@ -24,47 +27,65 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
CSMachineVideoSignalRGB
};
typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
CSMachineKeyboardInputModeKeyboard,
CSMachineKeyboardInputModeJoystick
};
// Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h"
#import "CSZX8081.h"
@interface CSMachine : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (nonnull instancetype)init NS_UNAVAILABLE;
/*!
Initialises an instance of CSMachine.
@param result The CSStaticAnalyser result that describes the machine needed.
*/
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
- (void)runForInterval:(NSTimeInterval)interval;
- (float)idealSamplingRateFromRange:(NSRange)range;
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
- (void)setView:(nullable CSOpenGLView *)view aspectRatio:(float)aspectRatio;
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed;
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
- (void)clearAllKeys;
@property (nonatomic, strong) CSAudioQueue *audioQueue;
@property (nonatomic, readonly) CSOpenGLView *view;
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
@property (nonatomic, strong, nullable) CSAudioQueue *audioQueue;
@property (nonatomic, readonly, nonnull) CSOpenGLView *view;
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
@property (nonatomic, readonly) NSString *userDefaultsPrefix;
@property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix;
- (void)paste:(NSString *)string;
- (void)paste:(nonnull NSString *)string;
@property (nonatomic, readonly, nonnull) NSBitmapImageRep *imageRepresentation;
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) CSMachineVideoSignal videoSignal;
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
@property (nonatomic, readonly) BOOL canInsertMedia;
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
// Input control.
@property (nonatomic, readonly) BOOL hasKeyboard;
@property (nonatomic, readonly) BOOL hasJoystick;
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
@property (nonatomic, nullable) CSJoystickManager *joystickManager;
// LED list.
@property (nonatomic, readonly, nonnull) NSArray<NSString *> *leds;
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
@property (nonatomic, readonly) CSAtari2600 *atari2600;
@property (nonatomic, readonly) CSZX8081 *zx8081;
@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600;
@property (nonatomic, readonly, nullable) CSZX8081 *zx8081;
@end

View File

@@ -11,13 +11,14 @@
#include "CSROMFetcher.hpp"
#include "ConfigurationTarget.hpp"
#include "MediaTarget.hpp"
#include "JoystickMachine.hpp"
#include "KeyboardMachine.hpp"
#include "KeyCodes.h"
#include "MachineForTarget.hpp"
#include "StandardOptions.hpp"
#include "Typer.hpp"
#include "../../../../Activity/Observer.hpp"
#import "CSStaticAnalyser+TargetVector.h"
#import "NSBundle+DataResource.h"
@@ -28,6 +29,7 @@
@interface CSMachine() <CSFastLoading>
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
- (void)addLED:(NSString *)led;
@end
struct LockProtectedDelegate {
@@ -50,14 +52,34 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
}
};
struct ActivityObserver: public Activity::Observer {
void register_led(const std::string &name) override {
[machine addLED:[NSString stringWithUTF8String:name.c_str()]];
}
void set_led_status(const std::string &name, bool lit) override {
[machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit];
}
void announce_drive_event(const std::string &name, DriveEvent event) override {
[machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]];
}
__unsafe_unretained CSMachine *machine;
};
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
ActivityObserver _activityObserver;
NSLock *_delegateMachineAccessLock;
CSStaticAnalyser *_analyser;
std::unique_ptr<Machine::DynamicMachine> _machine;
JoystickMachine::Machine *_joystickMachine;
CSJoystickManager *_joystickManager;
std::bitset<65536> _depressedKeys;
NSMutableArray<NSString *> *_leds;
}
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
@@ -69,10 +91,21 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error));
if(!_machine) return nil;
_inputMode = _machine->keyboard_machine() ? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick;
_leds = [[NSMutableArray alloc] init];
Activity::Source *const activity_source = _machine->activity_source();
if(activity_source) {
_activityObserver.machine = self;
activity_source->set_activity_observer(&_activityObserver);
}
_delegateMachineAccessLock = [[NSLock alloc] init];
_speakerDelegate.machine = self;
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
_joystickMachine = _machine->joystick_machine();
}
return self;
}
@@ -133,6 +166,54 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
- (void)runForInterval:(NSTimeInterval)interval {
@synchronized(self) {
if(_joystickMachine && _joystickManager) {
[_joystickManager update];
// TODO: configurable mapping from physical joypad inputs to machine inputs.
// Until then, apply a default mapping.
size_t c = 0;
std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks();
for(CSJoystick *joystick in _joystickManager.joysticks) {
size_t target = c % machine_joysticks.size();
++++c;
// Post the first two analogue axes presented by the controller as horizontal and vertical inputs,
// unless the user seems to be using a hat.
// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0]
if(!joystick.hats.count || !joystick.hats[0].direction) {
if(joystick.axes.count > 0) {
const float x_axis = joystick.axes[0].position;
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis);
}
if(joystick.axes.count > 1) {
const float y_axis = joystick.axes[1].position;
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
}
} else {
// Forward hats as directions; hats always override analogue inputs.
for(CSJoystickHat *hat in joystick.hats) {
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
}
}
// Forward all fire buttons, mapping as a function of index.
if(machine_joysticks[target]->get_number_of_fire_buttons()) {
std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons());
for(CSJoystickButton *button in joystick.buttons) {
if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true;
}
for(size_t index = 0; index < button_states.size(); ++index) {
machine_joysticks[target]->set_input(
Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index),
button_states[index]);
}
}
}
}
_machine->crt_machine()->run_for(interval);
}
}
@@ -162,16 +243,65 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
keyboardMachine->type_string([paste UTF8String]);
}
- (NSBitmapImageRep *)imageRepresentation {
// Get the current viewport to establish framebuffer size. Then determine how wide the
// centre 4/3 of that would be.
GLint dimensions[4];
glGetIntegerv(GL_VIEWPORT, dimensions);
GLint proportionalWidth = (dimensions[3] * 4) / 3;
// Grab the framebuffer contents.
std::vector<uint8_t> temporaryData(static_cast<size_t>(proportionalWidth * dimensions[3] * 3));
glReadPixels((dimensions[2] - proportionalWidth) >> 1, 0, proportionalWidth, dimensions[3], GL_RGB, GL_UNSIGNED_BYTE, temporaryData.data());
// Generate an NSBitmapImageRep and populate it with a vertical flip
// of the original data.
NSBitmapImageRep *const result =
[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:proportionalWidth
pixelsHigh:dimensions[3]
bitsPerSample:8
samplesPerPixel:3
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:3 * proportionalWidth
bitsPerPixel:0];
const size_t line_size = static_cast<size_t>(proportionalWidth * 3);
for(GLint y = 0; y < dimensions[3]; ++y) {
memcpy(
&result.bitmapData[static_cast<size_t>(y) * line_size],
&temporaryData[static_cast<size_t>(dimensions[3] - y - 1) * line_size],
line_size);
}
return result;
}
- (void)applyMedia:(const Analyser::Static::Media &)media {
@synchronized(self) {
ConfigurationTarget::Machine *const configurationTarget = _machine->configuration_target();
if(configurationTarget) configurationTarget->insert_media(media);
MediaTarget::Machine *const mediaTarget = _machine->media_target();
if(mediaTarget) mediaTarget->insert_media(media);
}
}
- (void)setJoystickManager:(CSJoystickManager *)joystickManager {
@synchronized(self) {
_joystickManager = joystickManager;
if(_joystickMachine) {
std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks();
for(const auto &joystick: machine_joysticks) {
joystick->reset_all_inputs();
}
}
}
}
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
auto keyboard_machine = _machine->keyboard_machine();
if(keyboard_machine) {
if(self.inputMode == CSMachineKeyboardInputModeKeyboard && keyboard_machine) {
// Don't pass anything on if this is not new information.
if(_depressedKeys[key] == !!isPressed) return;
_depressedKeys[key] = !!isPressed;
@@ -248,23 +378,27 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
}
auto joystick_machine = _machine->joystick_machine();
if(joystick_machine) {
if(self.inputMode == CSMachineKeyboardInputModeJoystick && joystick_machine) {
@synchronized(self) {
std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks = joystick_machine->get_joysticks();
if(!joysticks.empty()) {
// Convert to a C++ bool so that the following calls are resolved correctly even if overloaded.
bool is_pressed = !!isPressed;
switch(key) {
case VK_LeftArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Left, isPressed); break;
case VK_RightArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed); break;
case VK_UpArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed); break;
case VK_DownArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed); break;
case VK_Space: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break;
case VK_ANSI_A: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 0), isPressed); break;
case VK_ANSI_S: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 1), isPressed); break;
case VK_LeftArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Left, is_pressed); break;
case VK_RightArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Right, is_pressed); break;
case VK_UpArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Up, is_pressed); break;
case VK_DownArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Down, is_pressed); break;
case VK_Space: joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); break;
case VK_ANSI_A: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 0), is_pressed); break;
case VK_ANSI_S: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 1), is_pressed); break;
case VK_ANSI_D: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed); break;
case VK_ANSI_F: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed); break;
default:
if(characters) {
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput([characters characterAtIndex:0]), isPressed);
if(characters.length) {
joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed);
} else {
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed);
joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed);
}
break;
}
@@ -382,6 +516,10 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
return [[NSString stringWithUTF8String:name.c_str()] lowercaseString];
}
- (BOOL)canInsertMedia {
return !!_machine->media_target();
}
#pragma mark - Special machines
- (CSAtari2600 *)atari2600 {
@@ -392,4 +530,24 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
}
#pragma mark - Input device queries
- (BOOL)hasJoystick {
return !!_machine->joystick_machine();
}
- (BOOL)hasKeyboard {
return !!_machine->keyboard_machine();
}
#pragma mark - Activity observation
- (void)addLED:(NSString *)led {
[_leds addObject:led];
}
- (NSArray<NSString *> *)leds {
return _leds;
}
@end

View File

@@ -12,7 +12,9 @@
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
CSMachineAppleIIModelAppleII,
CSMachineAppleIIModelAppleIIPlus
CSMachineAppleIIModelAppleIIPlus,
CSMachineAppleIIModelAppleIIe,
CSMachineAppleIIModelAppleEnhancedIIe
};
typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) {

View File

@@ -168,7 +168,12 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
using Target = Analyser::Static::AppleII::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::AppleII;
target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus;
switch(model) {
default: target->model = Target::Model::II; break;
case CSMachineAppleIIModelAppleIIPlus: target->model = Target::Model::IIplus; break;
case CSMachineAppleIIModelAppleIIe: target->model = Target::Model::IIe; break;
case CSMachineAppleIIModelAppleEnhancedIIe: target->model = Target::Model::EnhancedIIe; break;
}
switch(diskController) {
default:
case CSMachineAppleIIDiskControllerNone: target->disk_controller = Target::DiskController::None; break;
@@ -183,7 +188,8 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
- (NSString *)optionsPanelNibName {
switch(_targets.front()->machine) {
case Analyser::Machine::AmstradCPC: return nil;
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
// case Analyser::Machine::AppleII: return @"AppleIIOptions";
case Analyser::Machine::Atari2600: return @"Atari2600Options";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -64,11 +64,11 @@ Gw
<tabViewItems>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="33" width="554" height="93"/>
<rect key="frame" x="10" y="33" width="554" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="15" y="71" width="46" height="17"/>
<rect key="frame" x="15" y="72" width="46" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -76,7 +76,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<rect key="frame" x="15" y="40" width="96" height="17"/>
<rect key="frame" x="15" y="41" width="96" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -84,7 +84,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
<rect key="frame" x="65" y="66" width="91" height="26"/>
<rect key="frame" x="65" y="67" width="115" height="26"/>
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -92,12 +92,14 @@ Gw
<items>
<menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/>
<menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/>
<menuItem title="Apple IIe" tag="2" id="AMt-WU-a0H"/>
<menuItem title="Enhanced IIe" tag="3" id="kUz-FG-lqW"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
<rect key="frame" x="115" y="35" width="132" height="26"/>
<rect key="frame" x="115" y="36" width="132" height="26"/>
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -128,11 +130,11 @@ Gw
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="33" width="554" height="99"/>
<rect key="frame" x="10" y="33" width="554" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
<rect key="frame" x="65" y="72" width="94" height="26"/>
<rect key="frame" x="65" y="67" width="94" height="26"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -146,7 +148,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
<rect key="frame" x="15" y="77" width="46" height="17"/>
<rect key="frame" x="15" y="72" width="46" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>

View File

@@ -130,6 +130,8 @@ class MachinePicker: NSObject {
var model: CSMachineAppleIIModel = .appleII
switch appleIIModelButton!.selectedTag() {
case 1: model = .appleIIPlus
case 2: model = .appleIIe
case 3: model = .appleEnhancedIIe
case 0: fallthrough
default: model = .appleII
}

View File

@@ -15,7 +15,7 @@ class MOS6502InterruptTests: XCTestCase {
super.setUp()
// create a machine full of NOPs
machine = CSTestMachine6502()
machine = CSTestMachine6502(is65C02: false)
for c in 0...65535 {
machine.setValue(0xea, forAddress: UInt16(c))
}

View File

@@ -12,7 +12,7 @@ import XCTest
class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler {
private var endTime: UInt32 = 0
private let machine = CSTestMachine6502()
private let machine = CSTestMachine6502(is65C02: false)
func testImplied() {
let code: [UInt8] = [

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