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

Compare commits

...

198 Commits

Author SHA1 Message Date
Thomas Harte
5810a1a98e Merge pull request #971 from TomHarte/ChaseHQ
Flip meaning of INT1 input read.
2021-07-09 22:48:01 -04:00
Thomas Harte
a4c011e3c0 Flip meaning of INT1 input read. 2021-07-09 22:39:51 -04:00
Thomas Harte
337fd15dc0 Merge pull request #970 from TomHarte/SwiftUniformity
Swift: be consisted on `.selectedTag()`.
2021-07-08 22:43:57 -04:00
Thomas Harte
9bc94f4536 Be consisted on .selectedTag(). 2021-07-08 22:38:54 -04:00
Thomas Harte
3f4cf35384 Merge pull request #969 from TomHarte/SixMHz
Adds the option of running an Enterprise at 6MHz.
2021-07-08 22:36:22 -04:00
Thomas Harte
4dd7f2cc09 Add 6Mhz option to Qt UI. 2021-07-08 22:30:35 -04:00
Thomas Harte
1b29cc34c4 Correct input list. 2021-07-08 22:22:48 -04:00
Thomas Harte
53c3c1f5ab Allows macOS users to select the 6MHz Enterprise. 2021-07-08 18:50:37 -04:00
Thomas Harte
6225abd751 Adds 6MHz Enterprise option. 2021-07-07 20:57:04 -04:00
Thomas Harte
c6fcd9a1eb Merge pull request #968 from TomHarte/DaveAudio
Dave: apply ring modulation during sync, too.
2021-07-06 23:41:37 -04:00
Thomas Harte
30fbb6ea53 Ensure run command is issued. 2021-07-06 23:16:16 -04:00
Thomas Harte
0e49258546 Remove caveman debugging. 2021-07-06 23:15:53 -04:00
Thomas Harte
264b8dfb28 Dave: apply ring modulation even in sync mode. 2021-07-06 23:11:30 -04:00
Thomas Harte
6a15b8f695 Merge pull request #967 from TomHarte/EnterpriseTiming
Correct Enterprise timing error.
2021-07-06 22:48:58 -04:00
Thomas Harte
5167d256cc Remove detritus. 2021-07-06 22:43:17 -04:00
Thomas Harte
16bd826491 Reduce nesting. 2021-07-06 22:32:59 -04:00
Thomas Harte
55af8fa5d9 Avoid erroneous Nick delays. 2021-07-06 22:28:44 -04:00
Thomas Harte
1ec8ff20af Ensure data bus is 0xff during interrupts. 2021-07-06 21:58:17 -04:00
Thomas Harte
99a65d3297 Merge pull request #966 from TomHarte/DaveUnifiedTimer
Switches to a unified counter for 1/50/1000Hz Dave interrupts.
2021-07-06 21:50:32 -04:00
Thomas Harte
94907b51aa Remove redundant parameter. 2021-07-06 20:47:49 -04:00
Thomas Harte
0085265d13 Test for a longer period; fix expected tone 1 count. 2021-07-06 20:46:22 -04:00
Thomas Harte
8e0893bd42 Clarifies control flow. 2021-07-06 20:28:32 -04:00
Thomas Harte
704dc9bdcb Improves test, to assert that state toggles happen at interrupts. 2021-07-06 20:25:32 -04:00
Thomas Harte
7a673a2448 Avoid confusing temporary storage. 2021-07-06 20:23:09 -04:00
Thomas Harte
33e2a4b21c Minor cleanups. 2021-07-06 20:20:13 -04:00
Thomas Harte
3e6b804896 Switches to linked 1/50/1000 Hz timers, and per-interrupt state toggling. 2021-07-06 20:12:44 -04:00
Thomas Harte
e98165a657 Merge pull request #965 from TomHarte/DaveDivider
Ensure two-cycle pauses in 12MHz mode.
2021-07-04 21:13:05 -04:00
Thomas Harte
2a7727d12b Merge branch 'master' into DaveDivider 2021-07-04 21:02:09 -04:00
Thomas Harte
c20e8f4062 Honours 8/12Mhz selection in non-video delays. 2021-07-03 23:05:09 -04:00
Thomas Harte
4ca9db7d49 Merge pull request #963 from TomHarte/DaveDivider
Obey Dave's 8/12MHz programmable divider.
2021-07-03 23:00:11 -04:00
Thomas Harte
4add48cffb Obey Dave's 8/12MHz programmable divider. 2021-07-03 22:43:20 -04:00
Thomas Harte
adbfb009f8 Merge pull request #960 from TomHarte/QtLEDs
Ensure LEDs are cleared between machines in Qt.
2021-07-03 19:17:51 -04:00
Thomas Harte
43ceca8711 Use type alias. 2021-07-03 19:10:39 -04:00
Thomas Harte
3ef28a4f03 Remove unused instance variable. 2021-07-03 19:10:29 -04:00
Thomas Harte
adcd580d5b Ensure LEDs are cleared upon a new machine. 2021-07-03 19:06:15 -04:00
Thomas Harte
5715c9183f The target is now definitely used. 2021-07-03 15:20:37 -04:00
Thomas Harte
ceb62ac7f9 Reenable the hardened runtime for macOS. 2021-07-03 13:41:32 -04:00
Thomas Harte
bda0756620 Merge pull request #959 from TomHarte/WriteCrash
Corrects buffer placement of decoded sectors.
2021-07-03 13:41:00 -04:00
Thomas Harte
6b47fb38c6 Corrects buffer placement of decoded sectors. 2021-07-03 13:36:01 -04:00
Thomas Harte
38bf8a06a7 Merge pull request #958 from TomHarte/EnterpriseFloatingBus
Makes a guess re: the Enterprise floating bus
2021-07-03 13:26:19 -04:00
Thomas Harte
196651d9aa Consolidates TODO. 2021-07-03 13:08:53 -04:00
Thomas Harte
6b46212a4e Deal with dangling TODO. 2021-07-03 13:07:41 -04:00
Thomas Harte
2a6fff2008 Takes a stab at what might happen if you read from Nick. 2021-07-03 13:06:07 -04:00
Thomas Harte
c5944efe50 Adds various method definitions. 2021-07-03 12:56:56 -04:00
Thomas Harte
f384370b18 Switch what's left of Enterprise logging to actual LOGs. 2021-07-03 12:50:46 -04:00
Thomas Harte
0c09275a9f Merge pull request #957 from TomHarte/EnterpriseTimingWindow
Correct various Enterprise timing discrepancies.
2021-07-03 12:47:29 -04:00
Thomas Harte
278671cdb9 Correct Nick interrupt prediction. 2021-07-03 00:05:13 -04:00
Thomas Harte
964d2d4fa4 Be consistent in expression of logic. 2021-07-03 00:00:00 -04:00
Thomas Harte
f371221dba Add a quick test of tone generator 1. 2021-07-02 23:57:11 -04:00
Thomas Harte
27b0579ec6 Avoid stack-error test case.
Also test that the interrupt is generated on the downward stroke.
2021-07-02 23:55:43 -04:00
Thomas Harte
283092cfbc With a unit test in aid, corrects some lingering TimedInterruptSource issues. 2021-07-02 23:41:19 -04:00
Thomas Harte
614953a222 Allows the low-pass filter to react to high-pass effects. 2021-07-02 22:36:35 -04:00
Thomas Harte
4fffb3cf19 Allow that final Z80 cycle to start anywhere in the first three of Nick's window of six. 2021-07-02 22:29:35 -04:00
Thomas Harte
850aa2b23a Merge pull request #956 from TomHarte/EnterpriseComposite
Adds Enterprise composite video option.
2021-07-02 22:22:09 -04:00
Thomas Harte
d715e5fd1d Expose composite/RGB option in Qt. 2021-07-02 21:51:48 -04:00
Thomas Harte
7826a26c7b Adds Enterprise composite video option.
While enabling more pixels on the left for RGB mode.
2021-07-02 21:42:09 -04:00
Thomas Harte
dc0a82cf9a Merge pull request #955 from TomHarte/FAT12
Adds a FAT12 parser.
2021-07-02 21:33:54 -04:00
Thomas Harte
2e60c81bd6 Enter :dir as a complete command. 2021-07-02 21:15:48 -04:00
Thomas Harte
763b9ba0ec Ensure the splash screen is skipped for self-booting disks. 2021-07-02 21:11:54 -04:00
Thomas Harte
bae8bb0c00 Gives the FAT parser responsibility for right trims. 2021-07-02 19:50:27 -04:00
Thomas Harte
bcf483fb7e Adds some basic loading command assistance. 2021-07-02 19:42:43 -04:00
Thomas Harte
a5b7d819a7 Correct FAT parser. 2021-07-02 19:28:13 -04:00
Thomas Harte
fe07a0b1d8 Starts to add a FAT[12] parser. 2021-07-02 18:56:43 -04:00
Thomas Harte
d9231e5d4a Merge pull request #954 from TomHarte/stddefRedux
The FIRFilter interface depends upon size_t.
2021-07-02 17:26:37 -04:00
Thomas Harte
b7aa1a1c84 The FIRFilter interface depends upon size_t. 2021-07-02 17:21:53 -04:00
Thomas Harte
32e144115d Add missing article, plus other minor corrections. 2021-07-02 11:03:14 -04:00
Thomas Harte
177cc96f49 Merge pull request #953 from TomHarte/stddef
Add missing stddef header where size_t is used.
2021-07-01 23:29:56 -04:00
Thomas Harte
51d98ef9ab Add missing stddef header where size_t is used. 2021-07-01 23:15:32 -04:00
Thomas Harte
2327c48cc4 Merge pull request #952 from TomHarte/EnterpriseTyper
Add typer support for the Enterprise.
2021-07-01 22:59:04 -04:00
Thomas Harte
742d44a532 Switch to an activity-based typing trigger; add a target loading command. 2021-07-01 22:53:23 -04:00
Thomas Harte
52b96db2b9 Correct syntax, mapping and inter-key timing. 2021-07-01 21:18:15 -04:00
Thomas Harte
0b9de78c38 Add typer support for the Enterprise. 2021-07-01 21:05:03 -04:00
Thomas Harte
2c28cb8c57 Merge pull request #951 from TomHarte/EnterpriseMention
Add Enterprise screenshots
2021-06-30 22:24:43 -04:00
Thomas Harte
483fe82e9d Add a second image, to even things out. 2021-06-30 22:23:26 -04:00
Thomas Harte
29492d6138 Add an Enterprise screenshot. 2021-06-30 22:18:29 -04:00
Thomas Harte
19310e32c4 Adds the Enterprise 64/128 as a bullet-pointed item.
No relevant screenshots yet.
2021-06-30 08:06:20 -04:00
Thomas Harte
c04a395499 Merge pull request #950 from TomHarte/Enterprise
Adds emulation of the Enterprise
2021-06-29 21:37:38 -04:00
Thomas Harte
1c424833a9 Correct EXDOS ROM name. 2021-06-29 21:04:53 -04:00
Thomas Harte
a46ff5590d Adds Enterprise new machine dialogue for Qt. 2021-06-29 21:04:17 -04:00
Thomas Harte
ab059b63fd Add Enterprise to Qt project file. 2021-06-29 20:36:28 -04:00
Thomas Harte
3d8fc9952d Remove dead TODO, correct for overflow position. 2021-06-29 15:44:02 -04:00
Thomas Harte
8ce8fbd977 Provide correct input when one of the tone generators is the interrupt source. 2021-06-29 15:41:08 -04:00
Thomas Harte
7f08218b28 The Nick interrupt input also seems to be a live poll, not a retrieval of the mask.
This corrects the two pieces of software I knew not to be working.
2021-06-28 22:10:11 -04:00
Thomas Harte
2c139ad931 Adds some notes to self.
I think I'm starting to find enough information to handle tapes.
2021-06-28 22:03:06 -04:00
Thomas Harte
1119779c8b Ensure EXDOS card is completely disabled if no FDC is present. 2021-06-28 21:47:53 -04:00
Thomas Harte
5351ac560f Ensure the motor goes off for unselected drives. 2021-06-28 21:40:12 -04:00
Thomas Harte
49f0ab0f15 Add note to self.
Although I still think there may be some issue lurking.
2021-06-28 21:31:55 -04:00
Thomas Harte
a5c57e777e VRES appears to work negatively in attribute mode too. 2021-06-28 21:24:13 -04:00
Thomas Harte
3c59042388 Fixes initial state for 1kHz. 2021-06-28 21:08:41 -04:00
Thomas Harte
919e211bc4 Reduces number of interrupt-related sequence points. 2021-06-28 19:30:12 -04:00
Thomas Harte
daa0737ce4 Ensure addresses tick upwards even during sync/burst; correct 2/4/8bpp character sizing. 2021-06-28 19:00:51 -04:00
Thomas Harte
36805cb120 Correct tone channel interrupts, remove dead warning. 2021-06-27 23:21:00 -04:00
Thomas Harte
7de69e9874 Makes an attempt to round out the timed interrupts. 2021-06-27 23:09:11 -04:00
Thomas Harte
b93575bbcc Spots that b0 and b2 of 0xb4 are 'dividers', not enables. 2021-06-27 22:33:20 -04:00
Thomas Harte
116e0f0105 Interupts 1kHz and 50Hz interrupts, while edging towards tone generator interrupts. 2021-06-27 22:08:38 -04:00
Thomas Harte
e4a650aaff Implements the 1Hz interrupt. 2021-06-27 21:47:21 -04:00
Thomas Harte
b5312b9ba0 get_interrupt_line can be const. 2021-06-27 21:37:11 -04:00
Thomas Harte
6afee7bb9b Captures appropriate fields.
No action yet.
2021-06-27 21:36:55 -04:00
Thomas Harte
5729e6e13a Corrects potential JustInTimeActor overflow. 2021-06-27 21:36:41 -04:00
Thomas Harte
2f53b105bb The Enterprise is now an Activity::Source; also sketches out the owner of Dave's timed interrupt logic. 2021-06-27 21:02:04 -04:00
Thomas Harte
b698056f78 Correct divisor. 2021-06-27 17:39:13 -04:00
Thomas Harte
95c906f03d Takes a serious shot at back_map. 2021-06-27 17:36:25 -04:00
Thomas Harte
be19fa9dde This mapping needs to know where it will occur. 2021-06-27 17:30:09 -04:00
Thomas Harte
81e9ba5608 This is correct from the Enterprise's side of things, I think.
I just need to complete the missing part of JustInTimeActor. After I do some empirical testing of this.
2021-06-27 17:24:21 -04:00
Thomas Harte
f2d7b9f6a9 Apply a crop, allow time until Z80 slot to be a future-based query. 2021-06-27 17:13:07 -04:00
Thomas Harte
1ea034310a Edge up very close to video waits.
I just need to implement back conversions that include marginal phase over in the JustInTimeActor.
2021-06-27 16:28:01 -04:00
Thomas Harte
bdcab447f9 Add a further accessor. 2021-06-27 16:27:26 -04:00
Thomas Harte
10bf6744aa Correct typo. 2021-06-27 16:26:55 -04:00
Thomas Harte
895d98e266 Implements out-of-video-area pauses. 2021-06-27 16:11:22 -04:00
Thomas Harte
903e343895 Attempts to complete Dave's audio duties. 2021-06-27 14:06:49 -04:00
Thomas Harte
f8b7c59616 Corrects tone frequency. 2021-06-26 23:51:43 -04:00
Thomas Harte
fcd267a3f9 Starts implementing noise. 2021-06-26 23:48:53 -04:00
Thomas Harte
f8bb66d2a0 Attempts an essentially-complete implementation of tone channels. 2021-06-26 23:39:59 -04:00
Thomas Harte
90782d3c27 Corrects for IntType != int. 2021-06-26 23:39:37 -04:00
Thomas Harte
f2336d2efc I think reloads occur after overflow, not before. 2021-06-26 23:16:00 -04:00
Thomas Harte
c2d093fa3c Respect user volume input.
Basic tones are now present. Neato!
2021-06-24 22:27:02 -04:00
Thomas Harte
1a97cc8a91 Start making some effort towards audio generation. 2021-06-24 22:21:01 -04:00
Thomas Harte
c34a548fa0 Ensure character pixel reads can't go out of bounds. 2021-06-24 22:19:50 -04:00
Thomas Harte
d1b89392a2 Improve exposiiton. 2021-06-24 22:18:31 -04:00
Thomas Harte
ed734754e5 Adds a through route for IMG files. 2021-06-24 21:04:21 -04:00
Thomas Harte
520c3c9218 Corrects colour deserialisation.
Long story short: the documentation I'm reading inexplicably lists the bits in reverse order. Luckily, a lot of the other documentation doesn't.
2021-06-24 20:59:04 -04:00
Thomas Harte
9230cf1726 Corrects bug when left_ or right_margin_ = 0. 2021-06-24 20:28:50 -04:00
Thomas Harte
6e616972a5 Better binds margin tests to window movements; simplifies line parameter addressing. 2021-06-24 18:55:15 -04:00
Thomas Harte
f98888824d Switches to an overt active/inactive state machine. 2021-06-24 18:34:21 -04:00
Thomas Harte
6c8b23e708 Alters 4bpp mapping; adds character mode 4bpp and 8bpp. 2021-06-23 19:35:47 -04:00
Thomas Harte
2c2bb3765f Withdraws the EPDOS option.
At least for now; it's something to worry about later.
2021-06-23 19:32:34 -04:00
Thomas Harte
0d165740ea Honours memory size request. 2021-06-22 21:48:55 -04:00
Thomas Harte
88f0f2b623 Adds to the macOS UI and wires through all Enterprise options. 2021-06-22 21:39:07 -04:00
Thomas Harte
0afa143375 Add missing include. 2021-06-22 21:31:46 -04:00
Thomas Harte
8319aca351 Correct syntax errors. 2021-06-22 20:50:03 -04:00
Thomas Harte
a66734883a Starts sketching out Dave. 2021-06-22 19:33:41 -04:00
Thomas Harte
d2ab0dd839 Adds a quick way to get the compiler to pick an integral type. 2021-06-22 19:33:29 -04:00
Thomas Harte
2574407afb Relocates MinIntTypeValue to Numeric. 2021-06-22 19:33:02 -04:00
Thomas Harte
83a54fd6d2 Use the FAT12 boot sector to determine geometry. 2021-06-22 06:54:17 -04:00
Thomas Harte
e062780968 Extends back to 128kb and stops halting on unrecognised ports. 2021-06-22 06:15:42 -04:00
Thomas Harte
3acd0be1f7 Copy and paste 2bpp character support. 2021-06-21 23:27:13 -04:00
Thomas Harte
69c0734975 WD1770: switch motor on even if spin-up is disabled. 2021-06-21 23:26:55 -04:00
Thomas Harte
c1678d7be7 Corrects exposition and transmission of drive selection.
What a klutz I've been.
2021-06-21 22:56:25 -04:00
Thomas Harte
117f9a9794 Adds notes on intended meaning of status register. 2021-06-21 07:31:58 -04:00
Thomas Harte
b49cc407c6 Adds some guesses as to the EXDos expansion.
... with plenty left to do.
2021-06-20 22:30:27 -04:00
Thomas Harte
954386f1cc Creates a shell for the disk-drive add-on card. 2021-06-20 20:50:23 -04:00
Thomas Harte
d7ff6bd04d Adds necessary declarations to install a DOS ROM. 2021-06-20 20:30:54 -04:00
Thomas Harte
6025516f9f Ensure addresses increment even when there's no target for pixels. 2021-06-20 14:31:02 -04:00
Thomas Harte
d8b9cdf7a2 Correct multiplier. 2021-06-20 14:25:37 -04:00
Thomas Harte
09dbff39f2 Also map keypad to F keys.
This is a pragmatic and arguably Apple-centric decision. But also it's fairly arbitrary, as the Enterprise doesn't have a number pad.
2021-06-20 14:25:28 -04:00
Thomas Harte
2fe15a6168 Switch to idealised Nick clock rate. 2021-06-20 14:21:56 -04:00
Thomas Harte
07dc26f8fa Adds TODO to resolve screen jumping. 2021-06-19 23:41:29 -04:00
Thomas Harte
a08d65b1ff Adds IMG -> Enterprise connection.
Albeit still without an Enterprise static analyser.
2021-06-19 23:16:33 -04:00
Thomas Harte
199621db08 Observes that the actual guess here is MS-DOS-style. 2021-06-19 23:11:51 -04:00
Thomas Harte
0e1e8c7faa Attempts to support the panoply of EXOS and BASIC versions. 2021-06-19 22:59:09 -04:00
Thomas Harte
42a98e1676 Fix composition with empty nodes. 2021-06-19 22:13:17 -04:00
Thomas Harte
23e26e0333 Attempts to complete handling of VRES. 2021-06-19 22:00:19 -04:00
Thomas Harte
fadb04f3f3 Attempts to implement LSBALT and MSBALT. 2021-06-19 21:57:26 -04:00
Thomas Harte
4968ccf46d Corrects attributed mode. 2021-06-19 13:08:14 -04:00
Thomas Harte
1dcac304d3 Implements the ALTIND bits and attempts ATTR mode. 2021-06-19 13:04:18 -04:00
Thomas Harte
1651efe4fc Ensures all keys are initially unpressed. 2021-06-19 13:03:31 -04:00
Thomas Harte
8f24aed43e Slightly reduces logging. 2021-06-18 23:17:44 -04:00
Thomas Harte
a381374e31 Drops back down to 64kb. 2021-06-18 23:14:44 -04:00
Thomas Harte
9411c37d23 Fleshes out the keyboard map. 2021-06-18 23:14:35 -04:00
Thomas Harte
6af6f21868 Attempts to implement interrupt latches and clears. 2021-06-18 22:59:41 -04:00
Thomas Harte
9a0022cfcb Removes temporary work. 2021-06-18 18:44:07 -04:00
Thomas Harte
266310d9c2 Fixes automatic flushing for non-1/1-clocked actors. 2021-06-18 18:43:08 -04:00
Thomas Harte
fbf1adef05 Introduces unit test and thereby seemingly fixes get_next_sequence_point.
There's still improper output in the actual machine though, so maybe something else is afoot?
2021-06-18 17:44:17 -04:00
Thomas Harte
311ddfb05a Add note to self for tomorrow. 2021-06-17 22:34:52 -04:00
Thomas Harte
2fd8a8aa66 Begins addition of interrupt feedback from Nick.
Also fixes clock rate. Though clearly get_next_sequence_point isn't quite right yet.
2021-06-17 22:30:24 -04:00
Thomas Harte
0c3e9dca28 Adds some basic keyboard inputs.
I think the next thing required is interrupts though.
2021-06-17 20:47:56 -04:00
Thomas Harte
c331d15429 Makes space to allow for 64kb EXOS ROMs.
I think some of the later ROMs have a more thorough memory test, which might provide better detail on whatever's going on here.
2021-06-16 22:25:00 -04:00
Thomas Harte
4414e96710 Adds enough text mode for now.
Discovered: a memory fault is being reported at startup.
2021-06-16 21:42:20 -04:00
Thomas Harte
7161783a4f Adds lpixel output. 2021-06-16 21:16:26 -04:00
Thomas Harte
cbac48da86 Attempts full run at pixel mode. 2021-06-16 20:43:22 -04:00
Thomas Harte
d9142d5427 Adjusts declared scale, accurately to communicate pixel clock. 2021-06-15 22:39:44 -04:00
Thomas Harte
e5e988b28f Adds an incorrect assumed-pixel-mode serialiser.
This actually shows something a bit like the Enterprise boot logo.
2021-06-15 22:31:50 -04:00
Thomas Harte
e94e051c87 Adds an allocator for pixels. 2021-06-15 22:03:41 -04:00
Thomas Harte
5fc91effb5 Corrects top border. 2021-06-15 21:48:11 -04:00
Thomas Harte
6c9dacbe89 Stabilises display, albeit that top border mode now appears to be off. 2021-06-15 21:31:07 -04:00
Thomas Harte
6a7eb832cc Introduces almost-stable block-level frame generation. 2021-06-15 20:55:58 -04:00
Thomas Harte
60cf8486bb Makes a genuine attempt at real line counting.
No output yet though.
2021-06-15 18:57:14 -04:00
Thomas Harte
90b8163d54 Add exposition. 2021-06-15 17:44:39 -04:00
Thomas Harte
a1e4389f63 Give Nick some RAM to inspect and just enough sense to know when it should reload its line parameter table. 2021-06-15 17:43:13 -04:00
Thomas Harte
440b11708b Adds an unused CRT. 2021-06-14 23:11:48 -04:00
Thomas Harte
f90dce5c54 Take a guess at Nick timing. 2021-06-14 22:56:26 -04:00
Thomas Harte
606c7709cf Shims in enough to get the Z80 to run perpetually.
Notably I don't actually currently know how the interrupt registers work, but getting some sort of display running might be the first order of business.
2021-06-14 22:28:31 -04:00
Thomas Harte
1d1e6d1820 Adds a shell of a Nick. 2021-06-14 22:19:25 -04:00
Thomas Harte
3eb4dd74a2 Exposes memory control.
Machine now runs as far as trying to interact with Nick.
2021-06-14 21:45:12 -04:00
Thomas Harte
853914480c Revised guess; there's a jump to C02E almost immediately. 2021-06-14 21:40:19 -04:00
Thomas Harte
fe04410681 Merge branch 'master' into Enterprise 2021-06-14 21:30:49 -04:00
Thomas Harte
1f686c4e6b Add missing AppleIIOptionsPanel class. 2021-06-14 21:30:30 -04:00
Thomas Harte
2a2ac1227b Makes first attempt at giving the Z80 something to do. 2021-06-14 21:29:56 -04:00
Thomas Harte
b5340c8f74 Correct syntax. 2021-06-14 21:17:35 -04:00
Thomas Harte
196c4dcdd9 Adds an appropriate ROM request. 2021-06-14 21:17:09 -04:00
Thomas Harte
c5a86f0ef7 Add Enterprise parts of the static analyser. 2021-06-14 21:11:06 -04:00
Thomas Harte
88f2a2940b Add Enterprise source paths. 2021-06-14 21:07:35 -04:00
Thomas Harte
26b019a4d4 Removes assumption that all machines produce audio. 2021-06-14 21:02:55 -04:00
Thomas Harte
5f7b3ae313 Adds bare minimum to get a non-functional machine. 2021-06-14 21:02:40 -04:00
Thomas Harte
61c127ed2e Adds Enterprise as a File -> New... machine.
And, similarly, exposes it for the route used by SDL.
2021-06-14 20:55:39 -04:00
Thomas Harte
333981e2a7 Merge branch 'master' into Enterprise 2021-06-13 22:22:44 -04:00
Thomas Harte
8e0a6df03b Merge branch 'master' into Enterprise 2021-06-10 21:41:57 -04:00
Thomas Harte
2a6e9c5e8a Add readme for Enterprise ROM names. 2021-05-30 19:28:26 -04:00
61 changed files with 4147 additions and 324 deletions

