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

Compare commits

...

125 Commits

Author SHA1 Message Date
Thomas Harte
506b4da6c3 Merge pull request #408 from TomHarte/MixerBalance
Enhances the CompoundSource so that constituents can have different volumes.
2018-04-07 14:32:47 -04:00
Thomas Harte
10f637d2cf Enhances the CompoundSource so that constituents can have different volumes. 2018-04-07 14:30:02 -04:00
Thomas Harte
0bab7c88f0 Merge pull request #407 from TomHarte/NameImplications
Allows the Vic-20 analyser to act on 'NTSC' in a filename.
2018-04-06 20:10:56 -04:00
Thomas Harte
78c612ca17 Adds a missing import, removes a redundant conversion. 2018-04-06 20:07:10 -04:00
Thomas Harte
e1c4035812 Switches away from C strings and allows Vic-20 region inference from filenames. 2018-04-06 17:42:24 -04:00
Thomas Harte
eb6d6c8033 Merge pull request #406 from TomHarte/NewFixes
Tweaks the 'new machine' dialogue for ZX memory size
2018-04-05 22:02:10 -04:00
Thomas Harte
7bf88565ce Resizes to fit all options. 2018-04-05 21:59:19 -04:00
Thomas Harte
ee10155296 Adds advice and withdraws the ZX 64kb option. 2018-04-05 21:57:26 -04:00
Thomas Harte
cc49140f6f Merge pull request #405 from TomHarte/VicFraming
Introduces different clipping zones for NTSC and PAL output.
2018-04-05 21:26:07 -04:00
Thomas Harte
3e846f89a1 Introduces different clipping zones for NTSC and PAL output. 2018-04-05 21:25:19 -04:00
Thomas Harte
5782cab2a0 Minor whitespace fix. 2018-04-05 21:15:25 -04:00
Thomas Harte
8c511e2b76 Merge pull request #404 from TomHarte/ProperShaderSetup
Ensures the SVideo shader gets all proper `enable_vertex_attribute_with_pointer`s.
2018-04-05 21:13:26 -04:00
Thomas Harte
ec72fb3baf Ensures the SVideo shader gets all proper enable_vertex_attribute_with_pointers. 2018-04-05 21:12:28 -04:00
Thomas Harte
bab1440f5c Merge pull request #403 from TomHarte/VicRange
Causes the 6560 to obey `set_sample_volume_range`.
2018-04-05 21:06:09 -04:00
Thomas Harte
60c1da6a66 Causes the 6560 to obey set_sample_volume_range.
Thereby resolves a clipping issue.
2018-04-05 21:04:46 -04:00
Thomas Harte
a849b3f2e4 Merge pull request #402 from TomHarte/AudioCutoff
Ensures artificial audio frequency limits are honoured.
2018-04-05 19:05:48 -04:00
Thomas Harte
dbe3c5c3f8 Ensures artificial frequency limits are honoured. 2018-04-05 18:40:07 -04:00
Thomas Harte
60cf6b3cfd Merge pull request #401 from TomHarte/VideoQuirks
Corrects composite output of the ZX80/81 and the Oric
2018-04-04 19:23:45 -04:00
Thomas Harte
5044aac337 Sizes up default window size better to fit machine selector. 2018-04-04 19:18:22 -04:00
Thomas Harte
36e0cb29c0 Ensures proper propagation of video choice through the Oric. 2018-04-04 19:14:42 -04:00
Thomas Harte
c0b4dd65da Mades the expected video signal usage explicit. 2018-04-04 19:01:18 -04:00
Thomas Harte
d061ea232b Ensures no attempt to compile an SVideo shader without appropriate source. 2018-04-04 19:01:01 -04:00
Thomas Harte
49feca4ddf Merge pull request #400 from TomHarte/NewCrash
Introduces a rudimentary 'new' dialogue for the Mac
2018-04-03 23:24:00 -04:00
Thomas Harte
46b1c57bf4 Enables the titlebar, inexplicably allowing the sheet to obtain focus. 2018-04-03 23:22:26 -04:00
Thomas Harte
eaf1482182 Reverts the once-again-unused document controller. 2018-04-03 23:11:19 -04:00
Thomas Harte
d3418550eb Attempts explicitly to disable promise of saving. 2018-04-03 23:06:48 -04:00
Thomas Harte
3ffa9e2751 Ensures complete machine picker state is preserved. 2018-04-03 23:01:12 -04:00
Thomas Harte
c697dd78f0 Ensures a new machine starts as first responder. 2018-04-03 22:22:39 -04:00
Thomas Harte
7dac791290 Causes the machine picker to show as a sheet.
Albeit with some user experience issues lingering.
2018-04-03 18:47:07 -04:00
Thomas Harte
cde2faeda6 Makes an unsuccessful attempt to show the new machine dialogue as a sheet.
Also corrects the 'open' case versus recent changes.
2018-04-02 23:31:36 -04:00
Thomas Harte
69f520428d Makes a first, ugly attempt at a 'new machine' dialogue for the Mac.
Which has implied getting much more specific about MSX disk drive attachment, and has prompted an excuse to offer the ZX80 with the ZX81 ROM.
2018-04-02 22:42:41 -04:00
Thomas Harte
80c84ddd75 Merge pull request #398 from TomHarte/SVideoOption
Exposes S-Video as a user-selectable option
2018-04-01 13:30:41 -04:00
Thomas Harte
fca8a58b36 Exposes S-Video option in the Mac UI. 2018-04-01 13:29:04 -04:00
Thomas Harte
33084899d0 Provides s-video as a command-line option. 2018-03-31 22:14:34 -04:00
Thomas Harte
7b381a8b6b Merge pull request #397 from TomHarte/Vic20FastTape
Improves Vic-20 fast tape ownership and simplifies memory logic.
2018-03-31 21:05:22 -04:00
Thomas Harte
9c75689a8d Increased verbosity. 2018-03-31 20:58:16 -04:00
Thomas Harte
0ee40e8556 Reintroduces 90% crop for VIC output. 2018-03-31 20:57:45 -04:00
Thomas Harte
8b45377b89 Simplifies storage underlying Vic memory.
In the hope of avoiding non-obvious bugs.
2018-03-31 18:54:40 -04:00
Thomas Harte
f6fb368d88 Allows the fast-tape mechanism to take ownership of tape handling.
Any successful fast tape interaction will now permanently pause the tape until a failed interaction occurs. This may or may not be a good idea.
2018-03-30 21:22:52 -04:00
Thomas Harte
183a5379de Merge pull request #396 from TomHarte/SVideo
Adds support for s-video.
2018-03-30 18:25:28 -04:00
Thomas Harte
912791d3d4 Causes the s-video path correctly to function. 2018-03-30 18:24:18 -04:00
Thomas Harte
163a61dd63 Corrects SVideo-as-composite output; the Atari and Vic-20 now both supply svideo. 2018-03-30 13:16:18 -04:00
Thomas Harte
207d462dbf Attempts to provide an implementation of SVideo support. 2018-03-30 12:41:20 -04:00
Thomas Harte
33281b9d89 Introduces S-Video as a video signal type at the interface level. 2018-03-30 10:25:41 -04:00
Thomas Harte
389979923e Performs update to and satisfaction of Xcode 9.3's preferred warnings. 2018-03-30 10:25:01 -04:00
Thomas Harte
067174965e Merge pull request #395 from TomHarte/TEDEsqueColours
Introduces Vic luminances sourced from the TED manual.
2018-03-30 09:39:02 -04:00
Thomas Harte
286259c83b Adds missing 6560 update hooks. 2018-03-29 20:49:36 -04:00
Thomas Harte
e1aa3e5a7f Imports chrominances from the TED documentation. They seem to apply to the VIC-I also. 2018-03-29 20:04:37 -04:00
Thomas Harte
78e1c2851a Merge pull request #393 from TomHarte/Vic20Faster
Introduces some minor Vic-20 optimisations.
2018-03-27 22:04:40 -04:00
Thomas Harte
0869213c55 Cuts detritus. 2018-03-27 22:00:13 -04:00
Thomas Harte
f3fe16215a Reintroduces options for the Vic-20, now tape loading speed only. 2018-03-27 21:55:43 -04:00
Thomas Harte
ec353ce663 Makes minor Vic-20 optimisations.
Specifically: the 6560 is updated only upon writes (more nuance can arrive), and tape sleeps are observed.
2018-03-27 21:52:52 -04:00
Thomas Harte
b7ff5ef9dd Merge pull request #392 from TomHarte/VicPalette
Tweaks VIC palette, especially PAL.
2018-03-26 21:25:12 -04:00
Thomas Harte
3b26e0a7c5 Tweaks NTSC colour generation. 2018-03-26 21:22:06 -04:00
Thomas Harte
6d464557a0 Reintroduces a warm-up run for the C1540.
That simulates the normal real-life scenario of switching the drive on slightly before the computer, and causes it to function correctly from immediate fast typing on an American Vic.

Also switches a couple of casts within the C1540 to functional style.
2018-03-26 21:06:07 -04:00
Thomas Harte
a776bec46a Tweaks PAL colours for the 6560 to be closer to screenshots found online. 2018-03-26 19:02:16 -04:00
Thomas Harte
a2da51c30b Commutes Vic-20 machine configuration options to its Target. 2018-03-26 19:01:57 -04:00
Thomas Harte
8067bf548a Merge pull request #390 from TomHarte/VicOptions
Ensures the Vic-20 doesn't show the ZX80/81 options panel on macOS.
2018-03-25 16:07:13 -04:00
Thomas Harte
62b0645ed0 Ensures the Vic-20 doesn't show the ZX80/81 options panel on macOS. 2018-03-25 16:04:44 -04:00
Thomas Harte
39a94874ae Merge pull request #389 from TomHarte/VicAnalysis
Strips back Vic-20 static analysis to the bare minimum.
2018-03-25 13:42:59 -04:00
Thomas Harte
e15d6717a1 Strips back Vic-20 static analysis to the bare minimum.
Also corrects an unsafe assumption in fast loading.
2018-03-25 13:37:33 -04:00
Thomas Harte
37ef46e7bb Merge branch 'SDLTravis' of github.com:TomHarte/CLK into SDLTravis 2018-03-23 21:52:27 -04:00
Thomas Harte
70c09b3031 Attempted to draft a travis.yml for SDL. 2018-03-23 21:51:15 -04:00
Thomas Harte
9378fbb0df Attempted to draft a travis.yml for SDL. 2018-03-23 21:40:46 -04:00
Thomas Harte
2118b9c0cd Merge pull request #385 from TomHarte/OricHFE
Corrects nullptr references in the CPC static analyser.
2018-03-23 18:40:13 -04:00
Thomas Harte
d0c53de250 Corrects nullptr references in the CPC static analyser. 2018-03-23 18:39:37 -04:00
Thomas Harte
d98507eab0 Merge pull request #384 from TomHarte/PlentifulIcons
Fills out the application icon set.
2018-03-23 18:33:02 -04:00
Thomas Harte
760c75103e Fills out the application icon set. 2018-03-23 18:29:18 -04:00
Thomas Harte
4407fd2f1f Merge pull request #383 from TomHarte/D64Crash
Ensures the rom fetcher is properly provided to the C1540.
2018-03-23 18:22:37 -04:00
Thomas Harte
7fcd243be0 Ensures the rom fetcher is properly recorded for potential provision to the C1540. 2018-03-23 18:20:17 -04:00
Thomas Harte
3165e9d82e Merge pull request #382 from TomHarte/Headers
Introduces missing #includes.
2018-03-23 18:08:55 -04:00
Thomas Harte
6656a08c60 Introduces missing #includes. 2018-03-23 18:05:51 -04:00
Thomas Harte
76661c0b51 Merge pull request #375 from TomHarte/UndefinedBehaviour
Resolves various pieces of undefined behaviour.
2018-03-22 22:01:19 -04:00
Thomas Harte
3bb496f9ae Enforces a maximum sector size to avoid impossible sizes.
Such as 128 * 2^255.
2018-03-22 22:00:26 -04:00
Thomas Harte
45be1c19df Resolves undefined behaviour of a signed shift left. 2018-03-22 21:59:39 -04:00
Thomas Harte
a301964bd0 Ensures all audio queues are fully merged before machine destruction.
Thereby avoids a race condition.
2018-03-22 21:59:19 -04:00
Thomas Harte
eea6858121 Resolves undefined behaviour from uninitialised limited-range values. 2018-03-22 21:58:42 -04:00
Thomas Harte
2a320fdf56 Merge pull request #374 from TomHarte/HFEFixup
Corrects two errors in all-machine HFE offering.
2018-03-22 20:24:24 -04:00
Thomas Harte
4695296ef2 Corrects bit mask for offering HFE around. 2018-03-22 20:23:39 -04:00
Thomas Harte
0fdbbeca1d Ensures the Commodore parser properly rejects non-GCR disks. 2018-03-22 20:23:21 -04:00
Thomas Harte
34cc39ad65 Merge pull request #373 from TomHarte/SpeakerCritical
Moves all LowpassSpeaker delegate calls outside of critical sections.
2018-03-22 20:07:20 -04:00
Thomas Harte
3d0c832a21 Moves all LowpassSpeaker delegate calls outside of critical sections. 2018-03-22 19:01:20 -04:00
Thomas Harte
1acdab9448 Expanded potential HFE targets to everything other than the MSX.
The MSX does not yet perform any sanity checks on disks. That's TODO.
2018-03-22 18:55:52 -04:00
Thomas Harte
93e85c5c4a The CPC now accepts disks only if it can make sense of them. 2018-03-22 18:52:43 -04:00
Thomas Harte
ab98189d25 Merge pull request #372 from TomHarte/MultiJoystick
Implements multimachine joystick support.
2018-03-22 11:09:38 -04:00
Thomas Harte
cd0fb7624b Pulls delegate messages out of the critical sections. 2018-03-22 11:08:07 -04:00
Thomas Harte
bae38497bb Implements multitarget joysticks. 2018-03-22 11:07:52 -04:00
Thomas Harte
29921bfa8d Merge pull request #371 from TomHarte/NanosecondMachines
Devolves time -> clock rate mapping to machines.
2018-03-22 10:08:58 -04:00
Thomas Harte
2712702461 Makes get_clock_rate protected. It's now an implementation detail. 2018-03-22 10:01:18 -04:00
Thomas Harte
a3fa9440d1 Renames method better to communicate purpose. 2018-03-22 09:49:36 -04:00
Thomas Harte
6419b0e619 Reintroduces CSMachineDelegate, allowing the Mac port to switch output audio rate dynamically. 2018-03-22 09:48:19 -04:00
Thomas Harte
58e5b6e3f1 Updates SDL kiosk mode to the death of CRTMachineDelegate. 2018-03-22 09:23:27 -04:00
Thomas Harte
682c3d8079 Adds new hook for watching audio output rate changes. 2018-03-22 09:23:01 -04:00
Thomas Harte
da3d65c18f Devolves time to cycle conversion to machines.
Thereby avoids a whole bunch of complicated machinations that would otherwise have been required of the multimachine.
2018-03-21 22:18:13 -04:00
Thomas Harte
ece3a05504 Merge pull request #370 from TomHarte/OricDiskDetection
Causes the Oric properly to evaluate disks offered to it.
2018-03-21 20:51:12 -04:00
Thomas Harte
927697b0f0 Causes the Oric properly to evaluate disks offered to it. 2018-03-21 20:48:21 -04:00
Thomas Harte
74dfc80b0f Merge pull request #369 from TomHarte/AnalyserUnion
Encapsulates per-platform analyser result fields.
2018-03-09 16:13:17 -05:00
Thomas Harte
a7f229bc4b Adds missing files. 2018-03-09 16:10:17 -05:00
Thomas Harte
89bec2919f Encapsulates machine configuration properties for all remaining platforms. 2018-03-09 16:07:29 -05:00
Thomas Harte
78eaecb29e Provides the proper framework for encapsulation of analyser target specifics.
... while making them a safe container for objects too. Uses the ZX80/81 as the pilot platform.
2018-03-09 15:36:11 -05:00
Thomas Harte
d410aea856 Merge pull request #368 from TomHarte/DiamondInheritance
Eliminates diamond inheritance of KeyboardMachine::Machine by typers.
2018-03-09 15:19:54 -05:00
Thomas Harte
6b1eef572b Eliminates diamond inheritance of KeyboardMachine::Machine by typers.
Specifically by pulling the key action stuff into a purely abstract class [/interface]. Takes the opportunity to unpublish a bunch of machine details.
2018-03-09 15:19:02 -05:00
Thomas Harte
719f5d79c2 Merge pull request #367 from TomHarte/DynamicVolume
Introduces formal setting of the output volume to `SampleSource`.
2018-03-09 14:10:55 -05:00
Thomas Harte
48737a32a7 Introduces formal setting of the output volume to SampleSource.
Previously every output device was making its own decision. Which is increasingly less sustainable due to the CompoundSource.
2018-03-09 13:23:18 -05:00
Thomas Harte
53f05efb2d Merge pull request #366 from TomHarte/MoreMemptr
Improves Z80 memptr behaviour.
2018-03-09 10:05:57 -05:00
Thomas Harte
0e73ba4b3e Introduces proper 5/3 SCF/CCF behaviour for the Z80.
While also `const`ing a bunch of things.
2018-03-09 09:47:00 -05:00
Thomas Harte
f0f9d5a6af Corrects memptr leakage via BIT, and ld (de/bc/nn), A behaviour. 2018-03-08 20:30:22 -05:00
Thomas Harte
03501df9e5 Merge pull request #365 from TomHarte/CartridgeDetermination
Works towards eliminating the special cases for Atari 2600 ROM handling.
2018-03-08 18:40:58 -05:00
Thomas Harte
dd6f85d4db Merge pull request #364 from TomHarte/TimingUpfront
Ensures the Coleco & MSX account for instruction lengths prior to outward accesses.
2018-03-07 17:29:32 -05:00
Thomas Harte
1804ea6849 Ensures the ColecoVision and MSX account for instruction lengths in advance when timing secondary components. 2018-03-07 17:00:18 -05:00
Thomas Harte
c8657e08f4 Merge remote-tracking branch 'origin/master' into CartridgeDetermination 2018-03-07 16:42:16 -05:00
Thomas Harte
a942e1319b Merge pull request #363 from TomHarte/ZonX
Introduces ZonX emulation and corrects a minor ColecoVision AY timing issue.
2018-03-07 16:23:51 -05:00
Thomas Harte
9e0a56b4f0 Withdraws the 2600 from .rom consideration.
Will return when it is performing more sanity checks; for the time being I don't want it constantly forcing multimachines.
2018-03-07 16:21:17 -05:00
Thomas Harte
9abc020818 Corrects potential ColecoVision SGM AY timing issues. 2018-03-07 16:16:58 -05:00
Thomas Harte
2dade8d353 Introduces ZonX emulation for the ZX81. 2018-03-07 16:16:29 -05:00
Thomas Harte
1100dc6993 Opens up .bin and .rom to all cartridge platforms, and adds a confidence estimate to the Atari 2600. 2018-03-07 14:26:07 -05:00
Thomas Harte
f212b18511 Declares a confidence for the ColecoVision equal to the probability that the special bytes are wrong. 2018-03-07 14:25:25 -05:00
Thomas Harte
a6ca69550f Standardises machines that aren't making a real guess on reporting a confidence of 0.5. 2018-03-07 14:24:52 -05:00
Thomas Harte
2452641844 Introduces a fast workaround to avert a MultiMachine where it would instantly end. 2018-03-06 19:08:02 -05:00
Thomas Harte
c82af4b814 Introduces get_confidence for the ColecoVision.
Based almost entirely on joypad accesses for now.
2018-03-06 19:06:35 -05:00
Thomas Harte
fdef914137 Corrects test target regression. 2018-03-06 18:32:21 -05:00
Thomas Harte
dfcc502a88 Merge pull request #360 from TomHarte/SDLJoystick
Introduces keyboard-as-joystick fallback for the SDL target.
2018-03-04 17:28:05 -05:00
Thomas Harte
1c6faaae88 Introduces keyboard-as-joystick fallback for the SDL target. 2018-03-04 17:26:32 -05:00
Thomas Harte
35c8a0dd8c Merge pull request #359 from TomHarte/MentionColecoVision
Adds the ColecoVision to the declared list of machines.
2018-03-03 19:05:05 -05:00
Thomas Harte
38feedaf6a Adds the ColecoVision. 2018-03-03 19:03:54 -05:00
168 changed files with 3027 additions and 1561 deletions

View File

@@ -1,5 +1,13 @@
language: objective-c # language: objective-c
osx_image: xcode8.2 # osx_image: xcode8.2
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj # xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
xcode_scheme: Clock Signal # xcode_scheme: Clock Signal
xcode_sdk: macosx10.12 # xcode_sdk: macosx10.12
language: cpp
before_install:
- sudo apt-get install libsdl2-dev
script: cd OSBindings/SDL && scons
compiler:
- clang
- gcc

View File

@@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_; return speaker_;
} }
void MultiCRTMachine::run_for(const Cycles cycles) { void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([=](::CRTMachine::Machine *machine) { perform_parallel([=](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(cycles); if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
}); });
if(delegate_) delegate_->multi_crt_did_run_machines(); if(delegate_) delegate_->multi_crt_did_run_machines();
} }
double MultiCRTMachine::get_clock_rate() {
// TODO: something smarter than this? Not all clock rates will necessarily be the same.
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_rate() : 0.0;
}
bool MultiCRTMachine::get_clock_is_unlimited() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_is_unlimited() : false;
}
void MultiCRTMachine::did_change_machine_order() { void MultiCRTMachine::did_change_machine_order() {
if(speaker_) { if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get()); speaker_->set_new_front_machine(machines_.front().get());
} }
} }
void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) {
// TODO:
}
void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) {
// TODO: consider passing along.
}
void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) {
// TODO: consider passing along.
}

View File

