mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
125 Commits
2018-03-03
...
2018-04-07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
506b4da6c3 | ||
|
|
10f637d2cf | ||
|
|
0bab7c88f0 | ||
|
|
78c612ca17 | ||
|
|
e1c4035812 | ||
|
|
eb6d6c8033 | ||
|
|
7bf88565ce | ||
|
|
ee10155296 | ||
|
|
cc49140f6f | ||
|
|
3e846f89a1 | ||
|
|
5782cab2a0 | ||
|
|
8c511e2b76 | ||
|
|
ec72fb3baf | ||
|
|
bab1440f5c | ||
|
|
60c1da6a66 | ||
|
|
a849b3f2e4 | ||
|
|
dbe3c5c3f8 | ||
|
|
60cf6b3cfd | ||
|
|
5044aac337 | ||
|
|
36e0cb29c0 | ||
|
|
c0b4dd65da | ||
|
|
d061ea232b | ||
|
|
49feca4ddf | ||
|
|
46b1c57bf4 | ||
|
|
eaf1482182 | ||
|
|
d3418550eb | ||
|
|
3ffa9e2751 | ||
|
|
c697dd78f0 | ||
|
|
7dac791290 | ||
|
|
cde2faeda6 | ||
|
|
69f520428d | ||
|
|
80c84ddd75 | ||
|
|
fca8a58b36 | ||
|
|
33084899d0 | ||
|
|
7b381a8b6b | ||
|
|
9c75689a8d | ||
|
|
0ee40e8556 | ||
|
|
8b45377b89 | ||
|
|
f6fb368d88 | ||
|
|
183a5379de | ||
|
|
912791d3d4 | ||
|
|
163a61dd63 | ||
|
|
207d462dbf | ||
|
|
33281b9d89 | ||
|
|
389979923e | ||
|
|
067174965e | ||
|
|
286259c83b | ||
|
|
e1aa3e5a7f | ||
|
|
78e1c2851a | ||
|
|
0869213c55 | ||
|
|
f3fe16215a | ||
|
|
ec353ce663 | ||
|
|
b7ff5ef9dd | ||
|
|
3b26e0a7c5 | ||
|
|
6d464557a0 | ||
|
|
a776bec46a | ||
|
|
a2da51c30b | ||
|
|
8067bf548a | ||
|
|
62b0645ed0 | ||
|
|
39a94874ae | ||
|
|
e15d6717a1 | ||
|
|
37ef46e7bb | ||
|
|
70c09b3031 | ||
|
|
9378fbb0df | ||
|
|
2118b9c0cd | ||
|
|
d0c53de250 | ||
|
|
d98507eab0 | ||
|
|
760c75103e | ||
|
|
4407fd2f1f | ||
|
|
7fcd243be0 | ||
|
|
3165e9d82e | ||
|
|
6656a08c60 | ||
|
|
76661c0b51 | ||
|
|
3bb496f9ae | ||
|
|
45be1c19df | ||
|
|
a301964bd0 | ||
|
|
eea6858121 | ||
|
|
2a320fdf56 | ||
|
|
4695296ef2 | ||
|
|
0fdbbeca1d | ||
|
|
34cc39ad65 | ||
|
|
3d0c832a21 | ||
|
|
1acdab9448 | ||
|
|
93e85c5c4a | ||
|
|
ab98189d25 | ||
|
|
cd0fb7624b | ||
|
|
bae38497bb | ||
|
|
29921bfa8d | ||
|
|
2712702461 | ||
|
|
a3fa9440d1 | ||
|
|
6419b0e619 | ||
|
|
58e5b6e3f1 | ||
|
|
682c3d8079 | ||
|
|
da3d65c18f | ||
|
|
ece3a05504 | ||
|
|
927697b0f0 | ||
|
|
74dfc80b0f | ||
|
|
a7f229bc4b | ||
|
|
89bec2919f | ||
|
|
78eaecb29e | ||
|
|
d410aea856 | ||
|
|
6b1eef572b | ||
|
|
719f5d79c2 | ||
|
|
48737a32a7 | ||
|
|
53f05efb2d | ||
|
|
0e73ba4b3e | ||
|
|
f0f9d5a6af | ||
|
|
03501df9e5 | ||
|
|
dd6f85d4db | ||
|
|
1804ea6849 | ||
|
|
c8657e08f4 | ||
|
|
a942e1319b | ||
|
|
9e0a56b4f0 | ||
|
|
9abc020818 | ||
|
|
2dade8d353 | ||
|
|
1100dc6993 | ||
|
|
f212b18511 | ||
|
|
a6ca69550f | ||
|
|
2452641844 | ||
|
|
c82af4b814 | ||
|
|
fdef914137 | ||
|
|
dfcc502a88 | ||
|
|
1c6faaae88 | ||
|
|
35c8a0dd8c | ||
|
|
38feedaf6a |
18
.travis.yml
18
.travis.yml
@@ -1,5 +1,13 @@
|
||||
language: objective-c
|
||||
osx_image: xcode8.2
|
||||
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
||||
xcode_scheme: Clock Signal
|
||||
xcode_sdk: macosx10.12
|
||||
# language: objective-c
|
||||
# osx_image: xcode8.2
|
||||
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
|
||||
# xcode_scheme: Clock Signal
|
||||
# xcode_sdk: macosx10.12
|
||||
|
||||
language: cpp
|
||||
before_install:
|
||||
- sudo apt-get install libsdl2-dev
|
||||
script: cd OSBindings/SDL && scons
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
@@ -75,41 +75,16 @@ Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
void MultiCRTMachine::run_for(const Cycles cycles) {
|
||||
void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
if(speaker_) {
|
||||
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.
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Dynamic {
|
||||
acquiring a supplied mutex. The owner should also call did_change_machine_order()
|
||||
if the order of machines changes.
|
||||
*/
|
||||
class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate {
|
||||
class MultiCRTMachine: public CRTMachine::Machine {
|
||||
public:
|
||||
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;
|
||||
Outputs::CRT::CRT *get_crt() override;
|
||||
Outputs::Speaker::Speaker *get_speaker() override;
|
||||
void run_for(const Cycles cycles) override;
|
||||
double get_clock_rate() override;
|
||||
bool get_clock_is_unlimited() override;
|
||||
void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
|
||||
void run_for(Time::Seconds duration) override;
|
||||
|
||||
private:
|
||||
// CRTMachine::Machine::Delegate
|
||||
void machine_did_change_clock_rate(Machine *machine) override;
|
||||
void machine_did_change_clock_is_unlimited(Machine *machine) override;
|
||||
|
||||
void run_for(const Cycles cycles) override {}
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::mutex &machines_mutex_;
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -29,7 +29,7 @@ struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
|
||||
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
|
||||
void configure_as_target(const Analyser::Static::Target &target) override;
|
||||
void configure_as_target(const Analyser::Static::Target *target) override;
|
||||
bool insert_media(const Analyser::Static::Media &media) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -8,13 +8,69 @@
|
||||
|
||||
#include "MultiJoystickMachine.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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) {
|
||||
std::size_t total_joysticks = 0;
|
||||
std::vector<JoystickMachine::Machine *> joystick_machines;
|
||||
for(const auto &machine: machines) {
|
||||
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() {
|
||||
|
||||
@@ -31,7 +31,6 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
|
||||
|
||||
private:
|
||||
std::vector<JoystickMachine::Machine *> machines_;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
if(delegate_ && speaker == front_speaker_) {
|
||||
delegate_->speaker_did_complete_samples(this, buffer);
|
||||
if(!delegate_) return;
|
||||
{
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,13 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
void set_new_front_machine(::Machine::DynamicMachine *machine);
|
||||
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum);
|
||||
void set_output_rate(float cycles_per_second, int buffer_size);
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate);
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_output_rate(float cycles_per_second, int buffer_size) override;
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||
|
||||
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);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
|
||||
@@ -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() {
|
||||
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
|
||||
#ifdef DEBUG
|
||||
for(const auto &machine: machines_) {
|
||||
CRTMachine::Machine *crt = machine->crt_machine();
|
||||
printf("%0.2f ", crt->get_confidence());
|
||||
@@ -71,6 +78,7 @@ void MultiMachine::multi_crt_did_run_machines() {
|
||||
printf("; ");
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
DynamicMachine *front = machines_.front().get();
|
||||
std::stable_sort(machines_.begin(), machines_.end(),
|
||||
@@ -84,10 +92,7 @@ void MultiMachine::multi_crt_did_run_machines() {
|
||||
crt_machine_.did_change_machine_order();
|
||||
}
|
||||
|
||||
if(
|
||||
(machines_.front()->crt_machine()->get_confidence() > 0.9f) ||
|
||||
(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence())
|
||||
) {
|
||||
if(would_collapse(machines_)) {
|
||||
pick_first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ namespace Dynamic {
|
||||
*/
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
|
||||
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);
|
||||
|
||||
ConfigurationTarget::Machine *configuration_target() override;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
@@ -56,13 +57,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
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);
|
||||
target->machine = Machine::Electron;
|
||||
target->confidence = 1.0; // TODO: a proper estimation
|
||||
target->acorn.has_dfs = false;
|
||||
target->acorn.has_adfs = false;
|
||||
target->acorn.should_shift_restart = false;
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
target->has_dfs = false;
|
||||
target->has_adfs = false;
|
||||
target->should_shift_restart = false;
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
@@ -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 || adfs_catalogue) {
|
||||
target->media.disks = media.disks;
|
||||
target->acorn.has_dfs = !!dfs_catalogue;
|
||||
target->acorn.has_adfs = !!adfs_catalogue;
|
||||
target->has_dfs = !!dfs_catalogue;
|
||||
target->has_adfs = !!adfs_catalogue;
|
||||
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None)
|
||||
target->acorn.should_shift_restart = true;
|
||||
target->should_shift_restart = true;
|
||||
else
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
28
Analyser/Static/Acorn/Target.hpp
Normal file
28
Analyser/Static/Acorn/Target.hpp
Normal 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 */
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.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(
|
||||
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;
|
||||
candidate_files.reserve(catalogue.files.size());
|
||||
@@ -153,10 +155,10 @@ static void InspectCatalogue(
|
||||
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::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
|
||||
// this disk was formatted and the filler byte never replaced.
|
||||
bool matched = true;
|
||||
@@ -177,24 +179,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
|
||||
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);
|
||||
target->machine = Machine::AmstradCPC;
|
||||
target->confidence = 1.0;
|
||||
target->media.disks = media.disks;
|
||||
target->media.tapes = media.tapes;
|
||||
target->media.cartridges = media.cartridges;
|
||||
target->confidence = 0.5;
|
||||
|
||||
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
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target->loading_command = "|tape\nrun\"\n1234567890";
|
||||
}
|
||||
|
||||
if(!target->media.disks.empty()) {
|
||||
if(!media.disks.empty()) {
|
||||
Storage::Disk::CPM::ParameterBlock data_format;
|
||||
data_format.sectors_per_track = 9;
|
||||
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.reserved_tracks = 0;
|
||||
|
||||
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format);
|
||||
if(data_catalogue) {
|
||||
InspectCatalogue(*data_catalogue, target);
|
||||
} else {
|
||||
if(!CheckBootSector(target->media.disks.front(), target)) {
|
||||
Storage::Disk::CPM::ParameterBlock system_format;
|
||||
system_format.sectors_per_track = 9;
|
||||
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;
|
||||
Storage::Disk::CPM::ParameterBlock system_format;
|
||||
system_format.sectors_per_track = 9;
|
||||
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);
|
||||
if(system_catalogue) {
|
||||
InspectCatalogue(*system_catalogue, target);
|
||||
}
|
||||
for(const auto &disk: media.disks) {
|
||||
// Check for an ordinary catalogue.
|
||||
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));
|
||||
}
|
||||
|
||||
33
Analyser/Static/AmstradCPC/Target.hpp
Normal file
33
Analyser/Static/AmstradCPC/Target.hpp
Normal 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 */
|
||||
@@ -8,11 +8,13 @@
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/6502.hpp"
|
||||
|
||||
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
|
||||
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
|
||||
// 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
|
||||
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
|
||||
// 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?)
|
||||
@@ -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[0] == 0x78
|
||||
) {
|
||||
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack;
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
|
||||
if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros;
|
||||
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
|
||||
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.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
|
||||
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k;
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
|
||||
|
||||
std::set<uint16_t> internal_accesses;
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
target.atari.paging_model =
|
||||
target.paging_model =
|
||||
(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) {
|
||||
DeterminePagingFor2kCartridge(target, segment);
|
||||
return;
|
||||
@@ -138,16 +140,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
|
||||
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 10495:
|
||||
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2;
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
|
||||
break;
|
||||
case 12288:
|
||||
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus;
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
|
||||
break;
|
||||
case 16384:
|
||||
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
||||
break;
|
||||
case 32768:
|
||||
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k;
|
||||
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
|
||||
break;
|
||||
case 65536:
|
||||
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
|
||||
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
|
||||
// next 128 bytes. So check for that.
|
||||
if( target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus &&
|
||||
target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) {
|
||||
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
|
||||
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
|
||||
bool has_superchip = true;
|
||||
for(std::size_t address = 0; address < 128; address++) {
|
||||
if(segment.data[address] != segment.data[address+128]) {
|
||||
@@ -168,24 +170,24 @@ static void DeterminePagingForCartridge(Analyser::Static::Target &target, const
|
||||
break;
|
||||
}
|
||||
}
|
||||
target.atari.uses_superchip = has_superchip;
|
||||
target.uses_superchip = has_superchip;
|
||||
}
|
||||
|
||||
// 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();
|
||||
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) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600.
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
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?
|
||||
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
|
||||
target->machine = Machine::Atari2600;
|
||||
target->confidence = 1.0;
|
||||
target->confidence = 0.5;
|
||||
target->media.cartridges = media.cartridges;
|
||||
target->atari.paging_model = Atari2600PagingModel::None;
|
||||
target->atari.uses_superchip = false;
|
||||
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
|
||||
target->uses_superchip = false;
|
||||
|
||||
// try to figure out the paging scheme
|
||||
if(!media.cartridges.empty()) {
|
||||
|
||||
43
Analyser/Static/Atari/Target.hpp
Normal file
43
Analyser/Static/Atari/Target.hpp
Normal 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 */
|
||||
@@ -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) {
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Machine::ColecoVision;
|
||||
target->confidence = 0.5;
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
if(!target->media.empty())
|
||||
destination.push_back(std::move(target));
|
||||
|
||||
@@ -71,19 +71,19 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
unsigned int proceed_to_next_block() {
|
||||
unsigned int proceed_to_next_block(int max_index_count) {
|
||||
// find GCR lead-in
|
||||
proceed_to_shift_value(0x3ff);
|
||||
if(shift_register_ != 0x3ff) return 0xff;
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// continue for a further nine bits
|
||||
bit_count_ = 0;
|
||||
while(bit_count_ < 9 && index_count_ < 2) {
|
||||
while(bit_count_ < 9 && index_count_ < max_index_count) {
|
||||
run_for(Cycles(1));
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
}
|
||||
|
||||
void proceed_to_shift_value(unsigned int shift_value) {
|
||||
index_count_ = 0;
|
||||
while(shift_register_ != shift_value && index_count_ < 2) {
|
||||
const int max_index_count = index_count_ + 2;
|
||||
while(shift_register_ != shift_value && index_count_ < max_index_count) {
|
||||
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> 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
|
||||
while(1) {
|
||||
if(proceed_to_next_block() == 0x08) break;
|
||||
if(index_count_ >= 2) return nullptr;
|
||||
if(proceed_to_next_block(max_index_count) == 0x08) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
@@ -144,8 +144,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
|
||||
// look for the following data
|
||||
while(1) {
|
||||
if(proceed_to_next_block() == 0x07) break;
|
||||
if(index_count_ >= 2) return nullptr;
|
||||
if(proceed_to_next_block(max_index_count) == 0x07) break;
|
||||
if(index_count_ >= max_index_count) return nullptr;
|
||||
}
|
||||
|
||||
checksum = 0;
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include "Disk.hpp"
|
||||
#include "File.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
using namespace Analyser::Static::Commodore;
|
||||
@@ -38,10 +40,10 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
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);
|
||||
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;
|
||||
std::vector<File> files;
|
||||
@@ -73,7 +75,7 @@ void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std
|
||||
}
|
||||
|
||||
if(!files.empty()) {
|
||||
target->vic20.memory_model = Vic20MemoryModel::Unexpanded;
|
||||
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
|
||||
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
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
printf("Starting address %04x?\n", files.front().starting_address);
|
||||
case 0x1001:
|
||||
default: break;
|
||||
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||
break;
|
||||
case 0x1201:
|
||||
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
break;
|
||||
case 0x0401:
|
||||
target->vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||
target->memory_model = Target::MemoryModel::EightKB;
|
||||
break;
|
||||
}
|
||||
|
||||
// General approach: increase memory size conservatively such that the largest file found will fit.
|
||||
for(File &file : files) {
|
||||
std::size_t file_size = file.data.size();
|
||||
// for(File &file : files) {
|
||||
// std::size_t file_size = file.data.size();
|
||||
// bool is_basic = file.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.
|
||||
// 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.
|
||||
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
|
||||
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
|
||||
if(starting_address + file_size > 0x2000)
|
||||
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
// if(starting_address + file_size > 0x2000)
|
||||
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
|
||||
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
42
Analyser/Static/Commodore/Target.hpp
Normal file
42
Analyser/Static/Commodore/Target.hpp
Normal 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 */
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "Cartridge.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/Z80.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);
|
||||
}
|
||||
|
||||
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->confidence = confidence;
|
||||
|
||||
@@ -259,9 +261,9 @@ static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFr
|
||||
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.
|
||||
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));
|
||||
|
||||
// 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.
|
||||
target->media.disks = media.disks;
|
||||
target->has_disk_drive = !media.disks.empty();
|
||||
|
||||
if(!target->media.empty()) {
|
||||
target->machine = Machine::MSX;
|
||||
target->confidence = 1.0;
|
||||
target->confidence = 0.5;
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
}
|
||||
|
||||
26
Analyser/Static/MSX/Target.hpp
Normal file
26
Analyser/Static/MSX/Target.hpp
Normal 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 */
|
||||
@@ -9,9 +9,15 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../Disassembler/6502.hpp"
|
||||
#include "../Disassembler/AddressMapper.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
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) {
|
||||
@@ -73,10 +79,31 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
|
||||
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);
|
||||
target->machine = Machine::Oric;
|
||||
target->confidence = 1.0;
|
||||
target->confidence = 0.5;
|
||||
|
||||
int basic10_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()) {
|
||||
target->oric.has_microdisc = true;
|
||||
target->media.disks = media.disks;
|
||||
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
|
||||
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 {
|
||||
target->oric.has_microdisc = false;
|
||||
target->has_microdrive = false;
|
||||
}
|
||||
|
||||
// TODO: really this should add two targets if not all votes agree
|
||||
target->oric.use_atmos_rom = basic11_votes >= basic10_votes;
|
||||
if(target->oric.has_microdisc) target->oric.use_atmos_rom = true;
|
||||
target->use_atmos_rom = basic11_votes >= basic10_votes;
|
||||
if(target->has_microdrive) target->use_atmos_rom = true;
|
||||
|
||||
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
|
||||
destination.push_back(std::move(target));
|
||||
|
||||
25
Analyser/Static/Oric/Target.hpp
Normal file
25
Analyser/Static/Oric/Target.hpp
Normal 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 */
|
||||
@@ -52,21 +52,16 @@
|
||||
|
||||
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
|
||||
// test as to file format.
|
||||
const char *mixed_case_extension = strrchr(file_name, '.');
|
||||
char *lowercase_extension = nullptr;
|
||||
if(mixed_case_extension) {
|
||||
lowercase_extension = strdup(mixed_case_extension+1);
|
||||
char *parser = lowercase_extension;
|
||||
while(*parser) {
|
||||
*parser = (char)tolower(*parser);
|
||||
parser++;
|
||||
}
|
||||
}
|
||||
std::string::size_type final_dot = file_name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return result;
|
||||
std::string extension = file_name.substr(final_dot + 1);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
Media result;
|
||||
#define Insert(list, class, platforms) \
|
||||
list.emplace_back(new Storage::class(file_name));\
|
||||
potential_platforms |= platforms;\
|
||||
@@ -78,74 +73,72 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
|
||||
Insert(list, class, platforms) \
|
||||
} catch(...) {}
|
||||
|
||||
#define Format(extension, list, class, platforms) \
|
||||
if(!std::strcmp(lowercase_extension, extension)) { \
|
||||
#define Format(ext, list, class, platforms) \
|
||||
if(extension == ext) { \
|
||||
TryInsert(list, class, platforms) \
|
||||
}
|
||||
|
||||
if(lowercase_extension) {
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
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::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format("hfe", result.disks, Disk::DiskImageHolder<Storage::Disk::HFE>, TargetPlatform::AmstradCPC) // HFE (TODO: plus other target platforms)
|
||||
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
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
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::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
|
||||
// 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
|
||||
if(!std::strcmp(lowercase_extension, "prg")) {
|
||||
// try instantiating as a ROM; failing that accept as a tape
|
||||
// PRG
|
||||
if(extension == "prg") {
|
||||
// try instantiating as a ROM; failing that accept as a tape
|
||||
try {
|
||||
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
|
||||
} catch(...) {
|
||||
try {
|
||||
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
|
||||
} catch(...) {
|
||||
try {
|
||||
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
|
||||
} catch(...) {}
|
||||
}
|
||||
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision) // ROM
|
||||
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::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
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::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
|
||||
#undef Format
|
||||
#undef Insert
|
||||
#undef TryInsert
|
||||
|
||||
// Filter potential platforms as per file preferences, if any.
|
||||
|
||||
free(lowercase_extension);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Media Analyser::Static::GetMedia(const char *file_name) {
|
||||
Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
TargetPlatform::IntType 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;
|
||||
|
||||
// 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::Atari2600) Atari::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::Oric) Oric::AddTargets(media, targets);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);
|
||||
|
||||
@@ -21,39 +21,6 @@
|
||||
namespace Analyser {
|
||||
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.
|
||||
*/
|
||||
@@ -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.
|
||||
*/
|
||||
struct Target {
|
||||
virtual ~Target() {}
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
|
||||
float confidence;
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
Media GetMedia(const char *file_name);
|
||||
Media GetMedia(const std::string &file_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Target.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
media.tapes.front()->reset();
|
||||
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;
|
||||
|
||||
// Guess the machine type from the file only if it isn't already known.
|
||||
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
|
||||
default:
|
||||
target->zx8081.isZX81 = files.front().isZX81;
|
||||
target->is_ZX81 = files.front().isZX81;
|
||||
break;
|
||||
|
||||
case TargetPlatform::ZX80: target->zx8081.isZX81 = false; break;
|
||||
case TargetPlatform::ZX81: target->zx8081.isZX81 = true; break;
|
||||
case TargetPlatform::ZX80: target->is_ZX81 = false; break;
|
||||
case TargetPlatform::ZX81: target->is_ZX81 = true; break;
|
||||
}
|
||||
|
||||
/*if(files.front().data.size() > 16384) {
|
||||
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
||||
} else*/ if(files.front().data.size() > 1024) {
|
||||
target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
|
||||
target->memory_model = Target::MemoryModel::SixteenKB;
|
||||
} else {
|
||||
target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
|
||||
target->memory_model = Target::MemoryModel::Unexpanded;
|
||||
}
|
||||
target->media.tapes = media.tapes;
|
||||
|
||||
// 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";
|
||||
} else {
|
||||
target->loading_command = "W\n";
|
||||
}
|
||||
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
Analyser/Static/ZX8081/Target.hpp
Normal file
34
Analyser/Static/ZX8081/Target.hpp
Normal 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 */
|
||||
18
ClockReceiver/TimeTypes.hpp
Normal file
18
ClockReceiver/TimeTypes.hpp
Normal 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 */
|
||||
@@ -18,7 +18,7 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
|
||||
|
||||
void AudioGenerator::set_volume(uint8_t volume) {
|
||||
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;
|
||||
// TODO: what's the real ratio of this stuff?
|
||||
target[c] = (
|
||||
target[c] = static_cast<int16_t>(
|
||||
(shift_registers_[0]&1) +
|
||||
(shift_registers_[1]&1) +
|
||||
(shift_registers_[2]&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 increment
|
||||
#undef update
|
||||
|
||||
@@ -25,8 +25,10 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
void set_volume(uint8_t volume);
|
||||
void set_control(int channel, uint8_t value);
|
||||
|
||||
// For ::SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void skip_samples(std::size_t number_of_samples);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
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 shift_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_),
|
||||
speaker_(audio_generator_)
|
||||
{
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||
"{"
|
||||
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
||||
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
||||
|
||||
"float chroma = cos(phase + phaseOffset);"
|
||||
"return mix(yc.x, step(yc.y, 0.75) * chroma, amplitude);"
|
||||
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
|
||||
"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
|
||||
set_output_mode(OutputMode::NTSC);
|
||||
}
|
||||
|
||||
~MOS6560() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate) {
|
||||
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) {
|
||||
output_mode_ = output_mode;
|
||||
|
||||
// Lumunances are encoded trivially: on a 0–255 scale.
|
||||
// Luminances are encoded trivially: on a 0–255 scale.
|
||||
const uint8_t luminances[16] = {
|
||||
0, 255, 109, 189,
|
||||
199, 144, 159, 161,
|
||||
126, 227, 227, 207,
|
||||
235, 173, 188, 196
|
||||
0, 255, 60, 189,
|
||||
80, 144, 40, 227,
|
||||
90, 161, 207, 227,
|
||||
200, 196, 160, 196
|
||||
};
|
||||
|
||||
// Chrominances are encoded such that 0–128 is a complete revolution of phase;
|
||||
// anything above 191 disables the colour subcarrier. Phase is relative to the
|
||||
// colour burst, so 0 is green.
|
||||
const uint8_t pal_chrominances[16] = {
|
||||
255, 255, 40, 112,
|
||||
8, 88, 120, 56,
|
||||
40, 48, 40, 112,
|
||||
8, 88, 120, 56,
|
||||
255, 255, 37, 101,
|
||||
19, 86, 123, 59,
|
||||
46, 53, 37, 101,
|
||||
19, 86, 123, 59,
|
||||
};
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
255, 255, 8, 72,
|
||||
32, 88, 48, 112,
|
||||
0, 0, 8, 72,
|
||||
32, 88, 48, 112,
|
||||
255, 255, 7, 71,
|
||||
25, 86, 48, 112,
|
||||
0, 119, 7, 71,
|
||||
25, 86, 48, 112,
|
||||
};
|
||||
const uint8_t *chrominances;
|
||||
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_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
|
||||
// switch(output_mode) {
|
||||
// case OutputMode::PAL:
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// case OutputMode::NTSC:
|
||||
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// }
|
||||
switch(output_mode) {
|
||||
case OutputMode::PAL:
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f));
|
||||
break;
|
||||
case OutputMode::NTSC:
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
break;
|
||||
}
|
||||
|
||||
for(int c = 0; c < 16; c++) {
|
||||
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
|
||||
|
||||
@@ -96,7 +96,7 @@ TMS9918::TMS9918(Personality p) {
|
||||
"{"
|
||||
"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_input_gamma(2.8f);
|
||||
|
||||
|
||||
@@ -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
|
||||
float max_volume = 8192;
|
||||
float root_two = sqrtf(2.0f);
|
||||
const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
|
||||
const float root_two = sqrtf(2.0f);
|
||||
for(int v = 0; v < 16; v++) {
|
||||
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
|
||||
}
|
||||
volumes_[0] = 0;
|
||||
evaluate_output_volume();
|
||||
}
|
||||
|
||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
|
||||
@@ -86,6 +86,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
bool is_zero_level();
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
@@ -27,7 +27,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
|
||||
std::size_t c = 0;
|
||||
while((master_divider_&7) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
target[c] = transient_output_level_;
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
|
||||
target[c] = output_volume_;
|
||||
target[c] = transient_output_level_;
|
||||
c++;
|
||||
master_divider_++;
|
||||
}
|
||||
@@ -86,18 +86,24 @@ void SCC::write(uint16_t address, uint8_t value) {
|
||||
}
|
||||
|
||||
void SCC::evaluate_output_volume() {
|
||||
output_volume_ =
|
||||
transient_output_level_ =
|
||||
static_cast<int16_t>(
|
||||
(
|
||||
((
|
||||
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
|
||||
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
|
||||
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
|
||||
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
|
||||
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
|
||||
)
|
||||
) * 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) {
|
||||
address &= 0xff;
|
||||
if(address < 0x80) {
|
||||
@@ -105,3 +111,5 @@ uint8_t SCC::read(uint16_t address) {
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
/// Writes to the SCC.
|
||||
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.
|
||||
int master_divider_ = 0;
|
||||
int16_t output_volume_ = 0;
|
||||
std::int16_t master_volume_ = 0;
|
||||
int16_t transient_output_level_ = 0;
|
||||
|
||||
struct Channel {
|
||||
int period = 0;
|
||||
|
||||
@@ -14,15 +14,7 @@
|
||||
using namespace TI;
|
||||
|
||||
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
|
||||
// Build a volume table.
|
||||
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();
|
||||
set_sample_volume_range(0);
|
||||
|
||||
switch(personality) {
|
||||
case Personality::SN76494:
|
||||
@@ -44,6 +36,18 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &
|
||||
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) {
|
||||
task_queue_.defer([value, this] () {
|
||||
if(value & 0x80) {
|
||||
|
||||
@@ -31,6 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
// As per SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
bool is_zero_level();
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
int master_divider_ = 0;
|
||||
|
||||
@@ -45,6 +45,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
flush();
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
#else
|
||||
@@ -82,6 +83,7 @@ void AsyncTaskQueue::flush() {
|
||||
|
||||
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
|
||||
perform();
|
||||
flush();
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
|
||||
|
||||
@@ -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 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.
|
||||
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
if(duration > 0) {
|
||||
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
|
||||
error_ = fmod(cycles, 1.0);
|
||||
|
||||
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
if(integer_duration > 0) {
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <chrono>
|
||||
|
||||
#include "AsyncTaskQueue.hpp"
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
@@ -30,15 +31,12 @@ class BestEffortUpdater {
|
||||
|
||||
/// A delegate receives timing cues.
|
||||
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.
|
||||
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.
|
||||
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_;
|
||||
bool has_previous_time_point_ = false;
|
||||
double error_ = 0.0;
|
||||
bool has_skipped_ = false;
|
||||
|
||||
Delegate *delegate_ = nullptr;
|
||||
double clock_rate_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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>> options;
|
||||
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"));
|
||||
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) {
|
||||
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
|
||||
@@ -67,6 +80,10 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
|
||||
result = Configurable::Display::RGB;
|
||||
return true;
|
||||
}
|
||||
if(display->value == "svideo") {
|
||||
result = Configurable::Display::SVideo;
|
||||
return true;
|
||||
}
|
||||
if(display->value == "composite") {
|
||||
result = Configurable::Display::Composite;
|
||||
return true;
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
namespace Configurable {
|
||||
|
||||
enum StandardOptions {
|
||||
DisplayRGBComposite = (1 << 0),
|
||||
QuickLoadTape = (1 << 1),
|
||||
AutomaticTapeMotorControl = (1 << 2)
|
||||
DisplayRGB = (1 << 0),
|
||||
DisplaySVideo = (1 << 1),
|
||||
DisplayComposite = (1 << 2),
|
||||
QuickLoadTape = (1 << 3),
|
||||
AutomaticTapeMotorControl = (1 << 4)
|
||||
};
|
||||
|
||||
enum class Display {
|
||||
RGB,
|
||||
SVideo,
|
||||
Composite
|
||||
};
|
||||
|
||||
|
||||
@@ -20,11 +20,17 @@
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@@ -120,6 +126,10 @@ class AYDeferrer {
|
||||
speaker_.set_input_rate(1000000);
|
||||
}
|
||||
|
||||
~AYDeferrer() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
/// Adds @c half_cycles half cycles to the amount of time that has passed.
|
||||
inline void run_for(HalfCycles 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;"
|
||||
"}");
|
||||
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.
|
||||
@@ -674,6 +684,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
|
||||
*/
|
||||
class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Sleeper::SleepObserver,
|
||||
@@ -867,19 +880,21 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
|
||||
void configure_as_target(const Analyser::Static::Target &target) override final {
|
||||
switch(target.amstradcpc.model) {
|
||||
case Analyser::Static::AmstradCPCModel::CPC464:
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target);
|
||||
|
||||
switch(cpc_target->model) {
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
|
||||
rom_model_ = ROMType::OS464;
|
||||
has_128k_ = false;
|
||||
has_fdc_ = false;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPCModel::CPC664:
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
|
||||
rom_model_ = ROMType::OS664;
|
||||
has_128k_ = false;
|
||||
has_fdc_ = true;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPCModel::CPC6128:
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC6128:
|
||||
rom_model_ = ROMType::OS6128;
|
||||
has_128k_ = true;
|
||||
has_fdc_ = true;
|
||||
@@ -901,11 +916,11 @@ class ConcreteMachine:
|
||||
read_pointers_[3] = roms_[upper_rom_].data();
|
||||
|
||||
// Type whatever is required.
|
||||
if(target.loading_command.length()) {
|
||||
type_string(target.loading_command);
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
|
||||
@@ -9,19 +9,12 @@
|
||||
#ifndef AmstradCPC_hpp
|
||||
#define AmstradCPC_hpp
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
/*!
|
||||
Models an Amstrad CPC.
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine {
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Atari/Target.hpp"
|
||||
|
||||
#include "Cartridges/Atari8k.hpp"
|
||||
#include "Cartridges/Atari16k.hpp"
|
||||
#include "Cartridges/Atari32k.hpp"
|
||||
@@ -72,11 +78,12 @@ class Joystick: public Inputs::Joystick {
|
||||
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public JoystickMachine::Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
frame_record_pointer_(0),
|
||||
is_ntsc_(true) {
|
||||
ConcreteMachine() {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
}
|
||||
|
||||
@@ -84,35 +91,38 @@ class ConcreteMachine:
|
||||
close_output();
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target &target) override {
|
||||
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
||||
switch(target.atari.paging_model) {
|
||||
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;
|
||||
void configure_as_target(const Analyser::Static::Target *target) override {
|
||||
auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target);
|
||||
const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data;
|
||||
|
||||
case Analyser::Static::Atari2600PagingModel::Atari8k:
|
||||
if(target.atari.uses_superchip) {
|
||||
using PagingModel = Analyser::Static::Atari::Target::PagingModel;
|
||||
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));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
|
||||
}
|
||||
break;
|
||||
case Analyser::Static::Atari2600PagingModel::Atari16k:
|
||||
if(target.atari.uses_superchip) {
|
||||
case PagingModel::Atari16k:
|
||||
if(atari_target->uses_superchip) {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
|
||||
}
|
||||
break;
|
||||
case Analyser::Static::Atari2600PagingModel::Atari32k:
|
||||
if(target.atari.uses_superchip) {
|
||||
case PagingModel::Atari32k:
|
||||
if(atari_target->uses_superchip) {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
|
||||
@@ -179,6 +189,7 @@ class ConcreteMachine:
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
bus_->run_for(cycles);
|
||||
bus_->apply_confidence(confidence_counter_);
|
||||
}
|
||||
|
||||
// to satisfy Outputs::CRT::Delegate
|
||||
@@ -219,6 +230,10 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
float get_confidence() override {
|
||||
return confidence_counter_.get_confidence();
|
||||
}
|
||||
|
||||
private:
|
||||
// the bus
|
||||
std::unique_ptr<Bus> bus_;
|
||||
@@ -230,9 +245,12 @@ class ConcreteMachine:
|
||||
|
||||
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
|
||||
} frame_records_[4];
|
||||
unsigned int frame_record_pointer_;
|
||||
bool is_ntsc_;
|
||||
unsigned int frame_record_pointer_ = 0;
|
||||
bool is_ntsc_ = true;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
// a confidence counter
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
#ifndef Atari2600_cpp
|
||||
#define Atari2600_cpp
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
|
||||
#include "Atari2600Inputs.h"
|
||||
|
||||
namespace Atari2600 {
|
||||
@@ -20,10 +16,7 @@ namespace Atari2600 {
|
||||
/*!
|
||||
Models an Atari 2600.
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public JoystickMachine::Machine {
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "TIA.hpp"
|
||||
#include "TIASound.hpp"
|
||||
|
||||
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
@@ -23,11 +24,14 @@ class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_sound_(audio_queue_),
|
||||
speaker_(tia_sound_),
|
||||
tia_input_value_{0xff, 0xff},
|
||||
cycles_since_speaker_update_(0) {}
|
||||
speaker_(tia_sound_) {}
|
||||
|
||||
virtual ~Bus() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
@@ -39,7 +43,7 @@ class Bus {
|
||||
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2];
|
||||
uint8_t tia_input_value_[2] = {0xff, 0xff};
|
||||
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
|
||||
@@ -39,7 +39,23 @@ template<class T> class Cartridge:
|
||||
// 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); }
|
||||
|
||||
// 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 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
|
||||
|
||||
case 0x04:
|
||||
@@ -189,6 +209,9 @@ template<class T> class Cartridge:
|
||||
|
||||
private:
|
||||
T bus_extender_;
|
||||
int horizontal_counter_resets_ = 0;
|
||||
Cycles cycle_count_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace {
|
||||
TIA::TIA(bool create_crt) {
|
||||
if(create_crt) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -123,20 +123,20 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
if(output_mode == OutputMode::NTSC) {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
|
||||
"return 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;
|
||||
} else {
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
@@ -145,10 +145,12 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
"uint direction = iPhase & 1u;"
|
||||
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
|
||||
"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;
|
||||
}
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -119,7 +119,11 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ class TIASound: public Outputs::Speaker::SampleSource {
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
void set_control(int channel, uint8_t control);
|
||||
|
||||
// To satisfy ::SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
@@ -41,6 +43,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
|
||||
int output_state_[2];
|
||||
|
||||
int divider_counter_[2];
|
||||
int16_t per_channel_volume_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
#include "../Outputs/CRT/CRT.hpp"
|
||||
#include "../Outputs/Speaker/Speaker.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
#include "ROMMachine.hpp"
|
||||
|
||||
#include "../Configurable/StandardOptions.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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.
|
||||
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.
|
||||
virtual float get_confidence() { return 0.5f; }
|
||||
virtual void print_type() {}
|
||||
|
||||
// TODO: sever the clock-rate stuff.
|
||||
virtual double get_clock_rate() {
|
||||
return clock_rate_;
|
||||
/// Runs the machine for @c duration seconds.
|
||||
virtual void run_for(Time::Seconds duration) {
|
||||
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:
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
void set_clock_rate(double clock_rate) {
|
||||
if(clock_rate_ != clock_rate) {
|
||||
clock_rate_ = clock_rate;
|
||||
if(delegate_) delegate_->machine_did_change_clock_rate(this);
|
||||
}
|
||||
clock_rate_ = clock_rate;
|
||||
}
|
||||
void set_clock_is_unlimited(bool clock_is_unlimited) {
|
||||
if(clock_is_unlimited != clock_is_unlimited_) {
|
||||
clock_is_unlimited_ = clock_is_unlimited;
|
||||
if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this);
|
||||
double get_clock_rate() {
|
||||
return clock_rate_;
|
||||
}
|
||||
|
||||
/*!
|
||||
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:
|
||||
Delegate *delegate_ = nullptr;
|
||||
double clock_rate_ = 1.0;
|
||||
bool clock_is_unlimited_ = false;
|
||||
double clock_conversion_error_ = 0.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
|
||||
|
||||
namespace {
|
||||
const int sn76489_divider = 2;
|
||||
}
|
||||
@@ -123,13 +125,17 @@ class ConcreteMachine:
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
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 {
|
||||
@@ -148,9 +154,9 @@ class ConcreteMachine:
|
||||
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_media(target.media);
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
@@ -192,9 +198,13 @@ class ConcreteMachine:
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
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;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < 0x2000) {
|
||||
if(super_game_module_.replace_bios) {
|
||||
@@ -245,6 +255,11 @@ class ConcreteMachine:
|
||||
} else {
|
||||
*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;
|
||||
|
||||
default:
|
||||
@@ -252,6 +267,7 @@ class ConcreteMachine:
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// Read AY data.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
|
||||
*cycle.value = ay_.get_data_output();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
@@ -289,12 +305,14 @@ class ConcreteMachine:
|
||||
break;
|
||||
case 0x50:
|
||||
// Set AY address.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::BC1);
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
break;
|
||||
case 0x51:
|
||||
// Set AY data.
|
||||
update_audio();
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(GI::AY38910::ControlLines(0));
|
||||
@@ -310,9 +328,6 @@ class ConcreteMachine:
|
||||
default: break;
|
||||
}
|
||||
|
||||
time_since_vdp_update_ += cycle.length;
|
||||
time_since_sn76489_update_ += cycle.length;
|
||||
|
||||
if(time_until_interrupt_ > 0) {
|
||||
time_until_interrupt_ -= cycle.length;
|
||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
||||
@@ -329,6 +344,11 @@ class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
float get_confidence() override {
|
||||
if(pc_zero_accesses_ > 1) return 0.0f;
|
||||
return confidence_counter_.get_confidence();
|
||||
}
|
||||
|
||||
private:
|
||||
inline void page_megacart(uint16_t address) {
|
||||
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_sn76489_update_;
|
||||
HalfCycles time_until_interrupt_;
|
||||
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||
int pc_zero_accesses_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -165,14 +165,15 @@ void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint
|
||||
attention_acknowledge_level_ = !(value&0x10);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
default: break;
|
||||
@@ -194,9 +195,10 @@ void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::P
|
||||
void SerialPortVIA::update_data_line() {
|
||||
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
|
||||
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"
|
||||
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_)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include "../../ConfigurationTarget.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../JoystickMachine.hpp"
|
||||
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Components/6560/6560.hpp"
|
||||
#include "../../../Components/6522/6522.hpp"
|
||||
@@ -26,6 +31,8 @@
|
||||
|
||||
#include "../../../Configurable/StandardOptions.hpp"
|
||||
|
||||
#include "../../../Analyser/Static/Commodore/Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -40,7 +47,9 @@ enum ROMSlot {
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -283,11 +292,17 @@ class Joystick: public Inputs::Joystick {
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public JoystickMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Machine {
|
||||
public Machine,
|
||||
public Sleeper::SleepObserver {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
@@ -313,17 +328,16 @@ class ConcreteMachine:
|
||||
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
tape_->set_sleep_observer(this);
|
||||
|
||||
// install a joystick
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
delete[] rom_;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
rom_fetcher_ = roms_with_names;
|
||||
|
||||
auto roms = roms_with_names(
|
||||
"Vic20",
|
||||
{
|
||||
@@ -353,24 +367,14 @@ class ConcreteMachine:
|
||||
return true;
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target &target) override final {
|
||||
if(target.loading_command.length()) {
|
||||
type_string(target.loading_command);
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target);
|
||||
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
}
|
||||
|
||||
switch(target.vic20.memory_model) {
|
||||
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()) {
|
||||
if(target->media.disks.size()) {
|
||||
// construct the 1540
|
||||
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
|
||||
|
||||
@@ -379,9 +383,12 @@ class ConcreteMachine:
|
||||
|
||||
// give it a means to obtain its ROM
|
||||
c1540_->set_rom_fetcher(rom_fetcher_);
|
||||
|
||||
// give it a little warm up
|
||||
c1540_->run_for(Cycles(2000000));
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
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;
|
||||
rom_length_ = static_cast<uint16_t>(rom_image.size());
|
||||
|
||||
rom_ = new uint8_t[0x2000];
|
||||
std::memcpy(rom_, rom_image.data(), rom_image.size());
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
|
||||
rom_ = rom_image;
|
||||
rom_.resize(0x2000);
|
||||
}
|
||||
|
||||
set_use_fast_tape();
|
||||
@@ -423,16 +429,6 @@ class ConcreteMachine:
|
||||
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() {
|
||||
set_clock_rate(1022727);
|
||||
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
|
||||
if(region_ == American || region_ == Japanese) {
|
||||
if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) {
|
||||
// NTSC
|
||||
set_ntsc_6560();
|
||||
} else {
|
||||
@@ -459,57 +455,73 @@ class ConcreteMachine:
|
||||
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_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||
memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map));
|
||||
|
||||
switch(memory_size_) {
|
||||
default: break;
|
||||
case ThreeKB:
|
||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000);
|
||||
#define set_ram(baseaddr, length) \
|
||||
write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \
|
||||
write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length);
|
||||
|
||||
// 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;
|
||||
case ThirtyTwoKB:
|
||||
write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000);
|
||||
write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000);
|
||||
case Analyser::Static::Commodore::Target::MemoryModel::EightKB:
|
||||
// An 8kb Vic-20 fills in the gap between the two blocks of RAM on an unexpanded machine.
|
||||
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;
|
||||
}
|
||||
|
||||
// install the system ROMs and VIC-visible memory
|
||||
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_));
|
||||
#undef set_ram
|
||||
|
||||
write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_));
|
||||
write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_));
|
||||
write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_));
|
||||
// all expansions also have colour RAM visible at 0x9400.
|
||||
write_to_map(processor_read_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
|
||||
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_));
|
||||
write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_));
|
||||
mos6560_->colour_memory = colour_memory_;
|
||||
// also push memory resources into the 6560 video memory map; the 6560 has only a
|
||||
// 14-bit address bus and the top bit is invested and used as bit 15 for the main
|
||||
// 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()));
|
||||
|
||||
// install the system ROM
|
||||
ROM character_rom;
|
||||
ROM kernel_rom;
|
||||
switch(region_) {
|
||||
switch(region) {
|
||||
default:
|
||||
character_rom = CharactersEnglish;
|
||||
kernel_rom = KernelPAL;
|
||||
break;
|
||||
case American:
|
||||
case Analyser::Static::Commodore::Target::Region::American:
|
||||
character_rom = CharactersEnglish;
|
||||
kernel_rom = KernelNTSC;
|
||||
break;
|
||||
case Danish:
|
||||
case Analyser::Static::Commodore::Target::Region::Danish:
|
||||
character_rom = CharactersDanish;
|
||||
kernel_rom = KernelDanish;
|
||||
break;
|
||||
case Japanese:
|
||||
case Analyser::Static::Commodore::Target::Region::Japanese:
|
||||
character_rom = CharactersJapanese;
|
||||
kernel_rom = KernelJapanese;
|
||||
break;
|
||||
case Swedish:
|
||||
case Analyser::Static::Commodore::Target::Region::Swedish:
|
||||
character_rom = CharactersSwedish;
|
||||
kernel_rom = KernelSwedish;
|
||||
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()));
|
||||
|
||||
// install the inserted ROM if there is one
|
||||
if(rom_) {
|
||||
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
|
||||
if(!rom_.empty()) {
|
||||
write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_);
|
||||
}
|
||||
}
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
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
|
||||
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
|
||||
if(isReadOperation(operation)) {
|
||||
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
|
||||
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&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address);
|
||||
}
|
||||
*value = result;
|
||||
|
||||
// This combined with the stuff below constitutes the fast tape hack. Performed here: if the
|
||||
// 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.
|
||||
// Consider applying the fast tape hack.
|
||||
if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
|
||||
if(address == 0xf7b2) {
|
||||
// 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;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
||||
|
||||
// serialise to wherever b2:b3 points
|
||||
uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8);
|
||||
const uint64_t tape_position = tape_->get_tape()->get_offset();
|
||||
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 {
|
||||
// no header found, so store end-of-tape
|
||||
user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape
|
||||
// no header found, so pretend this hack never interceded
|
||||
tape_->get_tape()->set_offset(tape_position);
|
||||
hold_tape_ = false;
|
||||
printf("Didn't find header\n");
|
||||
}
|
||||
|
||||
// clear status and the verify flag
|
||||
user_basic_memory_[0x90] = 0;
|
||||
user_basic_memory_[0x93] = 0;
|
||||
ram_[0x90] = 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) {
|
||||
uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
|
||||
if(x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||
uint16_t start_address, end_address;
|
||||
start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
|
||||
end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
|
||||
const uint64_t tape_position = tape_->get_tape()->get_offset();
|
||||
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||
if(data) {
|
||||
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
|
||||
uint8_t *data_ptr = data->data.data();
|
||||
std::size_t data_left = data->data.size();
|
||||
while(data_left && start_address != end_address) {
|
||||
uint8_t *page = processor_write_memory_map_[start_address >> 10];
|
||||
if(page) page[start_address & 0x3ff] = *data_ptr;
|
||||
data_ptr++;
|
||||
start_address++;
|
||||
data_left--;
|
||||
// perform a via-processor_write_memory_map_ memcpy
|
||||
uint8_t *data_ptr = data->data.data();
|
||||
std::size_t data_left = data->data.size();
|
||||
while(data_left && start_address != end_address) {
|
||||
uint8_t *page = processor_write_memory_map_[start_address >> 10];
|
||||
if(page) page[start_address & 0x3ff] = *data_ptr;
|
||||
data_ptr++;
|
||||
start_address++;
|
||||
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 {
|
||||
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&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&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value);
|
||||
}
|
||||
@@ -616,21 +648,18 @@ class ConcreteMachine:
|
||||
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));
|
||||
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
forceinline void flush() {
|
||||
void flush() {
|
||||
update_video();
|
||||
mos6560_->flush();
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
if(needs_configuration_) {
|
||||
needs_configuration_ = false;
|
||||
configure_memory();
|
||||
}
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
@@ -638,7 +667,7 @@ class ConcreteMachine:
|
||||
mos6560_.reset(new Vic6560());
|
||||
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
|
||||
set_pal_6560();
|
||||
set_memory_map(commodore_target_.memory_model, commodore_target_.region);
|
||||
}
|
||||
|
||||
void close_output() override final {
|
||||
@@ -682,21 +711,38 @@ class ConcreteMachine:
|
||||
allow_fast_tape_hack_ = quickload;
|
||||
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 selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::Composite);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::SVideo);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(void *component, bool is_sleeping) override {
|
||||
tape_is_sleeping_ = is_sleeping;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
private:
|
||||
void update_video() {
|
||||
mos6560_->run_for(cycles_since_mos6560_update_.flush());
|
||||
}
|
||||
Analyser::Static::Commodore::Target commodore_target_;
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
|
||||
std::vector<uint8_t> roms_[9];
|
||||
@@ -704,14 +750,11 @@ class ConcreteMachine:
|
||||
std::vector<uint8_t> character_rom_;
|
||||
std::vector<uint8_t> basic_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_;
|
||||
|
||||
uint8_t user_basic_memory_[0x0400];
|
||||
uint8_t screen_memory_[0x1000];
|
||||
uint8_t colour_memory_[0x0400];
|
||||
uint8_t ram_[0x8000];
|
||||
uint8_t colour_ram_[0x0400];
|
||||
|
||||
std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_;
|
||||
|
||||
@@ -727,13 +770,10 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
Region region_ = European;
|
||||
MemorySize memory_size_ = MemorySize::Default;
|
||||
bool needs_configuration_ = true;
|
||||
|
||||
Commodore::Vic20::KeyboardMapper keyboard_mapper_;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
Cycles cycles_since_mos6560_update_;
|
||||
std::unique_ptr<Vic6560> mos6560_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
|
||||
@@ -746,10 +786,11 @@ class ConcreteMachine:
|
||||
// Tape
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
bool use_fast_tape_hack_ = false;
|
||||
bool hold_tape_ = false;
|
||||
bool allow_fast_tape_hack_ = false;
|
||||
bool is_running_at_zero_cost_ = false;
|
||||
bool tape_is_sleeping_ = true;
|
||||
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
|
||||
|
||||
@@ -10,48 +10,19 @@
|
||||
#define Vic20_hpp
|
||||
|
||||
#include "../../../Configurable/Configurable.hpp"
|
||||
#include "../../ConfigurationTarget.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../JoystickMachine.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace Vic20 {
|
||||
|
||||
enum MemorySize {
|
||||
Default,
|
||||
ThreeKB,
|
||||
ThirtyTwoKB
|
||||
};
|
||||
|
||||
enum Region {
|
||||
American,
|
||||
Danish,
|
||||
Japanese,
|
||||
European,
|
||||
Swedish
|
||||
};
|
||||
|
||||
/// @returns The options available for a Vic-20.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public JoystickMachine::Machine,
|
||||
public Configurable::Device {
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns a Vic-20.
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace ConfigurationTarget {
|
||||
class Machine {
|
||||
public:
|
||||
/// Instructs the machine to configure itself as described by @c target and insert the included media.
|
||||
virtual void configure_as_target(const Analyser::Static::Target &target) = 0;
|
||||
virtual void configure_as_target(const Analyser::Static::Target *target) = 0;
|
||||
|
||||
/*!
|
||||
Requests that the machine insert @c media as a modification to current state
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "Electron.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
@@ -16,6 +20,7 @@
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include "../Utility/Typer.hpp"
|
||||
#include "../../Analyser/Static/Acorn/Target.hpp"
|
||||
|
||||
#include "Interrupts.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
@@ -28,12 +33,16 @@ namespace Electron {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_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:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Tape::Delegate,
|
||||
public Utility::TypeRecipient {
|
||||
@@ -52,6 +61,10 @@ class ConcreteMachine:
|
||||
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 {
|
||||
uint8_t *target = nullptr;
|
||||
switch(slot) {
|
||||
@@ -115,28 +128,30 @@ class ConcreteMachine:
|
||||
if(is_holding_shift_) set_key_state(KeyShift, true);
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target &target) override final {
|
||||
if(target.loading_command.length()) {
|
||||
type_string(target.loading_command);
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if(target.acorn.has_dfs || target.acorn.has_adfs) {
|
||||
if(acorn_target->has_dfs || acorn_target->has_adfs) {
|
||||
plus3_.reset(new Plus3);
|
||||
|
||||
if(target.acorn.has_dfs) {
|
||||
if(acorn_target->has_dfs) {
|
||||
set_rom(ROMSlot0, dfs_, true);
|
||||
}
|
||||
if(target.acorn.has_adfs) {
|
||||
if(acorn_target->has_adfs) {
|
||||
set_rom(ROMSlot4, adfs1_, true);
|
||||
set_rom(ROMSlot5, adfs2_, true);
|
||||
}
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
@@ -439,7 +454,7 @@ class ConcreteMachine:
|
||||
|
||||
Configurable::Display 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#define Electron_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#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
|
||||
Acorn Electron.
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device {
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
|
||||
@@ -15,10 +15,14 @@ using namespace Electron;
|
||||
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &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) {
|
||||
if(is_enabled_) {
|
||||
while(number_of_samples--) {
|
||||
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
|
||||
*target = static_cast<int16_t>((counter_ / (divider_+1)) * volume_);
|
||||
target++;
|
||||
counter_ = (counter_ + 1) % ((divider_+1) * 2);
|
||||
}
|
||||
|
||||
@@ -22,16 +22,19 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
|
||||
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 skip_samples(std::size_t number_of_samples);
|
||||
|
||||
static const unsigned int clock_rate_divider = 8;
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
unsigned int volume_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,30 @@
|
||||
|
||||
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:
|
||||
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.
|
||||
|
||||
|
||||
@@ -40,11 +40,13 @@
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../../Analyser/Static/MSX/Target.hpp"
|
||||
|
||||
namespace MSX {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_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 set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.defer([=] {
|
||||
level_ = enabled ? 4096 : 0;
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,7 +84,7 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
|
||||
|
||||
private:
|
||||
bool is_enabled_ = false;
|
||||
int16_t level_ = 0;
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
};
|
||||
|
||||
@@ -138,6 +144,13 @@ class ConcreteMachine:
|
||||
ay_.set_port_handler(&ay_port_handler_);
|
||||
speaker_.set_input_rate(3579545.0f / 2.0f);
|
||||
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 {
|
||||
@@ -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.
|
||||
if(!target.media.disks.empty()) {
|
||||
if(msx_target->has_disk_drive) {
|
||||
map(2, 0, 0x4000, 0x2000);
|
||||
unmap(2, 0x6000, 0x2000);
|
||||
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
|
||||
}
|
||||
|
||||
// Insert the media.
|
||||
insert_media(target.media);
|
||||
insert_media(target->media);
|
||||
|
||||
// Type whatever has been requested.
|
||||
if(target.loading_command.length()) {
|
||||
type_string(target.loading_command);
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +297,17 @@ class ConcreteMachine:
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
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;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
@@ -459,23 +485,12 @@ class ConcreteMachine:
|
||||
if(!tape_player_is_sleeping_)
|
||||
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) {
|
||||
time_until_interrupt_ -= total_length;
|
||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -554,7 +569,7 @@ class ConcreteMachine:
|
||||
|
||||
Configurable::Display 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "Microdisc.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
@@ -26,6 +30,8 @@
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Oric/Target.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -38,7 +44,7 @@ enum ROM {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_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:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
@@ -205,6 +215,10 @@ class ConcreteMachine:
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
// 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 {
|
||||
auto roms = roms_with_names(
|
||||
@@ -249,23 +263,21 @@ class ConcreteMachine:
|
||||
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
|
||||
void configure_as_target(const Analyser::Static::Target &target) override final {
|
||||
if(target.oric.has_microdisc) {
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target);
|
||||
|
||||
if(oric_target->has_microdrive) {
|
||||
microdisc_is_enabled_ = true;
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
}
|
||||
|
||||
if(target.loading_command.length()) {
|
||||
type_string(target.loading_command);
|
||||
if(target->loading_command.length()) {
|
||||
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_)));
|
||||
|
||||
is_using_basic11_ = true;
|
||||
@@ -281,7 +293,7 @@ class ConcreteMachine:
|
||||
tape_speed_address_ = 0x67;
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
@@ -376,7 +388,7 @@ class ConcreteMachine:
|
||||
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
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 {
|
||||
@@ -449,10 +461,14 @@ class ConcreteMachine:
|
||||
|
||||
Configurable::Display 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 selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#define Oric_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
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.
|
||||
*/
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device {
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
);
|
||||
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));
|
||||
}
|
||||
|
||||
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
|
||||
output_device_ = output_device;
|
||||
crt_->set_output_device(output_device);
|
||||
void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) {
|
||||
video_signal_ = video_signal;
|
||||
crt_->set_video_signal(video_signal);
|
||||
}
|
||||
|
||||
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(pixel_target_) {
|
||||
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[1] = static_cast<uint8_t>(ink_ ^ inverse_mask);
|
||||
} else {
|
||||
@@ -183,7 +183,7 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
pixel_target_[0] = pixel_target_[1] =
|
||||
pixel_target_[2] = pixel_target_[3] =
|
||||
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;
|
||||
|
||||
@@ -20,7 +20,7 @@ class VideoOutput {
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
void run_for(const Cycles cycles);
|
||||
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:
|
||||
uint8_t *ram_;
|
||||
@@ -33,7 +33,7 @@ class VideoOutput {
|
||||
// Output target and device
|
||||
uint16_t *pixel_target_;
|
||||
uint16_t colour_forms_[8];
|
||||
Outputs::CRT::OutputDevice output_device_;
|
||||
Outputs::CRT::VideoSignal video_signal_;
|
||||
|
||||
// Registers
|
||||
uint8_t ink_, paper_;
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
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;
|
||||
::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::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;
|
||||
@@ -71,7 +71,7 @@ namespace {
|
||||
if(targets.size() > 1) {
|
||||
std::vector<std::unique_ptr<Machine::DynamicMachine>> machines;
|
||||
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.
|
||||
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.
|
||||
return MachineForTarget(*targets.front(), rom_fetcher, error);
|
||||
return MachineForTarget(targets.front().get(), rom_fetcher, error);
|
||||
}
|
||||
|
||||
std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) {
|
||||
|
||||
@@ -47,7 +47,7 @@ class CharacterMapper {
|
||||
*/
|
||||
class Typer {
|
||||
public:
|
||||
class Delegate: public KeyboardMachine::Machine {
|
||||
class Delegate: public KeyboardMachine::KeyActions {
|
||||
public:
|
||||
virtual void typer_reset(Typer *typer) = 0;
|
||||
};
|
||||
@@ -56,7 +56,6 @@ class Typer {
|
||||
|
||||
void run_for(const HalfCycles duration);
|
||||
bool type_next_character();
|
||||
|
||||
bool is_completed();
|
||||
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
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));
|
||||
@@ -101,7 +100,6 @@ class TypeRecipient: public Typer::Delegate {
|
||||
typer_ = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
|
||||
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
|
||||
std::unique_ptr<Typer> typer_;
|
||||
|
||||
@@ -22,6 +22,7 @@ Video::Video() :
|
||||
"}");
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
|
||||
@@ -18,6 +23,10 @@
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "../../Analyser/Static/ZX8081/Target.hpp"
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
@@ -31,6 +40,11 @@ namespace {
|
||||
const unsigned int ZX8081ClockRate = 3250000;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Quiksilva sound support:
|
||||
// 7FFFh.W PSG index
|
||||
// 7FFEh.R/W PSG data
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
enum ROMType: uint8_t {
|
||||
@@ -44,20 +58,32 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
}
|
||||
|
||||
template<bool is_zx81> class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
z80_(*this),
|
||||
tape_player_(ZX8081ClockRate) {
|
||||
tape_player_(ZX8081ClockRate),
|
||||
ay_(audio_queue_),
|
||||
speaker_(ay_) {
|
||||
set_clock_rate(ZX8081ClockRate);
|
||||
speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f);
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
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;
|
||||
time_since_ay_update_ += cycle.length;
|
||||
|
||||
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
|
||||
video_->run_for(vsync_start_ - previous_counter);
|
||||
@@ -94,7 +120,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
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;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
@@ -106,6 +132,15 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
if(vsync_) line_counter_ = 0;
|
||||
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;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input: {
|
||||
@@ -121,6 +156,13 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
|
||||
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;
|
||||
} break;
|
||||
|
||||
@@ -144,7 +186,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
if(has_latched_video_byte_) {
|
||||
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_) {
|
||||
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
|
||||
} else {
|
||||
@@ -159,10 +201,10 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
// Check for use of the fast tape hack.
|
||||
if(use_fast_tape_hack_ && address == tape_trap_address_) {
|
||||
uint64_t prior_offset = tape_player_.get_tape()->get_offset();
|
||||
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
const uint64_t prior_offset = tape_player_.get_tape()->get_offset();
|
||||
const int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
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);
|
||||
*cycle.value = 0x00;
|
||||
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_) {
|
||||
*cycle.value = rom_[address & rom_mask_];
|
||||
} 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
|
||||
// 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);
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
forceinline void flush() {
|
||||
video_->flush();
|
||||
if(is_zx81) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return nullptr;
|
||||
return is_zx81 ? &speaker_ : nullptr;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target &target) override final {
|
||||
is_zx81_ = target.zx8081.isZX81;
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
|
||||
is_zx81_ = zx8081_target->is_ZX81;
|
||||
if(is_zx81_) {
|
||||
rom_ = zx81_rom_;
|
||||
tape_trap_address_ = 0x37c;
|
||||
@@ -249,7 +295,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
automatic_tape_motor_start_address_ = 0x0340;
|
||||
automatic_tape_motor_end_address_ = 0x03c3;
|
||||
} else {
|
||||
rom_ = zx80_rom_;
|
||||
rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_;
|
||||
tape_trap_address_ = 0x220;
|
||||
tape_return_address_ = 0x248;
|
||||
vsync_start_ = HalfCycles(26);
|
||||
@@ -259,18 +305,18 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
|
||||
|
||||
switch(target.zx8081.memory_model) {
|
||||
case Analyser::Static::ZX8081MemoryModel::Unexpanded:
|
||||
switch(zx8081_target->memory_model) {
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
|
||||
ram_.resize(1024);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 1023;
|
||||
break;
|
||||
case Analyser::Static::ZX8081MemoryModel::SixteenKB:
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
|
||||
ram_.resize(16384);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 16383;
|
||||
break;
|
||||
case Analyser::Static::ZX8081MemoryModel::SixtyFourKB:
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
|
||||
ram_.resize(65536);
|
||||
ram_base_ = 8192;
|
||||
ram_mask_ = 65535;
|
||||
@@ -278,11 +324,11 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
if(target.loading_command.length()) {
|
||||
type_string(target.loading_command);
|
||||
if(target->loading_command.length()) {
|
||||
type_string(target->loading_command);
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
@@ -301,7 +347,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
|
||||
// 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 {
|
||||
auto roms = roms_with_names(
|
||||
const auto roms = roms_with_names(
|
||||
"ZX8081",
|
||||
{
|
||||
"zx80.rom", "zx81.rom",
|
||||
@@ -320,8 +366,8 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
if(isPressed)
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
if(is_pressed)
|
||||
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
|
||||
else
|
||||
key_states_[key >> 8] |= static_cast<uint8_t>(key);
|
||||
@@ -443,6 +489,34 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
inline void update_sync() {
|
||||
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;
|
||||
|
||||
// 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.
|
||||
if(target_hint.zx8081.isZX81)
|
||||
if(hint->is_ZX81)
|
||||
return new ZX8081::ConcreteMachine<true>();
|
||||
else
|
||||
return new ZX8081::ConcreteMachine<false>();
|
||||
|
||||
@@ -10,24 +10,18 @@
|
||||
#define ZX8081_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
/// @returns The options available for a ZX80 or ZX81.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
class Machine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device {
|
||||
class Machine {
|
||||
public:
|
||||
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 bool get_tape_is_playing() = 0;
|
||||
|
||||
@@ -157,7 +157,6 @@
|
||||
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; };
|
||||
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.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 */; };
|
||||
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
|
||||
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 */; };
|
||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.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 */; };
|
||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
|
||||
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 */; };
|
||||
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
|
||||
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */; };
|
||||
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */; };
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
|
||||
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
|
||||
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
|
||||
@@ -605,6 +605,7 @@
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.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 */; };
|
||||
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -757,7 +756,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -780,6 +778,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -962,7 +961,6 @@
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -977,9 +975,11 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BA141BC2072E8A400A31EC9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
|
||||
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
|
||||
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
|
||||
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
|
||||
@@ -1327,9 +1327,16 @@
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1368,7 +1375,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1574,11 +1580,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
|
||||
4B2A539D1D117D36003C6002 /* CSVic20.h */,
|
||||
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
|
||||
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */,
|
||||
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
|
||||
4B2A539E1D117D36003C6002 /* CSVic20.mm */,
|
||||
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */,
|
||||
);
|
||||
path = Wrappers;
|
||||
@@ -1874,14 +1877,12 @@
|
||||
4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */,
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
|
||||
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
|
||||
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */,
|
||||
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
|
||||
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */,
|
||||
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
|
||||
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
|
||||
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
|
||||
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */,
|
||||
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
|
||||
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */,
|
||||
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */,
|
||||
);
|
||||
path = Documents;
|
||||
@@ -2164,6 +2165,7 @@
|
||||
4B8944EE201967B4007DE474 /* File.hpp */,
|
||||
4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4B8944EF201967B4007DE474 /* Tape.hpp */,
|
||||
4BE32313205327D7006EF799 /* Target.hpp */,
|
||||
);
|
||||
path = Acorn;
|
||||
sourceTree = "<group>";
|
||||
@@ -2173,6 +2175,7 @@
|
||||
children = (
|
||||
4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */,
|
||||
4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4BE3231720532CC0006EF799 /* Target.hpp */,
|
||||
);
|
||||
path = Atari;
|
||||
sourceTree = "<group>";
|
||||
@@ -2184,6 +2187,7 @@
|
||||
4B8944F9201967B4007DE474 /* Tape.cpp */,
|
||||
4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4B8944F8201967B4007DE474 /* Tape.hpp */,
|
||||
4BE3231620532BED006EF799 /* Target.hpp */,
|
||||
);
|
||||
path = Oric;
|
||||
sourceTree = "<group>";
|
||||
@@ -2199,6 +2203,7 @@
|
||||
4B8944FE201967B4007DE474 /* File.hpp */,
|
||||
4B8944FD201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4B8944FF201967B4007DE474 /* Tape.hpp */,
|
||||
4BE3231520532AA7006EF799 /* Target.hpp */,
|
||||
);
|
||||
path = Commodore;
|
||||
sourceTree = "<group>";
|
||||
@@ -2208,6 +2213,7 @@
|
||||
children = (
|
||||
4B894506201967B4007DE474 /* StaticAnalyser.cpp */,
|
||||
4B894505201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4BE3231220532443006EF799 /* Target.hpp */,
|
||||
);
|
||||
path = ZX8081;
|
||||
sourceTree = "<group>";
|
||||
@@ -2234,6 +2240,7 @@
|
||||
4B047075201ABC180047AB0D /* Cartridge.hpp */,
|
||||
4B894510201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4B894511201967B4007DE474 /* Tape.hpp */,
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */,
|
||||
);
|
||||
path = MSX;
|
||||
sourceTree = "<group>";
|
||||
@@ -2243,6 +2250,7 @@
|
||||
children = (
|
||||
4B894516201967B4007DE474 /* StaticAnalyser.cpp */,
|
||||
4B894515201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4BE32314205328FF006EF799 /* Target.hpp */,
|
||||
);
|
||||
path = AmstradCPC;
|
||||
sourceTree = "<group>";
|
||||
@@ -2257,6 +2265,16 @@
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BA141C02072E8B300A31EC9 /* MachinePicker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */,
|
||||
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */,
|
||||
);
|
||||
name = MachinePicker;
|
||||
path = "New Group";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2620,11 +2638,12 @@
|
||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
|
||||
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4B2A538F1D117D36003C6002 /* Audio */,
|
||||
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
||||
4B55CE551C3B7D360093A61B /* Documents */,
|
||||
4B2A53921D117D36003C6002 /* Machine */,
|
||||
4BA141C02072E8B300A31EC9 /* MachinePicker */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||
@@ -2960,6 +2979,7 @@
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
@@ -3054,7 +3074,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0900;
|
||||
LastUpgradeCheck = 0930;
|
||||
ORGANIZATIONNAME = "Thomas Harte";
|
||||
TargetAttributes = {
|
||||
4B055A691FAE763F0060FFFF = {
|
||||
@@ -3111,16 +3131,16 @@
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */,
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
||||
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */,
|
||||
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
|
||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
|
||||
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
|
||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
||||
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
4B38F34F1F2EC6BA00D9235D /* AmstradCPCOptions.xib in Resources */,
|
||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
||||
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
|
||||
);
|
||||
@@ -3648,6 +3668,7 @@
|
||||
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
|
||||
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
||||
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */,
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||
@@ -3803,14 +3824,6 @@
|
||||
name = OricOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B38F34D1F2EC6BA00D9235D /* AmstradCPCOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B38F34E1F2EC6BA00D9235D /* Base */,
|
||||
);
|
||||
name = AmstradCPCOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -3835,12 +3848,12 @@
|
||||
name = QuickLoadCompositeOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */ = {
|
||||
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B8FE21A1DA19D5F0090D3CE /* Base */,
|
||||
4BA141BC2072E8A400A31EC9 /* Base */,
|
||||
);
|
||||
name = Vic20Options.xib;
|
||||
name = MachinePicker.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = {
|
||||
@@ -3851,6 +3864,14 @@
|
||||
name = MainMenu.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4BD61663206B2AC700236112 /* Base */,
|
||||
);
|
||||
name = QuickLoadOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -3904,12 +3925,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_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_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -3957,12 +3980,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_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_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,9 +26,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -74,7 +73,6 @@
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
disableMainThreadChecker = "YES"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -3,46 +3,55 @@
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"filename" : "Icon16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"filename" : "Icon32.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"filename" : "Icon32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"filename" : "Icon64.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"filename" : "Icon128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"filename" : "Icon256.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"filename" : "Icon256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"filename" : "Icon512.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"filename" : "Icon512.png",
|
||||
"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 |
@@ -1,10 +1,12 @@
|
||||
<?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>
|
||||
<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>
|
||||
<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>
|
||||
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
|
||||
<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"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="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"/>
|
||||
<value key="minSize" type="size" width="228" height="171"/>
|
||||
<view key="contentView" id="gIp-Ho-8D9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<openGLView 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"/>
|
||||
<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="600" height="450"/>
|
||||
</openGLView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@@ -93,30 +94,30 @@
|
||||
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
|
||||
<menuItem title="Save…" enabled="NO" keyEquivalent="s" id="pxx-59-PXV">
|
||||
<connections>
|
||||
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
|
||||
<menuItem title="Save As…" enabled="NO" keyEquivalent="S" id="Bw7-FT-i3A">
|
||||
<connections>
|
||||
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Revert to Saved" id="KaW-ft-85H">
|
||||
<menuItem title="Revert to Saved" enabled="NO" id="KaW-ft-85H">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<connections>
|
||||
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
|
||||
<menuItem title="Print…" enabled="NO" keyEquivalent="p" id="aTl-1u-JFS">
|
||||
<connections>
|
||||
<action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/>
|
||||
</connections>
|
||||
@@ -128,23 +129,23 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||
</connections>
|
||||
@@ -154,19 +155,19 @@
|
||||
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<connections>
|
||||
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||
<menuItem title="Delete" enabled="NO" id="pa3-QI-u2k">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||
</connections>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,7 +17,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="83"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -40,7 +40,7 @@
|
||||
<menu key="menu" id="L06-TO-EF0">
|
||||
<items>
|
||||
<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>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,7 +17,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="83"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -34,13 +34,14 @@
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n">
|
||||
<rect key="frame" x="18" y="17" width="165" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="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"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="L06-TO-EF0">
|
||||
<items>
|
||||
<menuItem title="Monitor" state="on" id="tJM-kX-gaK"/>
|
||||
<menuItem title="Television" id="fFm-fS-rWG"/>
|
||||
<menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/>
|
||||
<menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/>
|
||||
<menuItem title="Television" tag="1" id="fFm-fS-rWG"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -34,9 +34,9 @@
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
|
||||
<constraint 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"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -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>
|
||||
@@ -6,7 +6,6 @@
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSVic20.h"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
#import "CSStaticAnalyser.h"
|
||||
|
||||
@@ -28,6 +28,7 @@ class MachineDocument:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var optionsPanelNibName: String?
|
||||
|
||||
func aspectRatio() -> NSSize {
|
||||
return NSSize(width: 4.0, height: 3.0)
|
||||
@@ -48,43 +49,64 @@ class MachineDocument:
|
||||
|
||||
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
|
||||
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!) {
|
||||
setupClockRate()
|
||||
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
|
||||
// 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!) {
|
||||
bestEffortLock.lock()
|
||||
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
|
||||
bestEffortLock.unlock()
|
||||
fileprivate func setupMachineOutput() {
|
||||
if let machine = self.machine, let openGLView = self.openGLView {
|
||||
// establish the output aspect ratio and audio
|
||||
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
|
||||
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
|
||||
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.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
|
||||
}
|
||||
|
||||
bestEffortLock.lock()
|
||||
self.bestEffortUpdater?.clockRate = self.machine.clockRate
|
||||
bestEffortLock.unlock()
|
||||
}
|
||||
|
||||
override func close() {
|
||||
@@ -105,9 +123,11 @@ class MachineDocument:
|
||||
optionsPanel = nil
|
||||
|
||||
bestEffortLock.lock()
|
||||
bestEffortUpdater!.delegate = nil
|
||||
bestEffortUpdater!.flush()
|
||||
bestEffortUpdater = nil
|
||||
if let bestEffortUpdater = bestEffortUpdater {
|
||||
bestEffortUpdater.delegate = nil
|
||||
bestEffortUpdater.flush()
|
||||
self.bestEffortUpdater = nil
|
||||
}
|
||||
bestEffortLock.unlock()
|
||||
|
||||
actionLock.lock()
|
||||
@@ -125,15 +145,12 @@ class MachineDocument:
|
||||
func configureAs(_ analysis: CSStaticAnalyser) {
|
||||
if let machine = CSMachine(analyser: analysis) {
|
||||
self.machine = machine
|
||||
}
|
||||
|
||||
if let optionsPanelNibName = analysis.optionsPanelNibName {
|
||||
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
|
||||
self.optionsPanel.machine = self.machine
|
||||
showOptions(self)
|
||||
self.optionsPanelNibName = analysis.optionsPanelNibName
|
||||
setupMachineOutput()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var shouldShowNewMachinePanel = false
|
||||
override func read(from url: URL, ofType typeName: String) throws {
|
||||
if let analyser = CSStaticAnalyser(fileAt: url) {
|
||||
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
|
||||
func paste(_ sender: AnyObject!) {
|
||||
let pasteboard = NSPasteboard.general
|
||||
@@ -152,21 +175,10 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
// MARK: CSBestEffortUpdaterDelegate
|
||||
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
|
||||
runForNumberOfCycles(Int32(cycles))
|
||||
}
|
||||
|
||||
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()
|
||||
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
|
||||
if actionLock.try() {
|
||||
self.machine.run(forInterval: duration)
|
||||
actionLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,21 +218,42 @@ class MachineDocument:
|
||||
|
||||
// MARK: Input management
|
||||
func windowDidResignKey(_ notification: Notification) {
|
||||
self.machine.clearAllKeys()
|
||||
if let machine = self.machine {
|
||||
machine.clearAllKeys()
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
self.machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
|
||||
self.machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control))
|
||||
self.machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command))
|
||||
self.machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option))
|
||||
if let machine = self.machine {
|
||||
machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
|
||||
machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control))
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
return prefixedUserDefaultsKey("displayType")
|
||||
}
|
||||
@IBOutlet var displayTypeButton: NSPopUpButton?
|
||||
@IBAction func setDisplayType(_ sender: NSPopUpButton!) {
|
||||
machine.useCompositeOutput = (sender.indexOfSelectedItem == 1)
|
||||
UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey)
|
||||
if let selectedItem = sender.selectedItem {
|
||||
machine.videoSignal = signalForTag(tag: selectedItem.tag)
|
||||
UserDefaults.standard.set(selectedItem.tag, forKey: self.displayTypeUserDefaultsKey)
|
||||
}
|
||||
}
|
||||
|
||||
func establishStoredOptions() {
|
||||
@@ -50,8 +61,21 @@ class MachinePanel: NSPanel {
|
||||
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
|
||||
}
|
||||
|
||||
let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey)
|
||||
machine.useCompositeOutput = (displayType == 1)
|
||||
self.displayTypeButton?.selectItem(at: displayType)
|
||||
if let displayTypeButton = self.displayTypeButton {
|
||||
// Enable or disable options as per machine support.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,15 @@
|
||||
|
||||
@class CSMachine;
|
||||
@protocol CSMachineDelegate
|
||||
- (void)machineDidChangeClockRate:(CSMachine *)machine;
|
||||
- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine;
|
||||
- (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine;
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
|
||||
CSMachineVideoSignalComposite,
|
||||
CSMachineVideoSignalSVideo,
|
||||
CSMachineVideoSignalRGB
|
||||
};
|
||||
|
||||
// Deliberately low; to ensure CSMachine has been declared as an @class already.
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSZX8081.h"
|
||||
@@ -33,7 +38,7 @@
|
||||
*/
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles;
|
||||
- (void)runForInterval:(NSTimeInterval)interval;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
|
||||
@@ -48,17 +53,16 @@
|
||||
@property (nonatomic, readonly) CSOpenGLView *view;
|
||||
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
|
||||
|
||||
@property (nonatomic, readonly) double clockRate;
|
||||
@property (nonatomic, readonly) BOOL clockIsUnlimited;
|
||||
|
||||
@property (nonatomic, readonly) NSString *userDefaultsPrefix;
|
||||
|
||||
- (void)paste:(NSString *)string;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) BOOL useCompositeOutput;
|
||||
@property (nonatomic, assign) CSMachineVideoSignal videoSignal;
|
||||
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
|
||||
|
||||
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
|
||||
|
||||
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
|
||||
@property (nonatomic, readonly) CSAtari2600 *atari2600;
|
||||
@property (nonatomic, readonly) CSZX8081 *zx8081;
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
|
||||
@interface CSMachine() <CSFastLoading>
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)machineDidChangeClockRate;
|
||||
- (void)machineDidChangeClockIsUnlimited;
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
||||
@end
|
||||
|
||||
struct LockProtectedDelegate {
|
||||
@@ -37,29 +36,20 @@ struct 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];
|
||||
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate {
|
||||
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
|
||||
void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override {
|
||||
[machineAccessLock lock];
|
||||
[machine machineDidChangeClockRate];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
|
||||
[machineAccessLock lock];
|
||||
[machine machineDidChangeClockIsUnlimited];
|
||||
[machine speakerDidChangeInputClock:speaker];
|
||||
[machineAccessLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
MachineDelegate _machineDelegate;
|
||||
NSLock *_delegateMachineAccessLock;
|
||||
|
||||
CSStaticAnalyser *_analyser;
|
||||
@@ -77,12 +67,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
|
||||
_delegateMachineAccessLock = [[NSLock alloc] init];
|
||||
|
||||
_machineDelegate.machine = self;
|
||||
_speakerDelegate.machine = self;
|
||||
_machineDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
|
||||
_machine->crt_machine()->set_delegate(&_machineDelegate);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -91,12 +77,8 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
|
||||
}
|
||||
|
||||
- (void)machineDidChangeClockRate {
|
||||
[self.delegate machineDidChangeClockRate:self];
|
||||
}
|
||||
|
||||
- (void)machineDidChangeClockIsUnlimited {
|
||||
[self.delegate machineDidChangeClockIsUnlimited:self];
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker {
|
||||
[self.delegate machineSpeakerDidChangeInputClock:self];
|
||||
}
|
||||
|
||||
- (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
|
||||
// call into the machine, not just the pointer access.
|
||||
[_delegateMachineAccessLock lock];
|
||||
_machineDelegate.machine = nil;
|
||||
_speakerDelegate.machine = nil;
|
||||
[_delegateMachineAccessLock unlock];
|
||||
|
||||
[_view performWithGLContext:^{
|
||||
@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) {
|
||||
_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);
|
||||
}
|
||||
|
||||
- (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 {
|
||||
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
|
||||
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();
|
||||
if(!configurable_device) return;
|
||||
|
||||
@synchronized(self) {
|
||||
_useCompositeOutput = useCompositeOutput;
|
||||
_videoSignal = videoSignal;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
- (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 {
|
||||
Configurable::Device *configurable_device = _machine->configurable_device();
|
||||
if(!configurable_device) return;
|
||||
|
||||
@@ -10,10 +10,39 @@
|
||||
|
||||
@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
|
||||
|
||||
- (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 *displayName;
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
|
||||
#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"
|
||||
|
||||
@implementation CSStaticAnalyser {
|
||||
@@ -32,6 +39,120 @@
|
||||
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 {
|
||||
switch(_targets.front()->machine) {
|
||||
case Analyser::Machine::AmstradCPC: return nil;
|
||||
@@ -39,7 +160,7 @@
|
||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
|
||||
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";
|
||||
default: return nil;
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
}
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@synchronized(_machine) {
|
||||
_atari2600->set_switch_is_enabled(toggleSwitch, false);
|
||||
@synchronized(self->_machine) {
|
||||
self->_atari2600->set_switch_is_enabled(toggleSwitch, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user