View File

@@ -19,6 +19,7 @@ enum class Machine {
AtariST,
ColecoVision,
Electron,
Enterprise,
Macintosh,
MasterSystem,
MSX,

View File

@@ -0,0 +1,81 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/06/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/FAT.hpp"
#include <algorithm>
namespace {
bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
return std::equal(
lhs.begin(), lhs.end(),
rhs.begin(), rhs.end(),
[] (char l, char r) {
return tolower(l) == tolower(r);
});
}
}
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// Otherwise, assume a return will happen.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Enterprise::Target;
auto *const target = new Target;
target->media = media;
// Always require a BASIC.
target->basic_version = Target::BASICVersion::Any;
// Inspect any supplied disks.
if(!media.disks.empty()) {
// DOS will be needed.
target->dos = Target::DOS::EXDOS;
// Grab the volume information, which includes the root directory.
auto volume = Storage::Disk::FAT::GetVolume(media.disks.front());
if(volume) {
// If there's an EXDOS.INI then this disk should be able to boot itself.
// If not but if there's only one .COM or .BAS, automatically load that.
// Failing that, issue a :DIR and give the user a clue as to how to load.
const Storage::Disk::FAT::File *selected_file = nullptr;
bool has_exdos_ini = false;
bool did_pick_file = false;
for(const auto &file: (*volume).root_directory) {
if(insensitive_equal(file.name, "exdos") && insensitive_equal(file.extension, "ini")) {
has_exdos_ini = true;
break;
}
if(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas")) {
did_pick_file = !selected_file;
selected_file = &file;
}
}
if(!has_exdos_ini) {
if(did_pick_file) {
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
} else {
target->loading_command = ":dir\n";
}
}
}
}
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

@@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/06/2021.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Enterprise {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */

View File

@@ -0,0 +1,57 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Enterprise_Target_h
#define Analyser_Static_Enterprise_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Enterprise {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
ReflectableEnum(DOS, EXDOS, None);
ReflectableEnum(Speed, FourMHz, SixMHz);
Model model = Model::Enterprise128;
EXOSVersion exos_version = EXOSVersion::Any;
BASICVersion basic_version = BASICVersion::None;
DOS dos = DOS::None;
Speed speed = Speed::FourMHz;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Enterprise) {
if(needs_declare()) {
AnnounceEnum(Model);
AnnounceEnum(EXOSVersion);
AnnounceEnum(BASICVersion);
AnnounceEnum(DOS);
AnnounceEnum(Speed);
DeclareField(model);
DeclareField(exos_version);
DeclareField(basic_version);
DeclareField(dos);
DeclareField(speed);
}
}
};
}
}
}
#endif /* Analyser_Static_Enterprise_Target_h */

View File

@@ -23,6 +23,7 @@
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "Enterprise/StaticAnalyser.hpp"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
@@ -43,9 +44,9 @@
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
@@ -147,7 +148,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format( "hfe",
@@ -157,6 +158,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
@@ -256,6 +258,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
Append(Coleco);
Append(Commodore);
Append(DiskII);
Append(Enterprise);
Append(Macintosh);
Append(MSX);
Append(Oric);

View File

@@ -213,7 +213,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.
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
*/
forceinline Cycles divide_cycles(const Cycles &divisor) {
const HalfCycles half_divisor = HalfCycles(divisor);

View File

@@ -103,9 +103,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
}
if constexpr (has_sequence_points<T>::value) {
time_until_event_ -= rhs;
time_until_event_ -= rhs * multiplier;
if(time_until_event_ <= LocalTimeScale(0)) {
time_overrun_ = time_until_event_;
time_overrun_ = time_until_event_ / divider;
flush();
update_sequence_point();
return true;
@@ -145,13 +145,21 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the amount of time since the object was last flushed, in the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
if constexpr (divider == 1) {
return time_since_update_;
}
return TargetTimeScale(time_since_update_.as_integral() / divider);
}
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
/// converted to the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
if constexpr (divider == 1) {
return time_since_update_ + offset;
}
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
}
/// Flushes all accumulated time.
///
/// This does not affect this actor's record of when the next sequence point will occur.
@@ -185,7 +193,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
/// supports sequence points; @c LocalTimeScale() otherwise.
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
return time_until_event_;
return time_until_event_ / divider;
}
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
@@ -196,10 +204,43 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
return rhs >= time_until_event_;
}
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
/// after @c duration, if that delay were to occur in @c offset units of time from now.
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
// A 1:1 mapping is easy.
if constexpr (multiplier == 1 && divider == 1) {
return duration;
}
// Work out when this query is placed, and the time to which it relates
const auto base = time_since_update_ + offset * divider;
const auto target = base + duration * divider;
// Figure out the number of whole input steps that is required to get
// past target, and subtract the number of whole input steps necessary
// to get to base.
const auto steps_to_base = base.as_integral() / multiplier;
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
return LocalTimeScale(steps_to_target - steps_to_base);
}
/// Updates this template's record of the next sequence point.
void update_sequence_point() {
if constexpr (has_sequence_points<T>::value) {
time_until_event_ = object_.get_next_sequence_point();
// Keep a fast path where no conversions will be applied; if conversions are
// going to be applied then do a direct max -> max translation rather than
// allowing the arithmetic to overflow.
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
time_until_event_ = object_.get_next_sequence_point();
} else {
const auto time = object_.get_next_sequence_point();
if(time == TargetTimeScale::max()) {
time_until_event_ = LocalTimeScale::max();
} else {
time_until_event_ = time * divider;
}
}
assert(time_until_event_ > LocalTimeScale(0));
}
}

View File

@@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) {
goto test_type1_type;
begin_type1_spin_up:
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
if((command_&0x08) || get_drive().get_motor_on()) {
set_motor_on(true);
goto test_type1_type;
}
SPIN_UP();
test_type1_type:
@@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
if((command_&0x08) && has_motor_on_line()) goto test_type2_delay;
if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay;
if(!has_motor_on_line() && !has_head_load_line()) {
if(has_motor_on_line()) set_motor_on(true);
goto test_type2_delay;
}
if(has_motor_on_line()) goto begin_type2_spin_up;
goto begin_type2_load_head;

View File

@@ -9,6 +9,7 @@
#ifndef Joystick_hpp
#define Joystick_hpp
#include <cstddef>
#include <vector>
namespace Inputs {

View File

@@ -8,6 +8,8 @@
#include "Keyboard.hpp"
#include <cstddef>
using namespace Inputs;
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {

View File

@@ -9,7 +9,7 @@
#ifndef CachingExecutor_hpp
#define CachingExecutor_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <array>
#include <cstdint>

View File

@@ -9,7 +9,7 @@
#ifndef Disassembler_hpp
#define Disassembler_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <list>
#include <map>

View File

@@ -271,7 +271,7 @@ class Keyboard {
/// so this may not be valid prior to Mode::PerformingCommand.
int command_ = 0;
/// Populated during PerformingCommand as the response to the most-recently-received command, this
/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
/// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
/// but not afterwards.
int response_ = 0;

View File

@@ -0,0 +1,337 @@
//
// Dave.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Dave.hpp"
using namespace Enterprise::Dave;
// MARK: - Audio generator
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Audio::write(uint16_t address, uint8_t value) {
address &= 0x1f;
audio_queue_.defer([address, value, this] {
switch(address) {
case 0: case 2: case 4:
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
break;
case 1: case 3: case 5:
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
channels_[address >> 1].distortion = Channel::Distortion((value >> 4)&3);
channels_[address >> 1].high_pass = value & 0x40;
channels_[address >> 1].ring_modulate = value & 0x80;
break;
case 6:
noise_.frequency = Noise::Frequency(value&3);
noise_.polynomial = Noise::Polynomial((value >> 2)&3);
noise_.swap_polynomial = value & 0x10;
noise_.low_pass = value & 0x20;
noise_.high_pass = value & 0x40;
noise_.ring_modulate = value & 0x80;
break;
case 7:
channels_[0].sync = value & 0x01;
channels_[1].sync = value & 0x02;
channels_[2].sync = value & 0x04;
use_direct_output_[0] = value & 0x08;
use_direct_output_[1] = value & 0x10;
// Interrupt bits are handled separately.
break;
case 8: case 9: case 10:
channels_[address - 8].amplitude[0] = value & 0x3f;
break;
case 12: case 13: case 14:
channels_[address - 12].amplitude[1] = value & 0x3f;
break;
case 11: noise_.amplitude[0] = value & 0x3f; break;
case 15: noise_.amplitude[1] = value & 0x3f; break;
case 31:
global_divider_reload_ = 2 + ((value >> 1)&1);
break;
}
});
}
void Audio::set_sample_volume_range(int16_t range) {
audio_queue_.defer([range, this] {
volume_ = range / (63*4);
});
}
void Audio::update_channel(int c) {
auto output = channels_[c].output & 1;
channels_[c].output <<= 1;
if(channels_[c].sync) {
channels_[c].count = channels_[c].reload;
output = 0;
} else {
if(!channels_[c].count) {
channels_[c].count = channels_[c].reload;
if(channels_[c].distortion == Channel::Distortion::None)
output ^= 1;
else
output = poly_state_[int(channels_[c].distortion)];
} else {
--channels_[c].count;
}
if(channels_[c].high_pass && (channels_[(c+1)%3].output&3) == 2) {
output = 0;
}
}
// Ring modulation applies even when sync is enabled, per SIDBasic.
if(channels_[c].ring_modulate) {
output = ~(output ^ channels_[(c+2)%3].output) & 1;
}
channels_[c].output |= output;
}
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
int16_t output_level[2];
size_t c = 0;
while(c < number_of_samples) {
// I'm unclear on the details of the time division multiplexing so,
// for now, just sum the outputs.
output_level[0] =
volume_ *
(use_direct_output_[0] ?
channels_[0].amplitude[0]
: (
channels_[0].amplitude[0] * (channels_[0].output & 1) +
channels_[1].amplitude[0] * (channels_[1].output & 1) +
channels_[2].amplitude[0] * (channels_[2].output & 1) +
noise_.amplitude[0] * noise_.final_output
));
output_level[1] =
volume_ *
(use_direct_output_[1] ?
channels_[0].amplitude[1]
: (
channels_[0].amplitude[1] * (channels_[0].output & 1) +
channels_[1].amplitude[1] * (channels_[1].output & 1) +
channels_[2].amplitude[1] * (channels_[2].output & 1) +
noise_.amplitude[1] * noise_.final_output
));
while(global_divider_ && c < number_of_samples) {
--global_divider_;
*reinterpret_cast<uint32_t *>(&target[c << 1]) = *reinterpret_cast<uint32_t *>(output_level);
++c;
}
global_divider_ = global_divider_reload_;
if(!global_divider_) {
global_divider_ = global_divider_reload_;
}
poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next();
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next();
if(noise_.swap_polynomial) {
poly_state_[int(Channel::Distortion::SevenBit)] = poly_state_[int(Channel::Distortion::None)];
}
// Update tone channels.
update_channel(0);
update_channel(1);
update_channel(2);
// Update noise channel.
// Step 1: decide whether there is a tick to apply.
bool noise_tick = false;
if(noise_.frequency == Noise::Frequency::DivideByFour) {
if(!noise_.count) {
noise_tick = true;
noise_.count = 3;
} else {
--noise_.count;
}
} else {
noise_tick = (channels_[int(noise_.frequency) - 1].output&3) == 2;
}
// Step 2: tick if necessary.
int noise_output = noise_.output & 1;
noise_.output <<= 1;
if(noise_tick) {
switch(noise_.polynomial) {
case Noise::Polynomial::SeventeenBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly17_.next());
break;
case Noise::Polynomial::FifteenBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly15_.next());
break;
case Noise::Polynomial::ElevenBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly11_.next());
break;
case Noise::Polynomial::NineBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly9_.next());
break;
}
noise_output = poly_state_[int(Channel::Distortion::None)];
}
noise_.output |= noise_output;
// Low pass: sample channel 2 on downward transitions of the prima facie output.
if(noise_.low_pass && (noise_.output & 3) == 2) {
noise_.output = (noise_.output & ~1) | (channels_[2].output & 1);
}
// Apply noise high-pass.
if(noise_.high_pass && (channels_[0].output & 3) == 2) {
noise_.output &= ~1;
}
// Update noise ring modulation, if any.
if(noise_.ring_modulate) {
noise_.final_output = !((noise_.output ^ channels_[1].output) & 1);
} else {
noise_.final_output = noise_.output & 1;
}
}
}
// MARK: - Interrupt source
uint8_t TimedInterruptSource::get_new_interrupts() {
const uint8_t result = interrupts_;
interrupts_ = 0;
return result;
}
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
address &= 0x1f;
switch(address) {
default: break;
case 0: case 2:
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
break;
case 1: case 3:
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
break;
case 7:
channels_[0].sync = value & 0x01;
channels_[1].sync = value & 0x02;
rate_ = InterruptRate((value >> 5) & 3);
break;
case 31:
global_divider_ = Cycles(2 + ((value >> 1)&1));
break;
}
}
void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement) {
if(channels_[c].sync) {
channels_[c].value = channels_[c].reload;
} else {
if(decrement <= channels_[c].value) {
channels_[c].value -= decrement;
} else {
// The decrement is greater than the current value, therefore
// there'll be at least one flip.
//
// After decreasing the decrement by the current value + 1,
// it'll be clear how many decrements are left after reload.
//
// Dividing that by the number of decrements necessary for a
// flip will provide the total number of flips.
const int decrements_after_flip = decrement - (channels_[c].value + 1);
const int num_flips = 1 + decrements_after_flip / (channels_[c].reload + 1);
// If this is a linked channel, set the interrupt mask if a transition
// from high to low is amongst the included flips.
if(is_linked && num_flips + channels_[c].level >= 2) {
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
programmable_level_ ^= true;
}
channels_[c].level ^= (num_flips & 1);
// Apply the modulo number of decrements to the reload value to
// figure out where things stand now.
channels_[c].value = channels_[c].reload - decrements_after_flip % (channels_[c].reload + 1);
}
}
}
void TimedInterruptSource::run_for(Cycles duration) {
// Determine total number of ticks.
run_length_ += duration;
const Cycles cycles = run_length_.divide(global_divider_);
if(cycles == Cycles(0)) {
return;
}
// Update the two-second counter, from which the 1Hz, 50Hz and 1000Hz signals
// are derived.
const int previous_counter = two_second_counter_;
two_second_counter_ = (two_second_counter_ + cycles.as<int>()) % 500'000;
// Check for a 1Hz rollover.
if(previous_counter / 250'000 != two_second_counter_ / 250'000) {
interrupts_ |= uint8_t(Interrupt::OneHz);
}
// Check for 1kHz or 50Hz rollover;
switch(rate_) {
default: break;
case InterruptRate::OnekHz:
if(previous_counter / 250 != two_second_counter_ / 250) {
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
programmable_level_ ^= true;
}
break;
case InterruptRate::FiftyHz:
if(previous_counter / 5'000 != two_second_counter_ / 5'000) {
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
programmable_level_ ^= true;
}
break;
}
// Update the two tone channels.
update_channel(0, rate_ == InterruptRate::ToneGenerator0, cycles.as<int>());
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
}
Cycles TimedInterruptSource::get_next_sequence_point() const {
// Since both the 1kHz and 50Hz timers are integer dividers of the 1Hz timer, there's no need
// to factor that one in when determining the next sequence point for either of those.
switch(rate_) {
default:
case InterruptRate::OnekHz: return Cycles(250 - (two_second_counter_ % 250));
case InterruptRate::FiftyHz: return Cycles(5000 - (two_second_counter_ % 5000));
case InterruptRate::ToneGenerator0:
case InterruptRate::ToneGenerator1: {
const auto &channel = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)];
const int cycles_until_interrupt = channel.value + 1 + (!channel.level) * (channel.reload + 1);
return Cycles(std::min(
250'000 - (two_second_counter_ % 250'000),
cycles_until_interrupt
));
}
}
}
uint8_t TimedInterruptSource::get_divider_state() {
return uint8_t((two_second_counter_ / 250'000) * 4 | programmable_level_);
}

View File

@@ -0,0 +1,188 @@
//
// Dave.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Dave_hpp
#define Dave_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Numeric/LFSR.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace Enterprise {
namespace Dave {
enum class Interrupt: uint8_t {
VariableFrequency = 0x02,
OneHz = 0x08,
Nick = 0x20,
};
/*!
Models the audio-production subset of Dave's behaviour.
*/
class Audio: public Outputs::Speaker::SampleSource {
public:
Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue);
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
/// already identified this write as being to an audio register.
void write(uint16_t address, uint8_t value);
// MARK: - SampleSource.
void set_sample_volume_range(int16_t range);
static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound.
void get_samples(std::size_t number_of_samples, int16_t *target);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
// Global divider (i.e. 8MHz/12Mhz switch).
uint8_t global_divider_;
uint8_t global_divider_reload_ = 2;
// Tone channels.
struct Channel {
// User-set values.
uint16_t reload = 0;
bool high_pass = false;
bool ring_modulate = false;
enum class Distortion {
None = 0,
FourBit = 1,
FiveBit = 2,
SevenBit = 3,
} distortion = Distortion::None;
uint8_t amplitude[2]{};
bool sync = false;
// Current state.
uint16_t count = 0;
int output = 0;
} channels_[3];
void update_channel(int);
// Noise channel.
struct Noise {
// User-set values.
uint8_t amplitude[2]{};
enum class Frequency {
DivideByFour,
ToneChannel0,
ToneChannel1,
ToneChannel2,
} frequency = Frequency::DivideByFour;
enum class Polynomial {
SeventeenBit,
FifteenBit,
ElevenBit,
NineBit
} polynomial = Polynomial::SeventeenBit;
bool swap_polynomial = false;
bool low_pass = false;
bool high_pass = false;
bool ring_modulate = false;
// Current state.
int count = 0;
int output = 0;
bool final_output = false;
} noise_;
void update_noise();
bool use_direct_output_[2]{};
// Global volume, per SampleSource obligations.
int16_t volume_ = 0;
// Polynomials that are always running.
Numeric::LFSRv<0xc> poly4_;
Numeric::LFSRv<0x14> poly5_;
Numeric::LFSRv<0x60> poly7_;
// The selectable, noise-related polynomial.
Numeric::LFSRv<0x110> poly9_;
Numeric::LFSRv<0x500> poly11_;
Numeric::LFSRv<0x6000> poly15_;
Numeric::LFSRv<0x12000> poly17_;
// Current state of the active polynomials.
uint8_t poly_state_[4];
};
/*!
Provides Dave's timed interrupts — those that are provided at 1 kHz,
50 Hz or according to the rate of tone generators 0 or 1, plus the fixed
1 Hz interrupt.
*/
class TimedInterruptSource {
public:
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
/// already identified this write as being to an audio register.
void write(uint16_t address, uint8_t value);
/// Returns a bitmask of interrupts that have become active since
/// the last time this method was called; flags are as defined in
/// @c Enterprise::Dave::Interrupt
uint8_t get_new_interrupts();
/// Returns the current high or low states of the inputs that trigger
/// the interrupts modelled here, as a bit mask compatible with that
/// exposed by Dave as the register at 0xb4.
uint8_t get_divider_state();
/// Advances the interrupt source.
void run_for(Cycles);
/// @returns The amount of time from now until the earliest that
/// @c get_new_interrupts() _might_ have new interrupts to report.
Cycles get_next_sequence_point() const;
private:
static constexpr Cycles clock_rate{250000};
static constexpr Cycles half_clock_rate{125000};
// Global divider (i.e. 8MHz/12Mhz switch).
Cycles global_divider_ = Cycles(2);
Cycles run_length_;
// Interrupts that have fired since get_new_interrupts()
// was last called.
uint8_t interrupts_ = 0;
// A counter for the 1Hz interrupt.
int two_second_counter_ = 0;
// A counter specific to the 1kHz and 50Hz timers, if in use.
enum class InterruptRate {
OnekHz,
FiftyHz,
ToneGenerator0,
ToneGenerator1,
} rate_ = InterruptRate::OnekHz;
bool programmable_level_ = false;
// A local duplicate of the counting state of the first two audio
// channels, maintained in case either of those is used as an
// interrupt source.
struct Channel {
int value = 100, reload = 100;
bool sync = false;
bool level = false;
} channels_[2];
void update_channel(int c, bool is_linked, int decrement);
};
}
}
#endif /* Dave_hpp */

View File

@@ -0,0 +1,78 @@
//
// EXDos.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "EXDos.hpp"
// TODO: disk_did_change_ should be on the drive. Some drives report it.
using namespace Enterprise;
EXDos::EXDos() : WD1770(P1770) {
emplace_drives(4, 8000000, 300, 2);
set_control_register(0x00);
}
void EXDos::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
get_drive(drive).set_disk(disk);
disk_did_change_ = true;
}
// Documentation for the control register:
//
// Write:
// b7 in use (???)
// b6 disk change reset
// b5 0 = double density, 1 = single density
// b4 side 1 select
// b3, b3, b1, b0 select drive 3, 2, 1, 0
//
// Read:
// b7 data request from WD1770
// b6 disk change
// b5, b4, b3, b2: not used
// b1 interrupt request from WD1770
// b0 drive ready
void EXDos::set_control_register(uint8_t control) {
if(control & 0x40) disk_did_change_ = false;
set_is_double_density(!(control & 0x20));
// Set side.
const int head = (control >> 4) & 1;
for(size_t c = 0; c < 4; c++) {
get_drive(c).set_head(head);
}
// Select drive, ensuring handover of the motor-on state.
const bool motor_state = get_drive().get_motor_on();
for_all_drives([] (Storage::Disk::Drive &drive, size_t) {
drive.set_motor_on(false);
});
set_drive(control & 0xf);
get_drive().set_motor_on(motor_state);
}
uint8_t EXDos::get_control_register() {
const uint8_t status =
(get_data_request_line() ? 0x80 : 0x00) |
(disk_did_change_ ? 0x40 : 0x00) |
(get_interrupt_request_line() ? 0x02 : 0x00) |
(get_drive().get_is_ready() ? 0x01 : 0x00);
return status;
}
void EXDos::set_motor_on(bool on) {
get_drive().set_motor_on(on);
}
void EXDos::set_activity_observer(Activity::Observer *observer) {
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
});
}

View File

@@ -0,0 +1,36 @@
//
// EXDos.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef EXDos_hpp
#define EXDos_hpp
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
namespace Enterprise {
class EXDos final : public WD::WD1770 {
public:
EXDos();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_control_register(uint8_t control);
uint8_t get_control_register();
void set_activity_observer(Activity::Observer *observer);
private:
bool disk_did_change_ = false;
void set_motor_on(bool on) override;
};
}
#endif /* EXDos_hpp */

View File