@@ -29,7 +29,7 @@ namespace Dynamic {
acquiring a supplied mutex. The owner should also call did_change_machine_order() acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes. if the order of machines changes.
*/ */
class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate { class MultiCRTMachine: public CRTMachine::Machine {
public: public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
@@ -57,16 +57,10 @@ class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::D
void close_output() override; void close_output() override;
Outputs::CRT::CRT *get_crt() override; Outputs::CRT::CRT *get_crt() override;
Outputs::Speaker::Speaker *get_speaker() override; Outputs::Speaker::Speaker *get_speaker() override;
void run_for(const Cycles cycles) override; void run_for(Time::Seconds duration) override;
double get_clock_rate() override;
bool get_clock_is_unlimited() override;
void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
private: private:
// CRTMachine::Machine::Delegate void run_for(const Cycles cycles) override {}
void machine_did_change_clock_rate(Machine *machine) override;
void machine_did_change_clock_is_unlimited(Machine *machine) override;
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::mutex &machines_mutex_; std::mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_; std::vector<Concurrency::AsyncTaskQueue> queues_;

View File

@@ -17,7 +17,7 @@ MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique
} }
} }
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target &target) { void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
} }
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {

View File

@@ -29,7 +29,7 @@ struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. // Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
void configure_as_target(const Analyser::Static::Target &target) override; void configure_as_target(const Analyser::Static::Target *target) override;
bool insert_media(const Analyser::Static::Media &media) override; bool insert_media(const Analyser::Static::Media &media) override;
private: private:

View File

@@ -8,13 +8,69 @@
#include "MultiJoystickMachine.hpp" #include "MultiJoystickMachine.hpp"
#include <algorithm>
using namespace Analyser::Dynamic; using namespace Analyser::Dynamic;
namespace {
class MultiJoystick: public Inputs::Joystick {
public:
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
joysticks_.push_back(joysticks[index].get());
}
}
}
std::vector<DigitalInput> get_inputs() override {
std::vector<DigitalInput> inputs;
for(const auto &joystick: joysticks_) {
std::vector<DigitalInput> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
}
}
}
return inputs;
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
for(const auto &joystick: joysticks_) {
joystick->set_digital_input(digital_input, is_active);
}
}
void reset_all_inputs() override {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
}
private:
std::vector<Inputs::Joystick *> joysticks_;
};
}
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::size_t total_joysticks = 0;
std::vector<JoystickMachine::Machine *> joystick_machines;
for(const auto &machine: machines) { for(const auto &machine: machines) {
JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
if(joystick_machine) machines_.push_back(joystick_machine); if(joystick_machine) {
joystick_machines.push_back(joystick_machine);
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
}
} }
for(std::size_t index = 0; index < total_joysticks; ++index) {
joysticks_.emplace_back(new MultiJoystick(joystick_machines, index));
}
} }
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {

View File

@@ -31,7 +31,6 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
private: private:
std::vector<JoystickMachine::Machine *> machines_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
}; };

View File

@@ -48,13 +48,29 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
} }
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); if(!delegate_) return;
if(delegate_ && speaker == front_speaker_) { {
delegate_->speaker_did_complete_samples(this, buffer); std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
} }
delegate_->speaker_did_complete_samples(this, buffer);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_change_input_clock(this);
} }
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); {
front_speaker_ = machine->crt_machine()->get_speaker(); std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
}
} }

View File

@@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
void set_new_front_machine(::Machine::DynamicMachine *machine); void set_new_front_machine(::Machine::DynamicMachine *machine);
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. // Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum); float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_output_rate(float cycles_per_second, int buffer_size); void set_output_rate(float cycles_per_second, int buffer_size) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate); void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
private: private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer); void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
void speaker_did_change_input_clock(Speaker *speaker) override;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_; std::vector<Outputs::Speaker::Speaker *> speakers_;

View File

@@ -62,8 +62,15 @@ Configurable::Device *MultiMachine::configurable_device() {
} }
} }
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
return
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
}
void MultiMachine::multi_crt_did_run_machines() { void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_); std::lock_guard<std::mutex> machines_lock(machines_mutex_);
#ifdef DEBUG
for(const auto &machine: machines_) { for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine(); CRTMachine::Machine *crt = machine->crt_machine();
printf("%0.2f ", crt->get_confidence()); printf("%0.2f ", crt->get_confidence());
@@ -71,6 +78,7 @@ void MultiMachine::multi_crt_did_run_machines() {
printf("; "); printf("; ");
} }
printf("\n"); printf("\n");
#endif
DynamicMachine *front = machines_.front().get(); DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(), std::stable_sort(machines_.begin(), machines_.end(),
@@ -84,10 +92,7 @@ void MultiMachine::multi_crt_did_run_machines() {
crt_machine_.did_change_machine_order(); crt_machine_.did_change_machine_order();
} }
if( if(would_collapse(machines_)) {
(machines_.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence())
) {
pick_first(); pick_first();
} }
} }

View File

@@ -40,6 +40,14 @@ namespace Dynamic {
*/ */
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
public: public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
requesting this class as a proxy.
@returns @c true if the multimachine would discard all but the first machine in this list;
@c false otherwise.
*/
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
ConfigurationTarget::Machine *configuration_target() override; ConfigurationTarget::Machine *configuration_target() override;

View File

@@ -10,6 +10,7 @@
#include "Disk.hpp" #include "Disk.hpp"
#include "Tape.hpp" #include "Tape.hpp"
#include "Target.hpp"
using namespace Analyser::Static::Acorn; using namespace Analyser::Static::Acorn;
@@ -56,13 +57,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges; return acorn_cartridges;
} }
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target); std::unique_ptr<Target> target(new Target);
target->machine = Machine::Electron; target->machine = Machine::Electron;
target->confidence = 1.0; // TODO: a proper estimation target->confidence = 0.5; // TODO: a proper estimation
target->acorn.has_dfs = false; target->has_dfs = false;
target->acorn.has_adfs = false; target->has_adfs = false;
target->acorn.should_shift_restart = false; target->should_shift_restart = false;
// strip out inappropriate cartridges // strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges); target->media.cartridges = AcornCartridgesFrom(media.cartridges);
@@ -109,17 +110,18 @@ void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::un
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) { if(dfs_catalogue || adfs_catalogue) {
target->media.disks = media.disks; target->media.disks = media.disks;
target->acorn.has_dfs = !!dfs_catalogue; target->has_dfs = !!dfs_catalogue;
target->acorn.has_adfs = !!adfs_catalogue; target->has_adfs = !!adfs_catalogue;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None) if(bootOption != Catalogue::BootOption::None)
target->acorn.should_shift_restart = true; target->should_shift_restart = true;
else else
target->loading_command = "*CAT\n"; target->loading_command = "*CAT\n";
} }
} }
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) {
destination.push_back(std::move(target)); destination.push_back(std::move(target));
}
} }

View File

@@ -0,0 +1,28 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Acorn_Target_h
#define Analyser_Static_Acorn_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target {
bool has_adfs = false;
bool has_dfs = false;
bool should_shift_restart = false;
};
}
}
}
#endif /* Analyser_Static_Acorn_Target_h */

View File

@@ -11,6 +11,8 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/CPM.hpp" #include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
@@ -58,7 +60,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
static void InspectCatalogue( static void InspectCatalogue(
const Storage::Disk::CPM::Catalogue &catalogue, const Storage::Disk::CPM::Catalogue &catalogue,
const std::unique_ptr<Analyser::Static::Target> &target) { const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
std::vector<const Storage::Disk::CPM::File *> candidate_files; std::vector<const Storage::Disk::CPM::File *> candidate_files;
candidate_files.reserve(catalogue.files.size()); candidate_files.reserve(catalogue.files.size());
@@ -153,10 +155,10 @@ static void InspectCatalogue(
target->loading_command = "cat\n"; target->loading_command = "cat\n";
} }
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) { static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
Storage::Encodings::MFM::Parser parser(true, disk); Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr && !boot_sector->samples.empty()) { if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably // Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced. // this disk was formatted and the filler byte never replaced.
bool matched = true; bool matched = true;
@@ -177,24 +179,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
return false; return false;
} }
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target); std::unique_ptr<Target> target(new Target);
target->machine = Machine::AmstradCPC; target->machine = Machine::AmstradCPC;
target->confidence = 1.0; target->confidence = 0.5;
target->media.disks = media.disks;
target->media.tapes = media.tapes;
target->media.cartridges = media.cartridges;
target->amstradcpc.model = AmstradCPCModel::CPC6128; target->model = Target::Model::CPC6128;
if(!media.tapes.empty()) {
// TODO: which of these are actually potentially CPC tapes?
target->media.tapes = media.tapes;
if(!target->media.tapes.empty()) {
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing // Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for // enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck! // a while. Yuck!
target->loading_command = "|tape\nrun\"\n1234567890"; target->loading_command = "|tape\nrun\"\n1234567890";
} }
if(!target->media.disks.empty()) { if(!media.disks.empty()) {
Storage::Disk::CPM::ParameterBlock data_format; Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9; data_format.sectors_per_track = 9;
data_format.tracks = 40; data_format.tracks = 40;
@@ -203,26 +205,40 @@ void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<st
data_format.catalogue_allocation_bitmap = 0xc000; data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0; data_format.reserved_tracks = 0;
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format); Storage::Disk::CPM::ParameterBlock system_format;
if(data_catalogue) { system_format.sectors_per_track = 9;
InspectCatalogue(*data_catalogue, target); system_format.tracks = 40;
} else { system_format.block_size = 1024;
if(!CheckBootSector(target->media.disks.front(), target)) { system_format.first_sector = 0x41;
Storage::Disk::CPM::ParameterBlock system_format; system_format.catalogue_allocation_bitmap = 0xc000;
system_format.sectors_per_track = 9; system_format.reserved_tracks = 2;
system_format.tracks = 40;
system_format.block_size = 1024;
system_format.first_sector = 0x41;
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format); for(const auto &disk: media.disks) {
if(system_catalogue) { // Check for an ordinary catalogue.
InspectCatalogue(*system_catalogue, target); std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
} if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
target->media.disks.push_back(disk);
continue;
}
// Failing that check for a boot sector.
if(CheckBootSector(disk, target)) {
target->media.disks.push_back(disk);
continue;
}
// Failing that check for a system catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
target->media.disks.push_back(disk);
continue;
} }
} }
} }
destination.push_back(std::move(target)); // If any media survived, add the target.
if(!target->media.empty())
destination.push_back(std::move(target));
} }

View File

@@ -0,0 +1,33 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AmstradCPC_Target_h
#define Analyser_Static_AmstradCPC_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public ::Analyser::Static::Target {
enum class Model {
CPC464,
CPC664,
CPC6128
};
Model model = Model::CPC464;
};
}
}
}
#endif /* Analyser_Static_AmstradCPC_Target_h */

View File

@@ -8,11 +8,13 @@
#include "StaticAnalyser.hpp" #include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "../Disassembler/6502.hpp" #include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari; using namespace Analyser::Static::Atari;
static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid // if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address; uint16_t entry_address, break_address;
@@ -46,10 +48,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, cons
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses // caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it // itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't // attempts to modify itself but it probably doesn't
if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid; if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
} }
static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and // Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all // always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?) // issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
@@ -58,12 +60,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && (segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78 segment.data[0] == 0x78
) { ) {
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack; target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
return; return;
} }
// make an assumption that this is the Atari paging model // make an assumption that this is the Atari paging model
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k; target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses; std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -83,13 +85,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, cons
tigervision_access_count += masked_address == 0x3f; tigervision_access_count += masked_address == 0x3f;
} }
if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros; if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
} }
static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model // make an assumption that this is the Atari paging model
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k; target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
std::set<uint16_t> internal_accesses; std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -104,17 +106,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, con
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
} }
if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork; if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
} }
static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F // make an assumption that this is a Tigervision if there is a write to 3F
target.atari.paging_model = target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? (disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy; Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
} }
static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) { if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment); DeterminePagingFor2kCartridge(target, segment);
return; return;
@@ -138,16 +140,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
DeterminePagingFor8kCartridge(target, segment, disassembly); DeterminePagingFor8kCartridge(target, segment, disassembly);
break; break;
case 10495: case 10495:
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2; target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
break; break;
case 12288: case 12288:
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus; target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
break; break;
case 16384: case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly); DeterminePagingFor16kCartridge(target, segment, disassembly);
break; break;
case 32768: case 32768:
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k; target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
break; break;
case 65536: case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly); DeterminePagingFor64kCartridge(target, segment, disassembly);
@@ -159,8 +161,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM // check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the // regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that. // next 128 bytes. So check for that.
if( target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus && if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) { target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
bool has_superchip = true; bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) { for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) { if(segment.data[address] != segment.data[address+128]) {
@@ -168,24 +170,24 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
break; break;
} }
} }
target.atari.uses_superchip = has_superchip; target.uses_superchip = has_superchip;
} }
// check for a Tigervision or Tigervision-esque scheme // check for a Tigervision or Tigervision-esque scheme
if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) { if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
} }
} }
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
// TODO: sanity checking; is this image really for an Atari 2600. // TODO: sanity checking; is this image really for an Atari 2600?
std::unique_ptr<Target> target(new Target); std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
target->machine = Machine::Atari2600; target->machine = Machine::Atari2600;
target->confidence = 1.0; target->confidence = 0.5;
target->media.cartridges = media.cartridges; target->media.cartridges = media.cartridges;
target->atari.paging_model = Atari2600PagingModel::None; target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
target->atari.uses_superchip = false; target->uses_superchip = false;
// try to figure out the paging scheme // try to figure out the paging scheme
if(!media.cartridges.empty()) { if(!media.cartridges.empty()) {

View File

@@ -0,0 +1,43 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Atari_Target_h
#define Analyser_Static_Atari_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Atari {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {
None,
CommaVid,
Atari8k,
Atari16k,
Atari32k,
ActivisionStack,
ParkerBros,
Tigervision,
CBSRamPlus,
MNetwork,
MegaBoy,
Pitfall2
};
// TODO: shouldn't these be properties of the cartridge?
PagingModel paging_model = PagingModel::None;
bool uses_superchip = false;
};
}
}
}
#endif /* Analyser_Static_Atari_Target_h */

View File

@@ -55,7 +55,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target); std::unique_ptr<Target> target(new Target);
target->machine = Machine::ColecoVision; target->machine = Machine::ColecoVision;
target->confidence = 0.5; target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges); target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty()) if(!target->media.empty())
destination.push_back(std::move(target)); destination.push_back(std::move(target));

View File

@@ -71,19 +71,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
bit_count_++; bit_count_++;
} }
unsigned int proceed_to_next_block() { unsigned int proceed_to_next_block(int max_index_count) {
// find GCR lead-in // find GCR lead-in
proceed_to_shift_value(0x3ff); proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff; if(shift_register_ != 0x3ff) return 0xff;
// find end of lead-in // find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < 2) { while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
run_for(Cycles(1)); run_for(Cycles(1));
} }
// continue for a further nine bits // continue for a further nine bits
bit_count_ = 0; bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < 2) { while(bit_count_ < 9 && index_count_ < max_index_count) {
run_for(Cycles(1)); run_for(Cycles(1));
} }
@@ -97,8 +97,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
} }
void proceed_to_shift_value(unsigned int shift_value) { void proceed_to_shift_value(unsigned int shift_value) {
index_count_ = 0; const int max_index_count = index_count_ + 2;
while(shift_register_ != shift_value && index_count_ < 2) { while(shift_register_ != shift_value && index_count_ < max_index_count) {
run_for(Cycles(1)); run_for(Cycles(1));
} }
} }
@@ -124,13 +124,13 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::shared_ptr<Sector> get_next_sector() { std::shared_ptr<Sector> get_next_sector() {
std::shared_ptr<Sector> sector(new Sector); std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0; const int max_index_count = index_count_ + 2;
while(index_count_ < 2) { while(index_count_ < max_index_count) {
// look for a sector header // look for a sector header
while(1) { while(1) {
if(proceed_to_next_block() == 0x08) break; if(proceed_to_next_block(max_index_count) == 0x08) break;
if(index_count_ >= 2) return nullptr; if(index_count_ >= max_index_count) return nullptr;
} }
// get sector details, skip if this looks malformed // get sector details, skip if this looks malformed
@@ -144,8 +144,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
// look for the following data // look for the following data
while(1) { while(1) {
if(proceed_to_next_block() == 0x07) break; if(proceed_to_next_block(max_index_count) == 0x07) break;
if(index_count_ >= 2) return nullptr; if(index_count_ >= max_index_count) return nullptr;
} }
checksum = 0; checksum = 0;

View File

@@ -11,8 +11,10 @@
#include "Disk.hpp" #include "Disk.hpp"
#include "File.hpp" #include "File.hpp"
#include "Tape.hpp" #include "Tape.hpp"
#include "Target.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include <algorithm>
#include <sstream> #include <sstream>
using namespace Analyser::Static::Commodore; using namespace Analyser::Static::Commodore;
@@ -38,10 +40,10 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return vic20_cartridges; return vic20_cartridges;
} }
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) {
std::unique_ptr<Target> target(new Target); std::unique_ptr<Target> target(new Target);
target->machine = Machine::Vic20; // TODO: machine estimation target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 1.0; // TODO: a proper estimation target->confidence = 0.5; // TODO: a proper estimation
int device = 0; int device = 0;
std::vector<File> files; std::vector<File> files;
@@ -73,7 +75,7 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
} }
if(!files.empty()) { if(!files.empty()) {
target->vic20.memory_model = Vic20MemoryModel::Unexpanded; target->memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream; std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) { if(files.front().is_basic()) {
@@ -86,19 +88,22 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
// make a first guess based on loading address // make a first guess based on loading address
switch(files.front().starting_address) { switch(files.front().starting_address) {
default:
printf("Starting address %04x?\n", files.front().starting_address);
case 0x1001: case 0x1001:
default: break; target->memory_model = Target::MemoryModel::Unexpanded;
break;
case 0x1201: case 0x1201:
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; target->memory_model = Target::MemoryModel::ThirtyTwoKB;
break; break;
case 0x0401: case 0x0401:
target->vic20.memory_model = Vic20MemoryModel::EightKB; target->memory_model = Target::MemoryModel::EightKB;
break; break;
} }
// General approach: increase memory size conservatively such that the largest file found will fit. // General approach: increase memory size conservatively such that the largest file found will fit.
for(File &file : files) { // for(File &file : files) {
std::size_t file_size = file.data.size(); // std::size_t file_size = file.data.size();
// bool is_basic = file.is_basic(); // bool is_basic = file.is_basic();
/*if(is_basic) /*if(is_basic)
@@ -124,18 +129,26 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000. // An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000. // A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
// A 32kb expanded Vic has memory in the entire low 32kb. // A 32kb expanded Vic has memory in the entire low 32kb.
uint16_t starting_address = file.starting_address; // uint16_t starting_address = file.starting_address;
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the // If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb. // region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
if(starting_address + file_size > 0x2000) // if(starting_address + file_size > 0x2000)
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; // target->memory_model = Target::MemoryModel::ThirtyTwoKB;
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) // else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; // target->memory_model = Target::MemoryModel::ThirtyTwoKB;
// } // }
} // }
} }
if(!target->media.empty()) if(!target->media.empty()) {
// Inspect filename for a region hint.
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Analyser::Static::Commodore::Target::Region::American;
}
destination.push_back(std::move(target)); destination.push_back(std::move(target));
}
} }

View File

@@ -10,12 +10,13 @@
#define StaticAnalyser_Commodore_StaticAnalyser_hpp #define StaticAnalyser_Commodore_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp" #include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser { namespace Analyser {
namespace Static { namespace Static {
namespace Commodore { namespace Commodore {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name);
} }
} }

View File

@@ -0,0 +1,42 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Region {
American,
Danish,
Japanese,
European,
Swedish
};
MemoryModel memory_model = MemoryModel::Unexpanded;
Region region = Region::European;
bool has_c1540 = false;
};
}
}
}
#endif /* Analyser_Static_Commodore_Target_h */

View File

@@ -10,6 +10,8 @@
#include "Cartridge.hpp" #include "Cartridge.hpp"
#include "Tape.hpp" #include "Tape.hpp"
#include "Target.hpp"
#include "../Disassembler/Z80.hpp" #include "../Disassembler/Z80.hpp"
#include "../Disassembler/AddressMapper.hpp" #include "../Disassembler/AddressMapper.hpp"
@@ -32,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
output_segments.emplace_back(start_address, segment.data); output_segments.emplace_back(start_address, segment.data);
} }
std::unique_ptr<Analyser::Static::Target> target(new Analyser::Static::Target); std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
target->machine = Analyser::Machine::MSX; target->machine = Analyser::Machine::MSX;
target->confidence = confidence; target->confidence = confidence;
@@ -259,9 +261,9 @@ static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFr
return targets; return targets;
} }
void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
// Append targets for any cartridges that look correct. // Append targets for any cartridges that look correct.
std::vector<std::unique_ptr<Target>> cartridge_targets = CartridgeTargetsFrom(media.cartridges); auto cartridge_targets = CartridgeTargetsFrom(media.cartridges);
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
// Consider building a target for disks and/or tapes. // Consider building a target for disks and/or tapes.
@@ -283,10 +285,11 @@ void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::uniq
// Blindly accept disks for now. // Blindly accept disks for now.
target->media.disks = media.disks; target->media.disks = media.disks;
target->has_disk_drive = !media.disks.empty();
if(!target->media.empty()) { if(!target->media.empty()) {
target->machine = Machine::MSX; target->machine = Machine::MSX;
target->confidence = 1.0; target->confidence = 0.5;
destination.push_back(std::move(target)); destination.push_back(std::move(target));
} }
} }

View File

@@ -0,0 +1,26 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_MSX_Target_h
#define Analyser_Static_MSX_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace MSX {
struct Target: public ::Analyser::Static::Target {
bool has_disk_drive = false;
};
}
}
}
#endif /* Analyser_Static_MSX_Target_h */

View File

@@ -9,9 +9,15 @@
#include "StaticAnalyser.hpp" #include "StaticAnalyser.hpp"
#include "Tape.hpp" #include "Tape.hpp"
#include "Target.hpp"
#include "../Disassembler/6502.hpp" #include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp" #include "../Disassembler/AddressMapper.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include <cstring>
using namespace Analyser::Static::Oric; using namespace Analyser::Static::Oric;
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
@@ -73,10 +79,31 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
return Score(disassembly, rom_functions, variable_locations); return Score(disassembly, rom_functions, variable_locations);
} }
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
if(!sector) return false;
if(sector->samples.empty()) return false;
const std::vector<uint8_t> &first_sample = sector->samples[0];
if(first_sample.size() != 256) return false;
const uint8_t signature[] = {
0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0,
0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00,
0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6,
0x12, 0x00
};
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target); std::unique_ptr<Target> target(new Target);
target->machine = Machine::Oric; target->machine = Machine::Oric;
target->confidence = 1.0; target->confidence = 0.5;
int basic10_votes = 0; int basic10_votes = 0;
int basic11_votes = 0; int basic11_votes = 0;
@@ -102,17 +129,22 @@ void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::uni
} }
} }
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
if(!media.disks.empty()) { if(!media.disks.empty()) {
target->oric.has_microdisc = true; // Only the Microdisc is emulated right now, so accept only disks that it can boot from.
target->media.disks = media.disks; for(const auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
target->has_microdrive = true;
target->media.disks.push_back(disk);
}
}
} else { } else {
target->oric.has_microdisc = false; target->has_microdrive = false;
} }
// TODO: really this should add two targets if not all votes agree // TODO: really this should add two targets if not all votes agree
target->oric.use_atmos_rom = basic11_votes >= basic10_votes; target->use_atmos_rom = basic11_votes >= basic10_votes;
if(target->oric.has_microdisc) target->oric.use_atmos_rom = true; if(target->has_microdrive) target->use_atmos_rom = true;
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
destination.push_back(std::move(target)); destination.push_back(std::move(target));

View File

@@ -0,0 +1,25 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public ::Analyser::Static::Target {
bool use_atmos_rom = false;
bool has_microdrive = false;
};
}
}
}
#endif /* Analyser_Static_Oric_Target_h */

View File

@@ -52,21 +52,16 @@
using namespace Analyser::Static; using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) { static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase // Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format. // test as to file format.
const char *mixed_case_extension = strrchr(file_name, '.'); std::string::size_type final_dot = file_name.find_last_of(".");
char *lowercase_extension = nullptr; if(final_dot == std::string::npos) return result;
if(mixed_case_extension) { std::string extension = file_name.substr(final_dot + 1);
lowercase_extension = strdup(mixed_case_extension+1); std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
char *parser = lowercase_extension;
while(*parser) {
*parser = (char)tolower(*parser);
parser++;
}
}
Media result;
#define Insert(list, class, platforms) \ #define Insert(list, class, platforms) \
list.emplace_back(new Storage::class(file_name));\ list.emplace_back(new Storage::class(file_name));\
potential_platforms |= platforms;\ potential_platforms |= platforms;\
@@ -78,74 +73,72 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
Insert(list, class, platforms) \ Insert(list, class, platforms) \
} catch(...) {} } catch(...) {}
#define Format(extension, list, class, platforms) \ #define Format(ext, list, class, platforms) \
if(!std::strcmp(lowercase_extension, extension)) { \ if(extension == ext) { \
TryInsert(list, class, platforms) \ TryInsert(list, class, platforms) \
} }
if(lowercase_extension) { Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80 Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81 Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64 Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX) Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
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("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64 Format( "hfe",
Format("hfe", result.disks, Disk::DiskImageHolder<Storage::Disk::HFE>, TargetPlatform::AmstradCPC) // HFE (TODO: plus other target platforms) result.disks,
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O Disk::DiskImageHolder<Storage::Disk::HFE>,
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81 // HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG // PRG
if(!std::strcmp(lowercase_extension, "prg")) { if(extension == "prg") {
// try instantiating as a ROM; failing that accept as a tape // try instantiating as a ROM; failing that accept as a tape
try {
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
} catch(...) {
try { try {
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore) Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) { } catch(...) {}
try {
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) {}
}
} }
}
Format( "rom", Format( "rom",
result.cartridges, result.cartridges,
Cartridge::BinaryDump, Cartridge::BinaryDump,
TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision) // ROM TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
#undef Format #undef Format
#undef Insert #undef Insert
#undef TryInsert #undef TryInsert
// Filter potential platforms as per file preferences, if any.
free(lowercase_extension);
}
return result; return result;
} }
Media Analyser::Static::GetMedia(const char *file_name) { Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetPlatform::IntType throwaway; TargetPlatform::IntType throwaway;
return GetMediaAndPlatforms(file_name, throwaway); return GetMediaAndPlatforms(file_name, throwaway);
} }
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *file_name) { std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) {
std::vector<std::unique_ptr<Target>> targets; std::vector<std::unique_ptr<Target>> targets;
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the // Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
@@ -159,7 +152,7 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *fi
if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets); if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets); if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets, file_name);
if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets); if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms); if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);

View File

@@ -21,39 +21,6 @@
namespace Analyser { namespace Analyser {
namespace Static { namespace Static {
enum class Vic20MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Atari2600PagingModel {
None,
CommaVid,
Atari8k,
Atari16k,
Atari32k,
ActivisionStack,
ParkerBros,
Tigervision,
CBSRamPlus,
MNetwork,
MegaBoy,
Pitfall2
};
enum class ZX8081MemoryModel {
Unexpanded,
SixteenKB,
SixtyFourKB
};
enum class AmstradCPCModel {
CPC464,
CPC664,
CPC6128
};
/*! /*!
A list of disks, tapes and cartridges. A list of disks, tapes and cartridges.
*/ */
@@ -72,45 +39,13 @@ struct Media {
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/ */
struct Target { struct Target {
virtual ~Target() {}
Machine machine; Machine machine;
Media media; Media media;
float confidence; float confidence;
std::string loading_command; std::string loading_command;
// TODO: this is too C-like a solution; make Target a base class and
// turn the following into information held by more specific subclasses.
union {
struct {
bool has_adfs;
bool has_dfs;
bool should_shift_restart;
} acorn;
struct {
Atari2600PagingModel paging_model;
bool uses_superchip;
} atari;
struct {
bool use_atmos_rom;
bool has_microdisc;
} oric;
struct {
Vic20MemoryModel memory_model;
bool has_c1540;
} vic20;
struct {
ZX8081MemoryModel memory_model;
bool isZX81;
} zx8081;
struct {
AmstradCPCModel model;
} amstradcpc;
};
}; };
/*! /*!
@@ -118,12 +53,12 @@ struct Target {
@returns The list of potential targets, sorted from most to least probable. @returns The list of potential targets, sorted from most to least probable.
*/ */
std::vector<std::unique_ptr<Target>> GetTargets(const char *file_name); std::vector<std::unique_ptr<Target>> GetTargets(const std::string &file_name);
/*! /*!
Inspects the supplied file and determines the media included. Inspects the supplied file and determines the media included.
*/ */
Media GetMedia(const char *file_name); Media GetMedia(const std::string &file_name);
} }
} }

View File

@@ -11,6 +11,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "Target.hpp"
#include "../../../Storage/Tape/Parsers/ZX8081.hpp" #include "../../../Storage/Tape/Parsers/ZX8081.hpp"
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
@@ -27,41 +28,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
return files; return files;
} }
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) { void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) {
if(!media.tapes.empty()) { if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset(); media.tapes.front()->reset();
if(!files.empty()) { if(!files.empty()) {
std::unique_ptr<Target> target(new Target); Target *target = new Target;
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
target->machine = Machine::ZX8081; target->machine = Machine::ZX8081;
// Guess the machine type from the file only if it isn't already known. // Guess the machine type from the file only if it isn't already known.
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
default: default:
target->zx8081.isZX81 = files.front().isZX81; target->is_ZX81 = files.front().isZX81;
break; break;
case TargetPlatform::ZX80: target->zx8081.isZX81 = false; break; case TargetPlatform::ZX80: target->is_ZX81 = false; break;
case TargetPlatform::ZX81: target->zx8081.isZX81 = true; break; case TargetPlatform::ZX81: target->is_ZX81 = true; break;
} }
/*if(files.front().data.size() > 16384) { /*if(files.front().data.size() > 16384) {
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
} else*/ if(files.front().data.size() > 1024) { } else*/ if(files.front().data.size() > 1024) {
target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB; target->memory_model = Target::MemoryModel::SixteenKB;
} else { } else {
target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded; target->memory_model = Target::MemoryModel::Unexpanded;
} }
target->media.tapes = media.tapes; target->media.tapes = media.tapes;
// TODO: how to run software once loaded? Might require a BASIC detokeniser. // TODO: how to run software once loaded? Might require a BASIC detokeniser.
if(target->zx8081.isZX81) { if(target->is_ZX81) {
target->loading_command = "J\"\"\n"; target->loading_command = "J\"\"\n";
} else { } else {
target->loading_command = "W\n"; target->loading_command = "W\n";
} }
destination.push_back(std::move(target));
} }
} }
} }

View File

@@ -0,0 +1,34 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
Unexpanded,
SixteenKB,
SixtyFourKB
};
MemoryModel memory_model = MemoryModel::Unexpanded;
bool is_ZX81 = false;
bool ZX80_uses_ZX81_ROM = false;
};
}
}
}
#endif /* Analyser_Static_ZX8081_Target_h */

View File

@@ -0,0 +1,18 @@
//
// TimeTypes.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef TimeTypes_h
#define TimeTypes_h
namespace Time {
typedef double Seconds;
}
#endif /* TimeTypes_h */

View File

@@ -18,7 +18,7 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
void AudioGenerator::set_volume(uint8_t volume) { void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.defer([=]() { audio_queue_.defer([=]() {
volume_ = volume; volume_ = static_cast<int16_t>(volume) * range_multiplier_;
}); });
} }
@@ -114,12 +114,12 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
// this sums the output of all three sounds channels plus a DC offset for volume; // this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff? // TODO: what's the real ratio of this stuff?
target[c] = ( target[c] = static_cast<int16_t>(
(shift_registers_[0]&1) + (shift_registers_[0]&1) +
(shift_registers_[1]&1) + (shift_registers_[1]&1) +
(shift_registers_[2]&1) + (shift_registers_[2]&1) +
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) ((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
) * volume_ * 700 + volume_ * 44; ) * volume_ + (volume_ >> 4);
} }
} }
@@ -132,6 +132,10 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
} }
} }
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = static_cast<int16_t>(range / 64);
}
#undef shift #undef shift
#undef increment #undef increment
#undef update #undef update

View File

@@ -25,8 +25,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
void set_volume(uint8_t volume); void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value); void set_control(int channel, uint8_t value);
// For ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples); void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
private: private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -34,7 +36,8 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0}; unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0}; uint8_t control_registers_[4] = {0, 0, 0, 0};
uint8_t volume_ = 0; int16_t volume_ = 0;
int16_t range_multiplier_ = 1;
}; };
/*! /*!
@@ -52,20 +55,28 @@ template <class T> class MOS6560 {
audio_generator_(audio_queue_), audio_generator_(audio_queue_),
speaker_(audio_generator_) speaker_(audio_generator_)
{ {
crt_->set_composite_sampling_function( crt_->set_svideo_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{" "{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);" "vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = cos(phase + phaseOffset);" "float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);" "float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
"return vec2(yc.x, chroma);"
"}"); "}");
// default to s-video output
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
// default to NTSC // default to NTSC
set_output_mode(OutputMode::NTSC); set_output_mode(OutputMode::NTSC);
} }
~MOS6560() {
audio_queue_.flush();
}
void set_clock_rate(double clock_rate) { void set_clock_rate(double clock_rate) {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0)); speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
} }
@@ -87,28 +98,28 @@ template <class T> class MOS6560 {
void set_output_mode(OutputMode output_mode) { void set_output_mode(OutputMode output_mode) {
output_mode_ = output_mode; output_mode_ = output_mode;
// Lumunances are encoded trivially: on a 0255 scale. // Luminances are encoded trivially: on a 0255 scale.
const uint8_t luminances[16] = { const uint8_t luminances[16] = {
0, 255, 109, 189, 0, 255, 60, 189,
199, 144, 159, 161, 80, 144, 40, 227,
126, 227, 227, 207, 90, 161, 207, 227,
235, 173, 188, 196 200, 196, 160, 196
}; };
// Chrominances are encoded such that 0128 is a complete revolution of phase; // Chrominances are encoded such that 0128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the // anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green. // colour burst, so 0 is green.
const uint8_t pal_chrominances[16] = { const uint8_t pal_chrominances[16] = {
255, 255, 40, 112, 255, 255, 37, 101,
8, 88, 120, 56, 19, 86, 123, 59,
40, 48, 40, 112, 46, 53, 37, 101,
8, 88, 120, 56, 19, 86, 123, 59,
}; };
const uint8_t ntsc_chrominances[16] = { const uint8_t ntsc_chrominances[16] = {
255, 255, 8, 72, 255, 255, 7, 71,
32, 88, 48, 112, 25, 86, 48, 112,
0, 0, 8, 72, 0, 119, 7, 71,
32, 88, 48, 112, 25, 86, 48, 112,
}; };
const uint8_t *chrominances; const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type; Outputs::CRT::DisplayType display_type;
@@ -134,16 +145,15 @@ template <class T> class MOS6560 {
} }
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type); crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
// switch(output_mode) { switch(output_mode) {
// case OutputMode::PAL: case OutputMode::PAL:
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f)); crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f));
// break; break;
// case OutputMode::NTSC: case OutputMode::NTSC:
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
// break; break;
// } }
for(int c = 0; c < 16; c++) { for(int c = 0; c < 16; c++) {
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]); uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);

View File

@@ -96,7 +96,7 @@ TMS9918::TMS9918(Personality p) {
"{" "{"
"return texture(sampler, coordinate).rgb / vec3(255.0);" "return texture(sampler, coordinate).rgb / vec3(255.0);"
"}"); "}");
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
crt_->set_input_gamma(2.8f); crt_->set_input_gamma(2.8f);

View File

@@ -56,13 +56,18 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_
} }
} }
set_sample_volume_range(0);
}
void AY38910::set_sample_volume_range(std::int16_t range) {
// set up volume lookup table // set up volume lookup table
float max_volume = 8192; const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
float root_two = sqrtf(2.0f); const float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++) { for(int v = 0; v < 16; v++) {
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf))); volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
} }
volumes_[0] = 0; volumes_[0] = 0;
evaluate_output_volume();
} }
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {

View File

@@ -86,6 +86,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level(); bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private: private:
Concurrency::DeferringAsyncTaskQueue &task_queue_; Concurrency::DeferringAsyncTaskQueue &task_queue_;

View File

@@ -27,7 +27,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::size_t c = 0; std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) { while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_; target[c] = transient_output_level_;
master_divider_++; master_divider_++;
c++; c++;
} }
@@ -44,7 +44,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
evaluate_output_volume(); evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
target[c] = output_volume_; target[c] = transient_output_level_;
c++; c++;
master_divider_++; master_divider_++;
} }
@@ -86,18 +86,24 @@ void SCC::write(uint16_t address, uint8_t value) {
} }
void SCC::evaluate_output_volume() { void SCC::evaluate_output_volume() {
output_volume_ = transient_output_level_ =
static_cast<int16_t>( static_cast<int16_t>(
( ((
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + (channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + (channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + (channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + (channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 (channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
) ) * master_volume_) / (255*15*5)
// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5.
); );
} }
void SCC::set_sample_volume_range(std::int16_t range) {
master_volume_ = range;
evaluate_output_volume();
}
uint8_t SCC::read(uint16_t address) { uint8_t SCC::read(uint16_t address) {
address &= 0xff; address &= 0xff;
if(address < 0x80) { if(address < 0x80) {
@@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) {
} }
return 0xff; return 0xff;
} }

View File

@@ -31,6 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
/// As per ::SampleSource; provides audio output. /// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target); void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
/// Writes to the SCC. /// Writes to the SCC.
void write(uint16_t address, uint8_t value); void write(uint16_t address, uint8_t value);
@@ -43,7 +44,8 @@ class SCC: public ::Outputs::Speaker::SampleSource {
// State from here on down is accessed ony from the audio thread. // State from here on down is accessed ony from the audio thread.
int master_divider_ = 0; int master_divider_ = 0;
int16_t output_volume_ = 0; std::int16_t master_volume_ = 0;
int16_t transient_output_level_ = 0;
struct Channel { struct Channel {
int period = 0; int period = 0;

View File

@@ -14,15 +14,7 @@
using namespace TI; using namespace TI;
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
// Build a volume table. set_sample_volume_range(0);
double multiplier = pow(10.0, -0.1);
double volume = 8191.0f;
for(int c = 0; c < 16; ++c) {
volumes_[c] = (int)round(volume);
volume *= multiplier;
}
volumes_[15] = 0;
evaluate_output_volume();
switch(personality) { switch(personality) {
case Personality::SN76494: case Personality::SN76494:
@@ -44,6 +36,18 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &
master_divider_period_ /= additional_divider; master_divider_period_ /= additional_divider;
} }
void SN76489::set_sample_volume_range(std::int16_t range) {
// Build a volume table.
double multiplier = pow(10.0, -0.1);
double volume = static_cast<float>(range) / 4.0f; // As there are four channels.
for(int c = 0; c < 16; ++c) {
volumes_[c] = (int)round(volume);
volume *= multiplier;
}
volumes_[15] = 0;
evaluate_output_volume();
}
void SN76489::set_register(uint8_t value) { void SN76489::set_register(uint8_t value) {
task_queue_.defer([value, this] () { task_queue_.defer([value, this] () {
if(value & 0x80) { if(value & 0x80) {

View File

@@ -31,6 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
// As per SampleSource. // As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target); void get_samples(std::size_t number_of_samples, std::int16_t *target);
bool is_zero_level(); bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private: private:
int master_divider_ = 0; int master_divider_ = 0;

View File

@@ -45,6 +45,7 @@ AsyncTaskQueue::AsyncTaskQueue()
AsyncTaskQueue::~AsyncTaskQueue() { AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__ #ifdef __APPLE__
flush();
dispatch_release(serial_dispatch_queue_); dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr; serial_dispatch_queue_ = nullptr;
#else #else
@@ -82,6 +83,7 @@ void AsyncTaskQueue::flush() {
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
perform(); perform();
flush();
} }
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {

View File

@@ -36,13 +36,11 @@ void BestEffortUpdater::update() {
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate // If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum — // if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments. // it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(duration > 0) { if(integer_duration > 0) {
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
error_ = fmod(cycles, 1.0);
if(delegate_) { if(delegate_) {
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_); const double duration = static_cast<double>(integer_duration) / 1e9;
delegate_->update(this, duration, has_skipped_);
} }
has_skipped_ = false; has_skipped_ = false;
} }
@@ -70,8 +68,3 @@ void BestEffortUpdater::set_delegate(Delegate *const delegate) {
}); });
} }
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
async_task_queue_.enqueue([this, clock_rate]() {
this->clock_rate_ = clock_rate;
});
}

View File

@@ -13,6 +13,7 @@
#include <chrono> #include <chrono>
#include "AsyncTaskQueue.hpp" #include "AsyncTaskQueue.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
namespace Concurrency { namespace Concurrency {
@@ -30,15 +31,12 @@ class BestEffortUpdater {
/// A delegate receives timing cues. /// A delegate receives timing cues.
struct Delegate { struct Delegate {
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0; virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
}; };
/// Sets the current delegate. /// Sets the current delegate.
void set_delegate(Delegate *); void set_delegate(Delegate *);
/// Sets the clock rate of the delegate.
void set_clock_rate(double clock_rate);
/*! /*!
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time. If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
The call is asynchronous; this method will return immediately. The call is asynchronous; this method will return immediately.
@@ -54,11 +52,9 @@ class BestEffortUpdater {
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_; std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
bool has_previous_time_point_ = false; bool has_previous_time_point_ = false;
double error_ = 0.0;
bool has_skipped_ = false; bool has_skipped_ = false;
Delegate *delegate_ = nullptr; Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
}; };
} }

View File

@@ -33,7 +33,13 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std:
std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) { std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
std::vector<std::unique_ptr<Configurable::Option>> options; std::vector<std::unique_ptr<Configurable::Option>> options;
if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload")); if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
if(mask & DisplayRGBComposite) options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"})); if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) {
std::vector<std::string> display_options;
if(mask & DisplayComposite) display_options.emplace_back("composite");
if(mask & DisplaySVideo) display_options.emplace_back("svideo");
if(mask & DisplayRGB) display_options.emplace_back("rgb");
options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
}
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
return options; return options;
} }
@@ -48,7 +54,14 @@ void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &s
} }
void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) { void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) {
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite")); std::string string_selection;
switch(selection) {
default:
case Display::RGB: string_selection = "rgb"; break;
case Display::SVideo: string_selection = "svideo"; break;
case Display::Composite: string_selection = "composite"; break;
}
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
} }
// MARK: - Selection parsers // MARK: - Selection parsers
@@ -67,6 +80,10 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
result = Configurable::Display::RGB; result = Configurable::Display::RGB;
return true; return true;
} }
if(display->value == "svideo") {
result = Configurable::Display::SVideo;
return true;
}
if(display->value == "composite") { if(display->value == "composite") {
result = Configurable::Display::Composite; result = Configurable::Display::Composite;
return true; return true;

View File

@@ -14,13 +14,16 @@
namespace Configurable { namespace Configurable {
enum StandardOptions { enum StandardOptions {
DisplayRGBComposite = (1 << 0), DisplayRGB = (1 << 0),
QuickLoadTape = (1 << 1), DisplaySVideo = (1 << 1),
AutomaticTapeMotorControl = (1 << 2) DisplayComposite = (1 << 2),
QuickLoadTape = (1 << 3),
AutomaticTapeMotorControl = (1 << 4)
}; };
enum class Display { enum class Display {
RGB, RGB,
SVideo,
Composite Composite
}; };

View File

@@ -20,11 +20,17 @@
#include "../Utility/MemoryFuzzer.hpp" #include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp" #include "../Utility/Typer.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../../ClockReceiver/ForceInline.hpp" #include "../../ClockReceiver/ForceInline.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
@@ -120,6 +126,10 @@ class AYDeferrer {
speaker_.set_input_rate(1000000); speaker_.set_input_rate(1000000);
} }
~AYDeferrer() {
audio_queue_.flush();
}
/// Adds @c half_cycles half cycles to the amount of time that has passed. /// Adds @c half_cycles half cycles to the amount of time that has passed.
inline void run_for(HalfCycles half_cycles) { inline void run_for(HalfCycles half_cycles) {
cycles_since_update_ += half_cycles; cycles_since_update_ += half_cycles;
@@ -307,7 +317,7 @@ class CRTCBusHandler {
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;" "return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}"); "}");
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f)); crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
} }
/// Destructs the CRT. /// Destructs the CRT.
@@ -674,6 +684,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
*/ */
class ConcreteMachine: class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Utility::TypeRecipient, public Utility::TypeRecipient,
public CPU::Z80::BusHandler, public CPU::Z80::BusHandler,
public Sleeper::SleepObserver, public Sleeper::SleepObserver,
@@ -867,19 +880,21 @@ class ConcreteMachine:
} }
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. /// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
void configure_as_target(const Analyser::Static::Target &target) override final { void configure_as_target(const Analyser::Static::Target *target) override final {
switch(target.amstradcpc.model) { auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target);
case Analyser::Static::AmstradCPCModel::CPC464:
switch(cpc_target->model) {
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
rom_model_ = ROMType::OS464; rom_model_ = ROMType::OS464;
has_128k_ = false; has_128k_ = false;
has_fdc_ = false; has_fdc_ = false;
break; break;
case Analyser::Static::AmstradCPCModel::CPC664: case Analyser::Static::AmstradCPC::Target::Model::CPC664:
rom_model_ = ROMType::OS664; rom_model_ = ROMType::OS664;
has_128k_ = false; has_128k_ = false;
has_fdc_ = true; has_fdc_ = true;
break; break;
case Analyser::Static::AmstradCPCModel::CPC6128: case Analyser::Static::AmstradCPC::Target::Model::CPC6128:
rom_model_ = ROMType::OS6128; rom_model_ = ROMType::OS6128;
has_128k_ = true; has_128k_ = true;
has_fdc_ = true; has_fdc_ = true;
@@ -901,11 +916,11 @@ class ConcreteMachine:
read_pointers_[3] = roms_[upper_rom_].data(); read_pointers_[3] = roms_[upper_rom_].data();
// Type whatever is required. // Type whatever is required.
if(target.loading_command.length()) { if(target->loading_command.length()) {
type_string(target.loading_command); type_string(target->loading_command);
} }
insert_media(target.media); insert_media(target->media);
} }
bool insert_media(const Analyser::Static::Media &media) override final { bool insert_media(const Analyser::Static::Media &media) override final {

View File

@@ -9,19 +9,12 @@
#ifndef AmstradCPC_hpp #ifndef AmstradCPC_hpp
#define AmstradCPC_hpp #define AmstradCPC_hpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
namespace AmstradCPC { namespace AmstradCPC {
/*! /*!
Models an Amstrad CPC. Models an Amstrad CPC.
*/ */
class Machine: class Machine {
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public: public:
virtual ~Machine(); virtual ~Machine();

View File

@@ -11,6 +11,12 @@
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../../Analyser/Static/Atari/Target.hpp"
#include "Cartridges/Atari8k.hpp" #include "Cartridges/Atari8k.hpp"
#include "Cartridges/Atari16k.hpp" #include "Cartridges/Atari16k.hpp"
#include "Cartridges/Atari32k.hpp" #include "Cartridges/Atari32k.hpp"
@@ -72,11 +78,12 @@ class Joystick: public Inputs::Joystick {
class ConcreteMachine: class ConcreteMachine:
public Machine, public Machine,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine,
public Outputs::CRT::Delegate { public Outputs::CRT::Delegate {
public: public:
ConcreteMachine() : ConcreteMachine() {
frame_record_pointer_(0),
is_ntsc_(true) {
set_clock_rate(NTSC_clock_rate); set_clock_rate(NTSC_clock_rate);
} }
@@ -84,35 +91,38 @@ class ConcreteMachine:
close_output(); close_output();
} }
void configure_as_target(const Analyser::Static::Target &target) override { void configure_as_target(const Analyser::Static::Target *target) override {
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target);
switch(target.atari.paging_model) { const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data;
case Analyser::Static::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case Analyser::Static::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case Analyser::Static::Atari2600PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case Analyser::Static::Atari2600PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case Analyser::Static::Atari2600PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case Analyser::Static::Atari2600PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case Analyser::Static::Atari2600PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Atari8k: using PagingModel = Analyser::Static::Atari::Target::PagingModel;
if(target.atari.uses_superchip) { switch(atari_target->paging_model) {
case PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case PagingModel::Atari8k:
if(atari_target->uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
} else { } else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
} }
break; break;
case Analyser::Static::Atari2600PagingModel::Atari16k: case PagingModel::Atari16k:
if(target.atari.uses_superchip) { if(atari_target->uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
} else { } else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
} }
break; break;
case Analyser::Static::Atari2600PagingModel::Atari32k: case PagingModel::Atari32k:
if(target.atari.uses_superchip) { if(atari_target->uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
} else { } else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
@@ -179,6 +189,7 @@ class ConcreteMachine:
void run_for(const Cycles cycles) override { void run_for(const Cycles cycles) override {
bus_->run_for(cycles); bus_->run_for(cycles);
bus_->apply_confidence(confidence_counter_);
} }
// to satisfy Outputs::CRT::Delegate // to satisfy Outputs::CRT::Delegate
@@ -219,6 +230,10 @@ class ConcreteMachine:
} }
} }
float get_confidence() override {
return confidence_counter_.get_confidence();
}
private: private:
// the bus // the bus
std::unique_ptr<Bus> bus_; std::unique_ptr<Bus> bus_;
@@ -230,9 +245,12 @@ class ConcreteMachine:
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
} frame_records_[4]; } frame_records_[4];
unsigned int frame_record_pointer_; unsigned int frame_record_pointer_ = 0;
bool is_ntsc_; bool is_ntsc_ = true;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
// a confidence counter
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
}; };
} }