@@ -0,0 +1,765 @@
//
// Enterprise.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Enterprise.hpp"
#include "Dave.hpp"
#include "EXDos.hpp"
#include "Keyboard.hpp"
#include "Nick.hpp"
#include "../MachineTypes.hpp"
#include "../Utility/Typer.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/Z80/Z80.hpp"
#define LOG_PREFIX "[Enterprise] "
#include "../../Outputs/Log.hpp"
namespace Enterprise {
/*
Notes to self on timing:
Nick divides each line into 57 windows; each window lasts 16 cycles and dedicates the
first 10 of those to VRAM accesses, leaving the final six for a Z80 video RAM access
if one has been requested.
The Z80 has a separate, asynchronous 4Mhz clock. That's that.
The documentation is also very forward in emphasising that Nick generates phaselocked
(i.e. in-phase) PAL video.
So: 57*16 = 912 cycles/line.
A standard PAL line lasts 64µs and during that time outputs 283.7516 colour cycles.
I shall _guess_ that the Enterprise stretches each line to 284 colour cycles rather than
reducing it to 283.
Therefore 912 cycles occurs in 284/283.7516 * 64 µs.
So one line = 181760000 / 2837516 µs = 45440000 / 709379 µs
=> one cycle = 45440000 / 709379*912 = 45440000 / 646953648 = 2840000 / 40434603 µs
=> clock rate of 40434603 / 2840000 Mhz
And, therefore, the ratio to a 4Mhz Z80 clock is:
40434603 / (2840000 * 4)
= 40434603 / 11360000
i.e. roughly 3.55 Nick cycles per Z80 cycle.
If that's true then the 6-cycle window is around 1.69 Z80 cycles long. Given that the Z80
clock in an Enterprise can be stopped in half-cycle increments only, the Z80 can only be
guaranteed to have around a 1.19 cycle minimum for its actual access. I'm therefore further
postulating that the clock stoppage takes place so as to align the final cycle of a relevant
access over the available window.
*/
template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
public Activity::Source,
public Configurable::Device,
public CPU::Z80::BusHandler,
public Machine,
public MachineTypes::AudioProducer,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine,
public Utility::TypeRecipient<CharacterMapper> {
private:
constexpr uint8_t min_ram_slot(const Analyser::Static::Enterprise::Target &target) {
size_t ram_size = 128*1024;
switch(target.model) {
case Analyser::Static::Enterprise::Target::Model::Enterprise64: ram_size = 64*1024; break;
case Analyser::Static::Enterprise::Target::Model::Enterprise128: ram_size = 128*1024; break;
case Analyser::Static::Enterprise::Target::Model::Enterprise256: ram_size = 256*1024; break;
}
return uint8_t(0x100 - ram_size / 0x4000);
}
static constexpr double clock_rate = is_6mhz ? 6'000'000.0 : 4'000'000.0;
using NickType =
std::conditional_t<is_6mhz,
JustInTimeActor<Nick, HalfCycles, 13478201, 5680000>,
JustInTimeActor<Nick, HalfCycles, 40434603, 11360000>>;
public:
ConcreteMachine(const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
min_ram_slot_(min_ram_slot(target)),
z80_(*this),
nick_(ram_.end() - 65536),
dave_audio_(audio_queue_),
speaker_(dave_audio_) {
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
set_clock_rate(clock_rate);
speaker_.set_input_rate(float(clock_rate) / float(dave_divider));
ROM::Request request;
using Target = Analyser::Static::Enterprise::Target;
// Pick one or more EXOS ROMs.
switch(target.exos_version) {
case Target::EXOSVersion::v10: request = request && ROM::Request(ROM::Name::EnterpriseEXOS10); break;
case Target::EXOSVersion::v20: request = request && ROM::Request(ROM::Name::EnterpriseEXOS20); break;
case Target::EXOSVersion::v21: request = request && ROM::Request(ROM::Name::EnterpriseEXOS21); break;
case Target::EXOSVersion::v23: request = request && ROM::Request(ROM::Name::EnterpriseEXOS23); break;
case Target::EXOSVersion::Any:
request =
request && (
ROM::Request(ROM::Name::EnterpriseEXOS10) || ROM::Request(ROM::Name::EnterpriseEXOS20) ||
ROM::Request(ROM::Name::EnterpriseEXOS21) || ROM::Request(ROM::Name::EnterpriseEXOS23)
);
break;
default: break;
}
// Similarly pick one or more BASIC ROMs.
switch(target.basic_version) {
case Target::BASICVersion::v10:
request = request && (
ROM::Request(ROM::Name::EnterpriseBASIC10) ||
(ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2))
);
break;
case Target::BASICVersion::v11:
request = request && (
ROM::Request(ROM::Name::EnterpriseBASIC11) ||
ROM::Request(ROM::Name::EnterpriseBASIC11Suffixed)
);
case Target::BASICVersion::v21:
request = request && ROM::Request(ROM::Name::EnterpriseBASIC21);
break;
case Target::BASICVersion::Any:
request =
request && (
ROM::Request(ROM::Name::EnterpriseBASIC10) ||
(ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2)) ||
ROM::Request(ROM::Name::EnterpriseBASIC11) ||
ROM::Request(ROM::Name::EnterpriseBASIC21)
);
break;
default: break;
}
// Possibly add in a DOS.
switch(target.dos) {
case Target::DOS::EXDOS: request = request && ROM::Request(ROM::Name::EnterpriseEXDOS); break;
default: break;
}
// Get and validate ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
// Extract the appropriate EXOS ROM.
exos_.fill(0xff);
for(const auto rom_name: { ROM::Name::EnterpriseEXOS10, ROM::Name::EnterpriseEXOS20, ROM::Name::EnterpriseEXOS21, ROM::Name::EnterpriseEXOS23 }) {
const auto exos = roms.find(rom_name);
if(exos != roms.end()) {
memcpy(exos_.data(), exos->second.data(), std::min(exos_.size(), exos->second.size()));
break;
}
}
// Extract the appropriate BASIC ROM[s] (if any).
basic_.fill(0xff);
bool has_basic = false;
for(const auto rom_name: { ROM::Name::EnterpriseBASIC10, ROM::Name::EnterpriseBASIC11, ROM::Name::EnterpriseBASIC11Suffixed, ROM::Name::EnterpriseBASIC21 }) {
const auto basic = roms.find(rom_name);
if(basic != roms.end()) {
memcpy(basic_.data(), basic->second.data(), std::min(basic_.size(), basic->second.size()));
has_basic = true;
break;
}
}
if(!has_basic) {
const auto basic1 = roms.find(ROM::Name::EnterpriseBASIC10Part1);
const auto basic2 = roms.find(ROM::Name::EnterpriseBASIC10Part2);
if(basic1 != roms.end() && basic2 != roms.end()) {
memcpy(&basic_[0x0000], basic1->second.data(), std::min(size_t(8192), basic1->second.size()));
memcpy(&basic_[0x2000], basic2->second.data(), std::min(size_t(8192), basic2->second.size()));
}
}
// Extract the appropriate DOS ROMs.
epdos_rom_.fill(0xff);
const auto epdos = roms.find(ROM::Name::EnterpriseEPDOS);
if(epdos != roms.end()) {
memcpy(epdos_rom_.data(), epdos->second.data(), std::min(epdos_rom_.size(), epdos->second.size()));
}
exdos_rom_.fill(0xff);
const auto exdos = roms.find(ROM::Name::EnterpriseEXDOS);
if(exdos != roms.end()) {
memcpy(exdos_rom_.data(), exdos->second.data(), std::min(exdos_rom_.size(), exdos->second.size()));
}
// Seed key state.
clear_all_keys();
// Take a reasonable guess at the initial memory configuration:
// put EXOS into the first bank since this is a Z80 and therefore
// starts from address 0; the third instruction in EXOS is a jump
// to $c02e so it's reasonable to assume EXOS is in the highest bank
// too, and it appears to act correctly if it's the first 16kb that's
// in the highest bank. From there I guess: all banks are initialised
// to 0.
page<0>(0x00);
page<1>(0x00);
page<2>(0x00);
page<3>(0x00);
// Pass on any media.
insert_media(target.media);
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
// Ensure the splash screen is automatically skipped if any media has been provided.
if(!target.media.empty()) {
should_skip_splash_screen_ = !target.media.empty();
typer_delay_ = 2;
}
}
~ConcreteMachine() {
audio_queue_.flush();
}
// MARK: - Z80::BusHandler.
forceinline void advance_nick(HalfCycles duration) {
if(nick_ += duration) {
const auto nick = nick_.last_valid();
const bool nick_interrupt_line = nick->get_interrupt_line();
if(nick_interrupt_line && !previous_nick_interrupt_line_) {
set_interrupts(uint8_t(Dave::Interrupt::Nick), nick_.last_sequence_point_overrun());
}
previous_nick_interrupt_line_ = nick_interrupt_line;
}
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
// Calculate an access penalty, if applicable.
//
// Rule applied here, which is slightly inferred:
//
// Non-video reads and writes are delayed by exactly a cycle or not delayed at all,
// depending on the programmer's configuration of Dave.
//
// Video reads and writes, and Nick port accesses, are delayed so that the last
// clock cycle of the machine cycle falls wholly inside the designated Z80 access
// window, per Nick.
//
// The switch statement below just attempts to implement that logic.
//
HalfCycles penalty;
switch(cycle.operation) {
default: break;
// For non-video pauses, insert during the initial part of the bus cycle.
case PartialMachineCycle::ReadStart:
case PartialMachineCycle::WriteStart:
if(!is_video_[address >> 14] && wait_mode_ == WaitMode::OnAllAccesses) {
penalty = dave_delay_;
}
break;
case PartialMachineCycle::ReadOpcodeStart: {
if(is_video_[address >> 14]) {
// Query Nick for the amount of delay that would occur with one cycle left
// in this read opcode.
const auto delay_time = nick_.time_since_flush(HalfCycles(2));
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
penalty = nick_.back_map(delay, delay_time);
} else if(wait_mode_ != WaitMode::None) {
penalty = dave_delay_;
}
} break;
// Video pauses: insert right at the end of the bus cycle.
case PartialMachineCycle::Write:
// Ensure all video that should have been collected prior to
// this write has been.
if(is_video_[address >> 14]) {
nick_.flush();
}
[[fallthrough]];
case PartialMachineCycle::Read:
if(is_video_[address >> 14]) {
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
// cycles from now (i.e. with one cycle left to run).
const auto delay_time = nick_.time_since_flush(HalfCycles(1));
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
penalty = nick_.back_map(delay, delay_time);
}
break;
case PartialMachineCycle::Input:
case PartialMachineCycle::Output: {
if((address & 0xf0) == 0x80) {
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
// cycles from now (i.e. with one cycle left to run).
const auto delay_time = nick_.time_since_flush(HalfCycles(1));
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
penalty = nick_.back_map(delay, delay_time);
}
}
}
const HalfCycles full_length = cycle.length + penalty;
time_since_audio_update_ += full_length;
advance_nick(full_length);
if(dave_timer_ += full_length) {
set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun());
}
// The WD/etc runs at a nominal 8Mhz.
if constexpr (has_disk_controller) {
exdos_.run_for(Cycles(full_length.as_integral()));
}
switch(cycle.operation) {
default: break;
case PartialMachineCycle::Interrupt:
*cycle.value = 0xff;
break;
case PartialMachineCycle::Input:
switch(address & 0xff) {
default:
LOG("Unhandled input from " << PADHEX(2) << (address & 0xff));
*cycle.value = 0xff;
break;
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
if constexpr (has_disk_controller) {
*cycle.value = exdos_.read(address);
} else {
*cycle.value = 0xff;
}
break;
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
if constexpr (has_disk_controller) {
*cycle.value = exdos_.get_control_register();
} else {
*cycle.value = 0xff;
}
break;
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: case 0x8b:
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
*cycle.value = nick_->read();
break;
case 0xb0: *cycle.value = pages_[0]; break;
case 0xb1: *cycle.value = pages_[1]; break;
case 0xb2: *cycle.value = pages_[2]; break;
case 0xb3: *cycle.value = pages_[3]; break;
case 0xb4:
*cycle.value =
(nick_->get_interrupt_line() ? 0x10 : 0x00) |
dave_timer_->get_divider_state() |
interrupt_state_;
break;
case 0xb5:
if(active_key_line_ < key_lines_.size()) {
*cycle.value = key_lines_[active_key_line_];
} else {
*cycle.value = 0xff;
}
break;
case 0xb6: {
// TODO: selected keyboard row, 0 to 9, should return one bit of joystick
// input. That being the case:
//
// b0: joystick input
// b1, b2: unused (in theory read from control port, but not used by any hardware)
// b3: 0 = printer ready; 1 = not ready
// b4: serial, data in
// b5: serial, status in
// b6: tape input volume level, 0 = high, 1 = low
// b7: tape data input
*cycle.value = 0xff;
} break;
}
break;
case PartialMachineCycle::Output:
switch(address & 0xff) {
default:
LOG("Unhandled output: " << PADHEX(2) << *cycle.value << " to " << PADHEX(2) << (address & 0xff));
break;
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
if constexpr(has_disk_controller) {
exdos_.write(address, *cycle.value);
}
break;
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
if constexpr(has_disk_controller) {
exdos_.set_control_register(*cycle.value);
}
break;
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: case 0x8b:
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
nick_->write(address, *cycle.value);
break;
case 0xb0: page<0>(*cycle.value); break;
case 0xb1: page<1>(*cycle.value); break;
case 0xb2: page<2>(*cycle.value); break;
case 0xb3: page<3>(*cycle.value); break;
case 0xbf:
switch((*cycle.value >> 2)&3) {
default: wait_mode_ = WaitMode::None; break;
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
case 1: wait_mode_ = WaitMode::OnM1; break;
}
// Dave delays (i.e. those affecting memory areas not associated with Nick)
// are one cycle in 8Mhz mode, two cycles in 12Mhz mode.
dave_delay_ = HalfCycles(2 + (*cycle.value)&2);
[[fallthrough]];
case 0xa0: case 0xa1: case 0xa2: case 0xa3:
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
case 0xa8: case 0xa9: case 0xaa: case 0xab:
case 0xac: case 0xad: case 0xae: case 0xaf:
update_audio();
dave_audio_.write(address, *cycle.value);
dave_timer_->write(address, *cycle.value);
break;
case 0xb4:
interrupt_mask_ = *cycle.value & 0x55;
interrupt_state_ &= ~*cycle.value;
update_interrupts();
break;
case 0xb5:
// Logic here: the ROM scans the keyboard by checking ascending
// lines. It also seems to provide a line of 0 when using port B5
// for non-keyboard uses.
//
// So: use the rollover from line 9 back to line 0 as a trigger to
// spot that a scan of the keyboard just finished. Which makes it
// time to enqueue the next keypress.
//
// Re: should_skip_splash_screen_ and typer_delay_, assume that a
// single keypress is necessary to get past the Enterprise splash
// screen, then a pause in keypressing while BASIC or whatever
// starts up, then presses can resume.
if(active_key_line_ == 9 && !(*cycle.value & 0xf) && (should_skip_splash_screen_ || typer_)) {
if(should_skip_splash_screen_) {
set_key_state(uint16_t(Key::Space), typer_delay_);
if(typer_delay_) {
--typer_delay_;
} else {
typer_delay_ = 60;
should_skip_splash_screen_ = false;
}
} else {
if(!typer_delay_) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_ = nullptr;
}
} else {
--typer_delay_;
}
}
}
active_key_line_ = *cycle.value & 0xf;
// TODO:
//
// b4: strobe output for printer
// b5: tape sound control (?)
// b6: tape motor control 1, 1 = on
// b7: tape motor control 2, 1 = on
break;
case 0xb6:
// Just 8 bits of printer data.
LOG("TODO: printer output " << PADHEX(2) << *cycle.value);
break;
case 0xb7:
// b0 = serial data out
// b1 = serial status out
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
break;
}
break;
case PartialMachineCycle::Read:
case PartialMachineCycle::ReadOpcode:
if(read_pointers_[address >> 14]) {
*cycle.value = read_pointers_[address >> 14][address];
} else {
*cycle.value = 0xff;
}
break;
case PartialMachineCycle::Write:
if(write_pointers_[address >> 14]) {
write_pointers_[address >> 14][address] = *cycle.value;
}
break;
}
return penalty;
}
void flush() {
nick_.flush();
update_audio();
audio_queue_.perform();
}
private:
// MARK: - Memory layout
std::array<uint8_t, 256 * 1024> ram_{};
std::array<uint8_t, 64 * 1024> exos_;
std::array<uint8_t, 16 * 1024> basic_;
std::array<uint8_t, 16 * 1024> exdos_rom_;
std::array<uint8_t, 32 * 1024> epdos_rom_;
const uint8_t min_ram_slot_;
const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};
template <size_t slot> void page(uint8_t offset) {
pages_[slot] = offset;
#define Map(location, source) \
if(offset >= location && offset < location + source.size() / 0x4000) { \
page<slot>(&source[(offset - location) * 0x4000], nullptr); \
is_video_[slot] = false; \
return; \
}
Map(0, exos_);
Map(16, basic_);
Map(32, exdos_rom_);
Map(48, epdos_rom_);
#undef Map
// Of whatever size of RAM I've declared above, use only the final portion.
// This correlated with Nick always having been handed the final 64kb and,
// at least while the RAM is the first thing declared above, does a little
// to benefit data locality. Albeit not in a useful sense.
if(offset >= min_ram_slot_) {
const auto ram_floor = 4194304 - ram_.size();
const size_t address = offset * 0x4000 - ram_floor;
is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption.
page<slot>(&ram_[address], &ram_[address]);
return;
}
page<slot>(nullptr, nullptr);
}
template <size_t slot> void page(const uint8_t *read, uint8_t *write) {
read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
}
// MARK: - Memory Timing
// The wait mode affects all memory accesses _outside of the video area_.
enum class WaitMode {
None,
OnM1,
OnAllAccesses
} wait_mode_ = WaitMode::OnAllAccesses;
bool is_video_[4]{};
// MARK: - ScanProducer
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
nick_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
return nick_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
nick_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return nick_.last_valid()->get_display_type();
}
// MARK: - AudioProducer
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
// MARK: - TimedMachine
void run_for(const Cycles cycles) override {
z80_.run_for(cycles);
}
// MARK: - KeyboardMachine
Enterprise::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
uint8_t active_key_line_ = 0;
std::array<uint8_t, 10> key_lines_;
void set_key_state(uint16_t key, bool is_pressed) final {
if(is_pressed) {
key_lines_[key >> 8] &= ~uint8_t(key);
} else {
key_lines_[key >> 8] |= uint8_t(key);
}
}
void clear_all_keys() final {
key_lines_.fill(0xff);
}
// MARK: - Utility::TypeRecipient
void type_string(const std::string &string) final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
if(z80_.get_is_resetting()) {
should_skip_splash_screen_ = true;
typer_delay_ = 1;
} else {
should_skip_splash_screen_ = false;
typer_delay_ = 0;
}
}
bool can_type(char c) const final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
bool should_skip_splash_screen_ = false;
int typer_delay_ = 30;
// MARK: - MediaTarget
bool insert_media(const Analyser::Static::Media &media) final {
if constexpr (has_disk_controller) {
if(!media.disks.empty()) {
exdos_.set_disk(media.disks.front(), 0);
}
}
return true;
}
// MARK: - Interrupts
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
void set_interrupts(uint8_t mask, HalfCycles offset = HalfCycles(0)) {
interrupt_state_ |= uint8_t(mask);
update_interrupts(offset);
}
void update_interrupts(HalfCycles offset = HalfCycles(0)) {
z80_.set_interrupt_line((interrupt_state_ >> 1) & interrupt_mask_, offset);
}
// MARK: - Chips.
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
NickType nick_;
bool previous_nick_interrupt_line_ = false;
// Cf. timing guesses above.
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave::Audio dave_audio_;
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
HalfCycles dave_delay_ = HalfCycles(2);
// The divider supplied to the JustInTimeActor and the manual divider used in
// update_audio() should match.
static constexpr int dave_divider = 8;
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, dave_divider> dave_timer_;
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider)));
}
// MARK: - EXDos card.
EXDos exdos_;
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
if constexpr (has_disk_controller) {
exdos_.set_activity_observer(observer);
}
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
};
}
using namespace Enterprise;
Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Enterprise::Target;
const Target *const enterprise_target = dynamic_cast<const Target *>(target);
#define BuildMachine(exdos, sixmhz) \
if((enterprise_target->dos == Target::DOS::None) != exdos && (enterprise_target->speed == Target::Speed::SixMHz) == sixmhz) { \
return new Enterprise::ConcreteMachine<exdos, sixmhz>(*enterprise_target, rom_fetcher); \
}
BuildMachine(false, false);
BuildMachine(false, true);
BuildMachine(true, false);
BuildMachine(true, true);
#undef BuildMachine
return nullptr;
}
Machine::~Machine() {}

View File

@@ -0,0 +1,47 @@
//
// Enterprise.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Enterprise_hpp
#define Enterprise_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include "../ROMMachine.hpp"
namespace Enterprise {
/*!
@abstract Represents an Elan Enterprise.
@discussion An instance of Enterprise::Machine represents the current state of an
Elan Enterprise.
*/
class Machine {
public:
virtual ~Machine();
static Machine *Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
/// Defines the runtime options available for an Enterprise.
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour) {
if(needs_declare()) {
declare_display_option();
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, Configurable::Display::CompositeMonochrome, -1);
}
}
};
};
};
#endif /* Enterprise_hpp */

View File