View File

@@ -9,10 +9,6 @@
#ifndef Atari2600_cpp #ifndef Atari2600_cpp
#define Atari2600_cpp #define Atari2600_cpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "Atari2600Inputs.h" #include "Atari2600Inputs.h"
namespace Atari2600 { namespace Atari2600 {
@@ -20,10 +16,7 @@ namespace Atari2600 {
/*! /*!
Models an Atari 2600. Models an Atari 2600.
*/ */
class Machine: class Machine {
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine {
public: public:
virtual ~Machine(); virtual ~Machine();

View File

@@ -14,6 +14,7 @@
#include "TIA.hpp" #include "TIA.hpp"
#include "TIASound.hpp" #include "TIASound.hpp"
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@@ -23,11 +24,14 @@ class Bus {
public: public:
Bus() : Bus() :
tia_sound_(audio_queue_), tia_sound_(audio_queue_),
speaker_(tia_sound_), speaker_(tia_sound_) {}
tia_input_value_{0xff, 0xff},
cycles_since_speaker_update_(0) {} virtual ~Bus() {
audio_queue_.flush();
}
virtual void run_for(const Cycles cycles) = 0; virtual void run_for(const Cycles cycles) = 0;
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
virtual void set_reset_line(bool state) = 0; virtual void set_reset_line(bool state) = 0;
// the RIOT, TIA and speaker // the RIOT, TIA and speaker
@@ -39,7 +43,7 @@ class Bus {
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_; Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
// joystick state // joystick state
uint8_t tia_input_value_[2]; uint8_t tia_input_value_[2] = {0xff, 0xff};
protected: protected:
// speaker backlog accumlation counter // speaker backlog accumlation counter

View File

@@ -39,7 +39,23 @@ template<class T> class Cartridge:
// consider doing something less fragile. // consider doing something less fragile.
} }
void run_for(const Cycles cycles) { m6502_.run_for(cycles); } void run_for(const Cycles cycles) {
// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
// title. Random memory accesses are likely to trigger random counter resets.
horizontal_counter_resets_ = 0;
cycle_count_ = cycles;
m6502_.run_for(cycles);
}
/*!
Adjusts @c confidence_counter according to the results of the most recent run_for.
*/
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) {
if(cycle_count_.as_int() < 200) return;
if(horizontal_counter_resets_ > 10)
confidence_counter.add_miss();
}
void set_reset_line(bool state) { m6502_.set_reset_line(state); } void set_reset_line(bool state) { m6502_.set_reset_line(state); }
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
@@ -108,7 +124,11 @@ template<class T> class Cartridge:
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break; case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x02: m6502_.set_ready_line(true); break; case 0x02: m6502_.set_ready_line(true); break;
case 0x03: update_video(); tia_->reset_horizontal_counter(); break; case 0x03:
update_video();
tia_->reset_horizontal_counter();
horizontal_counter_resets_++;
break;
// TODO: audio will now be out of synchronisation — fix // TODO: audio will now be out of synchronisation — fix
case 0x04: case 0x04:
@@ -189,6 +209,9 @@ template<class T> class Cartridge:
private: private:
T bus_extender_; T bus_extender_;
int horizontal_counter_resets_ = 0;
Cycles cycle_count_;
}; };
} }

View File

@@ -25,7 +25,7 @@ namespace {
TIA::TIA(bool create_crt) { TIA::TIA(bool create_crt) {
if(create_crt) { if(create_crt) {
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
crt_->set_output_device(Outputs::CRT::OutputDevice::Television); crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
set_output_mode(OutputMode::NTSC); set_output_mode(OutputMode::NTSC);
} }
@@ -123,20 +123,20 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
Outputs::CRT::DisplayType display_type; Outputs::CRT::DisplayType display_type;
if(output_mode == OutputMode::NTSC) { if(output_mode == OutputMode::NTSC) {
crt_->set_composite_sampling_function( crt_->set_svideo_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{" "{"
"uint c = texture(texID, coordinate).r;" "uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;" "uint y = c & 14u;"
"uint iPhase = (c >> 4);" "uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" "float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" "return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset));"
"}"); "}");
display_type = Outputs::CRT::DisplayType::NTSC60; display_type = Outputs::CRT::DisplayType::NTSC60;
} else { } else {
crt_->set_composite_sampling_function( crt_->set_svideo_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{" "{"
"uint c = texture(texID, coordinate).r;" "uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;" "uint y = c & 14u;"
@@ -145,10 +145,12 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
"uint direction = iPhase & 1u;" "uint direction = iPhase & 1u;"
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
"phaseOffset *= 6.283185308 / 12.0;" "phaseOffset *= 6.283185308 / 12.0;"
"return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" "return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));"
"}"); "}");
display_type = Outputs::CRT::DisplayType::PAL50; display_type = Outputs::CRT::DisplayType::PAL50;
} }
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari // line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari
// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled // outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled
// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply // later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply

View File

@@ -119,7 +119,11 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta
break; break;
} }
target[c] += volume_[channel] * 1024 * level; target[c] += (volume_[channel] * per_channel_volume_ * level) >> 4;
} }
} }
} }
void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) {
per_channel_volume_ = range / 2;
}

View File

@@ -26,7 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource {
void set_divider(int channel, uint8_t divider); void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control); void set_control(int channel, uint8_t control);
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
void set_sample_volume_range(std::int16_t range);
private: private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -41,6 +43,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
int output_state_[2]; int output_state_[2];
int divider_counter_[2]; int divider_counter_[2];
int16_t per_channel_volume_ = 0;
}; };
} }

View File

@@ -12,8 +12,13 @@
#include "../Outputs/CRT/CRT.hpp" #include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker/Speaker.hpp" #include "../Outputs/Speaker/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp" #include "../ClockReceiver/ClockReceiver.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
#include "ROMMachine.hpp" #include "ROMMachine.hpp"
#include "../Configurable/StandardOptions.hpp"
#include <cmath>
namespace CRTMachine { namespace CRTMachine {
/*! /*!
@@ -41,45 +46,58 @@ class Machine: public ROMMachine::Machine {
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. /// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual Outputs::Speaker::Speaker *get_speaker() = 0; virtual Outputs::Speaker::Speaker *get_speaker() = 0;
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
/// @returns The confidence that this machine is running content it understands. /// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; } virtual float get_confidence() { return 0.5f; }
virtual void print_type() {} virtual void print_type() {}
// TODO: sever the clock-rate stuff. /// Runs the machine for @c duration seconds.
virtual double get_clock_rate() { virtual void run_for(Time::Seconds duration) {
return clock_rate_; const double cycles = (duration * clock_rate_) + clock_conversion_error_;
clock_conversion_error_ = std::fmod(cycles, 1.0);
run_for(Cycles(static_cast<int>(cycles)));
} }
virtual bool get_clock_is_unlimited() {
return clock_is_unlimited_;
}
class Delegate {
public:
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected: protected:
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
void set_clock_rate(double clock_rate) { void set_clock_rate(double clock_rate) {
if(clock_rate_ != clock_rate) { clock_rate_ = clock_rate;
clock_rate_ = clock_rate;
if(delegate_) delegate_->machine_did_change_clock_rate(this);
}
} }
void set_clock_is_unlimited(bool clock_is_unlimited) { double get_clock_rate() {
if(clock_is_unlimited != clock_is_unlimited_) { return clock_rate_;
clock_is_unlimited_ = clock_is_unlimited; }
if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this);
/*!
Maps from Configurable::Display to Outputs::CRT::VideoSignal and calls
@c set_video_signal with the result.
*/
void set_video_signal_configurable(Configurable::Display type) {
Outputs::CRT::VideoSignal signal;
switch(type) {
default:
case Configurable::Display::RGB:
signal = Outputs::CRT::VideoSignal::RGB;
break;
case Configurable::Display::SVideo:
signal = Outputs::CRT::VideoSignal::SVideo;
break;
case Configurable::Display::Composite:
signal = Outputs::CRT::VideoSignal::Composite;
break;
} }
set_video_signal(signal);
}
/*!
Forwards the video signal to the CRT returned by get_crt().
*/
virtual void set_video_signal(Outputs::CRT::VideoSignal video_signal) {
get_crt()->set_video_signal(video_signal);
} }
private: private:
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0; double clock_rate_ = 1.0;
bool clock_is_unlimited_ = false; double clock_conversion_error_ = 0.0;
}; };
} }

View File

@@ -23,6 +23,8 @@
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
namespace { namespace {
const int sn76489_divider = 2; const int sn76489_divider = 2;
} }
@@ -123,13 +125,17 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick);
} }
~ConcreteMachine() {
audio_queue_.flush();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_; return joysticks_;
} }
void setup_output(float aspect_ratio) override { void setup_output(float aspect_ratio) override {
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
get_crt()->set_output_device(Outputs::CRT::OutputDevice::Television); get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
} }
void close_output() override { void close_output() override {
@@ -148,9 +154,9 @@ class ConcreteMachine:
z80_.run_for(cycles); z80_.run_for(cycles);
} }
void configure_as_target(const Analyser::Static::Target &target) override { void configure_as_target(const Analyser::Static::Target *target) override {
// Insert the media. // Insert the media.
insert_media(target.media); insert_media(target->media);
} }
bool insert_media(const Analyser::Static::Media &media) override { bool insert_media(const Analyser::Static::Media &media) override {
@@ -192,9 +198,13 @@ class ConcreteMachine:
// MARK: Z80::BusHandler // MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
time_since_vdp_update_ += cycle.length;
time_since_sn76489_update_ += cycle.length;
uint16_t address = cycle.address ? *cycle.address : 0x0000; uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) { switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode: case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(!address) pc_zero_accesses_++;
case CPU::Z80::PartialMachineCycle::Read: case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) { if(address < 0x2000) {
if(super_game_module_.replace_bios) { if(super_game_module_.replace_bios) {
@@ -245,6 +255,11 @@ class ConcreteMachine:
} else { } else {
*cycle.value = joystick->get_direction_input(); *cycle.value = joystick->get_direction_input();
} }
// Hitting exactly the recommended joypad input port is an indicator that
// this really is a ColecoVision game. The BIOS won't do this when just waiting
// to start a game (unlike accessing the VDP and SN).
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
} break; } break;
default: default:
@@ -252,6 +267,7 @@ class ConcreteMachine:
default: *cycle.value = 0xff; break; default: *cycle.value = 0xff; break;
case 0x52: case 0x52:
// Read AY data. // Read AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output(); *cycle.value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0)); ay_.set_control_lines(GI::AY38910::ControlLines(0));
@@ -289,12 +305,14 @@ class ConcreteMachine:
break; break;
case 0x50: case 0x50:
// Set AY address. // Set AY address.
update_audio();
ay_.set_control_lines(GI::AY38910::BC1); ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(*cycle.value); ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0)); ay_.set_control_lines(GI::AY38910::ControlLines(0));
break; break;
case 0x51: case 0x51:
// Set AY data. // Set AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(*cycle.value); ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0)); ay_.set_control_lines(GI::AY38910::ControlLines(0));
@@ -310,9 +328,6 @@ class ConcreteMachine:
default: break; default: break;
} }
time_since_vdp_update_ += cycle.length;
time_since_sn76489_update_ += cycle.length;
if(time_until_interrupt_ > 0) { if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= cycle.length; time_until_interrupt_ -= cycle.length;
if(time_until_interrupt_ <= HalfCycles(0)) { if(time_until_interrupt_ <= HalfCycles(0)) {
@@ -329,6 +344,11 @@ class ConcreteMachine:
audio_queue_.perform(); audio_queue_.perform();
} }
float get_confidence() override {
if(pc_zero_accesses_ > 1) return 0.0f;
return confidence_counter_.get_confidence();
}
private: private:
inline void page_megacart(uint16_t address) { inline void page_megacart(uint16_t address) {
const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size(); const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size();
@@ -368,6 +388,9 @@ class ConcreteMachine:
HalfCycles time_since_vdp_update_; HalfCycles time_since_vdp_update_;
HalfCycles time_since_sn76489_update_; HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_; HalfCycles time_until_interrupt_;
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
int pc_zero_accesses_ = 0;
}; };
} }

View File

@@ -165,14 +165,15 @@ void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint
attention_acknowledge_level_ = !(value&0x10); attention_acknowledge_level_ = !(value&0x10);
data_level_output_ = (value&0x02); data_level_output_ = (value&0x02);
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!(value&0x08)); // printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Clock), value ? "high" : "low");
serialPort->set_output(::Commodore::Serial::Line::Clock, static_cast<::Commodore::Serial::LineLevel>(!(value&0x08)));
update_data_line(); update_data_line();
} }
} }
} }
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
// printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low"); // printf("[C1540] %s input is %s\n", StringForLine(line), value ? "high" : "low");
switch(line) { switch(line) {
default: break; default: break;
@@ -194,9 +195,10 @@ void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::P
void SerialPortVIA::update_data_line() { void SerialPortVIA::update_data_line() {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) { if(serialPort) {
// printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Data), (!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)) ? "high" : "low");
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
serialPort->set_output(::Commodore::Serial::Line::Data, serialPort->set_output(::Commodore::Serial::Line::Data,
(::Commodore::Serial::LineLevel)(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); static_cast<::Commodore::Serial::LineLevel>(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
} }
} }

View File

@@ -10,6 +10,11 @@
#include "Keyboard.hpp" #include "Keyboard.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
#include "../../../Processors/6502/6502.hpp" #include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6560/6560.hpp" #include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp" #include "../../../Components/6522/6522.hpp"
@@ -26,6 +31,8 @@
#include "../../../Configurable/StandardOptions.hpp" #include "../../../Configurable/StandardOptions.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@@ -40,7 +47,9 @@ enum ROMSlot {
}; };
std::vector<std::unique_ptr<Configurable::Option>> get_options() { std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(Configurable::QuickLoadTape); return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape)
);
} }
enum JoystickInput { enum JoystickInput {
@@ -283,11 +292,17 @@ class Joystick: public Inputs::Joystick {
}; };
class ConcreteMachine: class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler, public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient, public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate, public Storage::Tape::BinaryTapePlayer::Delegate,
public Machine { public Machine,
public Sleeper::SleepObserver {
public: public:
ConcreteMachine() : ConcreteMachine() :
m6502_(*this), m6502_(*this),
@@ -313,17 +328,16 @@ class ConcreteMachine:
user_port_via_port_handler_->set_interrupt_delegate(this); user_port_via_port_handler_->set_interrupt_delegate(this);
keyboard_via_port_handler_->set_interrupt_delegate(this); keyboard_via_port_handler_->set_interrupt_delegate(this);
tape_->set_delegate(this); tape_->set_delegate(this);
tape_->set_sleep_observer(this);
// install a joystick // install a joystick
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
} }
~ConcreteMachine() {
delete[] rom_;
}
// Obtains the system ROMs. // Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
rom_fetcher_ = roms_with_names;
auto roms = roms_with_names( auto roms = roms_with_names(
"Vic20", "Vic20",
{ {
@@ -353,24 +367,14 @@ class ConcreteMachine:
return true; return true;
} }
void configure_as_target(const Analyser::Static::Target &target) override final { void configure_as_target(const Analyser::Static::Target *target) override final {
if(target.loading_command.length()) { commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target);
type_string(target.loading_command);
if(target->loading_command.length()) {
type_string(target->loading_command);
} }
switch(target.vic20.memory_model) { if(target->media.disks.size()) {
case Analyser::Static::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
case Analyser::Static::Vic20MemoryModel::EightKB:
set_memory_size(ThreeKB);
break;
case Analyser::Static::Vic20MemoryModel::ThirtyTwoKB:
set_memory_size(ThirtyTwoKB);
break;
}
if(target.media.disks.size()) {
// construct the 1540 // construct the 1540
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
@@ -379,9 +383,12 @@ class ConcreteMachine:
// give it a means to obtain its ROM // give it a means to obtain its ROM
c1540_->set_rom_fetcher(rom_fetcher_); c1540_->set_rom_fetcher(rom_fetcher_);
// give it a little warm up
c1540_->run_for(Cycles(2000000));
} }
insert_media(target.media); insert_media(target->media);
} }
bool insert_media(const Analyser::Static::Media &media) override final { bool insert_media(const Analyser::Static::Media &media) override final {
@@ -398,9 +405,8 @@ class ConcreteMachine:
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
rom_length_ = static_cast<uint16_t>(rom_image.size()); rom_length_ = static_cast<uint16_t>(rom_image.size());
rom_ = new uint8_t[0x2000]; rom_ = rom_image;
std::memcpy(rom_, rom_image.data(), rom_image.size()); rom_.resize(0x2000);
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
} }
set_use_fast_tape(); set_use_fast_tape();
@@ -423,16 +429,6 @@ class ConcreteMachine:
return joysticks_; return joysticks_;
} }
void set_memory_size(MemorySize size) override final {
memory_size_ = size;
needs_configuration_ = true;
}
void set_region(Region region) override final {
region_ = region;
needs_configuration_ = true;
}
void set_ntsc_6560() { void set_ntsc_6560() {
set_clock_rate(1022727); set_clock_rate(1022727);
if(mos6560_) { if(mos6560_) {
@@ -449,9 +445,9 @@ class ConcreteMachine:
} }
} }
void configure_memory() { void set_memory_map(Analyser::Static::Commodore::Target::MemoryModel memory_model, Analyser::Static::Commodore::Target::Region region) {
// Determine PAL/NTSC // Determine PAL/NTSC
if(region_ == American || region_ == Japanese) { if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) {
// NTSC // NTSC
set_ntsc_6560(); set_ntsc_6560();
} else { } else {
@@ -459,57 +455,73 @@ class ConcreteMachine:
set_pal_6560(); set_pal_6560();
} }
// Initialise the memory maps as all pointing to nothing
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
switch(memory_size_) { #define set_ram(baseaddr, length) \
default: break; write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \
case ThreeKB: write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length);
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000); // Add 6502-visible RAM as requested
switch(memory_model) {
case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded:
// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000.
set_ram(0x0000, 0x0400);
set_ram(0x1000, 0x1000);
break; break;
case ThirtyTwoKB: case Analyser::Static::Commodore::Target::MemoryModel::EightKB:
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000); // An 8kb Vic-20 fills in the gap between the two blocks of RAM on an unexpanded machine.
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000); set_ram(0x0000, 0x2000);
break;
case Analyser::Static::Commodore::Target::MemoryModel::ThirtyTwoKB:
// A 32kb Vic-20 fills the entire lower 32kb with RAM.
set_ram(0x0000, 0x8000);
break; break;
} }
// install the system ROMs and VIC-visible memory #undef set_ram
write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); // all expansions also have colour RAM visible at 0x9400.
write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); write_to_map(processor_read_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); write_to_map(processor_write_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_)); // also push memory resources into the 6560 video memory map; the 6560 has only a
write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_)); // 14-bit address bus and the top bit is invested and used as bit 15 for the main
mos6560_->colour_memory = colour_memory_; // memory bus.
for(int addr = 0; addr < 0x4000; addr += 0x400) {
int source_address = (addr & 0x1fff) | (((addr & 0x2000) << 2) ^ 0x8000);
if(processor_read_memory_map_[source_address >> 10]) {
write_to_map(mos6560_->video_memory_map, &ram_[source_address], static_cast<uint16_t>(addr), 0x400);
}
}
mos6560_->colour_memory = colour_ram_;
// install the BASIC ROM
write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size())); write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size()));
// install the system ROM
ROM character_rom; ROM character_rom;
ROM kernel_rom; ROM kernel_rom;
switch(region_) { switch(region) {
default: default:
character_rom = CharactersEnglish; character_rom = CharactersEnglish;
kernel_rom = KernelPAL; kernel_rom = KernelPAL;
break; break;
case American: case Analyser::Static::Commodore::Target::Region::American:
character_rom = CharactersEnglish; character_rom = CharactersEnglish;
kernel_rom = KernelNTSC; kernel_rom = KernelNTSC;
break; break;
case Danish: case Analyser::Static::Commodore::Target::Region::Danish:
character_rom = CharactersDanish; character_rom = CharactersDanish;
kernel_rom = KernelDanish; kernel_rom = KernelDanish;
break; break;
case Japanese: case Analyser::Static::Commodore::Target::Region::Japanese:
character_rom = CharactersJapanese; character_rom = CharactersJapanese;
kernel_rom = KernelJapanese; kernel_rom = KernelJapanese;
break; break;
case Swedish: case Analyser::Static::Commodore::Target::Region::Swedish:
character_rom = CharactersSwedish; character_rom = CharactersSwedish;
kernel_rom = KernelSwedish; kernel_rom = KernelSwedish;
break; break;
@@ -520,30 +532,30 @@ class ConcreteMachine:
write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size())); write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size()));
// install the inserted ROM if there is one // install the inserted ROM if there is one
if(rom_) { if(!rom_.empty()) {
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_);
} }
} }
// to satisfy CPU::MOS6502::Processor // to satisfy CPU::MOS6502::Processor
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
// run the phase-1 part of this cycle, in which the VIC accesses memory // run the phase-1 part of this cycle, in which the VIC accesses memory
if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); cycles_since_mos6560_update_++;
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be // run the phase-2 part of the cycle, which is whatever the 6502 said it should be
if(isReadOperation(operation)) { if(isReadOperation(operation)) {
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
if((address&0xfc00) == 0x9000) { if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address); if((address&0xff00) == 0x9000) {
update_video();
result &= mos6560_->get_register(address);
}
if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address); if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address);
if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address); if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address);
} }
*value = result; *value = result;
// This combined with the stuff below constitutes the fast tape hack. Performed here: if the // Consider applying the fast tape hack.
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
// CPU or 6560 costs.
if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
if(address == 0xf7b2) { if(address == 0xf7b2) {
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. // Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
@@ -551,58 +563,78 @@ class ConcreteMachine:
Storage::Tape::Commodore::Parser parser; Storage::Tape::Commodore::Parser parser;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape()); std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
// serialise to wherever b2:b3 points const uint64_t tape_position = tape_->get_tape()->get_offset();
uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8);
if(header) { if(header) {
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); // serialise to wherever b2:b3 points
const uint16_t tape_buffer_pointer = static_cast<uint16_t>(ram_[0xb2]) | static_cast<uint16_t>(ram_[0xb3] << 8);
header->serialise(&ram_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
hold_tape_ = true;
printf("Found header\n");
} else { } else {
// no header found, so store end-of-tape // no header found, so pretend this hack never interceded
user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape tape_->get_tape()->set_offset(tape_position);
hold_tape_ = false;
printf("Didn't find header\n");
} }
// clear status and the verify flag // clear status and the verify flag
user_basic_memory_[0x90] = 0; ram_[0x90] = 0;
user_basic_memory_[0x93] = 0; ram_[0x93] = 0;
*value = 0x0c; // i.e. NOP abs *value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
} else if(address == 0xf90b) { } else if(address == 0xf90b) {
uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X)); uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
if(x == 0xe) { if(x == 0xe) {
Storage::Tape::Commodore::Parser parser; Storage::Tape::Commodore::Parser parser;
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape()); const uint64_t tape_position = tape_->get_tape()->get_offset();
uint16_t start_address, end_address; const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8)); if(data) {
end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8)); uint16_t start_address, end_address;
start_address = static_cast<uint16_t>(ram_[0xc1] | (ram_[0xc2] << 8));
end_address = static_cast<uint16_t>(ram_[0xae] | (ram_[0xaf] << 8));
// perform a via-processor_write_memory_map_ memcpy // perform a via-processor_write_memory_map_ memcpy
uint8_t *data_ptr = data->data.data(); uint8_t *data_ptr = data->data.data();
std::size_t data_left = data->data.size(); std::size_t data_left = data->data.size();
while(data_left && start_address != end_address) { while(data_left && start_address != end_address) {
uint8_t *page = processor_write_memory_map_[start_address >> 10]; uint8_t *page = processor_write_memory_map_[start_address >> 10];
if(page) page[start_address & 0x3ff] = *data_ptr; if(page) page[start_address & 0x3ff] = *data_ptr;
data_ptr++; data_ptr++;
start_address++; start_address++;
data_left--; data_left--;
}
// set tape status, carry and flag
ram_[0x90] |= 0x40;
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
// ensure that the PC leaps to 0xfccf
m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
hold_tape_ = true;
printf("Found data\n");
} else {
tape_->get_tape()->set_offset(tape_position);
hold_tape_ = false;
printf("Didn't find data\n");
} }
// set tape status, carry and flag
user_basic_memory_[0x90] |= 0x40;
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
// ensure that the PC leaps to 0xfccf
m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
} }
} }
} }
} else { } else {
uint8_t *ram = processor_write_memory_map_[address >> 10]; uint8_t *ram = processor_write_memory_map_[address >> 10];
if(ram) ram[address & 0x3ff] = *value; if(ram) {
update_video();
ram[address & 0x3ff] = *value;
}
if((address&0xfc00) == 0x9000) { if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value); if((address&0xff00) == 0x9000) {
update_video();
mos6560_->set_register(address, *value);
}
if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value); if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value);
if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value); if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value);
} }
@@ -616,21 +648,18 @@ class ConcreteMachine:
typer_.reset(); typer_.reset();
} }
} }
tape_->run_for(Cycles(1)); if(!tape_is_sleeping_ && !hold_tape_) tape_->run_for(Cycles(1));
if(c1540_) c1540_->run_for(Cycles(1)); if(c1540_) c1540_->run_for(Cycles(1));
return Cycles(1); return Cycles(1);
} }
forceinline void flush() { void flush() {
update_video();
mos6560_->flush(); mos6560_->flush();
} }
void run_for(const Cycles cycles) override final { void run_for(const Cycles cycles) override final {
if(needs_configuration_) {
needs_configuration_ = false;
configure_memory();
}
m6502_.run_for(cycles); m6502_.run_for(cycles);
} }
@@ -638,7 +667,7 @@ class ConcreteMachine:
mos6560_.reset(new Vic6560()); mos6560_.reset(new Vic6560());
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20. mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set. // Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
set_pal_6560(); set_memory_map(commodore_target_.memory_model, commodore_target_.region);
} }
void close_output() override final { void close_output() override final {
@@ -682,21 +711,38 @@ class ConcreteMachine:
allow_fast_tape_hack_ = quickload; allow_fast_tape_hack_ = quickload;
set_use_fast_tape(); set_use_fast_tape();
} }
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
} }
Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set; Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false); Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::Composite);
return selection_set; return selection_set;
} }
Configurable::SelectionSet get_user_friendly_selections() override { Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set; Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true); Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_display_selection(selection_set, Configurable::Display::SVideo);
return selection_set; return selection_set;
} }
void set_component_is_sleeping(void *component, bool is_sleeping) override {
tape_is_sleeping_ = is_sleeping;
set_use_fast_tape();
}
private: private:
void update_video() {
mos6560_->run_for(cycles_since_mos6560_update_.flush());
}
Analyser::Static::Commodore::Target commodore_target_;
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
std::vector<uint8_t> roms_[9]; std::vector<uint8_t> roms_[9];
@@ -704,14 +750,11 @@ class ConcreteMachine:
std::vector<uint8_t> character_rom_; std::vector<uint8_t> character_rom_;
std::vector<uint8_t> basic_rom_; std::vector<uint8_t> basic_rom_;
std::vector<uint8_t> kernel_rom_; std::vector<uint8_t> kernel_rom_;
uint8_t expansion_ram_[0x8000];
uint8_t *rom_ = nullptr; std::vector<uint8_t> rom_;
uint16_t rom_address_, rom_length_; uint16_t rom_address_, rom_length_;
uint8_t ram_[0x8000];
uint8_t user_basic_memory_[0x0400]; uint8_t colour_ram_[0x0400];
uint8_t screen_memory_[0x1000];
uint8_t colour_memory_[0x0400];
std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_; std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_;
@@ -727,13 +770,10 @@ class ConcreteMachine:
} }
} }
Region region_ = European;
MemorySize memory_size_ = MemorySize::Default;
bool needs_configuration_ = true;
Commodore::Vic20::KeyboardMapper keyboard_mapper_; Commodore::Vic20::KeyboardMapper keyboard_mapper_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Cycles cycles_since_mos6560_update_;
std::unique_ptr<Vic6560> mos6560_; std::unique_ptr<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_port_handler_; std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_; std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
@@ -746,10 +786,11 @@ class ConcreteMachine:
// Tape // Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_ = false; bool use_fast_tape_hack_ = false;
bool hold_tape_ = false;
bool allow_fast_tape_hack_ = false; bool allow_fast_tape_hack_ = false;
bool is_running_at_zero_cost_ = false; bool tape_is_sleeping_ = true;
void set_use_fast_tape() { void set_use_fast_tape() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_->has_tape(); use_fast_tape_hack_ = !tape_is_sleeping_ && allow_fast_tape_hack_ && tape_->has_tape();
} }
// Disk // Disk

View File