@@ -0,0 +1,151 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace Enterprise;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest)
switch(key) {
default: break;
BIND(Backslash, Backslash);
BIND(CapsLock, Lock);
BIND(Tab, Tab);
BIND(Escape, Escape);
BIND(Hyphen, Hyphen);
BIND(Equals, Caret);
BIND(Backspace, Erase);
BIND(Delete, Delete);
BIND(Semicolon, Semicolon);
BIND(Quote, Colon);
BIND(OpenSquareBracket, OpenSquareBracket);
BIND(CloseSquareBracket, CloseSquareBracket);
BIND(End, Stop);
BIND(Insert, Insert);
BIND(BackTick, At);
BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4); BIND(k5, k5);
BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9); BIND(k0, k0);
BIND(F1, F1); BIND(F2, F2); BIND(F3, F3); BIND(F4, F4);
BIND(F5, F5); BIND(F6, F6); BIND(F7, F7); BIND(F8, F8);
BIND(Keypad1, F1); BIND(Keypad2, F2); BIND(Keypad3, F3); BIND(Keypad4, F4);
BIND(Keypad5, F5); BIND(Keypad6, F6); BIND(Keypad7, F7); BIND(Keypad8, F8);
BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T);
BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P);
BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G);
BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L);
BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V);
BIND(B, B); BIND(N, N); BIND(M, M);
BIND(FullStop, FullStop);
BIND(Comma, Comma);
BIND(ForwardSlash, ForwardSlash);
BIND(Space, Space); BIND(Enter, Enter);
BIND(LeftShift, LeftShift);
BIND(RightShift, RightShift);
BIND(LeftOption, Alt);
BIND(RightOption, Alt);
BIND(LeftControl, Control);
BIND(RightControl, Control);
BIND(Left, Left);
BIND(Right, Right);
BIND(Up, Up);
BIND(Down, Down);
}
#undef BIND
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
}
const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#define KEYS(x) {uint16_t(x), MachineTypes::MappedKeyboardMachine::KeyEndSequence}
#define SHIFT(x) {uint16_t(Key::LeftShift), uint16_t(x), MachineTypes::MappedKeyboardMachine::KeyEndSequence}
#define _ {MachineTypes::MappedKeyboardMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ _, /* SOH */ _,
/* STX */ _, /* ETX */ _,
/* EOT */ _, /* ENQ */ _,
/* ACK */ _, /* BEL */ _,
/* BS */ KEYS(Key::Erase), /* HT */ KEYS(Key::Tab),
/* LF */ KEYS(Key::Enter), /* VT */ _,
/* FF */ _, /* CR */ KEYS(Key::Enter),
/* SO */ _, /* SI */ _,
/* DLE */ _, /* DC1 */ _,
/* DC2 */ _, /* DC3 */ _,
/* DC4 */ _, /* NAK */ _,
/* SYN */ _, /* ETB */ _,
/* CAN */ _, /* EM */ _,
/* SUB */ _, /* ESC */ KEYS(Key::Escape),
/* FS */ _, /* GS */ _,
/* RS */ _, /* US */ _,
/* space */ KEYS(Key::Space), /* ! */ SHIFT(Key::k1),
/* " */ SHIFT(Key::k2), /* # */ _,
/* $ */ SHIFT(Key::k4), /* % */ SHIFT(Key::k5),
/* & */ SHIFT(Key::k6), /* ' */ SHIFT(Key::k7),
/* ( */ SHIFT(Key::k8), /* ) */ SHIFT(Key::k9),
/* * */ SHIFT(Key::Colon), /* + */ SHIFT(Key::Semicolon),
/* , */ KEYS(Key::Comma), /* - */ KEYS(Key::Hyphen),
/* . */ KEYS(Key::FullStop), /* / */ KEYS(Key::ForwardSlash),
/* 0 */ KEYS(Key::k0), /* 1 */ KEYS(Key::k1),
/* 2 */ KEYS(Key::k2), /* 3 */ KEYS(Key::k3),
/* 4 */ KEYS(Key::k4), /* 5 */ KEYS(Key::k5),
/* 6 */ KEYS(Key::k6), /* 7 */ KEYS(Key::k7),
/* 8 */ KEYS(Key::k8), /* 9 */ KEYS(Key::k9),
/* : */ KEYS(Key::Colon), /* ; */ KEYS(Key::Semicolon),
/* < */ SHIFT(Key::Comma), /* = */ SHIFT(Key::Hyphen),
/* > */ SHIFT(Key::FullStop), /* ? */ SHIFT(Key::ForwardSlash),
/* @ */ KEYS(Key::At), /* A */ SHIFT(Key::A),
/* B */ SHIFT(Key::B), /* C */ SHIFT(Key::C),
/* D */ SHIFT(Key::D), /* E */ SHIFT(Key::E),
/* F */ SHIFT(Key::F), /* G */ SHIFT(Key::G),
/* H */ SHIFT(Key::H), /* I */ SHIFT(Key::I),
/* J */ SHIFT(Key::J), /* K */ SHIFT(Key::K),
/* L */ SHIFT(Key::L), /* M */ SHIFT(Key::M),
/* N */ SHIFT(Key::N), /* O */ SHIFT(Key::O),
/* P */ SHIFT(Key::P), /* Q */ SHIFT(Key::Q),
/* R */ SHIFT(Key::R), /* S */ SHIFT(Key::S),
/* T */ SHIFT(Key::T), /* U */ SHIFT(Key::U),
/* V */ SHIFT(Key::V), /* W */ SHIFT(Key::W),
/* X */ SHIFT(Key::X), /* Y */ SHIFT(Key::Y),
/* Z */ SHIFT(Key::Z), /* [ */ KEYS(Key::OpenSquareBracket),
/* \ */ KEYS(Key::Backslash), /* ] */ KEYS(Key::CloseSquareBracket),
/* ^ */ SHIFT(Key::Caret), /* _ */ SHIFT(Key::k0),
/* ` */ SHIFT(Key::At), /* a */ KEYS(Key::A),
/* b */ KEYS(Key::B), /* c */ KEYS(Key::C),
/* d */ KEYS(Key::D), /* e */ KEYS(Key::E),
/* f */ KEYS(Key::F), /* g */ KEYS(Key::G),
/* h */ KEYS(Key::H), /* i */ KEYS(Key::I),
/* j */ KEYS(Key::J), /* k */ KEYS(Key::K),
/* l */ KEYS(Key::L), /* m */ KEYS(Key::M),
/* n */ KEYS(Key::N), /* o */ KEYS(Key::O),
/* p */ KEYS(Key::P), /* q */ KEYS(Key::Q),
/* r */ KEYS(Key::R), /* s */ KEYS(Key::S),
/* t */ KEYS(Key::T), /* u */ KEYS(Key::U),
/* v */ KEYS(Key::V), /* w */ KEYS(Key::W),
/* x */ KEYS(Key::X), /* y */ KEYS(Key::Y),
/* z */ KEYS(Key::Z), /* { */ SHIFT(Key::OpenSquareBracket),
/* | */ SHIFT(Key::Backslash), /* } */ SHIFT(Key::CloseSquareBracket),
/* ~ */ SHIFT(Key::Caret)
};
#undef _
#undef SHIFT
#undef KEYS
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@@ -0,0 +1,66 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Machines_Enterprise_Keyboard_hpp
#define Machines_Enterprise_Keyboard_hpp
#include "../KeyboardMachine.hpp"
#include "../Utility/Typer.hpp"
namespace Enterprise {
#define KeyCode(line, mask) (line << 8) | mask
enum class Key: uint16_t {
N = 0x0000 | 0x01, Backslash = 0x0000 | 0x02, B = 0x0000 | 0x04, C = 0x0000 | 0x08,
V = 0x0000 | 0x10, X = 0x0000 | 0x20, Z = 0x0000 | 0x40, LeftShift = 0x0000 | 0x80,
H = 0x0100 | 0x01, Lock = 0x0100 | 0x02, G = 0x0100 | 0x04, D = 0x0100 | 0x08,
F = 0x0100 | 0x10, S = 0x0100 | 0x20, A = 0x0100 | 0x40, Control = 0x0100 | 0x80,
U = 0x0200 | 0x01, Q = 0x0200 | 0x02, Y = 0x0200 | 0x04, R = 0x0200 | 0x08,
T = 0x0200 | 0x10, E = 0x0200 | 0x20, W = 0x0200 | 0x40, Tab = 0x0200 | 0x80,
k7 = 0x0300 | 0x01, k1 = 0x0300 | 0x02, k6 = 0x0300 | 0x04, k4 = 0x0300 | 0x08,
k5 = 0x0300 | 0x10, k3 = 0x0300 | 0x20, k2 = 0x0300 | 0x40, Escape = 0x0300 | 0x80,
F4 = 0x0400 | 0x01, F8 = 0x0400 | 0x02, F3 = 0x0400 | 0x04, F6 = 0x0400 | 0x08,
F5 = 0x0400 | 0x10, F7 = 0x0400 | 0x20, F2 = 0x0400 | 0x40, F1 = 0x0400 | 0x80,
k8 = 0x0500 | 0x01, k9 = 0x0500 | 0x04, Hyphen = 0x0500 | 0x08,
k0 = 0x0500 | 0x10, Caret = 0x0500 | 0x20, Erase = 0x0500 | 0x40,
J = 0x0600 | 0x01, K = 0x0600 | 0x04, Semicolon = 0x0600 | 0x08,
L = 0x0600 | 0x10, Colon = 0x0600 | 0x20, CloseSquareBracket = 0x0600 | 0x40,
Stop = 0x0700 | 0x01, Down = 0x0700 | 0x02, Right = 0x0700 | 0x04, Up = 0x0700 | 0x08,
Hold = 0x0700 | 0x10, Left = 0x0700 | 0x20, Enter = 0x0700 | 0x40, Alt = 0x0700 | 0x80,
M = 0x0800 | 0x01, Delete = 0x0800 | 0x02, Comma = 0x0800 | 0x04,
ForwardSlash = 0x0800 | 0x08,
FullStop = 0x0800 | 0x10,
RightShift = 0x0800 | 0x20, Space = 0x0800 | 0x40, Insert = 0x0800 | 0x80,
I = 0x0900 | 0x01, O = 0x0900 | 0x04, At = 0x0900 | 0x08,
P = 0x0900 | 0x10,
OpenSquareBracket = 0x0900 | 0x20
};
#undef KeyCode
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};
struct CharacterMapper: public ::Utility::CharacterMapper {
const uint16_t *sequence_for_character(char character) const override;
};
}
#endif /* Keyboard_hpp */

View File

@@ -0,0 +1,629 @@
//
// Nick.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Nick.hpp"
#include <cstdio>
namespace {
uint16_t mapped_colour(uint8_t source) {
// On the Enterprise, red and green are 3-bit quantities; blue is a 2-bit quantity.
int red = ((source&0x01) << 2) | ((source&0x08) >> 2) | ((source&0x40) >> 6);
int green = ((source&0x02) << 1) | ((source&0x10) >> 3) | ((source&0x80) >> 7);
int blue = ((source&0x04) >> 1) | ((source&0x20) >> 5);
assert(red <= 7);
assert(green <= 7);
assert(blue <= 3);
red = (red << 1) + (red >> 3);
green = (green << 1) + (green >> 3);
blue = (blue << 2) + blue;
assert(red <= 15);
assert(green <= 15);
assert(blue <= 15);
// Duplicate bits where necessary to map to a full 4-bit range per channel.
const uint8_t parts[2] = {
uint8_t(
red
),
uint8_t(
(green << 4) + blue
)
};
return *reinterpret_cast<const uint16_t *>(parts);
}
}
using namespace Enterprise;
Nick::Nick(const uint8_t *ram) :
crt_(57*16, 16, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
ram_(ram) {
// Just use RGB for now.
set_display_type(Outputs::Display::DisplayType::RGB);
// Crop to the centre 90% of the display.
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
}
void Nick::write(uint16_t address, uint8_t value) {
switch(address & 3) {
case 0:
// Ignored: everything to do with external colour.
for(int c = 0; c < 8; c++) {
palette_[c + 8] = mapped_colour(uint8_t(((value & 0x1f) << 3) + c));
}
break;
case 1:
if(output_type_ == OutputType::Border) {
set_output_type(OutputType::Border, true);
}
border_colour_ = mapped_colour(value);
break;
case 2:
line_parameter_base_ = uint16_t((line_parameter_base_ & 0xf000) | (value << 4));
break;
case 3:
line_parameter_base_ = uint16_t((line_parameter_base_ & 0x0ff0) | (value << 12));
// Still a mystery to me: the exact meaning of the top two bits here. For now
// just treat a 0 -> 1 transition of the MSB as a forced frame restart.
if((value^line_parameter_control_) & value & 0x80) {
// For now: just force this to be the final line of this mode block.
// I'm unclear whether I should also reset the horizontal counter
// (i.e. completely abandon current video phase).
lines_remaining_ = 0xff;
should_reload_line_parameters_ = true;
}
line_parameter_control_ = value & 0xc0;
break;
}
}
uint8_t Nick::read() {
return last_read_;
}
Cycles Nick::get_time_until_z80_slot(Cycles after_period) const {
// Place Z80 accesses in the first six cycles in each sixteen-cycle window.
// That models video accesses as being the final ten. Which has the net effect
// of responding to the line parameter table interrupt flag as soon as it's
// loaded.
// Assumed below: the Z80 can start its final cycle anywhere in the first three
// of the permitted six.
const int offset = (horizontal_counter_ + after_period.as<int>()) & 15;
if(offset < 3) {
return 0;
} else {
return 16 - offset;
}
}
void Nick::run_for(Cycles duration) {
constexpr int line_length = 912;
#define add_window(x) \
line_data_pointer_[0] += is_sync_or_pixels_ * line_data_per_column_increments_[0] * (x); \
line_data_pointer_[1] += is_sync_or_pixels_ * line_data_per_column_increments_[1] * (x); \
window += x; \
if(window != 57 && window == left_margin_) is_sync_or_pixels_ = true; \
if(window != 57 && window == right_margin_) is_sync_or_pixels_ = false;
int clocks_remaining = duration.as<int>();
while(clocks_remaining) {
// Determine how many cycles are left this line.
const int clocks_this_line = std::min(clocks_remaining, line_length - horizontal_counter_);
// Convert that into a [start/current] and end window.
int window = horizontal_counter_ >> 4;
const int end_window = (horizontal_counter_ + clocks_this_line) >> 4;
// Advance the line counters.
clocks_remaining -= clocks_this_line;
horizontal_counter_ = (horizontal_counter_ + clocks_this_line) % line_length;
// Do nothing if a window boundary isn't crossed.
if(window == end_window) continue;
// HSYNC is signalled for four windows at the start of the line.
// I currently believe this happens regardless of Vsync mode.
if(!window) {
set_output_type(OutputType::Sync);
// There's no increment to get to 0, it happens when the horizontal_counter_
// is reset. So test for active bit effect manually.
if(!left_margin_) is_sync_or_pixels_ = true;
if(!right_margin_) is_sync_or_pixels_ = false;
}
// Default to noting read.
last_read_ = 0xff;
while(window < 4 && window < end_window) {
if(should_reload_line_parameters_) {
switch(window) {
// First slot: line count, mode and interrupt flag.
case 0:
// Byte 0: lines remaining.
lines_remaining_ = ram_[line_parameter_pointer_];
// Byte 1: current interrupt output plus graphics modes...
last_read_ = ram_[line_parameter_pointer_ + 1];
// Set the new interrupt line output.
interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80;
// Determine the mode and depth, and hence the column size.
mode_ = Mode((ram_[line_parameter_pointer_ + 1] >> 1)&7);
bpp_ = 1 << ((ram_[line_parameter_pointer_ + 1] >> 5)&3);
switch(mode_) {
default:
case Mode::Pixel:
column_size_ = 16 / bpp_;
line_data_per_column_increments_[0] = 2;
line_data_per_column_increments_[1] = 0;
break;
case Mode::LPixel:
case Mode::CH64:
case Mode::CH128:
case Mode::CH256:
column_size_ = 8 / bpp_;
line_data_per_column_increments_[0] = 1;
line_data_per_column_increments_[1] = 0;
break;
case Mode::Attr:
column_size_ = 8;
line_data_per_column_increments_[0] = 1;
line_data_per_column_increments_[1] = 1;
break;
}
vres_ = ram_[line_parameter_pointer_ + 1] & 0x10;
reload_line_parameter_pointer_ = ram_[line_parameter_pointer_ + 1] & 0x01;
break;
// Second slot: margins and ALT/IND bits.
case 1:
// Determine the margins.
left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f;
right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f;
last_read_ = ram_[line_parameter_pointer_ + 3];
// Set up the alternative palettes,
switch(mode_) {
default:
break;
// NB: LSBALT/MSBALT and ALTIND0/ALTIND1 appear to have opposite effects on palette selection.
case Mode::Pixel:
case Mode::LPixel: {
const uint8_t flags = ram_[line_parameter_pointer_ + 2];
// Use MSBALT and LSBALT to pick the alt_ind_palettes.
//
// LSBALT = b6 of params[2], if set => character codes with bit 6 set should use palette indices 4... instead of 0... .
// MSBALT = b7 of params[2], if set => character codes with bit 7 set should use palette indices 2 and 3.
two_colour_mask_ = 0xff &~ (((flags&0x80) >> 7) | ((flags&0x40) << 1));
alt_ind_palettes[0] = palette_;
alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x80) ? 2 : 0);
alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x40) ? 4 : 0);
alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x40) ? 4 : 0);
} break;
case Mode::CH64:
case Mode::CH128:
case Mode::CH256: {
const uint8_t flags = ram_[line_parameter_pointer_ + 3];
// Use ALTIND0 and ALTIND1 to pick the alt_ind_palettes.
//
// ALTIND1 = b6 of params[3], if set => character codes with bit 7 set should use palette indices 2 and 3.
// ALTIND0 = b7 of params[3], if set => character codes with bit 6 set should use palette indices 4... instead of 0... .
alt_ind_palettes[0] = palette_;
alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x40) ? 2 : 0);
alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x80) ? 4 : 0);
alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x80) ? 4 : 0);
} break;
}
break;
// Third slot: Line data pointer 1.
case 2:
start_line_data_pointer_[0] = ram_[line_parameter_pointer_ + 4];
start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8;
line_data_pointer_[0] = start_line_data_pointer_[0];
last_read_ = ram_[line_parameter_pointer_ + 5];
break;
// Fourth slot: Line data pointer 2.
case 3:
start_line_data_pointer_[1] = ram_[line_parameter_pointer_ + 6];
start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8;
line_data_pointer_[1] = start_line_data_pointer_[1];
last_read_ = ram_[line_parameter_pointer_ + 7];
break;
}
}
++output_duration_;
add_window(1);
}
if(window == 4) {
if(mode_ == Mode::Vsync) {
set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank);
} else {
set_output_type(OutputType::Blank);
}
}
// Deal with vsync mode out here.
if(mode_ == Mode::Vsync) {
if(window >= 4) {
while(window < end_window) {
// Skip straight to the next event.
int next_event = end_window;
if(window < left_margin_) next_event = std::min(next_event, left_margin_);
if(window < right_margin_) next_event = std::min(next_event, right_margin_);
output_duration_ += next_event - window;
add_window(next_event - window);
set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank);
}
}
} else {
// If present then the colour burst is output for the period from
// the start of window 6 to the end of window 10.
//
// The first 8 palette entries also need to be fetched here.
while(window < first_pixel_window_ && window < end_window) {
if(window == 6) {
set_output_type(OutputType::ColourBurst);
}
if(should_reload_line_parameters_ && window < 8) {
const int base = (window - 4) << 1;
assert(base < 7);
palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]);
palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]);
last_read_ = ram_[line_parameter_pointer_ + base + 9];
}
++output_duration_;
add_window(1);
}
if(window >= first_pixel_window_) {
if(window == first_pixel_window_) {
set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border);
}
while(window < end_window) {
int next_event = end_window;
if(window < left_margin_) next_event = std::min(next_event, left_margin_);
if(window < right_margin_) next_event = std::min(next_event, right_margin_);
if(is_sync_or_pixels_) {
#define DispatchBpp(func) \
switch(bpp_) { \
default: \
case 1: func(1)(pixel_pointer_, output_duration); break; \
case 2: func(2)(pixel_pointer_, output_duration); break; \
case 4: func(4)(pixel_pointer_, output_duration); break; \
case 8: func(8)(pixel_pointer_, output_duration); break; \
}
#define pixel(x) output_pixel<x, false>
#define lpixel(x) output_pixel<x, true>
#define ch256(x) output_character<x, 8>
#define ch128(x) output_character<x, 7>
#define ch64(x) output_character<x, 6>
#define attr(x) output_attributed<x>
int columns_remaining = next_event - window;
while(columns_remaining) {
if(!pixel_pointer_) {
if(output_duration_) {
set_output_type(OutputType::Pixels, true);
}
pixel_pointer_ = allocated_pointer_ = reinterpret_cast<uint16_t *>(crt_.begin_data(allocation_size));
}
if(allocated_pointer_) {
const int output_duration = std::min(columns_remaining, int(allocated_pointer_ + allocation_size - pixel_pointer_) / column_size_);
switch(mode_) {
default:
case Mode::Pixel: DispatchBpp(pixel); break;
case Mode::LPixel: DispatchBpp(lpixel); break;
case Mode::CH256: DispatchBpp(ch256); break;
case Mode::CH128: DispatchBpp(ch128); break;
case Mode::CH64: DispatchBpp(ch64); break;
case Mode::Attr: DispatchBpp(attr); break;
}
pixel_pointer_ += output_duration * column_size_;
output_duration_ += output_duration;
if(pixel_pointer_ - allocated_pointer_ == allocation_size) {
set_output_type(OutputType::Pixels, true);
}
columns_remaining -= output_duration;
add_window(output_duration);
} else {
output_duration_ += columns_remaining;
add_window(columns_remaining);
columns_remaining = 0;
}
}
#undef attr
#undef ch64
#undef ch128
#undef ch256
#undef pixel
#undef lpixel
#undef DispatchBpp
} else {
output_duration_ += next_event - window;
add_window(next_event - window);
}
set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border);
}
}
}
// Check for end of line.
if(!horizontal_counter_) {
assert(window == 57);
++lines_remaining_;
if(!lines_remaining_) {
should_reload_line_parameters_ = true;
// Check for end-of-frame.
if(reload_line_parameter_pointer_) {
line_parameter_pointer_ = line_parameter_base_;
} else {
line_parameter_pointer_ += 16;
}
} else {
should_reload_line_parameters_ = false;
}
// Deal with VRES and other address reloading, dependant upon mode.
switch(mode_) {
default: break;
case Mode::CH64:
case Mode::CH128:
case Mode::CH256:
line_data_pointer_[0] = start_line_data_pointer_[0];
++line_data_pointer_[1];
break;
case Mode::Pixel:
case Mode::LPixel:
case Mode::Attr:
// Reload the pixel or attribute address if VRES is clear.
if(!vres_) {
line_data_pointer_[0] = start_line_data_pointer_[0];
}
break;
}
}
}
#undef add_window
}
void Nick::set_output_type(OutputType type, bool force_flush) {
if(type == output_type_ && !force_flush) {
return;
}
if(output_duration_) {
switch(output_type_) {
case OutputType::Border: {
uint16_t *const colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = border_colour_;
crt_.output_level(output_duration_*16);
} break;
case OutputType::Pixels: {
crt_.output_data(output_duration_*16, size_t(output_duration_*column_size_));
pixel_pointer_ = nullptr;
allocated_pointer_ = nullptr;
} break;
case OutputType::Sync: crt_.output_sync(output_duration_*16); break;
case OutputType::Blank: crt_.output_blank(output_duration_*16); break;
case OutputType::ColourBurst: crt_.output_colour_burst(output_duration_*16, 0); break;
}
}
output_duration_ = 0;
output_type_ = type;
}
// MARK: - Sequence points.
Cycles Nick::get_next_sequence_point() const {
constexpr int load_point = 16; // i.e. 16 cycles after the start of the line, the
// interrupt line may change. That is, after the
// second byte of the mode line has been read.
// Any mode line may cause a change in the interrupt output, so as a first blush
// just always report the time until the end of the mode line.
if(lines_remaining_ || horizontal_counter_ >= load_point) {
return Cycles(load_point + (912 - horizontal_counter_) + (0xff - lines_remaining_) * 912);
} else {
return Cycles(load_point - horizontal_counter_);
}
}
// MARK: - CRT passthroughs.
void Nick::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Nick::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status();
}
void Nick::set_display_type(Outputs::Display::DisplayType display_type) {
first_pixel_window_ = display_type == Outputs::Display::DisplayType::RGB ? 8 : 10;
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType Nick::get_display_type() const {
return crt_.get_display_type();
}
// MARK: - Specific pixel outputters.
#define output1bpp(x) \
target[0] = palette[(x & 0x80) >> 7]; \
target[1] = palette[(x & 0x40) >> 6]; \
target[2] = palette[(x & 0x20) >> 5]; \
target[3] = palette[(x & 0x10) >> 4]; \
target[4] = palette[(x & 0x08) >> 3]; \
target[5] = palette[(x & 0x04) >> 2]; \
target[6] = palette[(x & 0x02) >> 1]; \
target[7] = palette[(x & 0x01) >> 0]; \
target += 8
#define output2bpp(x) \
target[0] = palette_[((x & 0x80) >> 7) | ((x & 0x08) >> 2)]; \
target[1] = palette_[((x & 0x40) >> 6) | ((x & 0x04) >> 1)]; \
target[2] = palette_[((x & 0x20) >> 5) | ((x & 0x02) >> 0)]; \
target[3] = palette_[((x & 0x10) >> 4) | ((x & 0x01) << 1)]; \
target += 4
#define output4bpp(x) \
target[0] = palette_[((x & 0x02) << 2) | ((x & 0x20) >> 3) | ((x & 0x08) >> 2) | ((x & 0x80) >> 7)]; \
target[1] = palette_[((x & 0x01) << 3) | ((x & 0x10) >> 2) | ((x & 0x04) >> 1) | ((x & 0x40) >> 6)]; \
target += 2
#define output8bpp(x) \
target[0] = mapped_colour(x); \
++target
template <int bpp, bool is_lpixel> void Nick::output_pixel(uint16_t *target, int columns) const {
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
int index = 0;
for(int c = 0; c < columns; c++) {
uint8_t pixels[2] = {
ram_[(line_data_pointer_[0] + index) & 0xffff],
ram_[(line_data_pointer_[0] + index + 1) & 0xffff]
};
index += is_lpixel ? 1 : 2;
last_read_ = pixels[1];
switch(bpp) {
default:
case 1: {
const uint16_t *palette = alt_ind_palettes[((pixels[0] >> 6) & 0x02) | (pixels[0]&1)];
pixels[0] &= two_colour_mask_;
output1bpp(pixels[0]);
if constexpr (!is_lpixel) {
palette = alt_ind_palettes[((pixels[1] >> 6) & 0x02) | (pixels[1]&1)];
pixels[1] &= two_colour_mask_;
output1bpp(pixels[1]);
}
} break;
case 2:
output2bpp(pixels[0]);
if constexpr (!is_lpixel) {
output2bpp(pixels[1]);
}
break;
case 4:
output4bpp(pixels[0]);
if constexpr (!is_lpixel) {
output4bpp(pixels[1]);
}
break;
case 8:
output8bpp(pixels[0]);
if constexpr (!is_lpixel) {
output8bpp(pixels[1]);
}
break;
}
}
}
template <int bpp, int index_bits> void Nick::output_character(uint16_t *target, int columns) const {
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
for(int c = 0; c < columns; c++) {
const uint8_t character = ram_[(line_data_pointer_[0] + c) & 0xffff];
const uint8_t pixels = ram_[(
(line_data_pointer_[1] << index_bits) +
(character & ((1 << index_bits) - 1))
) & 0xffff];
last_read_ = pixels;
switch(bpp) {
default:
assert(false);
break;
case 1: {
// This applies ALTIND0 and ALTIND1.
const uint16_t *palette = alt_ind_palettes[character >> 6];
output1bpp(pixels);
} break;
case 2: output2bpp(pixels); break;
case 4: output4bpp(pixels); break;
case 8: output8bpp(pixels); break;
}
}
}
template <int bpp> void Nick::output_attributed(uint16_t *target, int columns) const {
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
for(int c = 0; c < columns; c++) {
const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff];
const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff];
last_read_ = pixels;
const uint16_t palette[2] = {
palette_[attributes >> 4], palette_[attributes & 0x0f]
};
output1bpp(pixels);
}
}
#undef output1bpp
#undef output2bpp
#undef output4bpp
#undef output8bpp

View File