@@ -10,48 +10,19 @@
#define Vic20_hpp #define Vic20_hpp
#include "../../../Configurable/Configurable.hpp" #include "../../../Configurable/Configurable.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
namespace Commodore { namespace Commodore {
namespace Vic20 { namespace Vic20 {
enum MemorySize {
Default,
ThreeKB,
ThirtyTwoKB
};
enum Region {
American,
Danish,
Japanese,
European,
Swedish
};
/// @returns The options available for a Vic-20. /// @returns The options available for a Vic-20.
std::vector<std::unique_ptr<Configurable::Option>> get_options(); std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine: class Machine {
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine,
public Configurable::Device {
public: public:
virtual ~Machine(); virtual ~Machine();
/// Creates and returns a Vic-20. /// Creates and returns a Vic-20.
static Machine *Vic20(); static Machine *Vic20();
/// Sets the memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 0;
}; };
} }

View File

@@ -23,7 +23,7 @@ namespace ConfigurationTarget {
class Machine { class Machine {
public: public:
/// Instructs the machine to configure itself as described by @c target and insert the included media. /// Instructs the machine to configure itself as described by @c target and insert the included media.
virtual void configure_as_target(const Analyser::Static::Target &target) = 0; virtual void configure_as_target(const Analyser::Static::Target *target) = 0;
/*! /*!
Requests that the machine insert @c media as a modification to current state Requests that the machine insert @c media as a modification to current state

View File

@@ -8,6 +8,10 @@
#include "Electron.hpp" #include "Electron.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp" #include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/StandardOptions.hpp" #include "../../Configurable/StandardOptions.hpp"
@@ -16,6 +20,7 @@
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../Utility/Typer.hpp" #include "../Utility/Typer.hpp"
#include "../../Analyser/Static/Acorn/Target.hpp"
#include "Interrupts.hpp" #include "Interrupts.hpp"
#include "Keyboard.hpp" #include "Keyboard.hpp"
@@ -28,12 +33,16 @@ namespace Electron {
std::vector<std::unique_ptr<Configurable::Option>> get_options() { std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options( return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape)
); );
} }
class ConcreteMachine: class ConcreteMachine:
public Machine, public Machine,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler, public CPU::MOS6502::BusHandler,
public Tape::Delegate, public Tape::Delegate,
public Utility::TypeRecipient { public Utility::TypeRecipient {
@@ -52,6 +61,10 @@ class ConcreteMachine:
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
} }
~ConcreteMachine() {
audio_queue_.flush();
}
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final { void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final {
uint8_t *target = nullptr; uint8_t *target = nullptr;
switch(slot) { switch(slot) {
@@ -115,28 +128,30 @@ class ConcreteMachine:
if(is_holding_shift_) set_key_state(KeyShift, true); if(is_holding_shift_) set_key_state(KeyShift, true);
} }
void configure_as_target(const Analyser::Static::Target &target) override final { void configure_as_target(const Analyser::Static::Target *target) override final {
if(target.loading_command.length()) { auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target);
type_string(target.loading_command);
if(target->loading_command.length()) {
type_string(target->loading_command);
} }
if(target.acorn.should_shift_restart) { if(acorn_target->should_shift_restart) {
shift_restart_counter_ = 1000000; shift_restart_counter_ = 1000000;
} }
if(target.acorn.has_dfs || target.acorn.has_adfs) { if(acorn_target->has_dfs || acorn_target->has_adfs) {
plus3_.reset(new Plus3); plus3_.reset(new Plus3);
if(target.acorn.has_dfs) { if(acorn_target->has_dfs) {
set_rom(ROMSlot0, dfs_, true); set_rom(ROMSlot0, dfs_, true);
} }
if(target.acorn.has_adfs) { if(acorn_target->has_adfs) {
set_rom(ROMSlot4, adfs1_, true); set_rom(ROMSlot4, adfs1_, true);
set_rom(ROMSlot5, adfs2_, true); set_rom(ROMSlot5, adfs2_, true);
} }
} }
insert_media(target.media); insert_media(target->media);
} }
bool insert_media(const Analyser::Static::Media &media) override final { bool insert_media(const Analyser::Static::Media &media) override final {
@@ -439,7 +454,7 @@ class ConcreteMachine:
Configurable::Display display; Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) { if(Configurable::get_display(selections_by_option, display)) {
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); set_video_signal_configurable(display);
} }
} }

View File

@@ -10,9 +10,6 @@
#define Electron_hpp #define Electron_hpp
#include "../../Configurable/Configurable.hpp" #include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
@@ -42,11 +39,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options();
@discussion An instance of Electron::Machine represents the current state of an @discussion An instance of Electron::Machine represents the current state of an
Acorn Electron. Acorn Electron.
*/ */
class Machine: class Machine {
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
public: public:
virtual ~Machine(); virtual ~Machine();

View File

@@ -15,10 +15,14 @@ using namespace Electron;
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {} audio_queue_(audio_queue) {}
void SoundGenerator::set_sample_volume_range(std::int16_t range) {
volume_ = static_cast<unsigned int>(range / 2);
}
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
if(is_enabled_) { if(is_enabled_) {
while(number_of_samples--) { while(number_of_samples--) {
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192); *target = static_cast<int16_t>((counter_ / (divider_+1)) * volume_);
target++; target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2); counter_ = (counter_ + 1) % ((divider_+1) * 2);
} }

View File

@@ -22,16 +22,19 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
void set_is_enabled(bool is_enabled); void set_is_enabled(bool is_enabled);
static const unsigned int clock_rate_divider = 8;
// To satisfy ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target); void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples); void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
static const unsigned int clock_rate_divider = 8;
private: private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counter_ = 0; unsigned int counter_ = 0;
unsigned int divider_ = 0; unsigned int divider_ = 0;
bool is_enabled_ = false; bool is_enabled_ = false;
unsigned int volume_ = 0;
}; };
} }

View File

@@ -16,21 +16,30 @@
namespace KeyboardMachine { namespace KeyboardMachine {
class Machine: public Inputs::Keyboard::Delegate { /*!
Covers just the actions necessary to communicate keyboard state, as a purely abstract class.
*/
struct KeyActions {
/*!
Indicates that the key @c key has been either pressed or released, according to
the state of @c isPressed.
*/
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
/*!
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() = 0;
};
/*!
Describes the full functionality of being an emulated machine with a keyboard: not just being
able to receive key actions, but being able to vend a generic keyboard and a keyboard mapper.
*/
class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
public: public:
Machine(); Machine();
/*!
Indicates that the key @c key has been either pressed or released, according to
the state of @c isPressed.
*/
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
/*!
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() = 0;
/*! /*!
Causes the machine to attempt to type the supplied string. Causes the machine to attempt to type the supplied string.

View File

@@ -40,11 +40,13 @@
#include "../../Configurable/StandardOptions.hpp" #include "../../Configurable/StandardOptions.hpp"
#include "../../ClockReceiver/ForceInline.hpp" #include "../../ClockReceiver/ForceInline.hpp"
#include "../../Analyser/Static/MSX/Target.hpp"
namespace MSX { namespace MSX {
std::vector<std::unique_ptr<Configurable::Option>> get_options() { std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options( return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape)
); );
} }
@@ -62,13 +64,17 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
} }
} }
void set_sample_volume_range(std::int16_t range) {
volume_ = range;
}
void skip_samples(const std::size_t number_of_samples) {} void skip_samples(const std::size_t number_of_samples) {}
void set_output(bool enabled) { void set_output(bool enabled) {
if(is_enabled_ == enabled) return; if(is_enabled_ == enabled) return;
is_enabled_ = enabled; is_enabled_ = enabled;
audio_queue_.defer([=] { audio_queue_.defer([=] {
level_ = enabled ? 4096 : 0; level_ = enabled ? volume_ : 0;
}); });
} }
@@ -78,7 +84,7 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
private: private:
bool is_enabled_ = false; bool is_enabled_ = false;
int16_t level_ = 0; int16_t level_ = 0, volume_ = 0;
Concurrency::DeferringAsyncTaskQueue &audio_queue_; Concurrency::DeferringAsyncTaskQueue &audio_queue_;
}; };
@@ -138,6 +144,13 @@ class ConcreteMachine:
ay_.set_port_handler(&ay_port_handler_); ay_.set_port_handler(&ay_port_handler_);
speaker_.set_input_rate(3579545.0f / 2.0f); speaker_.set_input_rate(3579545.0f / 2.0f);
tape_player_.set_sleep_observer(this); tape_player_.set_sleep_observer(this);
// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC.
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
}
~ConcreteMachine() {
audio_queue_.flush();
} }
void setup_output(float aspect_ratio) override { void setup_output(float aspect_ratio) override {
@@ -174,20 +187,22 @@ class ConcreteMachine:
} }
} }
void configure_as_target(const Analyser::Static::Target &target) override { void configure_as_target(const Analyser::Static::Target *target) override {
auto *const msx_target = dynamic_cast<const Analyser::Static::MSX::Target *>(target);
// Add a disk cartridge if any disks were supplied. // Add a disk cartridge if any disks were supplied.
if(!target.media.disks.empty()) { if(msx_target->has_disk_drive) {
map(2, 0, 0x4000, 0x2000); map(2, 0, 0x4000, 0x2000);
unmap(2, 0x6000, 0x2000); unmap(2, 0x6000, 0x2000);
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
} }
// Insert the media. // Insert the media.
insert_media(target.media); insert_media(target->media);
// Type whatever has been requested. // Type whatever has been requested.
if(target.loading_command.length()) { if(target->loading_command.length()) {
type_string(target.loading_command); type_string(target->loading_command);
} }
} }
@@ -282,6 +297,17 @@ class ConcreteMachine:
// MARK: Z80::BusHandler // MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
// but otherwise runs without pause.
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
const HalfCycles total_length = addition + cycle.length;
time_since_vdp_update_ += total_length;
time_since_ay_update_ += total_length;
memory_slots_[0].cycles_since_update += total_length;
memory_slots_[1].cycles_since_update += total_length;
memory_slots_[2].cycles_since_update += total_length;
memory_slots_[3].cycles_since_update += total_length;
uint16_t address = cycle.address ? *cycle.address : 0x0000; uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) { switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode: case CPU::Z80::PartialMachineCycle::ReadOpcode:
@@ -459,23 +485,12 @@ class ConcreteMachine:
if(!tape_player_is_sleeping_) if(!tape_player_is_sleeping_)
tape_player_.run_for(cycle.length.as_int()); tape_player_.run_for(cycle.length.as_int());
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
// but otherwise runs without pause.
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
const HalfCycles total_length = addition + cycle.length;
if(time_until_interrupt_ > 0) { if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= total_length; time_until_interrupt_ -= total_length;
if(time_until_interrupt_ <= HalfCycles(0)) { if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_interrupt_line(true, time_until_interrupt_); z80_.set_interrupt_line(true, time_until_interrupt_);
} }
} }
time_since_vdp_update_ += total_length;
time_since_ay_update_ += total_length;
memory_slots_[0].cycles_since_update += total_length;
memory_slots_[1].cycles_since_update += total_length;
memory_slots_[2].cycles_since_update += total_length;
memory_slots_[3].cycles_since_update += total_length;
return addition; return addition;
} }
@@ -554,7 +569,7 @@ class ConcreteMachine:
Configurable::Display display; Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) { if(Configurable::get_display(selections_by_option, display)) {
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); set_video_signal_configurable(display);
} }
} }

View File

@@ -12,6 +12,10 @@
#include "Microdisc.hpp" #include "Microdisc.hpp"
#include "Video.hpp" #include "Video.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../Utility/MemoryFuzzer.hpp" #include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp" #include "../Utility/Typer.hpp"
@@ -26,6 +30,8 @@
#include "../../Configurable/StandardOptions.hpp" #include "../../Configurable/StandardOptions.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Static/Oric/Target.hpp"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <vector> #include <vector>
@@ -38,7 +44,7 @@ enum ROM {
std::vector<std::unique_ptr<Configurable::Option>> get_options() { std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options( return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape) static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape)
); );
} }
@@ -184,6 +190,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
}; };
class ConcreteMachine: class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler, public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient, public Utility::TypeRecipient,
@@ -205,6 +215,10 @@ class ConcreteMachine:
Memory::Fuzz(ram_, sizeof(ram_)); Memory::Fuzz(ram_, sizeof(ram_));
} }
~ConcreteMachine() {
audio_queue_.flush();
}
// Obtains the system ROMs. // Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names( auto roms = roms_with_names(
@@ -249,23 +263,21 @@ class ConcreteMachine:
use_fast_tape_hack_ = activate; use_fast_tape_hack_ = activate;
} }
void set_output_device(Outputs::CRT::OutputDevice output_device) {
video_output_->set_output_device(output_device);
}
// to satisfy ConfigurationTarget::Machine // to satisfy ConfigurationTarget::Machine
void configure_as_target(const Analyser::Static::Target &target) override final { void configure_as_target(const Analyser::Static::Target *target) override final {
if(target.oric.has_microdisc) { auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target);
if(oric_target->has_microdrive) {
microdisc_is_enabled_ = true; microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_); microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this); microdisc_.set_delegate(this);
} }
if(target.loading_command.length()) { if(target->loading_command.length()) {
type_string(target.loading_command); type_string(target->loading_command);
} }
if(target.oric.use_atmos_rom) { if(oric_target->use_atmos_rom) {
std::memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_))); std::memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true; is_using_basic11_ = true;
@@ -281,7 +293,7 @@ class ConcreteMachine:
tape_speed_address_ = 0x67; tape_speed_address_ = 0x67;
} }
insert_media(target.media); insert_media(target->media);
} }
bool insert_media(const Analyser::Static::Media &media) override final { bool insert_media(const Analyser::Static::Media &media) override final {
@@ -376,7 +388,7 @@ class ConcreteMachine:
video_output_.reset(new VideoOutput(ram_)); video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
set_output_device(Outputs::CRT::OutputDevice::Monitor); set_video_signal(Outputs::CRT::VideoSignal::RGB);
} }
void close_output() override final { void close_output() override final {
@@ -449,10 +461,14 @@ class ConcreteMachine:
Configurable::Display display; Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) { if(Configurable::get_display(selections_by_option, display)) {
set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television); set_video_signal_configurable(display);
} }
} }
void set_video_signal(Outputs::CRT::VideoSignal video_signal) override {
video_output_->set_video_signal(video_signal);
}
Configurable::SelectionSet get_accurate_selections() override { Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set; Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false); Configurable::append_quick_load_tape_selection(selection_set, false);

View File

@@ -10,9 +10,6 @@
#define Oric_hpp #define Oric_hpp
#include "../../Configurable/Configurable.hpp" #include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
namespace Oric { namespace Oric {
@@ -22,11 +19,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options();
/*! /*!
Models an Oric 1/Atmos with or without a Microdisc. Models an Oric 1/Atmos with or without a Microdisc.
*/ */
class Machine: class Machine {
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
public: public:
virtual ~Machine(); virtual ~Machine();

View File

@@ -41,13 +41,13 @@ VideoOutput::VideoOutput(uint8_t *memory) :
); );
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f); crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
set_output_device(Outputs::CRT::OutputDevice::Television); set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
} }
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) { void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) {
output_device_ = output_device; video_signal_ = video_signal;
crt_->set_output_device(output_device); crt_->set_video_signal(video_signal);
} }
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) { void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
@@ -129,7 +129,7 @@ void VideoOutput::run_for(const Cycles cycles) {
if(control_byte & 0x60) { if(control_byte & 0x60) {
if(pixel_target_) { if(pixel_target_) {
uint16_t colours[2]; uint16_t colours[2];
if(output_device_ == Outputs::CRT::OutputDevice::Monitor) { if(video_signal_ == Outputs::CRT::VideoSignal::RGB) {
colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask); colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask);
colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask); colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask);
} else { } else {
@@ -183,7 +183,7 @@ void VideoOutput::run_for(const Cycles cycles) {
pixel_target_[0] = pixel_target_[1] = pixel_target_[0] = pixel_target_[1] =
pixel_target_[2] = pixel_target_[3] = pixel_target_[2] = pixel_target_[3] =
pixel_target_[4] = pixel_target_[5] = pixel_target_[4] = pixel_target_[5] =
(output_device_ == Outputs::CRT::OutputDevice::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask]; (video_signal_ == Outputs::CRT::VideoSignal::RGB) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
} }
} }
if(pixel_target_) pixel_target_ += 6; if(pixel_target_) pixel_target_ += 6;

View File

@@ -20,7 +20,7 @@ class VideoOutput {
Outputs::CRT::CRT *get_crt(); Outputs::CRT::CRT *get_crt();
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
void set_colour_rom(const std::vector<uint8_t> &rom); void set_colour_rom(const std::vector<uint8_t> &rom);
void set_output_device(Outputs::CRT::OutputDevice output_device); void set_video_signal(Outputs::CRT::VideoSignal output_device);
private: private:
uint8_t *ram_; uint8_t *ram_;
@@ -33,7 +33,7 @@ class VideoOutput {
// Output target and device // Output target and device
uint16_t *pixel_target_; uint16_t *pixel_target_;
uint16_t colour_forms_[8]; uint16_t colour_forms_[8];
Outputs::CRT::OutputDevice output_device_; Outputs::CRT::VideoSignal video_signal_;
// Registers // Registers
uint8_t ink_, paper_; uint8_t ink_, paper_;

View File

@@ -22,10 +22,10 @@
namespace { namespace {
::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { ::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) {
error = Machine::Error::None; error = Machine::Error::None;
::Machine::DynamicMachine *machine = nullptr; ::Machine::DynamicMachine *machine = nullptr;
switch(target.machine) { switch(target->machine) {
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break; case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break; case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break; case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break;
@@ -71,7 +71,7 @@ namespace {
if(targets.size() > 1) { if(targets.size() > 1) {
std::vector<std::unique_ptr<Machine::DynamicMachine>> machines; std::vector<std::unique_ptr<Machine::DynamicMachine>> machines;
for(const auto &target: targets) { for(const auto &target: targets) {
machines.emplace_back(MachineForTarget(*target, rom_fetcher, error)); machines.emplace_back(MachineForTarget(target.get(), rom_fetcher, error));
// Exit early if any errors have occurred. // Exit early if any errors have occurred.
if(error != Error::None) { if(error != Error::None) {
@@ -79,11 +79,17 @@ namespace {
} }
} }
return new Analyser::Dynamic::MultiMachine(std::move(machines)); // If a multimachine would just instantly collapse the list to a single machine, do
// so without the ongoing baggage of a multimachine.
if(Analyser::Dynamic::MultiMachine::would_collapse(machines)) {
return machines.front().release();
} else {
return new Analyser::Dynamic::MultiMachine(std::move(machines));
}
} }
// There's definitely exactly one target. // There's definitely exactly one target.
return MachineForTarget(*targets.front(), rom_fetcher, error); return MachineForTarget(targets.front().get(), rom_fetcher, error);
} }
std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) { std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) {

View File

@@ -47,7 +47,7 @@ class CharacterMapper {
*/ */
class Typer { class Typer {
public: public:
class Delegate: public KeyboardMachine::Machine { class Delegate: public KeyboardMachine::KeyActions {
public: public:
virtual void typer_reset(Typer *typer) = 0; virtual void typer_reset(Typer *typer) = 0;
}; };
@@ -56,7 +56,6 @@ class Typer {
void run_for(const HalfCycles duration); void run_for(const HalfCycles duration);
bool type_next_character(); bool type_next_character();
bool is_completed(); bool is_completed();
const char BeginString = 0x02; // i.e. ASCII start of text const char BeginString = 0x02; // i.e. ASCII start of text
@@ -81,7 +80,7 @@ class Typer {
which may or may not want to nominate an initial delay and typing frequency. which may or may not want to nominate an initial delay and typing frequency.
*/ */
class TypeRecipient: public Typer::Delegate { class TypeRecipient: public Typer::Delegate {
public: protected:
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source. /// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
void add_typer(const std::string &string, std::unique_ptr<CharacterMapper> character_mapper) { void add_typer(const std::string &string, std::unique_ptr<CharacterMapper> character_mapper) {
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this)); typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this));
@@ -101,7 +100,6 @@ class TypeRecipient: public Typer::Delegate {
typer_ = nullptr; typer_ = nullptr;
} }
protected:
virtual HalfCycles get_typer_delay() { return HalfCycles(0); } virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); } virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
std::unique_ptr<Typer> typer_; std::unique_ptr<Typer> typer_;

View File

@@ -22,6 +22,7 @@ Video::Video() :
"}"); "}");
// Show only the centre 80% of the TV frame. // Show only the centre 80% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
} }

View File

@@ -8,6 +8,11 @@
#include "ZX8081.hpp" #include "ZX8081.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Processors/Z80/Z80.hpp" #include "../../Processors/Z80/Z80.hpp"
#include "../../Storage/Tape/Tape.hpp" #include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/ZX8081.hpp" #include "../../Storage/Tape/Parsers/ZX8081.hpp"
@@ -18,6 +23,10 @@
#include "../Utility/MemoryFuzzer.hpp" #include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp" #include "../Utility/Typer.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Analyser/Static/ZX8081/Target.hpp"
#include "Keyboard.hpp" #include "Keyboard.hpp"
#include "Video.hpp" #include "Video.hpp"
@@ -31,6 +40,11 @@ namespace {
const unsigned int ZX8081ClockRate = 3250000; const unsigned int ZX8081ClockRate = 3250000;
} }
// TODO:
// Quiksilva sound support:
// 7FFFh.W PSG index
// 7FFEh.R/W PSG data
namespace ZX8081 { namespace ZX8081 {
enum ROMType: uint8_t { enum ROMType: uint8_t {
@@ -44,20 +58,32 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
} }
template<bool is_zx81> class ConcreteMachine: template<bool is_zx81> class ConcreteMachine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public Utility::TypeRecipient, public Utility::TypeRecipient,
public CPU::Z80::BusHandler, public CPU::Z80::BusHandler,
public Machine { public Machine {
public: public:
ConcreteMachine() : ConcreteMachine() :
z80_(*this), z80_(*this),
tape_player_(ZX8081ClockRate) { tape_player_(ZX8081ClockRate),
ay_(audio_queue_),
speaker_(ay_) {
set_clock_rate(ZX8081ClockRate); set_clock_rate(ZX8081ClockRate);
speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f);
clear_all_keys(); clear_all_keys();
} }
~ConcreteMachine() {
audio_queue_.flush();
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
HalfCycles previous_counter = horizontal_counter_; const HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length; horizontal_counter_ += cycle.length;
time_since_ay_update_ += cycle.length;
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) { if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_->run_for(vsync_start_ - previous_counter); video_->run_for(vsync_start_ - previous_counter);
@@ -94,7 +120,7 @@ template<bool is_zx81> class ConcreteMachine:
return Cycles(0); return Cycles(0);
} }
uint16_t address = cycle.address ? *cycle.address : 0; const uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false; bool is_opcode_read = false;
switch(cycle.operation) { switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output: case CPU::Z80::PartialMachineCycle::Output:
@@ -106,6 +132,15 @@ template<bool is_zx81> class ConcreteMachine:
if(vsync_) line_counter_ = 0; if(vsync_) line_counter_ = 0;
set_vsync(false); set_vsync(false);
} }
// The below emulates the ZonX AY expansion device.
if(is_zx81) {
if((address&0xef) == 0xcf) {
ay_set_register(*cycle.value);
} else if((address&0xef) == 0x0f) {
ay_set_data(*cycle.value);
}
}
break; break;
case CPU::Z80::PartialMachineCycle::Input: { case CPU::Z80::PartialMachineCycle::Input: {
@@ -121,6 +156,13 @@ template<bool is_zx81> class ConcreteMachine:
value &= ~(tape_player_.get_input() ? 0x00 : 0x80); value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
} }
// The below emulates the ZonX AY expansion device.
if(is_zx81) {
if((address&0xef) == 0x0f) {
value &= ay_read_data();
}
}
*cycle.value = value; *cycle.value = value;
} break; } break;
@@ -144,7 +186,7 @@ template<bool is_zx81> class ConcreteMachine:
} }
if(has_latched_video_byte_) { if(has_latched_video_byte_) {
std::size_t char_address = static_cast<std::size_t>((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_); std::size_t char_address = static_cast<std::size_t>((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff; const uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) { if(char_address < ram_base_) {
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask; latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
} else { } else {
@@ -159,10 +201,10 @@ template<bool is_zx81> class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::ReadOpcode: case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack. // Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) { if(use_fast_tape_hack_ && address == tape_trap_address_) {
uint64_t prior_offset = tape_player_.get_tape()->get_offset(); const uint64_t prior_offset = tape_player_.get_tape()->get_offset();
int next_byte = parser_.get_next_byte(tape_player_.get_tape()); const int next_byte = parser_.get_next_byte(tape_player_.get_tape());
if(next_byte != -1) { if(next_byte != -1) {
uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL); const uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = static_cast<uint8_t>(next_byte); ram_[hl & ram_mask_] = static_cast<uint8_t>(next_byte);
*cycle.value = 0x00; *cycle.value = 0x00;
z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1); z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
@@ -187,7 +229,7 @@ template<bool is_zx81> class ConcreteMachine:
if(address < ram_base_) { if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_]; *cycle.value = rom_[address & rom_mask_];
} else { } else {
uint8_t value = ram_[address & ram_mask_]; const uint8_t value = ram_[address & ram_mask_];
// If this is an M1 cycle reading from above the 32kb mark and HALT is not // If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise, // currently active, latch for video output and return a NOP. Otherwise,
@@ -210,12 +252,15 @@ template<bool is_zx81> class ConcreteMachine:
} }
if(typer_) typer_->run_for(cycle.length); if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0); return HalfCycles(0);
} }
forceinline void flush() { forceinline void flush() {
video_->flush(); video_->flush();
if(is_zx81) {
update_audio();
audio_queue_.perform();
}
} }
void setup_output(float aspect_ratio) override final { void setup_output(float aspect_ratio) override final {
@@ -231,15 +276,16 @@ template<bool is_zx81> class ConcreteMachine:
} }
Outputs::Speaker::Speaker *get_speaker() override final { Outputs::Speaker::Speaker *get_speaker() override final {
return nullptr; return is_zx81 ? &speaker_ : nullptr;
} }
void run_for(const Cycles cycles) override final { void run_for(const Cycles cycles) override final {
z80_.run_for(cycles); z80_.run_for(cycles);
} }
void configure_as_target(const Analyser::Static::Target &target) override final { void configure_as_target(const Analyser::Static::Target *target) override final {
is_zx81_ = target.zx8081.isZX81; auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
is_zx81_ = zx8081_target->is_ZX81;
if(is_zx81_) { if(is_zx81_) {
rom_ = zx81_rom_; rom_ = zx81_rom_;
tape_trap_address_ = 0x37c; tape_trap_address_ = 0x37c;
@@ -249,7 +295,7 @@ template<bool is_zx81> class ConcreteMachine:
automatic_tape_motor_start_address_ = 0x0340; automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3; automatic_tape_motor_end_address_ = 0x03c3;
} else { } else {
rom_ = zx80_rom_; rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_;
tape_trap_address_ = 0x220; tape_trap_address_ = 0x220;
tape_return_address_ = 0x248; tape_return_address_ = 0x248;
vsync_start_ = HalfCycles(26); vsync_start_ = HalfCycles(26);
@@ -259,18 +305,18 @@ template<bool is_zx81> class ConcreteMachine:
} }
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
switch(target.zx8081.memory_model) { switch(zx8081_target->memory_model) {
case Analyser::Static::ZX8081MemoryModel::Unexpanded: case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
ram_.resize(1024); ram_.resize(1024);
ram_base_ = 16384; ram_base_ = 16384;
ram_mask_ = 1023; ram_mask_ = 1023;
break; break;
case Analyser::Static::ZX8081MemoryModel::SixteenKB: case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
ram_.resize(16384); ram_.resize(16384);
ram_base_ = 16384; ram_base_ = 16384;
ram_mask_ = 16383; ram_mask_ = 16383;
break; break;
case Analyser::Static::ZX8081MemoryModel::SixtyFourKB: case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
ram_.resize(65536); ram_.resize(65536);
ram_base_ = 8192; ram_base_ = 8192;
ram_mask_ = 65535; ram_mask_ = 65535;
@@ -278,11 +324,11 @@ template<bool is_zx81> class ConcreteMachine:
} }
Memory::Fuzz(ram_); Memory::Fuzz(ram_);
if(target.loading_command.length()) { if(target->loading_command.length()) {
type_string(target.loading_command); type_string(target->loading_command);
} }
insert_media(target.media); insert_media(target->media);
} }
bool insert_media(const Analyser::Static::Media &media) override final { bool insert_media(const Analyser::Static::Media &media) override final {
@@ -301,7 +347,7 @@ template<bool is_zx81> class ConcreteMachine:
// Obtains the system ROMs. // Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names( const auto roms = roms_with_names(
"ZX8081", "ZX8081",
{ {
"zx80.rom", "zx81.rom", "zx80.rom", "zx81.rom",
@@ -320,8 +366,8 @@ template<bool is_zx81> class ConcreteMachine:
} }
// MARK: - Keyboard // MARK: - Keyboard
void set_key_state(uint16_t key, bool isPressed) override final { void set_key_state(uint16_t key, bool is_pressed) override final {
if(isPressed) if(is_pressed)
key_states_[key >> 8] &= static_cast<uint8_t>(~key); key_states_[key >> 8] &= static_cast<uint8_t>(~key);
else else
key_states_[key >> 8] |= static_cast<uint8_t>(key); key_states_[key >> 8] |= static_cast<uint8_t>(key);
@@ -443,6 +489,34 @@ template<bool is_zx81> class ConcreteMachine:
inline void update_sync() { inline void update_sync() {
video_->set_sync(vsync_ || hsync_); video_->set_sync(vsync_ || hsync_);
} }
// MARK: - Audio
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
}
inline void ay_set_data(uint8_t value) {
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
}
inline uint8_t ay_read_data() {
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
const uint8_t value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
return value;
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
}; };
} }
@@ -450,9 +524,11 @@ template<bool is_zx81> class ConcreteMachine:
using namespace ZX8081; using namespace ZX8081;
// See header; constructs and returns an instance of the ZX80 or 81. // See header; constructs and returns an instance of the ZX80 or 81.
Machine *Machine::ZX8081(const Analyser::Static::Target &target_hint) { Machine *Machine::ZX8081(const Analyser::Static::Target *target_hint) {
const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint);
// Instantiate the correct type of machine. // Instantiate the correct type of machine.
if(target_hint.zx8081.isZX81) if(hint->is_ZX81)
return new ZX8081::ConcreteMachine<true>(); return new ZX8081::ConcreteMachine<true>();
else else
return new ZX8081::ConcreteMachine<false>(); return new ZX8081::ConcreteMachine<false>();

View File

@@ -10,24 +10,18 @@
#define ZX8081_hpp #define ZX8081_hpp
#include "../../Configurable/Configurable.hpp" #include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
namespace ZX8081 { namespace ZX8081 {
/// @returns The options available for a ZX80 or ZX81. /// @returns The options available for a ZX80 or ZX81.
std::vector<std::unique_ptr<Configurable::Option>> get_options(); std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine: class Machine {
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
public: public:
virtual ~Machine(); virtual ~Machine();
static Machine *ZX8081(const Analyser::Static::Target &target_hint); static Machine *ZX8081(const Analyser::Static::Target *target_hint);
virtual void set_tape_is_playing(bool is_playing) = 0; virtual void set_tape_is_playing(bool is_playing) = 0;
virtual bool get_tape_is_playing() = 0; virtual bool get_tape_is_playing() = 0;

View File

@@ -157,7 +157,6 @@
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; }; 4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; };
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; }; 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; };
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; }; 4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */; };
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; }; 4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; }; 4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; }; 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; };
@@ -280,7 +279,6 @@
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; };
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; };
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; }; 4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; };
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */; };
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; }; 4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; }; 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; }; 4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
@@ -293,6 +291,8 @@
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; 4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; 4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; }; 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */; };
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */; };
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; }; 4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
@@ -605,6 +605,7 @@
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; }; 4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; 4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; };
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
@@ -723,8 +724,6 @@
4B2A53971D117D36003C6002 /* KeyCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; }; 4B2A53971D117D36003C6002 /* KeyCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; };
4B2A53991D117D36003C6002 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; }; 4B2A53991D117D36003C6002 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; };
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = "<group>"; }; 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = "<group>"; };
4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; }; 4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = "<group>"; };
4B2B3A471F9B8FA70062DABF /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; }; 4B2B3A471F9B8FA70062DABF /* Typer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Typer.cpp; sourceTree = "<group>"; };
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; }; 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
@@ -757,7 +756,6 @@
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; }; 4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; }; 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; };
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; }; 4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; };
4B38F34E1F2EC6BA00D9235D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AmstradCPCOptions.xib"; sourceTree = SOURCE_ROOT; };
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; }; 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; };
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; }; 4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; };
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; }; 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
@@ -780,6 +778,7 @@
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; }; 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; }; 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; };
4B449C942063389900A095C8 /* TimeTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimeTypes.hpp; sourceTree = "<group>"; };
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; }; 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; }; 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; }; 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
@@ -962,7 +961,6 @@
4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; };
4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; };
4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadCompositeOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadCompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
4B8FE21A1DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Options.xib"; sourceTree = SOURCE_ROOT; };
4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600OptionsPanel.swift; sourceTree = "<group>"; }; 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600OptionsPanel.swift; sourceTree = "<group>"; };
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePanel.swift; sourceTree = "<group>"; }; 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePanel.swift; sourceTree = "<group>"; };
4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; }; 4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; };
@@ -977,9 +975,11 @@
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; }; 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; };
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; }; 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; };
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; }; 4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20OptionsPanel.swift; sourceTree = "<group>"; };
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; }; 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; }; 4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; };
4BA141BC2072E8A400A31EC9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; }; 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; }; 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; }; 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
@@ -1327,9 +1327,16 @@
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; }; 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; }; 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; }; 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; }; 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE3231220532443006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE32313205327D7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE32314205328FF006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; }; 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; }; 4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; }; 4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
@@ -1368,7 +1375,6 @@
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; }; 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; }; 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; }; 4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSZX8081+Instantiation.h"; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; }; 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; }; 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = "<group>"; }; 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = "<group>"; };
@@ -1574,11 +1580,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4B2A53991D117D36003C6002 /* CSAtari2600.h */, 4B2A53991D117D36003C6002 /* CSAtari2600.h */,
4B2A539D1D117D36003C6002 /* CSVic20.h */,
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */, 4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */,
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */, 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
4B2A539E1D117D36003C6002 /* CSVic20.mm */,
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */, 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */,
); );
path = Wrappers; path = Wrappers;
@@ -1874,14 +1877,12 @@
4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */, 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */,
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */,
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */, 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */,
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
4B2A332B1DB86821002876E3 /* OricOptions.xib */, 4B2A332B1DB86821002876E3 /* OricOptions.xib */,
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */, 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */,
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */, 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */,
); );
path = Documents; path = Documents;
@@ -2164,6 +2165,7 @@
4B8944EE201967B4007DE474 /* File.hpp */, 4B8944EE201967B4007DE474 /* File.hpp */,
4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */, 4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944EF201967B4007DE474 /* Tape.hpp */, 4B8944EF201967B4007DE474 /* Tape.hpp */,
4BE32313205327D7006EF799 /* Target.hpp */,
); );
path = Acorn; path = Acorn;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2173,6 +2175,7 @@
children = ( children = (
4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */, 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */,
4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */, 4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */,
4BE3231720532CC0006EF799 /* Target.hpp */,
); );
path = Atari; path = Atari;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2184,6 +2187,7 @@
4B8944F9201967B4007DE474 /* Tape.cpp */, 4B8944F9201967B4007DE474 /* Tape.cpp */,
4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */, 4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944F8201967B4007DE474 /* Tape.hpp */, 4B8944F8201967B4007DE474 /* Tape.hpp */,
4BE3231620532BED006EF799 /* Target.hpp */,
); );
path = Oric; path = Oric;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2199,6 +2203,7 @@
4B8944FE201967B4007DE474 /* File.hpp */, 4B8944FE201967B4007DE474 /* File.hpp */,
4B8944FD201967B4007DE474 /* StaticAnalyser.hpp */, 4B8944FD201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944FF201967B4007DE474 /* Tape.hpp */, 4B8944FF201967B4007DE474 /* Tape.hpp */,
4BE3231520532AA7006EF799 /* Target.hpp */,
); );
path = Commodore; path = Commodore;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2208,6 +2213,7 @@
children = ( children = (
4B894506201967B4007DE474 /* StaticAnalyser.cpp */, 4B894506201967B4007DE474 /* StaticAnalyser.cpp */,
4B894505201967B4007DE474 /* StaticAnalyser.hpp */, 4B894505201967B4007DE474 /* StaticAnalyser.hpp */,
4BE3231220532443006EF799 /* Target.hpp */,
); );
path = ZX8081; path = ZX8081;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2234,6 +2240,7 @@
4B047075201ABC180047AB0D /* Cartridge.hpp */, 4B047075201ABC180047AB0D /* Cartridge.hpp */,
4B894510201967B4007DE474 /* StaticAnalyser.hpp */, 4B894510201967B4007DE474 /* StaticAnalyser.hpp */,
4B894511201967B4007DE474 /* Tape.hpp */, 4B894511201967B4007DE474 /* Tape.hpp */,
4BA141C12073100800A31EC9 /* Target.hpp */,
); );
path = MSX; path = MSX;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2243,6 +2250,7 @@
children = ( children = (
4B894516201967B4007DE474 /* StaticAnalyser.cpp */, 4B894516201967B4007DE474 /* StaticAnalyser.cpp */,
4B894515201967B4007DE474 /* StaticAnalyser.hpp */, 4B894515201967B4007DE474 /* StaticAnalyser.hpp */,
4BE32314205328FF006EF799 /* Target.hpp */,
); );
path = AmstradCPC; path = AmstradCPC;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2257,6 +2265,16 @@
path = Implementation; path = Implementation;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4BA141C02072E8B300A31EC9 /* MachinePicker */ = {
isa = PBXGroup;
children = (
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */,
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */,
);
name = MachinePicker;
path = "New Group";
sourceTree = "<group>";
};
4BAB62AA1D3272D200DF5BA0 /* Disk */ = { 4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -2620,11 +2638,12 @@
4BB73EAD1B587A5100552FC2 /* Info.plist */, 4BB73EAD1B587A5100552FC2 /* Info.plist */,
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
4BB73EA81B587A5100552FC2 /* Assets.xcassets */, 4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4B2A538F1D117D36003C6002 /* Audio */, 4B2A538F1D117D36003C6002 /* Audio */,
4B643F3D1D77B88000D431D6 /* Document Controller */, 4B643F3D1D77B88000D431D6 /* Document Controller */,
4B55CE551C3B7D360093A61B /* Documents */, 4B55CE551C3B7D360093A61B /* Documents */,
4B2A53921D117D36003C6002 /* Machine */, 4B2A53921D117D36003C6002 /* Machine */,
4BA141C02072E8B300A31EC9 /* MachinePicker */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4BE5F85A1C3E1C2500C43F01 /* Resources */, 4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BD5F1961D1352A000631CD1 /* Updater */, 4BD5F1961D1352A000631CD1 /* Updater */,
4B55CE5A1C3B7D6F0093A61B /* Views */, 4B55CE5A1C3B7D6F0093A61B /* Views */,
@@ -2960,6 +2979,7 @@
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4BB146C61F49D7D700253439 /* Sleeper.hpp */, 4BB146C61F49D7D700253439 /* Sleeper.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
); );
name = ClockReceiver; name = ClockReceiver;
path = ../../ClockReceiver; path = ../../ClockReceiver;
@@ -3054,7 +3074,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0700; LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0900; LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "Thomas Harte"; ORGANIZATIONNAME = "Thomas Harte";
TargetAttributes = { TargetAttributes = {
4B055A691FAE763F0060FFFF = { 4B055A691FAE763F0060FFFF = {
@@ -3111,16 +3131,16 @@
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */, 4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */, 4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */,
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, 4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */,
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
4B79E4441E3AF38600141F11 /* cassette.png in Resources */, 4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, 4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, 4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */,
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, 4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */, 4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
); );
@@ -3648,6 +3668,7 @@
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */, 4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */, 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
@@ -3803,14 +3824,6 @@
name = OricOptions.xib; name = OricOptions.xib;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4B38F34E1F2EC6BA00D9235D /* Base */,
);
name = AmstradCPCOptions.xib;
sourceTree = "<group>";
};
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
children = ( children = (
@@ -3835,12 +3848,12 @@
name = QuickLoadCompositeOptions.xib; name = QuickLoadCompositeOptions.xib;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */ = { 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
children = ( children = (
4B8FE21A1DA19D5F0090D3CE /* Base */, 4BA141BC2072E8A400A31EC9 /* Base */,
); );
name = Vic20Options.xib; name = MachinePicker.xib;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = { 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = {
@@ -3851,6 +3864,14 @@
name = MainMenu.xib; name = MainMenu.xib;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4BD61663206B2AC700236112 /* Base */,
);
name = QuickLoadOptions.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
@@ -3904,12 +3925,14 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -3957,12 +3980,14 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0900" LastUpgradeVersion = "0930"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -26,9 +26,8 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = "" codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES">
codeCoverageEnabled = "YES">
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
@@ -74,7 +73,6 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"
disableMainThreadChecker = "YES" disableMainThreadChecker = "YES"
language = ""
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"

View File

@@ -3,46 +3,55 @@
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "16x16", "size" : "16x16",
"filename" : "Icon16.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "16x16", "size" : "16x16",
"filename" : "Icon32.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "32x32", "size" : "32x32",
"filename" : "Icon32.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "32x32", "size" : "32x32",
"filename" : "Icon64.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "128x128", "size" : "128x128",
"filename" : "Icon128.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "128x128", "size" : "128x128",
"filename" : "Icon256.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "256x256", "size" : "256x256",
"filename" : "Icon256.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "256x256", "size" : "256x256",
"filename" : "Icon512.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac", "idiom" : "mac",
"size" : "512x512", "size" : "512x512",
"filename" : "Icon512.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<customObject id="-2" userLabel="File's Owner" customClass="Vic20Document" customModule="Clock_Signal" customModuleProvider="target"> <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections> <connections>
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/> <outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/> <outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
@@ -16,15 +18,15 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="133" y="235" width="400" height="300"/> <rect key="contentRect" x="133" y="235" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/> <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<value key="minSize" type="size" width="228" height="171"/> <value key="minSize" type="size" width="228" height="171"/>
<view key="contentView" id="gIp-Ho-8D9"> <view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/> <rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<openGLView useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView"> <openGLView hidden="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/> <rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
</openGLView> </openGLView>
</subviews> </subviews>
<constraints> <constraints>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
</dependencies> </dependencies>
<objects> <objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -93,30 +94,30 @@
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/> <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV"> <menuItem title="Save…" enabled="NO" keyEquivalent="s" id="pxx-59-PXV">
<connections> <connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/> <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A"> <menuItem title="Save As…" enabled="NO" keyEquivalent="S" id="Bw7-FT-i3A">
<connections> <connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/> <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Revert to Saved" id="KaW-ft-85H"> <menuItem title="Revert to Saved" enabled="NO" id="KaW-ft-85H">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/> <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK"> <menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections> <connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/> <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS"> <menuItem title="Print…" enabled="NO" keyEquivalent="p" id="aTl-1u-JFS">
<connections> <connections>
<action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/> <action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/>
</connections> </connections>
@@ -128,23 +129,23 @@
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl"> <menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items> <items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg"> <menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections> <connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/> <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam"> <menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam">
<connections> <connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/> <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG"> <menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG">
<connections> <connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/> <action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU"> <menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU">
<connections> <connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/> <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections> </connections>
@@ -154,19 +155,19 @@
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/> <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk"> <menuItem title="Paste and Match Style" enabled="NO" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections> <connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Delete" id="pa3-QI-u2k"> <menuItem title="Delete" enabled="NO" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m"> <menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m">
<connections> <connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections> </connections>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="83"/> <rect key="contentRect" x="83" y="102" width="200" height="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu"> <view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/> <rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
@@ -40,7 +40,7 @@
<menu key="menu" id="L06-TO-EF0"> <menu key="menu" id="L06-TO-EF0">
<items> <items>
<menuItem title="SCART" state="on" id="tJM-kX-gaK"/> <menuItem title="SCART" state="on" id="tJM-kX-gaK"/>
<menuItem title="Composite" id="fFm-fS-rWG"/> <menuItem title="Composite" tag="1" id="fFm-fS-rWG"/>
</items> </items>
</menu> </menu>
</popUpButtonCell> </popUpButtonCell>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="83"/> <rect key="contentRect" x="83" y="102" width="200" height="83"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu"> <view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/> <rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
@@ -34,13 +34,14 @@
</button> </button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n"> <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n">
<rect key="frame" x="18" y="17" width="165" height="26"/> <rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" title="Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1"> <popUpButtonCell key="cell" type="push" title="RGB Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
<menu key="menu" id="L06-TO-EF0"> <menu key="menu" id="L06-TO-EF0">
<items> <items>
<menuItem title="Monitor" state="on" id="tJM-kX-gaK"/> <menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/>
<menuItem title="Television" id="fFm-fS-rWG"/> <menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/>
<menuItem title="Television" tag="1" id="fFm-fS-rWG"/>
</items> </items>
</menu> </menu>
</popUpButtonCell> </popUpButtonCell>

View File

@@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/> <rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> <rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu"> <view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/> <rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
@@ -34,9 +34,9 @@
</button> </button>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/> <constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/> <constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/> <constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
</constraints> </constraints>
</view> </view>

View File

@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="Vic20OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="112"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="7Pv-WL-2Rq">
<rect key="frame" x="0.0" y="0.0" width="200" height="112"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
<rect key="frame" x="18" y="76" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MlB-rE-TXV" userLabel="Country Selector">
<rect key="frame" x="18" y="46" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="UIu-uz-pTu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="ajo-u0-WGk">
<items>
<menuItem title="Danish Machine" id="38Y-Wm-1uo"/>
<menuItem title="European Machine" id="5ju-Z0-BDa"/>
<menuItem title="Japanese Machine" id="YlT-9e-azY"/>
<menuItem title="Swedish Machine" id="joU-Bt-XFb"/>
<menuItem title="US Machine" id="FXe-ca-cTY"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setCountry:" target="ota-g7-hOL" id="YIc-QB-R1S"/>
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0NP-x1-qH2">
<rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="K81-0X-C4f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="diI-80-lCf">
<items>
<menuItem title="5 kb" id="ze7-6B-ois"/>
<menuItem title="8 kb" id="6C7-Iv-Wvl"/>
<menuItem title="32 kb" id="DOo-f6-OeZ"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setMemorySize:" target="ota-g7-hOL" id="lep-Qi-00V"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="MlB-rE-TXV" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="8" id="0kc-u0-05p"/>
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
<constraint firstItem="0NP-x1-qH2" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="7EF-L9-lIu"/>
<constraint firstAttribute="bottom" secondItem="0NP-x1-qH2" secondAttribute="bottom" constant="20" id="Dtd-kf-4oU"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
<constraint firstItem="0NP-x1-qH2" firstAttribute="top" secondItem="MlB-rE-TXV" secondAttribute="bottom" constant="8" id="NbW-5e-wGB"/>
<constraint firstAttribute="trailing" secondItem="0NP-x1-qH2" secondAttribute="trailing" constant="20" id="ero-D6-tJj"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
<constraint firstItem="MlB-rE-TXV" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="qb4-Lp-ZMc"/>
<constraint firstAttribute="trailing" secondItem="MlB-rE-TXV" secondAttribute="trailing" constant="20" id="v18-62-uee"/>
</constraints>
</view>
<connections>
<outlet property="countryButton" destination="MlB-rE-TXV" id="Duc-AC-ZRO"/>
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
<outlet property="memorySizeButton" destination="0NP-x1-qH2" id="qYy-3f-o94"/>
</connections>
<point key="canvasLocation" x="-2" y="21"/>
</window>
</objects>
</document>

View File

@@ -6,7 +6,6 @@
#import "CSFastLoading.h" #import "CSFastLoading.h"
#import "CSAtari2600.h" #import "CSAtari2600.h"
#import "CSVic20.h"
#import "CSZX8081.h" #import "CSZX8081.h"
#import "CSStaticAnalyser.h" #import "CSStaticAnalyser.h"

View File

@@ -28,6 +28,7 @@ class MachineDocument:
return nil return nil
} }
} }
var optionsPanelNibName: String?
func aspectRatio() -> NSSize { func aspectRatio() -> NSSize {
return NSSize(width: 4.0, height: 3.0) return NSSize(width: 4.0, height: 3.0)
@@ -48,43 +49,64 @@ class MachineDocument:
override func windowControllerDidLoadNib(_ aController: NSWindowController) { override func windowControllerDidLoadNib(_ aController: NSWindowController) {
super.windowControllerDidLoadNib(aController) super.windowControllerDidLoadNib(aController)
aController.window?.contentAspectRatio = self.aspectRatio()
setupMachineOutput()
// establish the output aspect ratio and audio
let displayAspectRatio = self.aspectRatio()
aController.window?.contentAspectRatio = displayAspectRatio
openGLView.perform(glContext: {
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
})
self.machine.delegate = self
self.bestEffortUpdater = CSBestEffortUpdater()
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
// hence the full setup of the best-effort updater prior to setting self as a delegate
self.openGLView.delegate = self
self.openGLView.responderDelegate = self
setupClockRate()
self.optionsPanel?.establishStoredOptions()
// bring OpenGL view-holding window on top of the options panel
self.openGLView.window!.makeKeyAndOrderFront(self)
// start accepting best effort updates
self.bestEffortUpdater!.delegate = self
} }
func machineDidChangeClockRate(_ machine: CSMachine!) { // Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
setupClockRate() // a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
// is visible, though it's a little premature.
func windowDidUpdate(_ notification: Notification) {
if self.shouldShowNewMachinePanel {
self.shouldShowNewMachinePanel = false
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil)
self.machinePicker?.establishStoredOptions()
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
}
} }
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) { fileprivate func setupMachineOutput() {
bestEffortLock.lock() if let machine = self.machine, let openGLView = self.openGLView {
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited // establish the output aspect ratio and audio
bestEffortLock.unlock() let aspectRatio = self.aspectRatio()
openGLView.perform(glContext: {
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
})
// attach an options panel if one is available
if let optionsPanelNibName = self.optionsPanelNibName {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
self.optionsPanel.machine = machine
self.optionsPanel?.establishStoredOptions()
showOptions(self)
}
machine.delegate = self
self.bestEffortUpdater = CSBestEffortUpdater()
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
// hence the full setup of the best-effort updater prior to setting self as a delegate
openGLView.delegate = self
openGLView.responderDelegate = self
setupAudioQueueClockRate()
// bring OpenGL view-holding window on top of the options panel and show the content
openGLView.isHidden = false
openGLView.window!.makeKeyAndOrderFront(self)
openGLView.window!.makeFirstResponder(openGLView)
// start accepting best effort updates
self.bestEffortUpdater!.delegate = self
}
} }
fileprivate func setupClockRate() { func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) {
setupAudioQueueClockRate()
}
fileprivate func setupAudioQueueClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate // establish and provide the audio queue, taking advice as to an appropriate sampling rate
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate() let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))) let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
@@ -94,10 +116,6 @@ class MachineDocument:
self.machine.audioQueue = self.audioQueue self.machine.audioQueue = self.audioQueue
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
} }
bestEffortLock.lock()
self.bestEffortUpdater?.clockRate = self.machine.clockRate
bestEffortLock.unlock()
} }
override func close() { override func close() {
@@ -105,9 +123,11 @@ class MachineDocument:
optionsPanel = nil optionsPanel = nil
bestEffortLock.lock() bestEffortLock.lock()
bestEffortUpdater!.delegate = nil if let bestEffortUpdater = bestEffortUpdater {
bestEffortUpdater!.flush() bestEffortUpdater.delegate = nil
bestEffortUpdater = nil bestEffortUpdater.flush()
self.bestEffortUpdater = nil
}
bestEffortLock.unlock() bestEffortLock.unlock()
actionLock.lock() actionLock.lock()
@@ -125,15 +145,12 @@ class MachineDocument:
func configureAs(_ analysis: CSStaticAnalyser) { func configureAs(_ analysis: CSStaticAnalyser) {
if let machine = CSMachine(analyser: analysis) { if let machine = CSMachine(analyser: analysis) {
self.machine = machine self.machine = machine
} self.optionsPanelNibName = analysis.optionsPanelNibName
setupMachineOutput()
if let optionsPanelNibName = analysis.optionsPanelNibName {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
self.optionsPanel.machine = self.machine
showOptions(self)
} }
} }
fileprivate var shouldShowNewMachinePanel = false
override func read(from url: URL, ofType typeName: String) throws { override func read(from url: URL, ofType typeName: String) throws {
if let analyser = CSStaticAnalyser(fileAt: url) { if let analyser = CSStaticAnalyser(fileAt: url) {
self.displayName = analyser.displayName self.displayName = analyser.displayName
@@ -143,6 +160,12 @@ class MachineDocument:
} }
} }
convenience init(type typeName: String) throws {
self.init()
self.fileType = typeName
self.shouldShowNewMachinePanel = true
}
// MARK: the pasteboard // MARK: the pasteboard
func paste(_ sender: AnyObject!) { func paste(_ sender: AnyObject!) {
let pasteboard = NSPasteboard.general let pasteboard = NSPasteboard.general
@@ -152,21 +175,10 @@ class MachineDocument:
} }
// MARK: CSBestEffortUpdaterDelegate // MARK: CSBestEffortUpdaterDelegate
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) { final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
runForNumberOfCycles(Int32(cycles)) if actionLock.try() {
} self.machine.run(forInterval: duration)
actionLock.unlock()
func runForNumberOfCycles(_ numberOfCycles: Int32) {
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortLock.unlock()
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
if actionLock.try() {
self.machine.runForNumber(ofCycles: cyclesToRunFor)
actionLock.unlock()
}
} else {
bestEffortLock.unlock()
} }
} }
@@ -206,21 +218,42 @@ class MachineDocument:
// MARK: Input management // MARK: Input management
func windowDidResignKey(_ notification: Notification) { func windowDidResignKey(_ notification: Notification) {
self.machine.clearAllKeys() if let machine = self.machine {
machine.clearAllKeys()
}
} }
func keyDown(_ event: NSEvent) { func keyDown(_ event: NSEvent) {
self.machine.setKey(event.keyCode, characters: event.characters, isPressed: true) if let machine = self.machine {
machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
}
} }
func keyUp(_ event: NSEvent) { func keyUp(_ event: NSEvent) {
self.machine.setKey(event.keyCode, characters: event.characters, isPressed: false) if let machine = self.machine {
machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
}
} }
func flagsChanged(_ newModifiers: NSEvent) { func flagsChanged(_ newModifiers: NSEvent) {
self.machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift)) if let machine = self.machine {
self.machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control)) machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
self.machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command)) machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control))
self.machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option)) machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command))
machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option))
}
}
// MARK: New machine creation
@IBOutlet var machinePicker: MachinePicker?
@IBOutlet var machinePickerPanel: NSWindow?
@IBAction func createMachine(_ sender: NSButton?) {
self.configureAs(machinePicker!.selectedMachine())
machinePicker = nil
sender?.window?.close()
}
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
close()
} }
} }

View File

@@ -28,13 +28,24 @@ class MachinePanel: NSPanel {
} }
} }
fileprivate func signalForTag(tag: Int) -> CSMachineVideoSignal {
switch tag {
case 1: return .composite
case 2: return .sVideo
default: break
}
return .RGB
}
var displayTypeUserDefaultsKey: String { var displayTypeUserDefaultsKey: String {
return prefixedUserDefaultsKey("displayType") return prefixedUserDefaultsKey("displayType")
} }
@IBOutlet var displayTypeButton: NSPopUpButton? @IBOutlet var displayTypeButton: NSPopUpButton?
@IBAction func setDisplayType(_ sender: NSPopUpButton!) { @IBAction func setDisplayType(_ sender: NSPopUpButton!) {
machine.useCompositeOutput = (sender.indexOfSelectedItem == 1) if let selectedItem = sender.selectedItem {
UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) machine.videoSignal = signalForTag(tag: selectedItem.tag)
UserDefaults.standard.set(selectedItem.tag, forKey: self.displayTypeUserDefaultsKey)
}
} }
func establishStoredOptions() { func establishStoredOptions() {
@@ -50,8 +61,21 @@ class MachinePanel: NSPanel {
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
} }
let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey) if let displayTypeButton = self.displayTypeButton {
machine.useCompositeOutput = (displayType == 1) // Enable or disable options as per machine support.
self.displayTypeButton?.selectItem(at: displayType) var titlesToRemove: [String] = []
for item in displayTypeButton.itemArray {
if !machine.supportsVideoSignal(signalForTag(tag: item.tag)) {
titlesToRemove.append(item.title)
}
}
for title in titlesToRemove {
displayTypeButton.removeItem(withTitle: title)
}
let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey)
displayTypeButton.selectItem(withTag: displayType)
setDisplayType(displayTypeButton)
}
} }
} }