@@ -0,0 +1,126 @@
//
// Nick.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Nick_hpp
#define Nick_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Outputs/CRT/CRT.hpp"
namespace Enterprise {
class Nick {
public:
Nick(const uint8_t *ram);
/// Writes to a Nick register; only the low two bits are decoded.
void write(uint16_t address, uint8_t value);
/// Reads from the Nick range. Nobody seems to be completely clear what
/// this should return; I've set it up to return the last fetched video or mode
/// line byte during periods when those things are being fetched, 0xff at all
/// other times. Including during refresh, since I don't know what addresses
/// are generated then.
///
/// This likely isn't accurate, but is the most accurate guess I could make.
uint8_t read();
void run_for(Cycles);
Cycles get_time_until_z80_slot(Cycles after_period) const;
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// @returns The amount of time until the next potential change in interrupt output.
Cycles get_next_sequence_point() const;
/*!
@returns The current state of the interrupt line — @c true for active;
@c false for inactive.
*/
inline bool get_interrupt_line() const {
return interrupt_line_;
}
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
private:
Outputs::CRT::CRT crt_;
const uint8_t *const ram_;
// CPU-provided state.
uint8_t line_parameter_control_ = 0xc0;
uint16_t line_parameter_base_ = 0x0000;
uint16_t border_colour_ = 0;
// Ephemerals, related to current video position.
int horizontal_counter_ = 0;
uint16_t line_parameter_pointer_ = 0x0000;
bool should_reload_line_parameters_ = true;
uint16_t line_data_pointer_[2];
uint16_t start_line_data_pointer_[2];
mutable uint8_t last_read_ = 0xff;
// Current mode line parameters.
uint8_t lines_remaining_ = 0x00;
uint8_t two_colour_mask_ = 0xff;
int left_margin_ = 0, right_margin_ = 0;
const uint16_t *alt_ind_palettes[4];
enum class Mode {
Vsync,
Pixel,
Attr,
CH256,
CH128,
CH64,
Unused,
LPixel,
} mode_ = Mode::Vsync;
bool is_sync_or_pixels_ = false;
int bpp_ = 0;
int column_size_ = 0;
bool interrupt_line_ = true;
int line_data_per_column_increments_[2] = {0, 0};
bool vres_ = false;
bool reload_line_parameter_pointer_ = false;
// The destination for new pixels.
static constexpr int allocation_size = 336;
static_assert((allocation_size % 16) == 0, "Allocation size must be a multiple of 16");
uint16_t *pixel_pointer_ = nullptr, *allocated_pointer_ = nullptr;
// Output transitions.
enum class OutputType {
Sync, Blank, Pixels, Border, ColourBurst
};
void set_output_type(OutputType, bool force_flush = false);
int output_duration_ = 0;
OutputType output_type_ = OutputType::Sync;
// Current palette.
uint16_t palette_[16]{};
// The first column with pixels on it; will be either 8 or 10 depending
// on whether the colour burst is meaningful to the current display type.
int first_pixel_window_ = 10;
// Specific outputters.
template <int bpp, bool is_lpixel> void output_pixel(uint16_t *target, int columns) const;
template <int bpp, int index_bits> void output_character(uint16_t *target, int columns) const;
template <int bpp> void output_attributed(uint16_t *target, int columns) const;
};
}
#endif /* Nick_hpp */

View File

@@ -20,6 +20,7 @@
#include "../ColecoVision/ColecoVision.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
#include "../Enterprise/Enterprise.hpp"
#include "../MasterSystem/MasterSystem.hpp"
#include "../MSX/MSX.hpp"
#include "../Oric/Oric.hpp"
@@ -34,6 +35,7 @@
#include "../../Analyser/Static/Atari2600/Target.hpp"
#include "../../Analyser/Static/AtariST/Target.hpp"
#include "../../Analyser/Static/Commodore/Target.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../Analyser/Static/Macintosh/Target.hpp"
#include "../../Analyser/Static/MSX/Target.hpp"
#include "../../Analyser/Static/Oric/Target.hpp"
@@ -49,7 +51,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
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 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)
@@ -61,6 +63,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
BindD(Coleco::Vision, ColecoVision)
BindD(Commodore::Vic20, Vic20)
Bind(Electron)
Bind(Enterprise)
Bind(MSX)
Bind(Oric)
BindD(Sega::MasterSystem, MasterSystem)
@@ -127,6 +130,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
case Analyser::Machine::AtariST: return "AtariST";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::Enterprise: return "Enterprise";
case Analyser::Machine::Macintosh: return "Macintosh";
case Analyser::Machine::MasterSystem: return "MasterSystem";
case Analyser::Machine::MSX: return "MSX";
@@ -148,6 +152,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
case Analyser::Machine::AtariST: return "Atari ST";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::Enterprise: return "Enterprise";
case Analyser::Machine::Macintosh: return "Apple Macintosh";
case Analyser::Machine::MasterSystem: return "Sega Master System";
case Analyser::Machine::MSX: return "MSX";
@@ -177,6 +182,7 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
AddName(AppleIIgs);
AddName(AtariST);
AddName(Electron);
AddName(Enterprise);
AddName(Macintosh);
AddName(MSX);
AddName(Oric);
@@ -201,6 +207,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
Emplace(AtariST, Atari::ST::Machine);
Emplace(ColecoVision, Coleco::Vision::Machine);
Emplace(Electron, Electron::Machine);
Emplace(Enterprise, Enterprise::Machine);
Emplace(Macintosh, Apple::Macintosh::Machine);
Emplace(MasterSystem, Sega::MasterSystem::Machine);
Emplace(MSX, MSX::Machine);
@@ -226,6 +233,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
Add(AppleIIgs);
Add(AtariST);
AddMapped(Electron, Acorn);
Add(Enterprise);
Add(Macintosh);
Add(MSX);
Add(Oric);

View File