View File

@@ -1,97 +0,0 @@
//
// Vic20OptionsPanel.swift
// Clock Signal
//
// Created by Thomas Harte on 03/10/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
class Vic20OptionsPanel: MachinePanel {
var vic20: CSVic20! {
get {
return self.machine as! CSVic20
}
}
// MARK: country selector
@IBOutlet var countryButton: NSPopUpButton?
var countryUserDefaultsKey: String {
get { return prefixedUserDefaultsKey("country") }
}
@IBAction func setCountry(_ sender: NSPopUpButton!) {
UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.countryUserDefaultsKey)
setCountry(sender.indexOfSelectedItem)
}
fileprivate func setCountry(_ countryID: Int) {
switch countryID {
case 0: // Danish
vic20.country = .danish
case 1: // European
vic20.country = .european
case 2: // Japanese
vic20.country = .japanese
case 3: // Swedish
vic20.country = .swedish
case 4: // US
vic20.country = .american
default: break
}
}
// MARK: memory model selector
@IBOutlet var memorySizeButton: NSPopUpButton?
var memorySizeUserDefaultsKey: String {
get { return prefixedUserDefaultsKey("memorySize") }
}
@IBAction func setMemorySize(_ sender: NSPopUpButton!) {
var selectedSize: Int?
switch sender.indexOfSelectedItem {
case 0: selectedSize = 5
case 1: selectedSize = 8
case 2: selectedSize = 32
default: break
}
if let selectedSize = selectedSize {
UserDefaults.standard.set(selectedSize, forKey: self.memorySizeUserDefaultsKey)
setMemorySize(sender.indexOfSelectedItem)
}
}
fileprivate func setMemorySize(_ sizeIndex: Int) {
switch sizeIndex {
case 2: vic20.memorySize = .size32Kb
case 1: vic20.memorySize = .size8Kb
default: vic20.memorySize = .size5Kb
}
}
// MARK: option restoration
override func establishStoredOptions() {
super.establishStoredOptions()
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.register(defaults: [
self.memorySizeUserDefaultsKey: 5,
self.countryUserDefaultsKey: 1
])
// let memorySize = standardUserDefaults.integer(forKey: self.memorySizeUserDefaultsKey)
// var indexToSelect: Int?
// switch memorySize {
// case 32: indexToSelect = 2
// case 8: indexToSelect = 1
// default: indexToSelect = 0
// }
// if let indexToSelect = indexToSelect {
// self.memorySizeButton?.selectItem(at: indexToSelect)
// setMemorySize(indexToSelect)
// }
// TODO: this should be part of the configuration
let country = standardUserDefaults.integer(forKey: self.countryUserDefaultsKey)
setCountry(country)
self.countryButton?.selectItem(at: country)
}
}

View File

@@ -15,10 +15,15 @@
@class CSMachine; @class CSMachine;
@protocol CSMachineDelegate @protocol CSMachineDelegate
- (void)machineDidChangeClockRate:(CSMachine *)machine; - (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine;
- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine;
@end @end
typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
CSMachineVideoSignalComposite,
CSMachineVideoSignalSVideo,
CSMachineVideoSignalRGB
};
// Deliberately low; to ensure CSMachine has been declared as an @class already. // Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h" #import "CSAtari2600.h"
#import "CSZX8081.h" #import "CSZX8081.h"
@@ -33,7 +38,7 @@
*/ */
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
- (void)runForNumberOfCycles:(int)numberOfCycles; - (void)runForInterval:(NSTimeInterval)interval;
- (float)idealSamplingRateFromRange:(NSRange)range; - (float)idealSamplingRateFromRange:(NSRange)range;
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize; - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
@@ -48,17 +53,16 @@
@property (nonatomic, readonly) CSOpenGLView *view; @property (nonatomic, readonly) CSOpenGLView *view;
@property (nonatomic, weak) id<CSMachineDelegate> delegate; @property (nonatomic, weak) id<CSMachineDelegate> delegate;
@property (nonatomic, readonly) double clockRate;
@property (nonatomic, readonly) BOOL clockIsUnlimited;
@property (nonatomic, readonly) NSString *userDefaultsPrefix; @property (nonatomic, readonly) NSString *userDefaultsPrefix;
- (void)paste:(NSString *)string; - (void)paste:(NSString *)string;
@property (nonatomic, assign) BOOL useFastLoadingHack; @property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL useCompositeOutput; @property (nonatomic, assign) CSMachineVideoSignal videoSignal;
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type. // Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
@property (nonatomic, readonly) CSAtari2600 *atari2600; @property (nonatomic, readonly) CSAtari2600 *atari2600;
@property (nonatomic, readonly) CSZX8081 *zx8081; @property (nonatomic, readonly) CSZX8081 *zx8081;

View File

@@ -25,8 +25,7 @@
@interface CSMachine() <CSFastLoading> @interface CSMachine() <CSFastLoading>
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
- (void)machineDidChangeClockRate; - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
- (void)machineDidChangeClockIsUnlimited;
@end @end
struct LockProtectedDelegate { struct LockProtectedDelegate {
@@ -37,29 +36,20 @@ struct LockProtectedDelegate {
}; };
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate { struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate {
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) { void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override {
[machineAccessLock lock]; [machineAccessLock lock];
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; [machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
[machineAccessLock unlock]; [machineAccessLock unlock];
} }
}; void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override {
struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate {
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
[machineAccessLock lock]; [machineAccessLock lock];
[machine machineDidChangeClockRate]; [machine speakerDidChangeInputClock:speaker];
[machineAccessLock unlock];
}
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
[machineAccessLock lock];
[machine machineDidChangeClockIsUnlimited];
[machineAccessLock unlock]; [machineAccessLock unlock];
} }
}; };
@implementation CSMachine { @implementation CSMachine {
SpeakerDelegate _speakerDelegate; SpeakerDelegate _speakerDelegate;
MachineDelegate _machineDelegate;
NSLock *_delegateMachineAccessLock; NSLock *_delegateMachineAccessLock;
CSStaticAnalyser *_analyser; CSStaticAnalyser *_analyser;
@@ -77,12 +67,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
_delegateMachineAccessLock = [[NSLock alloc] init]; _delegateMachineAccessLock = [[NSLock alloc] init];
_machineDelegate.machine = self;
_speakerDelegate.machine = self; _speakerDelegate.machine = self;
_machineDelegate.machineAccessLock = _delegateMachineAccessLock;
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; _speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
_machine->crt_machine()->set_delegate(&_machineDelegate);
} }
return self; return self;
} }
@@ -91,12 +77,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
} }
- (void)machineDidChangeClockRate { - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker {
[self.delegate machineDidChangeClockRate:self]; [self.delegate machineSpeakerDidChangeInputClock:self];
}
- (void)machineDidChangeClockIsUnlimited {
[self.delegate machineDidChangeClockIsUnlimited:self];
} }
- (void)dealloc { - (void)dealloc {
@@ -107,13 +89,12 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
// They are nilled inside an explicit lock because that allows the delegates to protect their entire // They are nilled inside an explicit lock because that allows the delegates to protect their entire
// call into the machine, not just the pointer access. // call into the machine, not just the pointer access.
[_delegateMachineAccessLock lock]; [_delegateMachineAccessLock lock];
_machineDelegate.machine = nil;
_speakerDelegate.machine = nil; _speakerDelegate.machine = nil;
[_delegateMachineAccessLock unlock]; [_delegateMachineAccessLock unlock];
[_view performWithGLContext:^{ [_view performWithGLContext:^{
@synchronized(self) { @synchronized(self) {
_machine->crt_machine()->close_output(); self->_machine->crt_machine()->close_output();
} }
}]; }];
} }
@@ -146,9 +127,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
} }
} }
- (void)runForNumberOfCycles:(int)numberOfCycles { - (void)runForInterval:(NSTimeInterval)interval {
@synchronized(self) { @synchronized(self) {
_machine->crt_machine()->run_for(Cycles(numberOfCycles)); _machine->crt_machine()->run_for(interval);
} }
} }
@@ -171,14 +152,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
_machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); _machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
} }
- (double)clockRate {
return _machine->crt_machine()->get_clock_rate();
}
- (BOOL)clockIsUnlimited {
return _machine->crt_machine()->get_clock_is_unlimited() ? YES : NO;
}
- (void)paste:(NSString *)paste { - (void)paste:(NSString *)paste {
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine(); KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
if(keyboardMachine) if(keyboardMachine)
@@ -316,19 +289,63 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
} }
} }
- (void)setUseCompositeOutput:(BOOL)useCompositeOutput { - (void)setVideoSignal:(CSMachineVideoSignal)videoSignal {
Configurable::Device *configurable_device = _machine->configurable_device(); Configurable::Device *configurable_device = _machine->configurable_device();
if(!configurable_device) return; if(!configurable_device) return;
@synchronized(self) { @synchronized(self) {
_useCompositeOutput = useCompositeOutput; _videoSignal = videoSignal;
Configurable::SelectionSet selection_set; Configurable::SelectionSet selection_set;
append_display_selection(selection_set, useCompositeOutput ? Configurable::Display::Composite : Configurable::Display::RGB); Configurable::Display display;
switch(videoSignal) {
case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break;
case CSMachineVideoSignalSVideo: display = Configurable::Display::SVideo; break;
case CSMachineVideoSignalComposite: display = Configurable::Display::Composite; break;
}
append_display_selection(selection_set, display);
configurable_device->set_selections(selection_set); configurable_device->set_selections(selection_set);
} }
} }
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal {
Configurable::Device *configurable_device = _machine->configurable_device();
if(!configurable_device) return NO;
// Get the options this machine provides.
std::vector<std::unique_ptr<Configurable::Option>> options;
@synchronized(self) {
options = configurable_device->get_options();
}
// Get the standard option for this video signal.
Configurable::StandardOptions option;
switch(videoSignal) {
case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break;
case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break;
case CSMachineVideoSignalComposite: option = Configurable::DisplayComposite; break;
}
std::unique_ptr<Configurable::Option> display_option = std::move(standard_options(option).front());
Configurable::ListOption *display_list_option = dynamic_cast<Configurable::ListOption *>(display_option.get());
NSAssert(display_list_option, @"Expected display option to be a list");
// See whether the video signal is included in the machine options.
for(auto &candidate: options) {
Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(candidate.get());
// Both should be list options
if(!list_option) continue;
// Check for same name of option.
if(candidate->short_name != display_option->short_name) continue;
// Check that the video signal option is included.
return std::find(list_option->options.begin(), list_option->options.end(), display_list_option->options.front()) != list_option->options.end();
}
return NO;
}
- (void)setUseAutomaticTapeMotorControl:(BOOL)useAutomaticTapeMotorControl { - (void)setUseAutomaticTapeMotorControl:(BOOL)useAutomaticTapeMotorControl {
Configurable::Device *configurable_device = _machine->configurable_device(); Configurable::Device *configurable_device = _machine->configurable_device();
if(!configurable_device) return; if(!configurable_device) return;

View File

@@ -10,10 +10,39 @@
@class CSMachine; @class CSMachine;
typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
CSMachineCPCModel464,
CSMachineCPCModel664,
CSMachineCPCModel6128
};
typedef NS_ENUM(NSInteger, CSMachineOricModel) {
CSMachineOricModelOric1,
CSMachineOricModelOricAtmos
};
typedef NS_ENUM(NSInteger, CSMachineVic20Region) {
CSMachineVic20RegionAmerican,
CSMachineVic20RegionEuropean,
CSMachineVic20RegionDanish,
CSMachineVic20RegionSwedish,
CSMachineVic20RegionJapanese,
};
typedef int Kilobytes;
@interface CSStaticAnalyser : NSObject @interface CSStaticAnalyser : NSObject
- (instancetype)initWithFileAtURL:(NSURL *)url; - (instancetype)initWithFileAtURL:(NSURL *)url;
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs;
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model;
- (instancetype)initWithMSXHasDiskDrive:(BOOL)hasDiskDrive;
- (instancetype)initWithOricModel:(CSMachineOricModel)model hasMicrodrive:(BOOL)hasMicrodrive;
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540;
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
@property(nonatomic, readonly) NSString *optionsPanelNibName; @property(nonatomic, readonly) NSString *optionsPanelNibName;
@property(nonatomic, readonly) NSString *displayName; @property(nonatomic, readonly) NSString *displayName;

View File

@@ -13,6 +13,13 @@
#include "StaticAnalyser.hpp" #include "StaticAnalyser.hpp"
#include "../../../../../Analyser/Static/Acorn/Target.hpp"
#include "../../../../../Analyser/Static/AmstradCPC/Target.hpp"
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../../../Analyser/Static/MSX/Target.hpp"
#include "../../../../../Analyser/Static/Oric/Target.hpp"
#include "../../../../../Analyser/Static/ZX8081/Target.hpp"
#import "Clock_Signal-Swift.h" #import "Clock_Signal-Swift.h"
@implementation CSStaticAnalyser { @implementation CSStaticAnalyser {
@@ -32,6 +39,120 @@
return self; return self;
} }
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs {
self = [super init];
if(self) {
using Target = Analyser::Static::Acorn::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::Electron;
target->has_dfs = !!dfs;
target->has_adfs = !!adfs;
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model {
self = [super init];
if(self) {
using Target = Analyser::Static::AmstradCPC::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::AmstradCPC;
switch(model) {
case CSMachineCPCModel464: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC464; break;
case CSMachineCPCModel664: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC664; break;
case CSMachineCPCModel6128: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC6128; break;
}
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithMSXHasDiskDrive:(BOOL)hasDiskDrive {
self = [super init];
if(self) {
using Target = Analyser::Static::MSX::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::MSX;
target->has_disk_drive = !!hasDiskDrive;
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithOricModel:(CSMachineOricModel)model hasMicrodrive:(BOOL)hasMicrodrive {
self = [super init];
if(self) {
using Target = Analyser::Static::Oric::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::Oric;
target->use_atmos_rom = (model == CSMachineOricModelOricAtmos);
target->has_microdrive = !!hasMicrodrive;
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540 {
self = [super init];
if(self) {
using Target = Analyser::Static::Commodore::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::Vic20;
switch(region) {
case CSMachineVic20RegionDanish: target->region = Target::Region::Danish; break;
case CSMachineVic20RegionSwedish: target->region = Target::Region::Swedish; break;
case CSMachineVic20RegionAmerican: target->region = Target::Region::American; break;
case CSMachineVic20RegionEuropean: target->region = Target::Region::European; break;
case CSMachineVic20RegionJapanese: target->region = Target::Region::Japanese; break;
}
switch(memorySize) {
default: target->memory_model = Target::MemoryModel::Unexpanded; break;
case 8: target->memory_model = Target::MemoryModel::EightKB; break;
case 32: target->memory_model = Target::MemoryModel::ThirtyTwoKB; break;
}
target->has_c1540 = !!hasC1540;
_targets.push_back(std::move(target));
}
return self;
}
static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(Kilobytes size) {
using MemoryModel = Analyser::Static::ZX8081::Target::MemoryModel;
switch(size) {
default: return MemoryModel::Unexpanded;
case 16: return MemoryModel::SixteenKB;
case 64: return MemoryModel::SixtyFourKB;
}
}
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM {
self = [super init];
if(self) {
using Target = Analyser::Static::ZX8081::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::ZX8081;
target->is_ZX81 = false;
target->ZX80_uses_ZX81_ROM = !!useZX81ROM;
target->memory_model = ZX8081MemoryModelFromSize(memorySize);
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize {
self = [super init];
if(self) {
using Target = Analyser::Static::ZX8081::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::ZX8081;
target->is_ZX81 = true;
target->memory_model = ZX8081MemoryModelFromSize(memorySize);
_targets.push_back(std::move(target));
}
return self;
}
- (NSString *)optionsPanelNibName { - (NSString *)optionsPanelNibName {
switch(_targets.front()->machine) { switch(_targets.front()->machine) {
case Analyser::Machine::AmstradCPC: return nil; case Analyser::Machine::AmstradCPC: return nil;
@@ -39,7 +160,7 @@
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions"; case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions"; case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Oric: return @"OricOptions"; case Analyser::Machine::Oric: return @"OricOptions";
case Analyser::Machine::Vic20: nil; //return @"Vic20Options"; case Analyser::Machine::Vic20: return @"QuickLoadCompositeOptions";
case Analyser::Machine::ZX8081: return @"ZX8081Options"; case Analyser::Machine::ZX8081: return @"ZX8081Options";
default: return nil; default: return nil;
} }

View File

@@ -66,8 +66,8 @@
} }
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@synchronized(_machine) { @synchronized(self->_machine) {
_atari2600->set_switch_is_enabled(toggleSwitch, false); self->_atari2600->set_switch_is_enabled(toggleSwitch, false);
} }
}); });
} }

View File

@@ -1,35 +0,0 @@
//
// CSVic20.h
// Clock Signal
//
// Created by Thomas Harte on 04/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSMachine.h"
#import "CSFastLoading.h"
typedef NS_ENUM(NSInteger, CSVic20Country)
{
CSVic20CountryAmerican,
CSVic20CountryDanish,
CSVic20CountryEuropean,
CSVic20CountryJapanese,
CSVic20CountrySwedish
};
typedef NS_ENUM(NSInteger, CSVic20MemorySize)
{
CSVic20MemorySize5Kb,
CSVic20MemorySize8Kb,
CSVic20MemorySize32Kb,
};
@interface CSVic20 : CSMachine <CSFastLoading>
- (instancetype)init;
@property (nonatomic, assign) CSVic20Country country;
@property (nonatomic, assign) CSVic20MemorySize memorySize;
@end

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