@@ -8,6 +8,8 @@
#include "MemoryPacker.hpp"
#include <cstddef>
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) {
for(size_t c = 0; c < source.size(); c += 2) {
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);

View File

@@ -27,8 +27,15 @@ Request::Request(Name name, bool optional) {
}
Request Request::append(Node::Type type, const Request &rhs) {
// Start with the easiest case: this is already an appropriate
// request, and so is the new thing.
// If either side is empty, act appropriately.
if(node.empty() && !rhs.node.empty()) {
return rhs;
}
if(rhs.node.empty()) {
return *this;
}
// Just copy in the RHS child nodes if types match.
if(node.type == type && rhs.node.type == type) {
Request new_request = *this;
new_request.node.children.insert(new_request.node.children.end(), rhs.node.children.begin(), rhs.node.children.end());
@@ -404,6 +411,62 @@ Description::Description(Name name) {
*this = Description(name, "DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620u);
break;
case Name::EnterpriseEXOS10: {
const std::initializer_list<std::string> filenames = {"exos10.bin", "Exos (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v1.0", filenames, 32 * 1024, 0x30b26387u);
} break;
case Name::EnterpriseEXOS20: {
const std::initializer_list<std::string> filenames = {"exos20.bin", "Expandible OS v2.0 (1984)(Intelligent Software).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.0", filenames, 32 * 1024, 0xd421795fu);
} break;
case Name::EnterpriseEXOS21: {
const std::initializer_list<std::string> filenames = {"exos21.bin", "Expandible OS v2.1 (1985)(Intelligent Software).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 32 * 1024, 0x982a3b44u);
} break;
case Name::EnterpriseEXOS23: {
const std::initializer_list<std::string> filenames = {"exos23.bin", "Expandible OS v2.3 (1987)(Intelligent Software).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 64 * 1024, 0x24838410u);
} break;
case Name::EnterpriseBASIC10: {
const std::initializer_list<std::string> filenames = {"basic10.bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0", filenames, 16 * 1024, 0xd62e4fb7u);
} break;
case Name::EnterpriseBASIC10Part1: {
const std::initializer_list<std::string> filenames = {"BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 1", filenames, 8193, 0x37bf48e1u);
} break;
case Name::EnterpriseBASIC10Part2: {
const std::initializer_list<std::string> filenames = {"BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 2", filenames, 8193, 0xc5298c79u);
} break;
case Name::EnterpriseBASIC11: {
const std::initializer_list<std::string> filenames = {"basic11.bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1", filenames, 16 * 1024, 0x683cf455u);
} break;
case Name::EnterpriseBASIC11Suffixed: {
const std::initializer_list<std::string> filenames = {"BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1, with trailing byte", filenames, 16385, 0xc96b7602u);
} break;
case Name::EnterpriseBASIC21: {
const std::initializer_list<std::string> filenames = {
"basic21.bin",
"BASIC Interpreter v2.1 (1985)(Intelligent Software).bin",
"BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin"
};
const std::initializer_list<uint32_t> crcs = { 0x55f96251, 0x683cf455 };
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v2.1", filenames, 16 * 1024, crcs);
} break;
case Name::EnterpriseEPDOS: {
const std::initializer_list<std::string> filenames = {"epdos.bin", "EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"};
*this = Description(name, "Enterprise", "the Enterprise EPDOS ROM", filenames, 32 * 1024, 0x201319ebu);
} break;
case Name::EnterpriseEXDOS: {
const std::initializer_list<std::string> filenames = {"exdos.bin", "EX-DOS EPROM (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXDOS ROM", filenames, 16 * 1024, 0xe6daa0e9u);
} break;
case Name::Macintosh128k: *this = Description(name, "Macintosh", "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28u); break;
case Name::Macintosh512k: *this = Description(name, "Macintosh", "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); break;
case Name::MacintoshPlus: {

View File

@@ -71,6 +71,22 @@ enum Name {
DiskIIStateMachine13Sector,
DiskIIBoot13Sector,
// Enterprise.
EnterpriseEXOS10,
EnterpriseEXOS20,
EnterpriseEXOS21,
EnterpriseEXOS23,
EnterpriseBASIC10,
EnterpriseBASIC10Part1,
EnterpriseBASIC10Part2,
EnterpriseBASIC11,
EnterpriseBASIC11Suffixed,
EnterpriseBASIC21,
EnterpriseEPDOS,
EnterpriseEXDOS,
// Macintosh.
Macintosh128k,
Macintosh512k,
@@ -237,6 +253,10 @@ struct Request {
bool is_optional = false;
std::vector<Node> children;
bool empty() const {
return type == Type::One && name == Name::None;
}
void add_descriptions(std::vector<Description> &) const;
bool validate(Map &) const;
void visit(

View File

@@ -12,6 +12,8 @@
#include <cstdint>
#include <cstdlib>
#include "Sizes.hpp"
namespace Numeric {
template <typename IntType> struct LSFRPolynomial {};
@@ -67,8 +69,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
determining the bit that was just shifted out.
*/
IntType next() {
const auto result = value_ & 1;
value_ = (value_ >> 1) ^ (result * polynomial);
const auto result = IntType(value_ & 1);
value_ = IntType((value_ >> 1) ^ (result * polynomial));
return result;
}
@@ -76,6 +78,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
IntType value_ = 0;
};
template <uint64_t polynomial> class LFSRv: public LFSR<typename MinIntTypeValue<polynomial>::type, polynomial> {};
}
#endif /* LFSR_h */

View File

@@ -10,6 +10,7 @@
#define Sizes_h
#include <limits>
#include <type_traits>
/*!
Maps to the smallest integral type that can contain max_value, from the following options:

View File

@@ -17,6 +17,17 @@
4B051C93266D9D6900CA44E8 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
4B051C95266EF50200CA44E8 /* AppleIIOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */; };
4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C96266EF5F600CA44E8 /* CSAppleII.mm */; };
4B051CA22676F52200CA44E8 /* Enterprise.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA12676F52200CA44E8 /* Enterprise.cpp */; };
4B051CA32676F52200CA44E8 /* Enterprise.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA12676F52200CA44E8 /* Enterprise.cpp */; };
4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */; };
4B051CA926781D6500CA44E8 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */; };
4B051CAC26783E2000CA44E8 /* Nick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAA26783E2000CA44E8 /* Nick.cpp */; };
4B051CAD26783E2000CA44E8 /* Nick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAA26783E2000CA44E8 /* Nick.cpp */; };
4B051CB0267C1CA200CA44E8 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */; };
4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */; };
4B051CB3267D3FF800CA44E8 /* EnterpriseNickTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */; };
4B051CB62680158600CA44E8 /* EXDos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CB42680158600CA44E8 /* EXDos.cpp */; };
4B051CB72680158600CA44E8 /* EXDos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CB42680158600CA44E8 /* EXDos.cpp */; };
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; };
4B05401F219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; };
4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; };
@@ -230,6 +241,8 @@
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; };
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
4B47770D26900685005C2340 /* EnterpriseDaveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */; };
4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; };
4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; };
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; };
@@ -312,7 +325,7 @@
4B778F0023A5EB990000D260 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518931F75FD1B00926311 /* G64.cpp */; };
4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */; };
4B778F0223A5EBA40000D260 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; };
4B778F0323A5EBB00000D260 /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */; };
4B778F0423A5EBB00000D260 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
4B778F0523A5EBB00000D260 /* ST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE0A3EC237BB170002AB46F /* ST.cpp */; };
4B778F0623A5EC150000D260 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
@@ -950,8 +963,8 @@
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */; };
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
4BEBFB4D2002C4BF000708CC /* FAT12.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */; };
4BEBFB4E2002C4BF000708CC /* FAT12.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */; };
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
@@ -987,6 +1000,8 @@
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; };
4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */; };
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
4BFEA2EF2682A7B900EBF94C /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; };
4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; };
4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */; };
@@ -1028,6 +1043,18 @@
4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIOptionsPanel.swift; sourceTree = "<group>"; };
4B051C96266EF5F600CA44E8 /* CSAppleII.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAppleII.mm; sourceTree = "<group>"; };
4B051C98266EF60500CA44E8 /* CSAppleII.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSAppleII.h; sourceTree = "<group>"; };
4B051CA02676F52200CA44E8 /* Enterprise.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Enterprise.hpp; sourceTree = "<group>"; };
4B051CA12676F52200CA44E8 /* Enterprise.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Enterprise.cpp; sourceTree = "<group>"; };
4B051CA526781D6500CA44E8 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B051CA626781D6500CA44E8 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B051CAA26783E2000CA44E8 /* Nick.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Nick.cpp; sourceTree = "<group>"; };
4B051CAB26783E2000CA44E8 /* Nick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Nick.hpp; sourceTree = "<group>"; };
4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseNickTests.mm; sourceTree = "<group>"; };
4B051CB42680158600CA44E8 /* EXDos.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EXDos.cpp; sourceTree = "<group>"; };
4B051CB52680158600CA44E8 /* EXDos.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EXDos.hpp; sourceTree = "<group>"; };
4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; };
4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
@@ -1251,6 +1278,9 @@
4B45189A1F75FD1B00926311 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
4B4518A71F76004200926311 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = Parsers/TapeParser.hpp; sourceTree = "<group>"; };
4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = "<group>"; };
4B477709268FBE4D005C2340 /* FAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FAT.cpp; path = Parsers/FAT.cpp; sourceTree = "<group>"; };
4B47770A268FBE4D005C2340 /* FAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FAT.hpp; path = Parsers/FAT.hpp; sourceTree = "<group>"; };
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseDaveTests.mm; sourceTree = "<group>"; };
4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = "<group>"; };
4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = "<group>"; };
@@ -1998,8 +2028,8 @@
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SoundGenerator.cpp; sourceTree = "<group>"; };
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundGenerator.hpp; sourceTree = "<group>"; };
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSXDSK.cpp; sourceTree = "<group>"; };
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = "<group>"; };
4BEBFB4B2002C4BF000708CC /* FAT12.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FAT12.cpp; sourceTree = "<group>"; };
4BEBFB4C2002C4BF000708CC /* FAT12.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FAT12.hpp; sourceTree = "<group>"; };
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskROM.cpp; sourceTree = "<group>"; };
4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskROM.hpp; sourceTree = "<group>"; };
4BEDA3B425B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
@@ -2049,6 +2079,9 @@
4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImplicitSectors.hpp; sourceTree = "<group>"; };
4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImplicitSectors.cpp; sourceTree = "<group>"; };
4BFE7B861FC39BF100160B38 /* StandardOptions.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StandardOptions.hpp; sourceTree = "<group>"; };
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Dave.cpp; sourceTree = "<group>"; };
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Dave.hpp; sourceTree = "<group>"; };
4BFEA2F12682A90200EBF94C /* Sizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Sizes.hpp; sourceTree = "<group>"; };
4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = "<group>"; };
@@ -2090,6 +2123,33 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4B051C9F2676F52200CA44E8 /* Enterprise */ = {
isa = PBXGroup;
children = (
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */,
4B051CA12676F52200CA44E8 /* Enterprise.cpp */,
4B051CB42680158600CA44E8 /* EXDos.cpp */,
4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */,
4B051CAA26783E2000CA44E8 /* Nick.cpp */,
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */,
4B051CA02676F52200CA44E8 /* Enterprise.hpp */,
4B051CB52680158600CA44E8 /* EXDos.hpp */,
4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */,
4B051CAB26783E2000CA44E8 /* Nick.hpp */,
);
path = Enterprise;
sourceTree = "<group>";
};
4B051CA426781D6500CA44E8 /* Enterprise */ = {
isa = PBXGroup;
children = (
4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */,
4B051CA526781D6500CA44E8 /* StaticAnalyser.hpp */,
4B051CA626781D6500CA44E8 /* Target.hpp */,
);
path = Enterprise;
sourceTree = "<group>";
};
4B055A761FAE78210060FFFF /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -2602,7 +2662,9 @@
isa = PBXGroup;
children = (
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */,
4B477709268FBE4D005C2340 /* FAT.cpp */,
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */,
4B47770A268FBE4D005C2340 /* FAT.hpp */,
);
name = Parsers;
sourceTree = "<group>";
@@ -2655,34 +2717,36 @@
4B45188C1F75FD1B00926311 /* Formats */ = {
isa = PBXGroup;
children = (
4B80CD74256CA15E00176FCC /* 2MG.cpp */,
4B45188D1F75FD1B00926311 /* AcornADF.cpp */,
4B0333AD2094081A0050B93D /* AppleDSK.cpp */,
4B45188F1F75FD1B00926311 /* CPCDSK.cpp */,
4B4518911F75FD1B00926311 /* D64.cpp */,
4BAF2B4C2004580C00480230 /* DMK.cpp */,
4BEBFB4B2002C4BF000708CC /* FAT12.cpp */,
4B4518931F75FD1B00926311 /* G64.cpp */,
4B4518951F75FD1B00926311 /* HFE.cpp */,
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */,
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
4BC131782346DF2B00E4FF3D /* MSA.cpp */,
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */,
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */,
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
4B4518991F75FD1B00926311 /* SSD.cpp */,
4BE0A3EC237BB170002AB46F /* ST.cpp */,
4B7BA03323C58B1E00B98D9E /* STX.cpp */,
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
4B80CD75256CA15E00176FCC /* 2MG.hpp */,
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
4B4518901F75FD1B00926311 /* CPCDSK.hpp */,
4B4518921F75FD1B00926311 /* D64.hpp */,
4BAF2B4D2004580C00480230 /* DMK.hpp */,
4BEBFB4C2002C4BF000708CC /* FAT12.hpp */,
4B4518941F75FD1B00926311 /* G64.hpp */,
4B4518961F75FD1B00926311 /* HFE.hpp */,
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */,
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
4BC131792346DF2B00E4FF3D /* MSA.hpp */,
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */,
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */,
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
4B45189A1F75FD1B00926311 /* SSD.hpp */,
@@ -2690,8 +2754,6 @@
4B7BA03223C58B1E00B98D9E /* STX.hpp */,
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
4BFDD7891F7F2DB4008579B9 /* Utility */,
4B80CD74256CA15E00176FCC /* 2MG.cpp */,
4B80CD75256CA15E00176FCC /* 2MG.hpp */,
);
path = Formats;
sourceTree = "<group>";
@@ -3048,6 +3110,7 @@
children = (
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
);
name = Numeric;
path = ../../Numeric;
@@ -3185,6 +3248,7 @@
4B8944FB201967B4007DE474 /* Commodore */,
4B894507201967B4007DE474 /* Disassembler */,
4BD67DC8209BE4D600AB2146 /* DiskII */,
4B051CA426781D6500CA44E8 /* Enterprise */,
4BB4BFB622A4372E0069048D /* Macintosh */,
4B89450F201967B4007DE474 /* MSX */,
4B8944F6201967B4007DE474 /* Oric */,
@@ -3990,12 +4054,8 @@
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
isa = PBXGroup;
children = (
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
4BC751B11D157E61006C31D9 /* 6522Tests.swift */,
4B1E85801D176468001EF87D /* 6532Tests.swift */,
4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */,
4B8DF5132550D62900F3433C /* 65816kromTests.swift */,
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
@@ -4004,43 +4064,49 @@
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */,
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */,
4BD388872239E198002D14B5 /* 68000Tests.mm */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
4B049CDC1DA3C82F00322067 /* BCDTest.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */,
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */,
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */,
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */,
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */,
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */,
4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */,
4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4B4F477B253530B7004245B8 /* Jeek816Tests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */,
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */,
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */,
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */,
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */,
4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */,
4BE76CF822641ED300ACD6FA /* QLTests.mm */,
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */,
4B1414631B588A1100E04248 /* Test Binaries */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
4BC751B11D157E61006C31D9 /* 6522Tests.swift */,
4B1E85801D176468001EF87D /* 6532Tests.swift */,
4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */,
4B8DF5132550D62900F3433C /* 65816kromTests.swift */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B049CDC1DA3C82F00322067 /* BCDTest.swift */,
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */,
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */,
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */,
4B4F477B253530B7004245B8 /* Jeek816Tests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */,
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */,
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */,
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */,
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */,
4B1414631B588A1100E04248 /* Test Binaries */,
);
path = "Clock SignalTests";
sourceTree = "<group>";
@@ -4075,6 +4141,7 @@
4B7A90E22041097C008514A2 /* ColecoVision */,
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
4B2E2D9E1C3A070900138695 /* Electron */,
4B051C9F2676F52200CA44E8 /* Enterprise */,
4B7F188B2154825D00388727 /* MasterSystem */,
4B79A4FC1FC8FF9800EEDAD5 /* MSX */,
4BCF1FA51DADC3E10039D2E7 /* Oric */,
@@ -5177,6 +5244,7 @@
4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */,
4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */,
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */,
4B051CA926781D6500CA44E8 /* StaticAnalyser.cpp in Sources */,
4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */,
4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */,
4B0F1BE62602FF9D00B85C66 /* ZX8081.cpp in Sources */,
@@ -5186,6 +5254,7 @@
4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */,
4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */,
4B0ACC2B23775819008902D0 /* Video.cpp in Sources */,
4B051CA32676F52200CA44E8 /* Enterprise.cpp in Sources */,
4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */,
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */,
4BCD634A22D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
@@ -5214,6 +5283,7 @@
4BB307BC235001C300457D33 /* 6850.cpp in Sources */,
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
4B89451D201967B4007DE474 /* Disk.cpp in Sources */,
4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */,
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */,
4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */,
@@ -5230,7 +5300,7 @@
4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */,
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */,
4B2E86E325DC95150024F1E9 /* Joystick.cpp in Sources */,
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */,
4BEBFB4E2002C4BF000708CC /* FAT12.cpp in Sources */,
4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */,
4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */,
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */,
@@ -5248,6 +5318,7 @@
4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */,
4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */,
4BE21200253FC80900435408 /* StaticAnalyser.cpp in Sources */,
4B051CB72680158600CA44E8 /* EXDos.cpp in Sources */,
4B8318B222D3E53C006DB630 /* Video.cpp in Sources */,
4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */,
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
@@ -5292,6 +5363,7 @@
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */,
4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */,
4B051CAD26783E2000CA44E8 /* Nick.cpp in Sources */,
4B89451F201967B4007DE474 /* Tape.cpp in Sources */,
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
4BEDA3BC25B25563000C2DBD /* Decoder.cpp in Sources */,
@@ -5338,6 +5410,7 @@
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */,
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
4B302185208A550100773308 /* DiskII.cpp in Sources */,
4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */,
4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */,
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
4B89452D201967B4007DE474 /* Tape.cpp in Sources */,
@@ -5461,6 +5534,7 @@
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */,
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */,
4BFEA2EF2682A7B900EBF94C /* Dave.cpp in Sources */,
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
@@ -5492,11 +5566,13 @@
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4B051CA22676F52200CA44E8 /* Enterprise.cpp in Sources */,
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */,
4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */,
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */,
4B051CB0267C1CA200CA44E8 /* Keyboard.cpp in Sources */,
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */,
4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
@@ -5505,11 +5581,13 @@
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */,
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
4B47770B268FBE4D005C2340 /* FAT.cpp in Sources */,
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4B2E86CF25D8D8C70024F1E9 /* Keyboard.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
@@ -5538,7 +5616,7 @@
4B894536201967B4007DE474 /* Z80.cpp in Sources */,
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */,
4BEBFB4D2002C4BF000708CC /* FAT12.cpp in Sources */,
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */,
@@ -5549,9 +5627,11 @@
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */,
4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
4B051CB62680158600CA44E8 /* EXDos.cpp in Sources */,
4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */,
4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */,
4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
4B051CAC26783E2000CA44E8 /* Nick.cpp in Sources */,
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */,
4B0F1BDA2602FF9800B85C66 /* Video.cpp in Sources */,
4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */,
@@ -5684,7 +5764,7 @@
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
4B778F6023A5F3460000D260 /* Disk.cpp in Sources */,
4B778F5C23A5F3070000D260 /* MSX.cpp in Sources */,
4B778F0323A5EBB00000D260 /* MSXDSK.cpp in Sources */,
4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */,
4B778F4023A5F1910000D260 /* z8530.cpp in Sources */,
4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */,
4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */,
@@ -5692,10 +5772,12 @@
4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */,
4B778F2223A5EDDD0000D260 /* PulseQueuedTape.cpp in Sources */,
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */,
4B051CB3267D3FF800CA44E8 /* EnterpriseNickTests.mm in Sources */,
4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */,
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,
4B778F5623A5F2AF0000D260 /* CPM.cpp in Sources */,
4B778F1C23A5ED3F0000D260 /* TimedEventLoop.cpp in Sources */,
4B47770D26900685005C2340 /* EnterpriseDaveTests.mm in Sources */,
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
4B778F3823A5F11C0000D260 /* SegmentParser.cpp in Sources */,
4B778F0723A5EC150000D260 /* CommodoreTAP.cpp in Sources */,

View File

@@ -85,8 +85,12 @@
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=amstradcpc"
isEnabled = "NO">
argument = "--new=enterprise"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--basic-version=any"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col&quot;"
@@ -126,15 +130,15 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--model=mac512k"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=macintosh"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--use-square-pixels"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>

View File

@@ -73,6 +73,7 @@
debugDocumentVersioning = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
enableGPUShaderValidationMode = "2"
allowLocationSimulation = "NO">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -0,0 +1,37 @@
//
// AppleIIOptionsPanel.swift
// Clock Signal
//
// Created by Thomas Harte on 07/06/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
class AppleIIOptionsPanel: MachinePanel {
var appleII: CSAppleII! {
get {
return self.machine.appleII
}
}
var squarePixelsUserDefaultsKey: String {
return prefixedUserDefaultsKey("useSquarePixels")
}
@IBOutlet var squarePixelButton: NSButton!
@IBAction func optionDidChange(_ sender: AnyObject!) {
let useSquarePixels = squarePixelButton.state == .on
appleII.useSquarePixels = useSquarePixels
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.set(useSquarePixels, forKey: squarePixelsUserDefaultsKey)
}
override func establishStoredOptions() {
super.establishStoredOptions()
let standardUserDefaults = UserDefaults.standard
let useSquarePixels = standardUserDefaults.bool(forKey: squarePixelsUserDefaultsKey)
appleII.useSquarePixels = useSquarePixels
squarePixelButton.state = useSquarePixels ? .on : .off
}
}

View File

@@ -41,6 +41,35 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
CSMachineCPCModel6128
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseModel) {
CSMachineEnterpriseModel64,
CSMachineEnterpriseModel128,
CSMachineEnterpriseModel256,
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseSpeed) {
CSMachineEnterpriseSpeed4MHz,
CSMachineEnterpriseSpeed6MHz
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseEXOS) {
CSMachineEnterpriseEXOSVersion21,
CSMachineEnterpriseEXOSVersion20,
CSMachineEnterpriseEXOSVersion10,
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseBASIC) {
CSMachineEnterpriseBASICVersion21,
CSMachineEnterpriseBASICVersion11,
CSMachineEnterpriseBASICVersion10,
CSMachineEnterpriseBASICNone,
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseDOS) {
CSMachineEnterpriseDOSEXDOS,
CSMachineEnterpriseDOSNone,
};
typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) {
CSMachineMacintoshModel128k,
CSMachineMacintoshModel512k,
@@ -96,6 +125,7 @@ typedef int Kilobytes;
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize;
- (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model;
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model speed:(CSMachineEnterpriseSpeed)speed exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos;
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
- (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive;
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;

View File

@@ -19,6 +19,7 @@
#include "../../../../../Analyser/Static/AppleIIgs/Target.hpp"
#include "../../../../../Analyser/Static/AtariST/Target.hpp"
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../../../Analyser/Static/Enterprise/Target.hpp"
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
#include "../../../../../Analyser/Static/MSX/Target.hpp"
#include "../../../../../Analyser/Static/Oric/Target.hpp"
@@ -130,6 +131,51 @@
return self;
}
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model speed:(CSMachineEnterpriseSpeed)speed exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos {
self = [super init];
if(self) {
using Target = Analyser::Static::Enterprise::Target;
auto target = std::make_unique<Target>();
switch(model) {
case CSMachineEnterpriseModel64: target->model = Target::Model::Enterprise64; break;
default:
case CSMachineEnterpriseModel128: target->model = Target::Model::Enterprise128; break;
case CSMachineEnterpriseModel256: target->model = Target::Model::Enterprise256; break;
}
switch(speed) {
case CSMachineEnterpriseSpeed6MHz: target->speed = Target::Speed::SixMHz; break;
default:
case CSMachineEnterpriseSpeed4MHz: target->speed = Target::Speed::FourMHz; break;
}
switch(exosVersion) {
case CSMachineEnterpriseEXOSVersion21: target->exos_version = Target::EXOSVersion::v21; break;
default:
case CSMachineEnterpriseEXOSVersion20: target->exos_version = Target::EXOSVersion::v20; break;
case CSMachineEnterpriseEXOSVersion10: target->exos_version = Target::EXOSVersion::v10; break;
}
switch(basicVersion) {
case CSMachineEnterpriseBASICNone: target->basic_version = Target::BASICVersion::None; break;
default:
case CSMachineEnterpriseBASICVersion21: target->basic_version = Target::BASICVersion::v21; break;
case CSMachineEnterpriseBASICVersion11: target->basic_version = Target::BASICVersion::v11; break;
case CSMachineEnterpriseBASICVersion10: target->basic_version = Target::BASICVersion::v10; break;
}
switch(dos) {
case CSMachineEnterpriseDOSEXDOS: target->dos = Target::DOS::EXDOS; break;
default:
case CSMachineEnterpriseDOSNone: target->dos = Target::DOS::None; break;
}
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model {
self = [super init];
if(self) {
@@ -276,6 +322,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
case Analyser::Machine::AtariST: return @"CompositeOptions";
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Enterprise: return @"CompositeOptions";
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
case Analyser::Machine::MasterSystem: return @"CompositeOptions";
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="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,10 +17,10 @@
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="316"/>
<rect key="contentRect" x="196" y="240" width="590" height="353"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="316"/>
<rect key="frame" x="0.0" y="0.0" width="590" height="353"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
@@ -59,16 +59,16 @@ Gw
</textFieldCell>
</textField>
<tabView type="noTabsBezelBorder" translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
<rect key="frame" x="154" y="56" width="420" height="206"/>
<rect key="frame" x="154" y="56" width="420" height="243"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="18" y="163" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<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="18" y="133" width="96" height="16"/>
<rect key="frame" x="18" y="155" width="96" height="16"/>
<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="67" y="157" width="117" height="25"/>
<rect key="frame" x="67" y="179" width="117" height="25"/>
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -99,7 +99,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
<rect key="frame" x="117" y="127" width="134" height="25"/>
<rect key="frame" x="117" y="149" width="134" height="25"/>
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -115,11 +115,11 @@ Gw
</subviews>
<constraints>
<constraint firstItem="WnO-ef-IC6" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="5RF-1w-9HW"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="5" id="865-cv-qVk"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="20" symbolic="YES" id="865-cv-qVk"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="trailing" constant="20" symbolic="YES" id="9GL-al-1qi"/>
<constraint firstItem="WnO-ef-IC6" firstAttribute="centerY" secondItem="LSB-WP-FMi" secondAttribute="centerY" id="Fuj-zT-MIm"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="jli-ac-Sij" secondAttribute="trailing" constant="20" symbolic="YES" id="I8d-OR-ICN"/>
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="5" id="Qi1-CV-A0c"/>
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="20" symbolic="YES" id="Qi1-CV-A0c"/>
<constraint firstItem="V5Z-dX-Ns4" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="SWc-iX-1We"/>
<constraint firstItem="LSB-WP-FMi" firstAttribute="leading" secondItem="WnO-ef-IC6" secondAttribute="trailing" constant="8" symbolic="YES" id="bte-XA-xNQ"/>
<constraint firstItem="LSB-WP-FMi" firstAttribute="top" secondItem="jli-ac-Sij" secondAttribute="bottom" constant="10" symbolic="YES" id="ki5-JR-vRe"/>
@@ -129,12 +129,12 @@ Gw
</view>
</tabViewItem>
<tabViewItem label="Apple IIgs" identifier="appleiigs" id="u5E-8n-ghF">
<view key="view" ambiguous="YES" id="jOM-9f-vkk">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<view key="view" id="jOM-9f-vkk">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="kiv-1P-FWc">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -142,7 +142,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<rect key="frame" x="18" y="23" width="85" height="16"/>
<rect key="frame" x="18" y="155" width="85" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory size:" id="OLJ-nC-yyj">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -150,7 +150,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gcS-uy-mzl">
<rect key="frame" x="67" y="47" width="89" height="25"/>
<rect key="frame" x="67" y="179" width="89" height="25"/>
<popUpButtonCell key="cell" type="push" title="ROM 03" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="axesIndependently" inset="2" selectedItem="0TS-DO-O9h" id="hjw-g8-e2f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -164,7 +164,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nQa-YS-utT">
<rect key="frame" x="106" y="17" width="82" height="25"/>
<rect key="frame" x="106" y="149" width="82" height="25"/>
<popUpButtonCell key="cell" type="push" title="8 mb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8192" imageScaling="axesIndependently" inset="2" selectedItem="UHg-gU-Xnn" id="dl3-cq-uWO">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -182,24 +182,24 @@ Gw
<constraint firstItem="LES-76-Ovz" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="2D3-Ve-6CN"/>
<constraint firstItem="0d9-IG-gKU" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="8Xz-86-tDf"/>
<constraint firstItem="0d9-IG-gKU" firstAttribute="centerY" secondItem="gcS-uy-mzl" secondAttribute="centerY" id="Eww-Qq-eBT"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="5" id="F6i-cP-7AR"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="20" symbolic="YES" id="F6i-cP-7AR"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="leading" secondItem="0d9-IG-gKU" secondAttribute="trailing" constant="8" symbolic="YES" id="LUm-rI-LYP"/>
<constraint firstItem="LES-76-Ovz" firstAttribute="centerY" secondItem="nQa-YS-utT" secondAttribute="centerY" id="UdP-U6-OFE"/>
<constraint firstItem="nQa-YS-utT" firstAttribute="leading" secondItem="LES-76-Ovz" secondAttribute="trailing" constant="8" symbolic="YES" id="Xm1-iG-Dgj"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gcS-uy-mzl" secondAttribute="trailing" constant="20" symbolic="YES" id="a38-Cx-CEh"/>
<constraint firstItem="nQa-YS-utT" firstAttribute="top" secondItem="gcS-uy-mzl" secondAttribute="bottom" constant="10" symbolic="YES" id="aM9-m8-s7z"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="trailing" constant="20" symbolic="YES" id="cqx-Jc-rUb"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="5" id="sbT-If-NTU"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="20" symbolic="YES" id="sbT-If-NTU"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
<rect key="frame" x="67" y="47" width="96" height="25"/>
<rect key="frame" x="67" y="179" width="96" height="25"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -213,7 +213,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<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"/>
@@ -224,8 +224,8 @@ Gw
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="20" symbolic="YES" id="4AF-5C-2IF"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="20" symbolic="YES" id="Wof-5h-gfD"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="5" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="5" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="20" symbolic="YES" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="20" symbolic="YES" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" symbolic="YES" id="mA8-US-ndo"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
</constraints>
@@ -233,11 +233,11 @@ Gw
</tabViewItem>
<tabViewItem label="Atari ST" identifier="atarist" id="a6Y-mx-yFn">
<view key="view" id="nnv-Wi-7hc">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nLf-LI-nWO">
<rect key="frame" x="18" y="57" width="728" height="16"/>
<rect key="frame" x="18" y="204" width="364" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="At present only a 512k Atari ST is supported." id="gBA-ke-mur">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -254,32 +254,32 @@ Gw
</tabViewItem>
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
<view key="view" id="SRc-2D-95G">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
<rect key="frame" x="18" y="56" width="168" height="18"/>
<rect key="frame" x="18" y="186" width="168" height="18"/>
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
<rect key="frame" x="18" y="34" width="232" height="18"/>
<rect key="frame" x="18" y="164" width="232" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cG2-Ph-S3Z">
<rect key="frame" x="18" y="12" width="231" height="18"/>
<rect key="frame" x="18" y="142" width="231" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Plus 6 Utility ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="yjF-XS-zx6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
<rect key="frame" x="18" y="-10" width="284" height="18"/>
<rect key="frame" x="18" y="120" width="284" height="18"/>
<buttonCell key="cell" type="check" title="Fill unused ROM banks with sideways RAM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="JEz-eK-uWp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -289,13 +289,13 @@ Gw
<constraints>
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="1iM-70-oZq"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="E9b-RP-9vj"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="5" id="FM6-AA-Vhf"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="20" symbolic="YES" id="FM6-AA-Vhf"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="NfY-dE-aJw"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="cG2-Ph-S3Z" secondAttribute="bottom" constant="6" symbolic="YES" id="S45-42-Gtv"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="trailing" constant="20" symbolic="YES" id="X3p-qJ-ENH"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="b5a-SX-2ty"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="trailing" constant="20" symbolic="YES" id="dmY-PV-ap4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="3" id="ggl-QH-mV4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="20" symbolic="YES" id="ggl-QH-mV4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cG2-Ph-S3Z" secondAttribute="trailing" constant="20" symbolic="YES" id="m6t-dP-71d"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" symbolic="YES" id="mvO-UZ-BtT"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="npw-IZ-6xU"/>
@@ -303,13 +303,158 @@ Gw
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Enterprise" identifier="enterprise" id="zhO-EO-wUe">
<view key="view" id="1cs-PX-RAH">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PhH-bu-pb5">
<rect key="frame" x="80" y="179" width="129" height="25"/>
<popUpButtonCell key="cell" type="push" title="Enterprise 128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="128" imageScaling="axesIndependently" inset="2" selectedItem="roH-nL-f8o" id="z9O-XC-XBv">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="ojI-Vb-iHz">
<items>
<menuItem title="Enterprise 256" tag="256" id="Al3-A0-tvw"/>
<menuItem title="Enterprise 128" state="on" tag="128" id="roH-nL-f8o"/>
<menuItem title="Enterprise 64" tag="64" id="vNG-Tv-bDI"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lfl-5c-b8j">
<rect key="frame" x="67" y="149" width="77" height="25"/>
<popUpButtonCell key="cell" type="push" title="4 Mhz" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="axesIndependently" inset="2" selectedItem="5N6-tD-uN7" id="BU2-NJ-Kii">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="ANK-p0-M77">
<items>
<menuItem title="4 Mhz" state="on" tag="4" id="5N6-tD-uN7"/>
<menuItem title="6 Mhz" tag="6" id="bVT-qO-U7n"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nen-Za-7zH">
<rect key="frame" x="64" y="119" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="uNC-hA-d5z">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="Qja-xZ-wVM"/>
<menuItem title="Version 2.0" tag="20" id="XTj-l7-KX3"/>
<menuItem title="Version 1.0" tag="10" id="Ky2-2D-wcY"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-GH-7xi">
<rect key="frame" x="67" y="89" width="105" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="VcH-Xm-1hY">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="TME-cv-Jh1"/>
<menuItem title="Version 1.1" tag="11" id="7P2-aF-6fp"/>
<menuItem title="Version 1.0" tag="10" id="j8p-uY-BhG"/>
<menuItem title="None" id="eGk-Bj-IVT"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syE-e7-TjU">
<rect key="frame" x="57" y="59" width="83" height="25"/>
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="DOS" id="sdr-al-7mi">
<items>
<menuItem title="EXDOS" state="on" tag="1" id="8rP-2w-PdU"/>
<menuItem title="None" id="qoS-KO-iEZ"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<rect key="frame" x="18" y="125" width="43" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
<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>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<rect key="frame" x="18" y="185" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Machine:" id="uTv-hH-mIC">
<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>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<rect key="frame" x="18" y="95" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
<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>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<rect key="frame" x="18" y="65" width="36" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
<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>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
<rect key="frame" x="17" y="155" width="47" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="sAw-C9-Sf7">
<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="dzd-tH-BjX" firstAttribute="centerY" secondItem="hIr-GH-7xi" secondAttribute="centerY" id="3TV-RU-Kgh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="hIr-GH-7xi" secondAttribute="trailing" constant="20" symbolic="YES" id="44v-9O-y7L"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="centerY" secondItem="PhH-bu-pb5" secondAttribute="centerY" id="6Wc-aR-wuL"/>
<constraint firstItem="dzd-tH-BjX" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="7RZ-Om-TAa"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="centerY" secondItem="nen-Za-7zH" secondAttribute="centerY" id="CLa-6E-8BB"/>
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="Lfl-5c-b8j" secondAttribute="bottom" constant="10" symbolic="YES" id="Df8-qv-3dY"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="ENF-TY-TQ7"/>
<constraint firstItem="nen-Za-7zH" firstAttribute="leading" secondItem="ykc-W1-YaS" secondAttribute="trailing" constant="8" symbolic="YES" id="GWR-VI-9PG"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="bottom" constant="20" symbolic="YES" id="K3s-FA-zMB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="PhH-bu-pb5" secondAttribute="trailing" constant="20" symbolic="YES" id="LwB-ef-uF4"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="top" secondItem="1cs-PX-RAH" secondAttribute="top" constant="20" symbolic="YES" id="Myt-i4-jVq"/>
<constraint firstItem="Lfl-5c-b8j" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Nl3-hL-jwg"/>
<constraint firstItem="pxr-Bq-yh0" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="Qzp-IY-Pa0"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="T3e-u7-fiQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="trailing" constant="20" symbolic="YES" id="TEX-Nw-y2K"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="TgR-RR-eA1"/>
<constraint firstItem="pxr-Bq-yh0" firstAttribute="centerY" secondItem="syE-e7-TjU" secondAttribute="centerY" id="UYw-uz-Am0"/>
<constraint firstItem="hIr-GH-7xi" firstAttribute="top" secondItem="nen-Za-7zH" secondAttribute="bottom" constant="10" symbolic="YES" id="VOc-2v-s3u"/>
<constraint firstItem="syE-e7-TjU" firstAttribute="top" secondItem="hIr-GH-7xi" secondAttribute="bottom" constant="10" symbolic="YES" id="W9S-on-Heq"/>
<constraint firstItem="rHr-bh-QMV" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="19" id="XZf-lB-NnA"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="bAP-lx-pdi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nen-Za-7zH" secondAttribute="trailing" constant="20" symbolic="YES" id="eEI-5Q-TnO"/>
<constraint firstItem="syE-e7-TjU" firstAttribute="leading" secondItem="pxr-Bq-yh0" secondAttribute="trailing" constant="8" symbolic="YES" id="fmM-Ma-Jyu"/>
<constraint firstItem="hIr-GH-7xi" firstAttribute="leading" secondItem="dzd-tH-BjX" secondAttribute="trailing" constant="8" symbolic="YES" id="jDQ-TF-Ogf"/>
<constraint firstItem="rHr-bh-QMV" firstAttribute="centerY" secondItem="Lfl-5c-b8j" secondAttribute="centerY" id="mnp-JF-dVQ"/>
<constraint firstItem="Lfl-5c-b8j" firstAttribute="leading" secondItem="rHr-bh-QMV" secondAttribute="trailing" constant="8" symbolic="YES" id="yfb-SL-v1H"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
<view key="view" id="7Yf-vi-Q0W">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -317,7 +462,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xa6-NA-JY5">
<rect key="frame" x="67" y="47" width="75" height="25"/>
<rect key="frame" x="67" y="179" width="75" height="25"/>
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="axesIndependently" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -333,9 +478,9 @@ Gw
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="5" id="3hY-Ca-mnR"/>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="20" symbolic="YES" id="3hY-Ca-mnR"/>
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="20" symbolic="YES" id="5s6-87-VT6"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="5" id="KYf-GJ-Y7k"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="20" symbolic="YES" id="KYf-GJ-Y7k"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="trailing" constant="20" symbolic="YES" id="LZ5-xH-fU0"/>
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="centerY" secondItem="xa6-NA-JY5" secondAttribute="centerY" id="Wa5-KX-3Me"/>
<constraint firstItem="xa6-NA-JY5" firstAttribute="leading" secondItem="ZOY-4E-Cfl" secondAttribute="trailing" constant="8" symbolic="YES" id="ktS-sr-F8L"/>
@@ -344,18 +489,18 @@ Gw
</tabViewItem>
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
<view key="view" id="mWD-An-tR7">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
<rect key="frame" x="18" y="26" width="128" height="18"/>
<rect key="frame" x="18" y="158" width="128" height="18"/>
<buttonCell key="cell" type="check" title="Attach disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CB3-nA-VTM">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LG6-mP-SeG">
<rect key="frame" x="71" y="47" width="146" height="25"/>
<rect key="frame" x="71" y="179" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -369,7 +514,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<rect key="frame" x="18" y="53" width="50" height="16"/>
<rect key="frame" x="18" y="185" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -380,9 +525,9 @@ Gw
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="20" symbolic="YES" id="0Oc-n7-gaM"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="8" symbolic="YES" id="LBt-4m-GDc"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="5" id="bcb-ZZ-VpQ"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="20" symbolic="YES" id="bcb-ZZ-VpQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="20" symbolic="YES" id="l8P-UW-8ig"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="3" id="mga-YX-Bek"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="20" symbolic="YES" id="mga-YX-Bek"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="q8Q-kh-Opj"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="leading" secondItem="ZaD-7v-rMS" secondAttribute="trailing" constant="8" symbolic="YES" id="svb-nH-GlP"/>
<constraint firstItem="ZaD-7v-rMS" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="zgh-a5-FNF"/>
@@ -392,11 +537,11 @@ Gw
</tabViewItem>
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
<view key="view" id="sOR-e0-8iZ">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -404,7 +549,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ENP-hI-BVZ">
<rect key="frame" x="67" y="47" width="107" height="25"/>
<rect key="frame" x="67" y="179" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Oric-1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="jGN-1a-biF" id="Jll-EJ-cMr">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -418,7 +563,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
<rect key="frame" x="113" y="17" width="130" height="25"/>
<rect key="frame" x="113" y="149" width="130" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -434,7 +579,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<rect key="frame" x="18" y="23" width="92" height="16"/>
<rect key="frame" x="18" y="155" width="92" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -443,7 +588,7 @@ Gw
</textField>
</subviews>
<constraints>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="5" id="Bgq-8R-8I4"/>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="20" symbolic="YES" id="Bgq-8R-8I4"/>
<constraint firstItem="fYL-p6-wyn" firstAttribute="leading" secondItem="okM-ZI-NbF" secondAttribute="trailing" constant="8" symbolic="YES" id="Pra-lC-JPB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ENP-hI-BVZ" secondAttribute="trailing" constant="20" symbolic="YES" id="Sr5-QZ-xU3"/>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="leading" secondItem="0ct-tf-uRH" secondAttribute="trailing" constant="8" symbolic="YES" id="Wcg-1P-z6l"/>
@@ -453,17 +598,17 @@ Gw
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="trailing" constant="20" symbolic="YES" id="ewV-Dw-zj2"/>
<constraint firstItem="0ct-tf-uRH" firstAttribute="leading" secondItem="sOR-e0-8iZ" secondAttribute="leading" constant="20" symbolic="YES" id="huH-9L-97Y"/>
<constraint firstItem="fYL-p6-wyn" firstAttribute="top" secondItem="ENP-hI-BVZ" secondAttribute="bottom" constant="10" symbolic="YES" id="l3T-Ve-0Jw"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="5" id="mN9-AZ-wSn"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="20" symbolic="YES" id="mN9-AZ-wSn"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
<view key="view" id="fLI-XB-QCr">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
<rect key="frame" x="71" y="47" width="146" height="25"/>
<rect key="frame" x="71" y="179" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="45i-0n-gau" id="yi7-eo-I0q">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -479,7 +624,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2eV-Us-eEv">
<rect key="frame" x="108" y="17" width="116" height="25"/>
<rect key="frame" x="108" y="149" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="fOl-8Q-fsA" id="rH0-7T-pJE">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -493,7 +638,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<rect key="frame" x="18" y="53" width="50" height="16"/>
<rect key="frame" x="18" y="185" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -501,7 +646,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<rect key="frame" x="18" y="23" width="87" height="16"/>
<rect key="frame" x="18" y="155" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -509,7 +654,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lrf-gL-6EI">
<rect key="frame" x="18" y="-4" width="177" height="18"/>
<rect key="frame" x="18" y="128" width="177" height="18"/>
<buttonCell key="cell" type="check" title="Attach C-1540 disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tsq-YD-xw8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -521,7 +666,7 @@ Gw
<constraint firstItem="MTh-9p-FqC" firstAttribute="centerY" secondItem="ueK-gq-gaF" secondAttribute="centerY" id="8KD-Bm-KRA"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="leading" secondItem="MTh-9p-FqC" secondAttribute="trailing" constant="8" symbolic="YES" id="9m6-6j-8j2"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="trailing" constant="20" symbolic="YES" id="M08-mP-Plz"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="5" id="XN3-GK-BX9"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="20" symbolic="YES" id="XN3-GK-BX9"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ueK-gq-gaF" secondAttribute="trailing" constant="20" symbolic="YES" id="YZ9-7N-ssA"/>
<constraint firstItem="MTh-9p-FqC" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="bgZ-k9-IQC"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2eV-Us-eEv" secondAttribute="trailing" constant="20" symbolic="YES" id="eiB-vH-17d"/>
@@ -529,18 +674,18 @@ Gw
<constraint firstItem="2eV-Us-eEv" firstAttribute="leading" secondItem="gRS-DK-rIy" secondAttribute="trailing" constant="8" symbolic="YES" id="hQ9-uy-KHg"/>
<constraint firstItem="gRS-DK-rIy" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="iyp-1K-rdC"/>
<constraint firstItem="gRS-DK-rIy" firstAttribute="centerY" secondItem="2eV-Us-eEv" secondAttribute="centerY" id="qWf-Nb-PfJ"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="3" id="vNb-Sw-Mxw"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="20" symbolic="YES" id="vNb-Sw-Mxw"/>
<constraint firstItem="Lrf-gL-6EI" firstAttribute="top" secondItem="2eV-Us-eEv" secondAttribute="bottom" constant="8" symbolic="YES" id="zBX-Qq-j5f"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
<view key="view" id="8hL-Vn-Hg0">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
<rect key="frame" x="108" y="47" width="116" height="25"/>
<rect key="frame" x="108" y="179" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -554,7 +699,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<rect key="frame" x="18" y="53" width="87" height="16"/>
<rect key="frame" x="18" y="185" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -562,7 +707,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
<rect key="frame" x="18" y="26" width="118" height="18"/>
<rect key="frame" x="18" y="158" width="118" height="18"/>
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -574,8 +719,8 @@ Gw
<constraint firstItem="NCX-4e-lSu" firstAttribute="centerY" secondItem="I1a-Eu-5UB" secondAttribute="centerY" id="1ve-sc-QwI"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="leading" secondItem="NCX-4e-lSu" secondAttribute="trailing" constant="8" symbolic="YES" id="Bu6-60-74x"/>
<constraint firstItem="NCX-4e-lSu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="W09-iG-ARI"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="5" id="fkf-wO-Q8i"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="3" id="fmT-7E-hUT"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="20" symbolic="YES" id="fkf-wO-Q8i"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="20" symbolic="YES" id="fmT-7E-hUT"/>
<constraint firstItem="ReP-bV-Thu" firstAttribute="top" secondItem="I1a-Eu-5UB" secondAttribute="bottom" constant="8" symbolic="YES" id="hYk-xC-63o"/>
<constraint firstItem="ReP-bV-Thu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="qen-KS-rWi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="trailing" constant="20" symbolic="YES" id="r7F-DT-oRc"/>
@@ -584,11 +729,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
<view key="view" id="bmd-gL-gzT">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
<rect key="frame" x="108" y="157" width="116" height="25"/>
<rect key="frame" x="108" y="179" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -602,7 +747,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="163" width="87" height="16"/>
<rect key="frame" x="18" y="185" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -613,37 +758,37 @@ Gw
<constraints>
<constraint firstItem="8tU-73-XEE" firstAttribute="centerY" secondItem="5aO-UX-HnX" secondAttribute="centerY" id="1Jm-YL-OKU"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="trailing" constant="20" symbolic="YES" id="ALj-0x-bFb"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="5" id="LJ2-W9-fOf"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="20" symbolic="YES" id="LJ2-W9-fOf"/>
<constraint firstItem="8tU-73-XEE" firstAttribute="leading" secondItem="bmd-gL-gzT" secondAttribute="leading" constant="20" symbolic="YES" id="M6Y-jN-LAf"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="5" id="PPD-Jz-qCL"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="20" symbolic="YES" id="PPD-Jz-qCL"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="leading" secondItem="8tU-73-XEE" secondAttribute="trailing" constant="8" symbolic="YES" id="j1u-n4-2IK"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
<view key="view" id="bMx-F6-JUb">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
<rect key="frame" x="67" y="157" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<rect key="frame" x="67" y="179" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8lt-dk-zPr">
<items>
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="16kb" state="on" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+2a" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="163" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -655,23 +800,23 @@ Gw
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="trailing" constant="20" symbolic="YES" id="90E-uI-MQg"/>
<constraint firstItem="fJ3-ma-Byy" firstAttribute="leading" secondItem="bMx-F6-JUb" secondAttribute="leading" constant="20" symbolic="YES" id="9kE-iQ-dxd"/>
<constraint firstItem="fJ3-ma-Byy" firstAttribute="centerY" secondItem="gFZ-d4-WFv" secondAttribute="centerY" id="LxG-5E-Q5Y"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="5" id="d8S-vX-B5e"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="20" symbolic="YES" id="d8S-vX-B5e"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="leading" secondItem="fJ3-ma-Byy" secondAttribute="trailing" constant="8" symbolic="YES" id="hKS-47-R2y"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="5" id="wsX-Wq-iPt"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="20" symbolic="YES" id="wsX-Wq-iPt"/>
</constraints>
</view>
</tabViewItem>
</tabViewItems>
</tabView>
<scrollView borderType="line" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="z5Q-Bs-hJj">
<rect key="frame" x="20" y="60" width="130" height="201"/>
<rect key="frame" x="20" y="60" width="130" height="238"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
<clipView key="contentView" id="O8s-Vw-9yQ">
<rect key="frame" x="1" y="1" width="128" height="199"/>
<rect key="frame" x="1" y="1" width="128" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" id="3go-Eb-GOy">
<rect key="frame" x="0.0" y="0.0" width="128" height="199"/>
<rect key="frame" x="0.0" y="0.0" width="128" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@@ -708,7 +853,7 @@ Gw
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<rect key="frame" x="18" y="269" width="554" height="27"/>
<rect key="frame" x="18" y="306" width="554" height="27"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose a machine" id="32m-Vs-dPO">
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -737,7 +882,7 @@ Gw
<constraint firstItem="9YM-5x-pc0" firstAttribute="top" secondItem="z5Q-Bs-hJj" secondAttribute="bottom" constant="14" id="suf-rn-Bmy"/>
</constraints>
</view>
<point key="canvasLocation" x="-1" y="88"/>
<point key="canvasLocation" x="-1" y="106.5"/>
</window>
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
<connections>
@@ -750,6 +895,11 @@ Gw
<outlet property="electronAP6Button" destination="cG2-Ph-S3Z" id="vkq-1J-KBG"/>
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
<outlet property="electronSidewaysRAMButton" destination="lzo-8g-o4S" id="LtS-wv-tMf"/>
<outlet property="enterpriseBASICButton" destination="hIr-GH-7xi" id="fM6-It-9UO"/>
<outlet property="enterpriseDOSButton" destination="syE-e7-TjU" id="sCW-Bj-ZTW"/>
<outlet property="enterpriseEXOSButton" destination="nen-Za-7zH" id="NwS-ua-FdA"/>
<outlet property="enterpriseModelButton" destination="PhH-bu-pb5" id="8wD-sW-aBw"/>
<outlet property="enterpriseSpeedButton" destination="Lfl-5c-b8j" id="ClN-yN-Nbx"/>
<outlet property="machineNameTable" destination="3go-Eb-GOy" id="Ppf-S0-IP1"/>
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>

View File

@@ -27,14 +27,21 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
@IBOutlet var appleIIgsModelButton: NSPopUpButton!
@IBOutlet var appleIIgsMemorySizeButton: NSPopUpButton!
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
// MARK: - Electron properties
@IBOutlet var electronDFSButton: NSButton!
@IBOutlet var electronADFSButton: NSButton!
@IBOutlet var electronAP6Button: NSButton!
@IBOutlet var electronSidewaysRAMButton: NSButton!
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
// MARK: - Enterprise properties
@IBOutlet var enterpriseModelButton: NSPopUpButton!
@IBOutlet var enterpriseSpeedButton: NSPopUpButton!
@IBOutlet var enterpriseEXOSButton: NSPopUpButton!
@IBOutlet var enterpriseBASICButton: NSPopUpButton!
@IBOutlet var enterpriseDOSButton: NSPopUpButton!
// MARK: - Macintosh properties
@IBOutlet var macintoshModelTypeButton: NSPopUpButton!
@@ -93,14 +100,21 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
appleIIgsModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsModel"))
appleIIgsMemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsMemorySize"))
// CPC settings
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Electron settings
electronDFSButton.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off
electronADFSButton.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off
electronAP6Button.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off
electronSidewaysRAMButton.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off
// CPC settings
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Enterprise settings
enterpriseModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseModel"))
enterpriseSpeedButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseSpeed"))
enterpriseEXOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseEXOSVersion"))
enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion"))
enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS"))
// Macintosh settings
macintoshModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
@@ -143,14 +157,21 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
standardUserDefaults.set(appleIIgsModelButton.selectedTag(), forKey: "new.appleIIgsModel")
standardUserDefaults.set(appleIIgsMemorySizeButton.selectedTag(), forKey: "new.appleIIgsMemorySize")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
// Electron settings
standardUserDefaults.set(electronDFSButton.state == .on, forKey: "new.electronDFS")
standardUserDefaults.set(electronADFSButton.state == .on, forKey: "new.electronADFS")
standardUserDefaults.set(electronAP6Button.state == .on, forKey: "new.electronAP6")
standardUserDefaults.set(electronSidewaysRAMButton.state == .on, forKey: "new.electronSidewaysRAM")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
// Enterprise settings
standardUserDefaults.set(enterpriseModelButton.selectedTag(), forKey: "new.enterpriseModel")
standardUserDefaults.set(enterpriseSpeedButton.selectedTag(), forKey: "new.enterpriseSpeed")
standardUserDefaults.set(enterpriseEXOSButton.selectedTag(), forKey: "new.enterpriseEXOSVersion")
standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion")
standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS")
// Macintosh settings
standardUserDefaults.set(macintoshModelTypeButton.selectedTag(), forKey: "new.macintoshModel")
@@ -205,12 +226,6 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
storeOptions()
switch machineSelector.selectedTabViewItem!.identifier as! String {
case "electron":
return CSStaticAnalyser(
electronDFS: electronDFSButton.state == .on,
adfs: electronADFSButton.state == .on,
ap6: electronAP6Button.state == .on,
sidewaysRAM: electronSidewaysRAMButton.state == .on)
case "appleii":
var model: CSMachineAppleIIModel = .appleII
@@ -241,22 +256,71 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
default: model = .ROM00
}
let memorySize = Kilobytes(appleIIgsMemorySizeButton.selectedItem!.tag)
let memorySize = Kilobytes(appleIIgsMemorySizeButton.selectedTag())
return CSStaticAnalyser(appleIIgsModel: model, memorySize: memorySize)
case "atarist":
return CSStaticAnalyser(atariSTModel: .model512k)
case "cpc":
switch cpcModelTypeButton.selectedItem!.tag {
switch cpcModelTypeButton.selectedTag() {
case 464: return CSStaticAnalyser(amstradCPCModel: .model464)
case 664: return CSStaticAnalyser(amstradCPCModel: .model664)
case 6128: fallthrough
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
}
case "electron":
return CSStaticAnalyser(
electronDFS: electronDFSButton.state == .on,
adfs: electronADFSButton.state == .on,
ap6: electronAP6Button.state == .on,
sidewaysRAM: electronSidewaysRAMButton.state == .on)
case "enterprise":
var model: CSMachineEnterpriseModel = .model128
switch enterpriseModelButton.selectedTag() {
case 64: model = .model64
case 256: model = .model256
case 128: fallthrough
default: model = .model128
}
var speed: CSMachineEnterpriseSpeed = .speed4MHz
switch enterpriseSpeedButton.selectedTag() {
case 6: speed = .speed6MHz
case 4: fallthrough
default: speed = .speed4MHz
}
var exos: CSMachineEnterpriseEXOS = .version21
switch enterpriseEXOSButton.selectedTag() {
case 10: exos = .version10
case 20: exos = .version20
case 21: fallthrough
default: exos = .version21
}
var basic: CSMachineEnterpriseBASIC = .version21
switch enterpriseBASICButton.selectedTag() {
case 0: basic = .none
case 10: basic = .version10
case 11: basic = .version11
case 21: fallthrough
default: basic = .version21
}
var dos: CSMachineEnterpriseDOS = .dosNone
switch enterpriseDOSButton.selectedTag() {
case 1: dos = .DOSEXDOS
case 0: fallthrough
default: dos = .dosNone
}
return CSStaticAnalyser(enterpriseModel: model, speed: speed, exosVersion: exos, basicVersion: basic, dos: dos)
case "mac":
switch macintoshModelTypeButton.selectedItem!.tag {
switch macintoshModelTypeButton.selectedTag() {
case 0: return CSStaticAnalyser(macintoshModel: .model128k)
case 1: return CSStaticAnalyser(macintoshModel: .model512k)
case 2: return CSStaticAnalyser(macintoshModel: .model512ke)
@@ -266,7 +330,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
case "msx":
let hasDiskDrive = msxHasDiskDriveButton.state == .on
switch msxRegionButton.selectedItem!.tag {
switch msxRegionButton.selectedTag() {
case 2:
return CSStaticAnalyser(msxRegion: .japanese, hasDiskDrive: hasDiskDrive)
case 1:
@@ -287,7 +351,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
}
var model: CSMachineOricModel = .oric1
switch oricModelTypeButton.selectedItem!.tag {
switch oricModelTypeButton.selectedTag() {
case 1: model = .oricAtmos
case 2: model = .pravetz
default: break
@@ -297,7 +361,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
case "spectrum":
var model: CSMachineSpectrumModel = .plus2a
switch spectrumModelTypeButton.selectedItem!.tag {
switch spectrumModelTypeButton.selectedTag() {
case 16: model = .sixteenK
case 48: model = .fortyEightK
case 128: model = .oneTwoEightK
@@ -310,9 +374,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
return CSStaticAnalyser(spectrumModel: model)
case "vic20":
let memorySize = Kilobytes(vic20MemorySizeButton.selectedItem!.tag)
let memorySize = Kilobytes(vic20MemorySizeButton.selectedTag())
let hasC1540 = vic20HasC1540Button.state == .on
switch vic20RegionButton.selectedItem!.tag {
switch vic20RegionButton.selectedTag() {
case 1:
return CSStaticAnalyser(vic20Region: .american, memorySize: memorySize, hasC1540: hasC1540)
case 2:
@@ -327,10 +391,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
}
case "zx80":
return CSStaticAnalyser(zx80MemorySize: Kilobytes(zx80MemorySizeButton.selectedItem!.tag), useZX81ROM: zx80UsesZX81ROMButton.state == .on)
return CSStaticAnalyser(zx80MemorySize: Kilobytes(zx80MemorySizeButton.selectedTag()), useZX81ROM: zx80UsesZX81ROMButton.state == .on)
case "zx81":
return CSStaticAnalyser(zx81MemorySize: Kilobytes(zx81MemorySizeButton.selectedItem!.tag))
return CSStaticAnalyser(zx81MemorySize: Kilobytes(zx81MemorySizeButton.selectedTag()))
default: return CSStaticAnalyser()
}

View File

@@ -0,0 +1,102 @@
//
// EnterpriseDaveTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 02/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../Machines/Enterprise/Dave.hpp"
#include <memory>
@interface EnterpriseDaveTests : XCTestCase
@end
@implementation EnterpriseDaveTests {
std::unique_ptr<Enterprise::Dave::TimedInterruptSource> _interruptSource;
}
- (void)setUp {
[super setUp];
_interruptSource = std::make_unique<Enterprise::Dave::TimedInterruptSource>();
}
/// Tests that the programmable timer flag toggles and produces interrupts
/// at the rate specified, and that the flag toggles when interrupts are signalled.
- (void)performTestExpectedInterrupts:(double)expectedInterruptsPerSecond mode:(int)mode {
// If a programmable timer mode is requested, synchronise both channels.
if(mode >= 2) {
_interruptSource->write(0xa7, 3);
_interruptSource->run_for(Cycles(2));
}
// Set mode (and disable sync, if it was applied).
_interruptSource->write(0xa7, mode << 5);
int toggles = 0;
int interrupts = 0;
uint8_t dividerState = _interruptSource->get_divider_state() & 1;
int nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
for(int c = 0; c < 250000 * 5; c++) {
// Advance one cycle. Clock is 500,000 Hz.
_interruptSource->run_for(Cycles(2));
--nextSequencePoint;
// Check for a status bit change.
const uint8_t newDividerState = _interruptSource->get_divider_state();
const bool didToggle = (dividerState^newDividerState)&0x1;
dividerState = newDividerState;
toggles += didToggle;
// Check for the relevant interrupt.
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
if(newInterrupts) {
XCTAssertEqual(nextSequencePoint, 0);
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
if(newInterrupts & 0x02) {
++interrupts;
XCTAssertTrue(didToggle);
} else {
// Failing that, confirm that the other interrupt happend.
XCTAssertTrue(newInterrupts & 0x08);
}
}
XCTAssertEqual(nextSequencePoint, _interruptSource->get_next_sequence_point().as<int>(), @"At cycle %d", c);
}
XCTAssertEqual(toggles, int(expectedInterruptsPerSecond * 5.0));
XCTAssertEqual(interrupts, int(expectedInterruptsPerSecond * 5.0));
}
- (void)test1kHzTimer {
[self performTestExpectedInterrupts:1000.0 mode:0];
}
- (void)test50HzTimer {
[self performTestExpectedInterrupts:50.0 mode:1];
}
- (void)testTone0Timer {
// Set tone generator 0 as the interrupt source, with a divider of 137;
// apply sync momentarily.
_interruptSource->write(0, 137);
_interruptSource->write(1, 0);
[self performTestExpectedInterrupts:250000.0/(138.0 * 2.0) mode:2];
}
- (void)testTone1Timer {
// Set tone generator 1 as the interrupt source, with a divider of 961;
// apply sync momentarily.
_interruptSource->write(2, 961 & 0xff);
_interruptSource->write(3, (961 >> 8) & 0xff);
[self performTestExpectedInterrupts:250000.0/(962.0 * 2.0) mode:3];
}
@end

View File

@@ -0,0 +1,72 @@
//
// EnterpriseNickTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 18/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../Machines/Enterprise/Nick.hpp"
#include <memory>
@interface EnterpriseNickTests : XCTestCase
@end
@implementation EnterpriseNickTests {
std::unique_ptr<Enterprise::Nick> _nick;
uint8_t _ram[64*1024];
int _totalLines;
}
- (void)setUp {
[super setUp];
// Create a Nick.
_nick = std::make_unique<Enterprise::Nick>(_ram);
// Add a basic line table of blocks proceeding in length: 1, 2, 3, 4, etc and toggling the interrupt bit.
_totalLines = 0;
int nextLength = 0;
int pointer = 0;
uint8_t interruptFlag = 0x80;
while(nextLength < 256) {
_ram[pointer] = 0x100 - nextLength;
_ram[pointer+1] = interruptFlag;
pointer += 16;
++nextLength;
interruptFlag ^= 0x80;
_totalLines += nextLength;
}
// For now: assume Nick starts at address 0 from creation.
}
- (void)testInterruptPrediction {
// Run for the number of cycles implied by the number of lines.
int next_sequence_point = _nick->get_next_sequence_point().as<int>();
bool last_interrupt_line = _nick->get_interrupt_line();
for(int c = 0; c < _totalLines*912; c++) {
// Check that interrupt line transitions happen only on declared sequence points.
_nick->run_for(Cycles(1));
--next_sequence_point;
const bool interrupt_line = _nick->get_interrupt_line();
if(interrupt_line != last_interrupt_line) {
XCTAssertEqual(next_sequence_point, 0);
}
last_interrupt_line = interrupt_line;
if(!next_sequence_point) {
next_sequence_point = _nick->get_next_sequence_point().as<int>();
} else {
const int expected_next_sequence_point = _nick->get_next_sequence_point().as<int>();
XCTAssertEqual(next_sequence_point, expected_next_sequence_point);
}
}
}
@end

View File

@@ -47,6 +47,7 @@ SOURCES += \
$$SRC/Analyser/Static/Commodore/*.cpp \
$$SRC/Analyser/Static/Disassembler/*.cpp \
$$SRC/Analyser/Static/DiskII/*.cpp \
$$SRC/Analyser/Static/Enterprise/*.cpp \
$$SRC/Analyser/Static/Macintosh/*.cpp \
$$SRC/Analyser/Static/MSX/*.cpp \
$$SRC/Analyser/Static/Oric/*.cpp \
@@ -92,6 +93,7 @@ SOURCES += \
$$SRC/Machines/Commodore/1540/Implementation/*.cpp \
$$SRC/Machines/Commodore/Vic-20/*.cpp \
$$SRC/Machines/Electron/*.cpp \
$$SRC/Machines/Enterprise/*.cpp \
$$SRC/Machines/MasterSystem/*.cpp \
$$SRC/Machines/MSX/*.cpp \
$$SRC/Machines/Oric/*.cpp \
@@ -166,6 +168,7 @@ HEADERS += \
$$SRC/Analyser/Static/Commodore/*.hpp \
$$SRC/Analyser/Static/Disassembler/*.hpp \
$$SRC/Analyser/Static/DiskII/*.hpp \
$$SRC/Analyser/Static/Enterprise/*.hpp \
$$SRC/Analyser/Static/Macintosh/*.hpp \
$$SRC/Analyser/Static/MSX/*.hpp \
$$SRC/Analyser/Static/Oric/*.hpp \
@@ -221,6 +224,7 @@ HEADERS += \
$$SRC/Machines/Commodore/1540/Implementation/*.hpp \
$$SRC/Machines/Commodore/Vic-20/*.hpp \
$$SRC/Machines/Electron/*.hpp \
$$SRC/Machines/Enterprise/*.hpp \
$$SRC/Machines/MasterSystem/*.hpp \
$$SRC/Machines/MSX/*.hpp \
$$SRC/Machines/Oric/*.hpp \

View File

@@ -424,6 +424,10 @@ void MainWindow::launchMachine() {
addEnhancementsMenu(settingsPrefix, true, false);
break;
case Analyser::Machine::Enterprise:
addDisplayMenu(settingsPrefix, "Composite", "", "", "RGB");
break;
case Analyser::Machine::Macintosh:
addEnhancementsMenu(settingsPrefix, false, true);
break;
@@ -953,6 +957,7 @@ void MainWindow::setButtonPressed(int index, bool isPressed) {
#include "../../Analyser/Static/AppleIIgs/Target.hpp"
#include "../../Analyser/Static/AtariST/Target.hpp"
#include "../../Analyser/Static/Commodore/Target.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../Analyser/Static/Macintosh/Target.hpp"
#include "../../Analyser/Static/MSX/Target.hpp"
#include "../../Analyser/Static/Oric/Target.hpp"
@@ -973,6 +978,7 @@ void MainWindow::startMachine() {
TEST(amstradCPC);
TEST(atariST);
TEST(electron);
TEST(enterprise);
TEST(macintosh);
TEST(msx);
TEST(oric);
@@ -1057,6 +1063,43 @@ void MainWindow::start_electron() {
launchTarget(std::move(target));
}
void MainWindow::start_enterprise() {
using Target = Analyser::Static::Enterprise::Target;
auto target = std::make_unique<Target>();
switch(ui->enterpriseModelComboBox->currentIndex()) {
default: target->model = Target::Model::Enterprise64; break;
case 1: target->model = Target::Model::Enterprise128; break;
case 2: target->model = Target::Model::Enterprise256; break;
}
switch(ui->enterpriseSpeedComboBox->currentIndex()) {
default: target->speed = Target::Speed::FourMHz; break;
case 1: target->speed = Target::Speed::SixMHz; break;
}
switch(ui->enterpriseEXOSComboBox->currentIndex()) {
default: target->exos_version = Target::EXOSVersion::v10; break;
case 1: target->exos_version = Target::EXOSVersion::v20; break;
case 2: target->exos_version = Target::EXOSVersion::v21; break;
case 3: target->exos_version = Target::EXOSVersion::v23; break;
}
switch(ui->enterpriseBASICComboBox->currentIndex()) {
default: target->basic_version = Target::BASICVersion::None; break;
case 1: target->basic_version = Target::BASICVersion::v10; break;
case 2: target->basic_version = Target::BASICVersion::v11; break;
case 3: target->basic_version = Target::BASICVersion::v21; break;
}
switch(ui->enterpriseDOSComboBox->currentIndex()) {
default: target->dos = Target::DOS::None; break;
case 1: target->dos = Target::DOS::EXDOS; break;
}
launchTarget(std::move(target));
}
void MainWindow::start_macintosh() {
using Target = Analyser::Static::Macintosh::Target;
auto target = std::make_unique<Target>();
@@ -1212,6 +1255,13 @@ void MainWindow::launchTarget(std::unique_ptr<Analyser::Static::Target> &&target
CheckBox(electronAP6CheckBox, "electron.hasAP6"); \
CheckBox(electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM"); \
\
/* Enterprise. */ \
ComboBox(enterpriseModelComboBox, "enterprise.model"); \
ComboBox(enterpriseSpeedComboBox, "enterprise.speed"); \
ComboBox(enterpriseEXOSComboBox, "enterprise.exos"); \
ComboBox(enterpriseBASICComboBox, "enterprise.basic"); \
ComboBox(enterpriseDOSComboBox, "enterprise.dos"); \
\
/* Macintosh. */ \
ComboBox(macintoshModelComboBox, "macintosh.model"); \
\
@@ -1265,6 +1315,7 @@ void MainWindow::restoreSelections() {
// MARK: - Activity observation
void MainWindow::addActivityObserver() {
ledStatuses.clear();
auto activitySource = machine->activity_source();
if(!activitySource) return;

View File

@@ -91,6 +91,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
void start_amstradCPC();
void start_atariST();
void start_electron();
void start_enterprise();
void start_macintosh();
void start_msx();
void start_oric();

View File

@@ -27,7 +27,7 @@
<item>
<widget class="QTabWidget" name="machineSelectionTabs">
<property name="currentIndex">
<number>1</number>
<number>5</number>
</property>
<widget class="QWidget" name="appleIITab">
<attribute name="title">
@@ -310,6 +310,159 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="enterpriseTab">
<attribute name="title">
<string>Enterprise</string>
</attribute>
<layout class="QVBoxLayout" name="enterpriseLayout">
<item>
<layout class="QHBoxLayout" name="enterpriseHorizontalLayout">
<item>
<layout class="QFormLayout" name="enterpriseFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="enterpriseModelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="enterpriseModelComboBox">
<item>
<property name="text">
<string>Enterprise 64</string>
</property>
</item>
<item>
<property name="text">
<string>Enterprise 128</string>
</property>
</item>
<item>
<property name="text">
<string>Enterprise 256</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="enterpriseSpeedLabel">
<property name="text">
<string>Speed:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="enterpriseSpeedComboBox">
<item>
<property name="text">
<string>4 MHz</string>
</property>
</item>
<item>
<property name="text">
<string>6 MHz</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="enterpriseEXOSLabel">
<property name="text">
<string>EXOS:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="enterpriseEXOSComboBox">
<item>
<property name="text">
<string>Version 1.0</string>
</property>
</item>
<item>
<property name="text">
<string>Version 2.0</string>
</property>
</item>
<item>
<property name="text">
<string>Version 2.1</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="enterpriseBASICLabel">
<property name="text">
<string>BASIC:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="enterpriseBASICComboBox">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Version 1.0</string>
</property>
</item>
<item>
<property name="text">
<string>Version 1.1</string>
</property>
</item>
<item>
<property name="text">
<string>Version 2.1</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="enterpriseDOSLabel">
<property name="text">
<string>DOS:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="enterpriseDOSComboBox">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>EXDOS</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="enterpriseHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="macintoshTab">
<attribute name="title">
<string>Macintosh</string>
@@ -533,75 +686,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumTab">
<attribute name="title">
<string>Spectrum</string>
</attribute>
<layout class="QVBoxLayout" name="spectrumLayout">
<item>
<layout class="QHBoxLayout" name="spectrumHorizontalLayout">
<item>
<layout class="QFormLayout" name="spectrumFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="spectrumModelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="spectrumModelComboBox">
<item>
<property name="text">
<string>16kb</string>
</property>
</item>
<item>
<property name="text">
<string>48kb</string>
</property>
</item>
<item>
<property name="text">
<string>128kb</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
<item>
<property name="text">
<string>+2a</string>
</property>
</item>
<item>
<property name="text">
<string>+3</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="spectrumHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="vic20Tab">
<attribute name="title">
<string>Vic-20</string>
@@ -830,6 +914,75 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumTab">
<attribute name="title">
<string>ZX Spectrum</string>
</attribute>
<layout class="QVBoxLayout" name="spectrumLayout">
<item>
<layout class="QHBoxLayout" name="spectrumHorizontalLayout">
<item>
<layout class="QFormLayout" name="spectrumFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="spectrumModelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="spectrumModelComboBox">
<item>
<property name="text">
<string>16kb</string>
</property>
</item>
<item>
<property name="text">
<string>48kb</string>
</property>
</item>
<item>
<property name="text">
<string>128kb</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
<item>
<property name="text">
<string>+2a</string>
</property>
</item>
<item>
<property name="text">
<string>+3</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="spectrumHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item alignment="Qt::AlignBottom">

View File

@@ -31,6 +31,7 @@ SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/DiskII/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Enterprise/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Macintosh/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp')
@@ -79,6 +80,7 @@ SOURCES += glob.glob('../../Machines/Commodore/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp')
SOURCES += glob.glob('../../Machines/Electron/*.cpp')
SOURCES += glob.glob('../../Machines/Enterprise/*.cpp')
SOURCES += glob.glob('../../Machines/MasterSystem/*.cpp')
SOURCES += glob.glob('../../Machines/MSX/*.cpp')
SOURCES += glob.glob('../../Machines/Oric/*.cpp')

View File

@@ -832,8 +832,11 @@ int main(int argc, char *argv[]) {
} else if(volume < 0.0 || volume > 1.0) {
std::cerr << "Cannot run with volume " << volume_string << "; volumes must be between 0.0 and 1.0." << std::endl;
} else {
const auto speaker = machine->audio_producer()->get_speaker();
if(speaker) speaker->set_output_volume(volume);
const auto audio_producer = machine->audio_producer();
if(audio_producer) {
const auto speaker = machine->audio_producer()->get_speaker();
if(speaker) speaker->set_output_volume(volume);
}
}
}
}
@@ -907,26 +910,29 @@ int main(int argc, char *argv[]) {
machine->scan_producer()->set_scan_target(&scan_target);
// For now, lie about audio output intentions.
auto speaker = machine->audio_producer()->get_speaker();
if(speaker) {
// Create an audio pipe.
SDL_AudioSpec desired_audio_spec;
SDL_AudioSpec obtained_audio_spec;
const auto audio_producer = machine->audio_producer();
if(audio_producer) {
auto speaker = audio_producer->get_speaker();
if(speaker) {
// Create an audio pipe.
SDL_AudioSpec desired_audio_spec;
SDL_AudioSpec obtained_audio_spec;
SDL_zero(desired_audio_spec);
desired_audio_spec.freq = 48000; // TODO: how can I get SDL to reveal the output rate of this machine?
desired_audio_spec.format = AUDIO_S16;
desired_audio_spec.channels = 1 + int(speaker->get_is_stereo());
desired_audio_spec.samples = Uint16(SpeakerDelegate::buffered_samples);
desired_audio_spec.callback = SpeakerDelegate::SDL_audio_callback;
desired_audio_spec.userdata = &speaker_delegate;
SDL_zero(desired_audio_spec);
desired_audio_spec.freq = 48000; // TODO: how can I get SDL to reveal the output rate of this machine?
desired_audio_spec.format = AUDIO_S16;
desired_audio_spec.channels = 1 + int(speaker->get_is_stereo());
desired_audio_spec.samples = Uint16(SpeakerDelegate::buffered_samples);
desired_audio_spec.callback = SpeakerDelegate::SDL_audio_callback;
desired_audio_spec.userdata = &speaker_delegate;
speaker_delegate.audio_device = SDL_OpenAudioDevice(nullptr, 0, &desired_audio_spec, &obtained_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
speaker_delegate.audio_device = SDL_OpenAudioDevice(nullptr, 0, &desired_audio_spec, &obtained_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
speaker->set_output_rate(obtained_audio_spec.freq, desired_audio_spec.samples, obtained_audio_spec.channels == 2);
speaker_delegate.is_stereo = obtained_audio_spec.channels == 2;
speaker->set_delegate(&speaker_delegate);
SDL_PauseAudioDevice(speaker_delegate.audio_device, 0);
speaker->set_output_rate(obtained_audio_spec.freq, desired_audio_spec.samples, obtained_audio_spec.channels == 2);
speaker_delegate.is_stereo = obtained_audio_spec.channels == 2;
speaker->set_delegate(&speaker_delegate);
SDL_PauseAudioDevice(speaker_delegate.audio_device, 0);
}
}
/*

View File

@@ -10,6 +10,7 @@
#define WDC65816_hpp
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <vector>

View File

@@ -145,6 +145,12 @@ struct PartialMachineCycle {
forceinline bool is_wait() const {
return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait;
}
/*!
@returns @c true if this partial machine cycle is a memory access; @c false otherwise.
*/
forceinline bool is_memory_access() const {
return operation <= Operation::Write;
}
enum Line {
CLK = 1 << 0,

View File

@@ -15,6 +15,7 @@ It currently contains emulations of the:
* Atari ST;
* ColecoVision;
* Commodore Vic-20 (and Commodore 1540/1);
* Enterprise 64/128;
* Macintosh 512ke and Plus;
* MSX 1;
* Oric 1/Atmos;
@@ -83,6 +84,7 @@ This emulator attempts cycle-accurate emulation of all supported machines. In so
|![SG1000 Chack'n'Pop](READMEImages/SGChackNPop.png) | ![Atari 2600 Solaris](READMEImages/Atari2600Solaris.png)
|![Vic-20 Gridrunner](READMEImages/Vic20Gridrunner.png) | ![VIC-20 BASIC](READMEImages/Vic20BASIC.png)
|![ZX Spectrum Menu](READMEImages/ZXSpectrumMenu.png) | ![ZX Spectrum Chromatrons Attack](READMEImages/ZXSpectrumChromatronsAttack.png)
|![Enterprise HERO](READMEImages/EnterpriseHERO.png) | ![Enterprise Startup](READMEImages/EnterpriseStartup.png)
![macOS Version](READMEImages/MultipleSystems.png)
![Qt Version](READMEImages/MultipleSystems-Ubuntu.png)
@@ -91,4 +93,4 @@ This emulator attempts cycle-accurate emulation of all supported machines. In so
I've been asked several times whether it is possible to sponsor this project; I think that's a poor fit for this emulator's highly-malleable scope, and it makes me uncomfortable because as the author I primarily see only its defects.
An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Per the optimism of some book sellers, please don't purchase anything that is currnetly listed only at an absurd price; they were sorted by secondhand price when added to the list, with cheapest being $5.
An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Despite the optimism of some book sellers, please don't purchase anything that is currently listed only at an absurd price; they were sorted by secondhand price when added to the list, with the cheapest being $5.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,5 @@
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
exos.rom

View File

@@ -7,6 +7,7 @@
//
#include "FIRFilter.hpp"
#include <cmath>
#ifndef M_PI

View File

@@ -17,6 +17,7 @@
#define USE_ACCELERATE
#endif
#include <cstddef>
#include <vector>
namespace SignalProcessing {

View File

@@ -49,8 +49,10 @@ class Controller:
void run_for(const Cycles cycles);
/*!
Sets the current drive(s). Normally this will be exactly one, but some machines allow
zero or multiple drives to be attached, with useless results.
Sets the current drive(s), by bit mask. Normally this will be exactly one, but some
machines allow zero or multiple drives to be attached, with useless results.
E.g. supply 1 to select drive 0, 2 to select drive 1, 4 to select drive 2, etc.
*/
void set_drive(int index_mask);

View File

@@ -0,0 +1,57 @@
//
// FAT12.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "FAT12.hpp"
#include "Utility/ImplicitSectors.hpp"
using namespace Storage::Disk;
FAT12::FAT12(const std::string &file_name) :
MFMSectorDump(file_name) {
// The only sanity check here is whether a sensible
// geometry is encoded in the first sector, or can be guessed.
off_t file_size = file_.stats().st_size;
if(file_size < 512) throw Error::InvalidFormat;
// Inspect the FAT.
file_.seek(11, SEEK_SET);
sector_size_ = file_.get16le();
file_.seek(19, SEEK_SET);
const uint16_t total_sectors = file_.get16le();
file_.seek(24, SEEK_SET);
sector_count_ = file_.get16le();
head_count_ = file_.get16le();
// Throw if there would seemingly be an incomplete track.
if(file_size != total_sectors*sector_size_) throw Error::InvalidFormat;
if(total_sectors % (head_count_ * sector_count_)) throw Error::InvalidFormat;
track_count_ = int(total_sectors / (head_count_ * sector_count_));
// Check that there is a valid power-of-two sector size.
uint8_t log_sector_size = 2;
while(log_sector_size < 5 && (1 << (7+log_sector_size)) != sector_size_) {
++log_sector_size;
}
if(log_sector_size >= 5) throw Error::InvalidFormat;
set_geometry(sector_count_, log_sector_size, 1, true);
}
HeadPosition FAT12::get_maximum_head_position() {
return HeadPosition(track_count_);
}
int FAT12::get_head_count() {
return head_count_;
}
long FAT12::get_file_offset_for_position(Track::Address address) {
return (address.position.as_int()*head_count_ + address.head) * sector_size_ * sector_count_;
}

View File

@@ -1,5 +1,5 @@
//
// MSXDSK.hpp
// FAT12.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
@@ -17,12 +17,12 @@ namespace Storage {
namespace Disk {
/*!
Provides a @c DiskImage descriging an MSX-style disk image:
Provides a @c DiskImage holding an MSDOS-style FAT12 disk image:
a sector dump of appropriate proportions.
*/
class MSXDSK: public MFMSectorDump {
class FAT12: public MFMSectorDump {
public:
MSXDSK(const std::string &file_name);
FAT12(const std::string &file_name);
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
@@ -31,6 +31,8 @@ class MSXDSK: public MFMSectorDump {
int head_count_;
int track_count_;
int sector_count_;
int sector_size_;
};
}

View File

@@ -1,59 +0,0 @@
//
// MSXDSK.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MSXDSK.hpp"
#include "Utility/ImplicitSectors.hpp"
namespace {
constexpr int sectors_per_track = 9;
constexpr int sector_size = 2;
constexpr off_t track_size = (128 << sector_size)*sectors_per_track;
}
using namespace Storage::Disk;
MSXDSK::MSXDSK(const std::string &file_name) :
MFMSectorDump(file_name) {
// The only sanity check here is whether a sensible
// geometry can be guessed.
off_t file_size = file_.stats().st_size;
// Throw if there would seemingly be an incomplete track.
if(file_size % track_size) throw Error::InvalidFormat;
track_count_ = int(file_size / track_size);
head_count_ = 1;
// Throw if too large or too small or too large for single sided and
// clearly not double sided.
if(track_count_ < 40) throw Error::InvalidFormat;
if(track_count_ > 82*2) throw Error::InvalidFormat;
if(track_count_ > 82 && track_count_&1) throw Error::InvalidFormat;
// The below effectively prefers the idea of a single-sided 80-track disk
// to a double-sided 40-track disk. Emulators have to guess.
if(track_count_ > 82) {
track_count_ /= 2;
head_count_ = 2;
}
set_geometry(sectors_per_track, sector_size, 1, true);
}
HeadPosition MSXDSK::get_maximum_head_position() {
return HeadPosition(track_count_);
}
int MSXDSK::get_head_count() {
return head_count_;
}
long MSXDSK::get_file_offset_for_position(Track::Address address) {
return (address.position.as_int()*head_count_ + address.head) * 512 * 9;
}

View File

@@ -57,6 +57,6 @@ void Storage::Disk::decode_sectors(Track &track, uint8_t *const destination, uin
if(pair.second.address.sector < first_sector) continue;
if(pair.second.size != sector_size) continue;
if(pair.second.samples.empty()) continue;
std::memcpy(&destination[pair.second.address.sector * byte_size], pair.second.samples[0].data(), std::min(pair.second.samples[0].size(), byte_size));
std::memcpy(&destination[(pair.second.address.sector - first_sector) * byte_size], pair.second.samples[0].data(), std::min(pair.second.samples[0].size(), byte_size));
}
}

View File

@@ -0,0 +1,179 @@
//
// FAT.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "FAT.hpp"
#include "../Encodings/MFM/Parser.hpp"
#include <iostream>
using namespace Storage::Disk;
FAT::Volume::CHS FAT::Volume::chs_for_sector(int sector) const {
const auto track = sector / sectors_per_track;
// Sides are interleaved.
return CHS{
track / head_count,
track % head_count,
1 + (sector % sectors_per_track)
};
}
int FAT::Volume::sector_for_cluster(uint16_t cluster) const {
// The first cluster in the data area is numbered as 2.
return ((cluster - 2) * sectors_per_cluster) + first_data_sector;
}
namespace {
template <typename CharT> std::string trim(CharT start, CharT end) {
std::string result(start, end);
result.erase(std::find_if(result.rbegin(), result.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), result.end());
return result;
}
FAT::Directory directory_from(const std::vector<uint8_t> &contents) {
FAT::Directory result;
// Worst case: parse until the amount of data supplied is fully consumed.
for(size_t base = 0; base < contents.size(); base += 32) {
// An entry starting with byte 0 indicates end-of-directory.
if(!contents[base]) {
break;
}
// An entry starting in 0xe5 is merely deleted.
if(contents[base] == 0xe5) {
continue;
}
// Otherwise create and populate a new entry.
result.emplace_back();
result.back().name = trim(&contents[base], &contents[base+8]);
result.back().extension = trim(&contents[base+8], &contents[base+11]);
result.back().attributes = contents[base + 11];
result.back().time = uint16_t(contents[base+22] | (contents[base+23] << 8));
result.back().date = uint16_t(contents[base+24] | (contents[base+25] << 8));
result.back().starting_cluster = uint16_t(contents[base+26] | (contents[base+27] << 8));
result.back().size = uint32_t(
contents[base+28] |
(contents[base+29] << 8) |
(contents[base+30] << 16) |
(contents[base+31] << 24)
);
}
return result;
}
}
std::optional<FAT::Volume> FAT::GetVolume(const std::shared_ptr<Storage::Disk::Disk> &disk) {
Storage::Encodings::MFM::Parser parser(true, disk);
// Grab the boot sector; that'll be enough to establish the volume.
Storage::Encodings::MFM::Sector *const boot_sector = parser.get_sector(0, 0, 1);
if(!boot_sector || boot_sector->samples.empty() || boot_sector->samples[0].size() < 512) {
return std::nullopt;
}
// Obtain volume details.
const auto &data = boot_sector->samples[0];
FAT::Volume volume;
volume.bytes_per_sector = uint16_t(data[11] | (data[12] << 8));
volume.sectors_per_cluster = data[13];
volume.reserved_sectors = uint16_t(data[14] | (data[15] << 8));
volume.fat_copies = data[16];
const uint16_t root_directory_entries = uint16_t(data[17] | (data[18] << 8));
volume.total_sectors = uint16_t(data[19] | (data[20] << 8));
volume.sectors_per_fat = uint16_t(data[22] | (data[23] << 8));
volume.sectors_per_track = uint16_t(data[24] | (data[25] << 8));
volume.head_count = uint16_t(data[26] | (data[27] << 8));
volume.correct_signature = data[510] == 0x55 && data[511] == 0xaa;
const size_t root_directory_sectors = (root_directory_entries*32 + volume.bytes_per_sector - 1) / volume.bytes_per_sector;
volume.first_data_sector = int(volume.reserved_sectors + volume.sectors_per_fat*volume.fat_copies + root_directory_sectors);
// Grab the FAT.
std::vector<uint8_t> source_fat;
for(int c = 0; c < volume.sectors_per_fat; c++) {
const int sector_number = volume.reserved_sectors + c;
const auto address = volume.chs_for_sector(sector_number);
Storage::Encodings::MFM::Sector *const fat_sector =
parser.get_sector(address.head, address.cylinder, uint8_t(address.sector));
if(!fat_sector || fat_sector->samples.empty() || fat_sector->samples[0].size() != volume.bytes_per_sector) {
return std::nullopt;
}
std::copy(fat_sector->samples[0].begin(), fat_sector->samples[0].end(), std::back_inserter(source_fat));
}
// Decode the FAT.
// TODO: stop assuming FAT12 here.
for(size_t c = 0; c < source_fat.size(); c += 3) {
const uint32_t double_cluster = uint32_t(source_fat[c] + (source_fat[c + 1] << 8) + (source_fat[c + 2] << 16));
volume.fat.push_back(uint16_t(double_cluster & 0xfff));
volume.fat.push_back(uint16_t(double_cluster >> 12));
}
// Grab the root directory.
std::vector<uint8_t> root_directory;
for(size_t c = 0; c < root_directory_sectors; c++) {
const auto sector_number = int(volume.reserved_sectors + c + volume.sectors_per_fat*volume.fat_copies);
const auto address = volume.chs_for_sector(sector_number);
Storage::Encodings::MFM::Sector *const sector =
parser.get_sector(address.head, address.cylinder, uint8_t(address.sector));
if(!sector || sector->samples.empty() || sector->samples[0].size() != volume.bytes_per_sector) {
return std::nullopt;
}
std::copy(sector->samples[0].begin(), sector->samples[0].end(), std::back_inserter(root_directory));
}
volume.root_directory = directory_from(root_directory);
return volume;
}
std::optional<std::vector<uint8_t>> FAT::GetFile(const std::shared_ptr<Storage::Disk::Disk> &disk, const Volume &volume, const File &file) {
Storage::Encodings::MFM::Parser parser(true, disk);
std::vector<uint8_t> contents;
// In FAT cluster numbers describe a linked list via the FAT table, with values above $FF0 being reserved
// (relevantly: FF7 means bad cluster; FF8FFF mean end-of-file).
uint16_t cluster = file.starting_cluster;
do {
const int sector = volume.sector_for_cluster(cluster);
for(int c = 0; c < volume.sectors_per_cluster; c++) {
const auto address = volume.chs_for_sector(sector + c);
Storage::Encodings::MFM::Sector *const sector_contents =
parser.get_sector(address.head, address.cylinder, uint8_t(address.sector));
if(!sector_contents || sector_contents->samples.empty() || sector_contents->samples[0].size() != volume.bytes_per_sector) {
return std::nullopt;
}
std::copy(sector_contents->samples[0].begin(), sector_contents->samples[0].end(), std::back_inserter(contents));
}
cluster = volume.fat[cluster];
} while(cluster < 0xff0);
return contents;
}
std::optional<FAT::Directory> FAT::GetDirectory(const std::shared_ptr<Storage::Disk::Disk> &disk, const Volume &volume, const File &file) {
const auto contents = GetFile(disk, volume, file);
if(!contents) {
return std::nullopt;
}
return directory_from(*contents);
}

View File

@@ -0,0 +1,79 @@
//
// FAT.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_Disk_Parsers_FAT_hpp
#define Storage_Disk_Parsers_FAT_hpp
#include "../Disk.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace Storage {
namespace Disk {
namespace FAT {
struct File {
std::string name;
std::string extension;
uint8_t attributes = 0;
uint16_t time = 0; // TODO: offer time/date decoders.
uint16_t date = 0;
uint16_t starting_cluster = 0;
uint32_t size = 0;
enum Attribute: uint8_t {
ReadOnly = (1 << 0),
Hidden = (1 << 1),
System = (1 << 2),
VolumeLabel = (1 << 3),
Directory = (1 << 4),
Archive = (1 << 5),
};
};
using Directory = std::vector<File>;
struct Volume {
uint16_t bytes_per_sector = 0;
uint8_t sectors_per_cluster = 0;
uint16_t reserved_sectors = 0;
uint8_t fat_copies = 0;
uint16_t total_sectors = 0;
uint16_t sectors_per_fat = 0;
uint16_t sectors_per_track = 0;
uint16_t head_count = 0;
uint16_t hidden_sectors = 0;
bool correct_signature = false;
int first_data_sector = 0;
std::vector<uint16_t> fat;
Directory root_directory;
struct CHS {
int cylinder;
int head;
int sector;
};
/// @returns a direct sector -> CHS address translation.
CHS chs_for_sector(int sector) const;
/// @returns the CHS address for the numbered cluster within the data area.
int sector_for_cluster(uint16_t cluster) const;
};
std::optional<Volume> GetVolume(const std::shared_ptr<Storage::Disk::Disk> &disk);
std::optional<std::vector<uint8_t>> GetFile(const std::shared_ptr<Storage::Disk::Disk> &disk, const Volume &volume, const File &file);
std::optional<Directory> GetDirectory(const std::shared_ptr<Storage::Disk::Disk> &disk, const Volume &volume, const File &file);
}
}
}
#endif /* FAT_hpp */

View File

@@ -29,13 +29,14 @@ enum Type: IntType {
Coleco = 1 << 10,
Commodore = 1 << 11,
DiskII = 1 << 12,
Sega = 1 << 13,
Macintosh = 1 << 14,
MSX = 1 << 15,
Oric = 1 << 16,
ZX80 = 1 << 17,
ZX81 = 1 << 18,
ZXSpectrum = 1 << 19,
Enterprise = 1 << 13,
Sega = 1 << 14,
Macintosh = 1 << 15,
MSX = 1 << 16,
Oric = 1 << 17,
ZX80 = 1 << 18,
ZX81 = 1 << 19,
ZXSpectrum = 1 << 20,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,