mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
190 Commits
2020-01-15
...
2020-02-16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e1b245cd8 | ||
|
|
5400c47f07 | ||
|
|
4153442703 | ||
|
|
5e4b721e97 | ||
|
|
aca41ac089 | ||
|
|
01a883e669 | ||
|
|
1e4356f83a | ||
|
|
545a6177bb | ||
|
|
50d356be2f | ||
|
|
9835e800ec | ||
|
|
5242362f31 | ||
|
|
808e4e8537 | ||
|
|
43740a4b2f | ||
|
|
f99d672237 | ||
|
|
337cb4fb86 | ||
|
|
90856a0e7a | ||
|
|
ea1c8a3b81 | ||
|
|
d55d077a95 | ||
|
|
f760a68173 | ||
|
|
e66a3523b6 | ||
|
|
89d6b85b83 | ||
|
|
e02d109864 | ||
|
|
743981e9ad | ||
|
|
49b8e771b5 | ||
|
|
dde672701f | ||
|
|
9ca2d8f9f2 | ||
|
|
fd786412aa | ||
|
|
eb88c7cfba | ||
|
|
e1892ff370 | ||
|
|
763159a6f6 | ||
|
|
6810a6ee58 | ||
|
|
65e6c3a9fe | ||
|
|
dcbbf988c1 | ||
|
|
199cafebcf | ||
|
|
555d807d76 | ||
|
|
003c6ad11b | ||
|
|
dc77d87427 | ||
|
|
cfc44cf778 | ||
|
|
3df99788ff | ||
|
|
3600d2d193 | ||
|
|
5f661adb7f | ||
|
|
109d072cb6 | ||
|
|
0c1c5a0ab8 | ||
|
|
e01c66fd65 | ||
|
|
9f32fa7f5b | ||
|
|
91a3d42919 | ||
|
|
3cb6bbf771 | ||
|
|
452e281009 | ||
|
|
3da948db52 | ||
|
|
0c2f77305f | ||
|
|
05bcd73f82 | ||
|
|
654f5b0478 | ||
|
|
886d923e30 | ||
|
|
6624cb7a78 | ||
|
|
6147134423 | ||
|
|
bf6bc7c684 | ||
|
|
0b0a7e241b | ||
|
|
705d14259c | ||
|
|
f1cd35fa16 | ||
|
|
6bda4034c6 | ||
|
|
b04daca98e | ||
|
|
85dcdbfe9e | ||
|
|
24340d1d4f | ||
|
|
6ae42d07a7 | ||
|
|
2ea1e059a8 | ||
|
|
b5d6126a2d | ||
|
|
dac217c98c | ||
|
|
c26c8992ae | ||
|
|
b76a5870b3 | ||
|
|
7c0f3bb237 | ||
|
|
f615d096ca | ||
|
|
09132306e4 | ||
|
|
f95b07efea | ||
|
|
14d976eecb | ||
|
|
e1cbad0b6d | ||
|
|
e7410b8ed8 | ||
|
|
5caf74b930 | ||
|
|
b41920990f | ||
|
|
709c229cd7 | ||
|
|
01fd1b1a2e | ||
|
|
96769c52f6 | ||
|
|
cf9729c74f | ||
|
|
0f2783075f | ||
|
|
256f4a6679 | ||
|
|
0310f94f0c | ||
|
|
085529ed72 | ||
|
|
8aabf1b374 | ||
|
|
ff39f71ca0 | ||
|
|
019474300d | ||
|
|
af976b8b3d | ||
|
|
f3db1a0c60 | ||
|
|
ce28213a5e | ||
|
|
f9ce50d2bb | ||
|
|
ee16095863 | ||
|
|
f0a6e0f3d5 | ||
|
|
8c4fb0f688 | ||
|
|
baa51853c4 | ||
|
|
0e29c6b0ab | ||
|
|
1b27eedf6b | ||
|
|
8b1f183198 | ||
|
|
4766ec55fe | ||
|
|
c5edc879b6 | ||
|
|
65309e60c4 | ||
|
|
5c4623e9f7 | ||
|
|
2c0cab9e4d | ||
|
|
d0117556d1 | ||
|
|
b1ff031b54 | ||
|
|
7e8405e68a | ||
|
|
c8fd00217d | ||
|
|
9d340599a6 | ||
|
|
8e094598ca | ||
|
|
189122ab84 | ||
|
|
4b53f6a9f0 | ||
|
|
561e149058 | ||
|
|
5975fc8e63 | ||
|
|
7316a3aa88 | ||
|
|
50be991415 | ||
|
|
52e49439a6 | ||
|
|
6bcdd3177d | ||
|
|
83dbd257e1 | ||
|
|
b514756272 | ||
|
|
7e4c13c43e | ||
|
|
79bb0f8222 | ||
|
|
43bf6aca67 | ||
|
|
03d23aad41 | ||
|
|
c398aa60c1 | ||
|
|
9666193c67 | ||
|
|
3f57020b00 | ||
|
|
294e09f275 | ||
|
|
ba516387ba | ||
|
|
2103e1b470 | ||
|
|
7bac439e95 | ||
|
|
9136917f00 | ||
|
|
6802318784 | ||
|
|
428d141bc9 | ||
|
|
a86fb33789 | ||
|
|
beefb70f75 | ||
|
|
3c6a00dc3c | ||
|
|
8404409c0d | ||
|
|
a5f285b4ce | ||
|
|
9d97a294a7 | ||
|
|
56448373ae | ||
|
|
a71c5946f0 | ||
|
|
e7fff6e123 | ||
|
|
82e5def7c4 | ||
|
|
d97a073d1b | ||
|
|
e74f37d6ed | ||
|
|
3aa2c297a2 | ||
|
|
290db67f09 | ||
|
|
4de121142b | ||
|
|
3c760e585a | ||
|
|
8adb2283b5 | ||
|
|
cb61e84868 | ||
|
|
8349005c4b | ||
|
|
a2847f4f8e | ||
|
|
add3ebcb44 | ||
|
|
98daad45c7 | ||
|
|
1b4b6b0aee | ||
|
|
8f94da9daf | ||
|
|
357137918d | ||
|
|
b0f7b762af | ||
|
|
da3ee381f4 | ||
|
|
d27d14d2b0 | ||
|
|
b0326530d6 | ||
|
|
c2bd5be51a | ||
|
|
84f5feab70 | ||
|
|
4b2c68c3d3 | ||
|
|
5391a699a4 | ||
|
|
f3f8345e5e | ||
|
|
c755411636 | ||
|
|
f02759b76b | ||
|
|
f34ddce28f | ||
|
|
50348c9fe7 | ||
|
|
3bfeebf2a1 | ||
|
|
dca79ea10e | ||
|
|
b7fd4de32f | ||
|
|
78d08278ed | ||
|
|
d4be052e76 | ||
|
|
d674fd0e67 | ||
|
|
229b7b36ed | ||
|
|
8a8b8db5d1 | ||
|
|
d30f83871d | ||
|
|
1422f8a93a | ||
|
|
f0da75f8e9 | ||
|
|
cb8a7a4137 | ||
|
|
efd684dc56 | ||
|
|
aeac6b5888 | ||
|
|
ecb5807ec0 | ||
|
|
942986aadc | ||
|
|
1f539822ee |
@@ -22,7 +22,7 @@ namespace Dynamic {
|
||||
class ConfidenceCounter: public ConfidenceSource {
|
||||
public:
|
||||
/*! @returns The computed probability, based on the history of events. */
|
||||
float get_confidence() override;
|
||||
float get_confidence() final;
|
||||
|
||||
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
|
||||
void add_hit();
|
||||
|
||||
@@ -32,7 +32,7 @@ class ConfidenceSummary: public ConfidenceSource {
|
||||
const std::vector<float> &weights);
|
||||
|
||||
/*! @returns The weighted sum of all sources. */
|
||||
float get_confidence() override;
|
||||
float get_confidence() final;
|
||||
|
||||
private:
|
||||
std::vector<ConfidenceSource *> sources_;
|
||||
|
||||
@@ -60,12 +60,19 @@ void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target)
|
||||
if(crt_machine) crt_machine->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus MultiCRTMachine::get_scan_status() const {
|
||||
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
|
||||
if(crt_machine) crt_machine->get_scan_status();
|
||||
|
||||
return Outputs::Display::ScanStatus();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||
perform_parallel([=](::CRTMachine::Machine *machine) {
|
||||
perform_parallel([duration](::CRTMachine::Machine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
@@ -75,7 +82,7 @@ void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||
void MultiCRTMachine::did_change_machine_order() {
|
||||
if(scan_target_) scan_target_->will_change_owner();
|
||||
|
||||
perform_serial([=](::CRTMachine::Machine *machine) {
|
||||
perform_serial([](::CRTMachine::Machine *machine) {
|
||||
machine->set_scan_target(nullptr);
|
||||
});
|
||||
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
|
||||
|
||||
@@ -53,12 +53,13 @@ class MultiCRTMachine: public CRTMachine::Machine {
|
||||
}
|
||||
|
||||
// Below is the standard CRTMachine::Machine interface; see there for documentation.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
|
||||
Outputs::Speaker::Speaker *get_speaker() override;
|
||||
void run_for(Time::Seconds duration) override;
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
void run_for(Time::Seconds duration) final;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles cycles) override {}
|
||||
void run_for(const Cycles cycles) final {}
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
|
||||
@@ -28,10 +28,10 @@ class MultiConfigurable: public Configurable::Device {
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
|
||||
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
|
||||
Configurable::SelectionSet get_accurate_selections() override;
|
||||
Configurable::SelectionSet get_user_friendly_selections() override;
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final;
|
||||
void set_selections(const Configurable::SelectionSet &selection_by_option) final;
|
||||
Configurable::SelectionSet get_accurate_selections() final;
|
||||
Configurable::SelectionSet get_user_friendly_selections() final;
|
||||
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
|
||||
@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Input> &get_inputs() override {
|
||||
std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
@@ -40,19 +40,19 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, bool is_active) override {
|
||||
void set_input(const Input &digital_input, bool is_active) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, float value) override {
|
||||
void set_input(const Input &digital_input, float value) final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_inputs() override {
|
||||
void reset_all_inputs() final {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
@@ -32,10 +32,10 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
|
||||
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) override;
|
||||
void reset_all_keys() override;
|
||||
const std::set<Key> &observed_keys() override;
|
||||
bool is_exclusive() override;
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() final;
|
||||
bool is_exclusive() final;
|
||||
|
||||
private:
|
||||
const std::vector<::KeyboardMachine::Machine *> &machines_;
|
||||
@@ -48,10 +48,10 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
|
||||
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
|
||||
void clear_all_keys() override;
|
||||
void set_key_state(uint16_t key, bool is_pressed) override;
|
||||
void type_string(const std::string &) override;
|
||||
Inputs::Keyboard &get_keyboard() override;
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ struct MultiMediaTarget: public MediaTarget::Machine {
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &media) override;
|
||||
bool insert_media(const Analyser::Static::Media &media) final;
|
||||
|
||||
private:
|
||||
std::vector<MediaTarget::Machine *> targets_;
|
||||
|
||||
@@ -37,12 +37,23 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
return ideal / static_cast<float>(speakers_.size());
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
|
||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
|
||||
stereo_output_ = stereo;
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_rate(cycles_per_second, buffer_size);
|
||||
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiSpeaker::get_is_stereo() {
|
||||
// Return as stereo if any subspeaker is stereo.
|
||||
for(const auto &speaker: speakers_) {
|
||||
if(speaker->get_is_stereo()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
@@ -53,7 +64,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
}
|
||||
delegate_->speaker_did_complete_samples(this, buffer);
|
||||
did_complete_samples(this, buffer, stereo_output_);
|
||||
}
|
||||
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
|
||||
@@ -39,18 +39,21 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
|
||||
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
|
||||
void set_output_rate(float cycles_per_second, int buffer_size) override;
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||
bool get_is_stereo() override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) override;
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
void speaker_did_change_input_clock(Speaker *speaker) final;
|
||||
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
|
||||
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers_;
|
||||
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
|
||||
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
|
||||
std::mutex front_speaker_mutex_;
|
||||
|
||||
bool stereo_output_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -50,17 +50,17 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
|
||||
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
|
||||
|
||||
Activity::Source *activity_source() override;
|
||||
Configurable::Device *configurable_device() override;
|
||||
CRTMachine::Machine *crt_machine() override;
|
||||
JoystickMachine::Machine *joystick_machine() override;
|
||||
MouseMachine::Machine *mouse_machine() override;
|
||||
KeyboardMachine::Machine *keyboard_machine() override;
|
||||
MediaTarget::Machine *media_target() override;
|
||||
void *raw_pointer() override;
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
CRTMachine::Machine *crt_machine() final;
|
||||
JoystickMachine::Machine *joystick_machine() final;
|
||||
MouseMachine::Machine *mouse_machine() final;
|
||||
KeyboardMachine::Machine *keyboard_machine() final;
|
||||
MediaTarget::Machine *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
|
||||
private:
|
||||
void multi_crt_did_run_machines() override;
|
||||
void multi_crt_did_run_machines() final;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
@@ -19,12 +19,10 @@ using namespace Analyser::Static::Commodore;
|
||||
|
||||
class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
public:
|
||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
drive = std::make_shared<Storage::Disk::Drive>(4000000, 300, 2);
|
||||
set_drive(drive);
|
||||
drive->set_motor_on(true);
|
||||
emplace_drive(4000000, 300, 2);
|
||||
set_drive(1);
|
||||
get_drive().set_motor_on(true);
|
||||
}
|
||||
|
||||
struct Sector {
|
||||
@@ -61,6 +59,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
return get_sector(sector);
|
||||
}
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
@@ -170,7 +172,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
std::vector<File> files;
|
||||
CommodoreGCRParser parser;
|
||||
parser.drive->set_disk(disk);
|
||||
parser.set_disk(disk);
|
||||
|
||||
// find any sector whatsoever to establish the current track
|
||||
std::shared_ptr<CommodoreGCRParser::Sector> sector;
|
||||
|
||||
@@ -13,62 +13,68 @@
|
||||
#include <vector>
|
||||
|
||||
/*!
|
||||
A DeferredQueue maintains a list of ordered actions and the times at which
|
||||
they should happen, and divides a total execution period up into the portions
|
||||
that occur between those actions, triggering each action when it is reached.
|
||||
Provides the logic to insert into and traverse a list of future scheduled items.
|
||||
*/
|
||||
template <typename TimeUnit> class DeferredQueue {
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
|
||||
Actions must be scheduled in the order they will occur. It is undefined behaviour
|
||||
to schedule them out of order.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
// If there are no pending actions, just run for the entire length.
|
||||
// This should be the normal branch.
|
||||
if(pending_actions_.empty()) {
|
||||
target_(length);
|
||||
// Apply immediately if there's no delay (or a negative delay).
|
||||
if(delay <= TimeUnit(0)) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide the time to run according to the pending actions.
|
||||
while(length > TimeUnit(0)) {
|
||||
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
|
||||
target_(next_period);
|
||||
length -= next_period;
|
||||
if(!pending_actions_.empty()) {
|
||||
// Otherwise enqueue, having subtracted the delay for any preceding events,
|
||||
// and subtracting from the subsequent, if any.
|
||||
auto insertion_point = pending_actions_.begin();
|
||||
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
|
||||
delay -= insertion_point->delay;
|
||||
++insertion_point;
|
||||
}
|
||||
if(insertion_point != pending_actions_.end()) {
|
||||
insertion_point->delay -= delay;
|
||||
}
|
||||
|
||||
off_t performances = 0;
|
||||
for(auto &action: pending_actions_) {
|
||||
action.delay -= next_period;
|
||||
if(!action.delay) {
|
||||
action.action();
|
||||
++performances;
|
||||
}
|
||||
}
|
||||
if(performances) {
|
||||
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
|
||||
pending_actions_.emplace(insertion_point, delay, action);
|
||||
} else {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The amount of time until the next enqueued action will occur,
|
||||
or TimeUnit(-1) if the queue is empty.
|
||||
*/
|
||||
TimeUnit time_until_next_action() {
|
||||
if(pending_actions_.empty()) return TimeUnit(-1);
|
||||
return pending_actions_.front().delay;
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the queue the specified amount of time, performing any actions it reaches.
|
||||
*/
|
||||
void advance(TimeUnit time) {
|
||||
auto erase_iterator = pending_actions_.begin();
|
||||
while(erase_iterator != pending_actions_.end()) {
|
||||
erase_iterator->delay -= time;
|
||||
if(erase_iterator->delay <= TimeUnit(0)) {
|
||||
time = -erase_iterator->delay;
|
||||
erase_iterator->action();
|
||||
++erase_iterator;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(erase_iterator != pending_actions_.begin()) {
|
||||
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
@@ -79,4 +85,40 @@ template <typename TimeUnit> class DeferredQueue {
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
};
|
||||
|
||||
/*!
|
||||
A DeferredQueue maintains a list of ordered actions and the times at which
|
||||
they should happen, and divides a total execution period up into the portions
|
||||
that occur between those actions, triggering each action when it is reached.
|
||||
|
||||
This list is efficient only for short queues.
|
||||
*/
|
||||
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
|
||||
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
|
||||
target_(time_to_next);
|
||||
length -= time_to_next;
|
||||
DeferredQueue<TimeUnit>::advance(time_to_next);
|
||||
}
|
||||
|
||||
DeferredQueue<TimeUnit>::advance(length);
|
||||
target_(length);
|
||||
|
||||
// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
};
|
||||
|
||||
#endif /* DeferredQueue_h */
|
||||
|
||||
@@ -43,6 +43,13 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Acts exactly as per the standard ->, but preserves constness.
|
||||
forceinline const T *operator->() const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
|
||||
non_const_this->flush();
|
||||
return &object_;
|
||||
}
|
||||
|
||||
/// Returns a pointer to the included object without flushing time.
|
||||
forceinline T *last_valid() {
|
||||
return &object_;
|
||||
@@ -53,7 +60,8 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
|
||||
if(!is_flushed_) {
|
||||
is_flushed_ = true;
|
||||
if constexpr (divider == 1) {
|
||||
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
|
||||
const auto duration = time_since_update_.template flush<TargetTimeScale>();
|
||||
object_.run_for(duration);
|
||||
} else {
|
||||
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
|
||||
if(duration > TargetTimeScale(0))
|
||||
@@ -68,6 +76,48 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
|
||||
bool is_flushed_ = true;
|
||||
};
|
||||
|
||||
/*!
|
||||
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
|
||||
Time added will be performed immediately.
|
||||
|
||||
Its primary purpose is to allow consumers to remain flexible in their scheduling.
|
||||
*/
|
||||
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
|
||||
public:
|
||||
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
|
||||
|
||||
forceinline void operator += (const LocalTimeScale &rhs) {
|
||||
if constexpr (multiplier == 1 && divider == 1) {
|
||||
object_.run_for(TargetTimeScale(rhs));
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (multiplier == 1) {
|
||||
accumulated_time_ += rhs;
|
||||
} else {
|
||||
accumulated_time_ += rhs * multiplier;
|
||||
}
|
||||
|
||||
if constexpr (divider == 1) {
|
||||
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
|
||||
object_.run_for(duration);
|
||||
} else {
|
||||
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
|
||||
if(duration > TargetTimeScale(0))
|
||||
object_.run_for(duration);
|
||||
}
|
||||
}
|
||||
|
||||
forceinline T *operator->() { return &object_; }
|
||||
forceinline const T *operator->() const { return &object_; }
|
||||
forceinline T *last_valid() { return &object_; }
|
||||
forceinline void flush() {}
|
||||
|
||||
private:
|
||||
T object_;
|
||||
LocalTimeScale accumulated_time_;
|
||||
};
|
||||
|
||||
/*!
|
||||
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
|
||||
Any time the amount of accumulated time crosses a threshold provided at construction time,
|
||||
|
||||
88
ClockReceiver/ScanSynchroniser.hpp
Normal file
88
ClockReceiver/ScanSynchroniser.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// ScanSynchroniser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/02/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ScanSynchroniser_h
|
||||
#define ScanSynchroniser_h
|
||||
|
||||
#include "../Outputs/ScanTarget.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace Time {
|
||||
|
||||
/*!
|
||||
Where an emulated machine is sufficiently close to a host machine's frame rate that a small nudge in
|
||||
its speed multiplier will bring it into frame synchronisation, the ScanSynchroniser provides a sequence of
|
||||
speed multipliers designed both to adjust the machine to the proper speed and, in a reasonable amount
|
||||
of time, to bring it into phase.
|
||||
*/
|
||||
class ScanSynchroniser {
|
||||
public:
|
||||
/*!
|
||||
@returns @c true if the emulated machine can be synchronised with the host frame output based on its
|
||||
current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
|
||||
*/
|
||||
bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) {
|
||||
ratio_ = 1.0;
|
||||
if(scan_status.field_duration_gradient < 0.00001) {
|
||||
// Check out the machine's current frame time.
|
||||
// If it's within 3% of a non-zero integer multiple of the
|
||||
// display rate, mark this time window to be split over the sync.
|
||||
ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
|
||||
const double integer_ratio = round(ratio_);
|
||||
if(integer_ratio > 0.0) {
|
||||
ratio_ /= integer_ratio;
|
||||
return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
|
||||
Results are undefined if @c can_synchroise returned @c false.
|
||||
*/
|
||||
double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
|
||||
// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
|
||||
// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
|
||||
// no benefit to second guessing it here — just take it to be correct.
|
||||
//
|
||||
// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
|
||||
// So the set speed multiplier may be adjusted slightly to aim for that.
|
||||
double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
|
||||
if(scan_status.current_position > 0.0) {
|
||||
if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
|
||||
else speed_multiplier *= phase_adjustment_ratio;
|
||||
}
|
||||
speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
|
||||
return speed_multiplier_ * base_multiplier_;
|
||||
}
|
||||
|
||||
void set_base_speed_multiplier(double multiplier) {
|
||||
base_multiplier_ = multiplier;
|
||||
}
|
||||
|
||||
double get_base_speed_multiplier() {
|
||||
return base_multiplier_;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double maximum_rate_adjustment = 1.03;
|
||||
static constexpr double phase_adjustment_ratio = 1.005;
|
||||
|
||||
// Managed local state.
|
||||
double speed_multiplier_ = 1.0;
|
||||
double base_multiplier_ = 1.0;
|
||||
|
||||
// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
|
||||
double ratio_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ScanSynchroniser_h */
|
||||
@@ -9,9 +9,16 @@
|
||||
#ifndef TimeTypes_h
|
||||
#define TimeTypes_h
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Time {
|
||||
|
||||
typedef double Seconds;
|
||||
typedef int64_t Nanos;
|
||||
|
||||
inline Nanos nanos_now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -481,7 +481,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
status.data_request = true;
|
||||
});
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 128 << header_[3]) {
|
||||
if(distance_into_section_ == 128 << (header_[3]&3)) {
|
||||
distance_into_section_ = 0;
|
||||
goto type2_check_crc;
|
||||
}
|
||||
@@ -564,7 +564,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
*/
|
||||
write_byte(data_);
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 128 << header_[3]) {
|
||||
if(distance_into_section_ == 128 << (header_[3]&3)) {
|
||||
goto type2_write_crc;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,20 +45,20 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
enum Flag: uint8_t {
|
||||
NotReady = 0x80,
|
||||
NotReady = 0x80, // 0x80
|
||||
MotorOn = 0x80,
|
||||
WriteProtect = 0x40,
|
||||
RecordType = 0x20,
|
||||
WriteProtect = 0x40, // 0x40
|
||||
RecordType = 0x20, // 0x20
|
||||
SpinUp = 0x20,
|
||||
HeadLoaded = 0x20,
|
||||
RecordNotFound = 0x10,
|
||||
RecordNotFound = 0x10, // 0x10
|
||||
SeekError = 0x10,
|
||||
CRCError = 0x08,
|
||||
LostData = 0x04,
|
||||
CRCError = 0x08, // 0x08
|
||||
LostData = 0x04, // 0x04
|
||||
TrackZero = 0x04,
|
||||
DataRequest = 0x02,
|
||||
DataRequest = 0x02, // 0x02
|
||||
Index = 0x02,
|
||||
Busy = 0x01
|
||||
Busy = 0x01 // 0x01
|
||||
};
|
||||
|
||||
/// @returns The current value of the IRQ line output.
|
||||
|
||||
@@ -17,13 +17,13 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
|
||||
|
||||
|
||||
void AudioGenerator::set_volume(uint8_t volume) {
|
||||
audio_queue_.defer([=]() {
|
||||
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
|
||||
audio_queue_.defer([this, volume]() {
|
||||
volume_ = int16_t(volume) * range_multiplier_;
|
||||
});
|
||||
}
|
||||
|
||||
void AudioGenerator::set_control(int channel, uint8_t value) {
|
||||
audio_queue_.defer([=]() {
|
||||
audio_queue_.defer([this, channel, value]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class AudioGenerator: public ::Outputs::Speaker::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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
@@ -83,8 +84,9 @@ template <class BusHandler> class MOS6560 {
|
||||
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||
|
||||
void set_high_frequency_cutoff(float cutoff) {
|
||||
|
||||
@@ -67,7 +67,7 @@ class i8272 : public Storage::Disk::MFMController {
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type) override;
|
||||
void posit_event(int type) final;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
@@ -117,6 +117,14 @@ void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus TMS9918::get_scaled_scan_status() const {
|
||||
// The input was scaled by 3/4 to convert half cycles to internal ticks,
|
||||
// so undo that and also allow for: (i) the multiply by 4 that it takes
|
||||
// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
|
||||
// and this should really reply in whole cycles.
|
||||
return crt_.get_scaled_scan_status() * (4.0f / (3.0f * 8.0f));
|
||||
}
|
||||
|
||||
void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ class TMS9918: public Base {
|
||||
/*! Sets the scan target this TMS will post content to. */
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*! Sets the type of display the CRT will request. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "AY38910.hpp"
|
||||
|
||||
#include <cmath>
|
||||
//namespace GI {
|
||||
//namespace AY38910 {
|
||||
|
||||
using namespace GI::AY38910;
|
||||
|
||||
AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||
template <bool is_stereo>
|
||||
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||
// Don't use the low bit of the envelope position if this is an AY.
|
||||
envelope_position_mask_ |= personality == Personality::AY38910;
|
||||
|
||||
@@ -70,18 +74,34 @@ AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &
|
||||
set_sample_volume_range(0);
|
||||
}
|
||||
|
||||
void AY38910::set_sample_volume_range(std::int16_t range) {
|
||||
// set up volume lookup table
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::int16_t range) {
|
||||
// Set up volume lookup table; the function below is based on a combination of the graph
|
||||
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
|
||||
// values reported elsewhere.
|
||||
const float max_volume = float(range) / 3.0f; // As there are three channels.
|
||||
constexpr float root_two = 1.414213562373095f;
|
||||
for(int v = 0; v < 32; v++) {
|
||||
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f));
|
||||
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f));
|
||||
}
|
||||
volumes_[0] = 0; // Tie level 0 to silence.
|
||||
|
||||
// Tie level 0 to silence.
|
||||
for(int v = 31; v >= 0; --v) {
|
||||
volumes_[v] -= volumes_[0];
|
||||
}
|
||||
|
||||
evaluate_output_volume();
|
||||
}
|
||||
|
||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
|
||||
a_left_ = uint8_t(a_left * 255.0f);
|
||||
b_left_ = uint8_t(b_left * 255.0f);
|
||||
c_left_ = uint8_t(c_left * 255.0f);
|
||||
a_right_ = uint8_t(a_right * 255.0f);
|
||||
b_right_ = uint8_t(b_right * 255.0f);
|
||||
c_right_ = uint8_t(c_right * 255.0f);
|
||||
}
|
||||
|
||||
template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
// Note on structure below: the real AY has a built-in divider of 8
|
||||
// prior to applying its tone and noise dividers. But the YM fills the
|
||||
// same total periods for noise and tone with double-precision envelopes.
|
||||
@@ -93,7 +113,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
|
||||
std::size_t c = 0;
|
||||
while((master_divider_&3) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
if constexpr (is_stereo) {
|
||||
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
|
||||
} else {
|
||||
target[c] = int16_t(output_volume_);
|
||||
}
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
@@ -135,7 +159,11 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
|
||||
target[c] = output_volume_;
|
||||
if constexpr (is_stereo) {
|
||||
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
|
||||
} else {
|
||||
target[c] = int16_t(output_volume_);
|
||||
}
|
||||
c++;
|
||||
master_divider_++;
|
||||
}
|
||||
@@ -144,7 +172,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
master_divider_ &= 3;
|
||||
}
|
||||
|
||||
void AY38910::evaluate_output_volume() {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
|
||||
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
|
||||
|
||||
// The output level for a channel is:
|
||||
@@ -184,34 +212,47 @@ void AY38910::evaluate_output_volume() {
|
||||
};
|
||||
#undef channel_volume
|
||||
|
||||
// Mix additively.
|
||||
output_volume_ = static_cast<int16_t>(
|
||||
volumes_[volumes[0]] * channel_levels[0] +
|
||||
volumes_[volumes[1]] * channel_levels[1] +
|
||||
volumes_[volumes[2]] * channel_levels[2]
|
||||
);
|
||||
// Mix additively, weighting if in stereo.
|
||||
if constexpr (is_stereo) {
|
||||
int16_t *const output_volumes = reinterpret_cast<int16_t *>(&output_volume_);
|
||||
output_volumes[0] = int16_t((
|
||||
volumes_[volumes[0]] * channel_levels[0] * a_left_ +
|
||||
volumes_[volumes[1]] * channel_levels[1] * b_left_ +
|
||||
volumes_[volumes[2]] * channel_levels[2] * c_left_
|
||||
) >> 8);
|
||||
output_volumes[1] = int16_t((
|
||||
volumes_[volumes[0]] * channel_levels[0] * a_right_ +
|
||||
volumes_[volumes[1]] * channel_levels[1] * b_right_ +
|
||||
volumes_[volumes[2]] * channel_levels[2] * c_right_
|
||||
) >> 8);
|
||||
} else {
|
||||
output_volume_ = uint32_t(
|
||||
volumes_[volumes[0]] * channel_levels[0] +
|
||||
volumes_[volumes[1]] * channel_levels[1] +
|
||||
volumes_[volumes[2]] * channel_levels[2]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool AY38910::is_zero_level() {
|
||||
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() {
|
||||
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
|
||||
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
|
||||
}
|
||||
|
||||
// MARK: - Register manipulation
|
||||
|
||||
void AY38910::select_register(uint8_t r) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::select_register(uint8_t r) {
|
||||
selected_register_ = r;
|
||||
}
|
||||
|
||||
void AY38910::set_register_value(uint8_t value) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t value) {
|
||||
// There are only 16 registers.
|
||||
if(selected_register_ > 15) return;
|
||||
|
||||
// If this is a register that affects audio output, enqueue a mutation onto the
|
||||
// audio generation thread.
|
||||
if(selected_register_ < 14) {
|
||||
const int selected_register = selected_register_;
|
||||
task_queue_.defer([=] () {
|
||||
task_queue_.defer([this, selected_register = selected_register_, value] () {
|
||||
// Perform any register-specific mutation to output generation.
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register) {
|
||||
@@ -220,7 +261,7 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
int channel = selected_register >> 1;
|
||||
|
||||
if(selected_register & 1)
|
||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
|
||||
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | uint16_t((value&0xf) << 8);
|
||||
else
|
||||
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
|
||||
}
|
||||
@@ -235,7 +276,7 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
break;
|
||||
|
||||
case 12:
|
||||
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
|
||||
envelope_period_ = (envelope_period_ & 0xff) | int(value << 8);
|
||||
break;
|
||||
|
||||
case 13:
|
||||
@@ -273,7 +314,7 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
if(update_port_a) set_port_output(false);
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_register_value() {
|
||||
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() {
|
||||
// This table ensures that bits that aren't defined within the AY are returned as 0s
|
||||
// when read, conforming to CPC-sourced unit tests.
|
||||
const uint8_t register_masks[16] = {
|
||||
@@ -287,24 +328,24 @@ uint8_t AY38910::get_register_value() {
|
||||
|
||||
// MARK: - Port querying
|
||||
|
||||
uint8_t AY38910::get_port_output(bool port_b) {
|
||||
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_port_output(bool port_b) {
|
||||
return registers_[port_b ? 15 : 14];
|
||||
}
|
||||
|
||||
// MARK: - Bus handling
|
||||
|
||||
void AY38910::set_port_handler(PortHandler *handler) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_port_handler(PortHandler *handler) {
|
||||
port_handler_ = handler;
|
||||
set_port_output(true);
|
||||
set_port_output(false);
|
||||
}
|
||||
|
||||
void AY38910::set_data_input(uint8_t r) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_data_input(uint8_t r) {
|
||||
data_input_ = r;
|
||||
update_bus();
|
||||
}
|
||||
|
||||
void AY38910::set_port_output(bool port_b) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b) {
|
||||
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
|
||||
// so that when in the "input" mode, all pins will read normally high". Therefore,
|
||||
// report programmer selection of input mode as creating an output of 0xff.
|
||||
@@ -314,7 +355,7 @@ void AY38910::set_port_output(bool port_b) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_data_output() {
|
||||
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_data_output() {
|
||||
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
|
||||
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
|
||||
// value returned to the CPU when reading it is the and of the output value and any input.
|
||||
@@ -330,22 +371,22 @@ uint8_t AY38910::get_data_output() {
|
||||
return data_output_;
|
||||
}
|
||||
|
||||
void AY38910::set_control_lines(ControlLines control_lines) {
|
||||
switch(static_cast<int>(control_lines)) {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLines control_lines) {
|
||||
switch(int(control_lines)) {
|
||||
default: control_state_ = Inactive; break;
|
||||
|
||||
case static_cast<int>(BDIR | BC2 | BC1):
|
||||
case int(BDIR | BC2 | BC1):
|
||||
case BDIR:
|
||||
case BC1: control_state_ = LatchAddress; break;
|
||||
|
||||
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
|
||||
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
|
||||
case int(BC2 | BC1): control_state_ = Read; break;
|
||||
case int(BDIR | BC2): control_state_ = Write; break;
|
||||
}
|
||||
|
||||
update_bus();
|
||||
}
|
||||
|
||||
void AY38910::update_bus() {
|
||||
template <bool is_stereo> void AY38910<is_stereo>::update_bus() {
|
||||
// Assume no output, unless this turns out to be a read.
|
||||
data_output_ = 0xff;
|
||||
switch(control_state_) {
|
||||
@@ -355,3 +396,7 @@ void AY38910::update_bus() {
|
||||
case Read: data_output_ = get_register_value(); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure both mono and stereo versions of the AY are built.
|
||||
template class GI::AY38910::AY38910<true>;
|
||||
template class GI::AY38910::AY38910<false>;
|
||||
|
||||
@@ -63,8 +63,10 @@ enum class Personality {
|
||||
Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
|
||||
noise generator and a volume envelope generator, which also provides two bidirectional
|
||||
interface ports.
|
||||
|
||||
This AY has an attached mono or stereo mixer.
|
||||
*/
|
||||
class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
|
||||
@@ -91,10 +93,23 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
*/
|
||||
void set_port_handler(PortHandler *);
|
||||
|
||||
/*!
|
||||
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
|
||||
channels in each of the output channels.
|
||||
|
||||
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
|
||||
|
||||
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
|
||||
|
||||
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
|
||||
*/
|
||||
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
|
||||
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);
|
||||
static constexpr bool get_is_stereo() { return is_stereo; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
@@ -135,14 +150,21 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
|
||||
uint8_t data_input_, data_output_;
|
||||
|
||||
int16_t output_volume_;
|
||||
void evaluate_output_volume();
|
||||
uint32_t output_volume_;
|
||||
|
||||
void update_bus();
|
||||
PortHandler *port_handler_ = nullptr;
|
||||
void set_port_output(bool port_b);
|
||||
|
||||
void evaluate_output_volume();
|
||||
|
||||
// Output mixing control.
|
||||
uint8_t a_left_ = 255, a_right_ = 255;
|
||||
uint8_t b_left_ = 255, b_right_ = 255;
|
||||
uint8_t c_left_ = 255, c_right_ = 255;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ void Toggle::skip_samples(const std::size_t number_of_samples) {}
|
||||
void Toggle::set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.defer([=] {
|
||||
audio_queue_.defer([this, enabled] {
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
void skip_samples(const std::size_t number_of_samples);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
void set_output(bool enabled);
|
||||
bool get_output();
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Apple {
|
||||
/*!
|
||||
Provides an emulation of the Apple Disk II.
|
||||
*/
|
||||
class DiskII final:
|
||||
class DiskII :
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
@@ -98,8 +98,8 @@ class DiskII final:
|
||||
void select_drive(int drive);
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
|
||||
void process_event(const Storage::Disk::Drive::Event &event) final;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
|
||||
|
||||
const Cycles::IntType clock_rate_ = 0;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class IWM:
|
||||
|
||||
private:
|
||||
// Storage::Disk::Drive::EventDelegate.
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
void process_event(const Storage::Disk::Drive::Event &event) final;
|
||||
|
||||
const int clock_rate_;
|
||||
|
||||
@@ -91,7 +91,7 @@ class IWM:
|
||||
IWMDrive *drives_[2] = {nullptr, nullptr};
|
||||
bool drive_is_rotating_[2] = {false, false};
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
|
||||
Cycles cycles_until_disable_;
|
||||
uint8_t write_handshake_ = 0x80;
|
||||
|
||||
@@ -32,14 +32,14 @@ class DoubleDensityDrive: public IWMDrive {
|
||||
*/
|
||||
void set_rotation_speed(float revolutions_per_minute);
|
||||
|
||||
void set_enabled(bool) override;
|
||||
void set_control_lines(int) override;
|
||||
bool read() override;
|
||||
|
||||
private:
|
||||
void set_enabled(bool) final;
|
||||
void set_control_lines(int) final;
|
||||
bool read() final;
|
||||
|
||||
// To receive the proper notifications from Storage::Disk::Drive.
|
||||
void did_step(Storage::Disk::HeadPosition to_position) override;
|
||||
void did_set_disk() override;
|
||||
void did_step(Storage::Disk::HeadPosition to_position) final;
|
||||
void did_set_disk() final;
|
||||
|
||||
const bool is_800k_;
|
||||
bool has_new_disk_ = false;
|
||||
|
||||
@@ -55,7 +55,7 @@ void SCC::write(uint16_t address, uint8_t value) {
|
||||
address &= 0xff;
|
||||
if(address < 0x80) ram_[address] = value;
|
||||
|
||||
task_queue_.defer([=] {
|
||||
task_queue_.defer([this, address, value] {
|
||||
// Check for a write into waveform memory.
|
||||
if(address < 0x80) {
|
||||
waves_[address >> 5].samples[address & 0x1f] = value;
|
||||
|
||||
@@ -32,6 +32,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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
/// Writes to the SCC.
|
||||
void write(uint16_t address, uint8_t value);
|
||||
|
||||
@@ -32,6 +32,7 @@ class SN76489: public Outputs::Speaker::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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
int master_divider_ = 0;
|
||||
|
||||
@@ -12,61 +12,89 @@
|
||||
|
||||
using namespace Concurrency;
|
||||
|
||||
BestEffortUpdater::BestEffortUpdater() {
|
||||
// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means.
|
||||
update_is_ongoing_.clear();
|
||||
}
|
||||
BestEffortUpdater::BestEffortUpdater() :
|
||||
update_thread_([this]() {
|
||||
this->update_loop();
|
||||
}) {}
|
||||
|
||||
BestEffortUpdater::~BestEffortUpdater() {
|
||||
// Don't allow further deconstruction until the task queue is stopped.
|
||||
// Sever the delegate now, as soon as possible, then wait for any
|
||||
// pending tasks to finish.
|
||||
set_delegate(nullptr);
|
||||
flush();
|
||||
|
||||
// Wind up the update thread.
|
||||
should_quit_ = true;
|
||||
update();
|
||||
update_thread_.join();
|
||||
}
|
||||
|
||||
void BestEffortUpdater::update() {
|
||||
// Perform an update only if one is not currently ongoing.
|
||||
if(!update_is_ongoing_.test_and_set()) {
|
||||
async_task_queue_.enqueue([this]() {
|
||||
// Get time now using the highest-resolution clock provided by the implementation, and determine
|
||||
// the duration since the last time this section was entered.
|
||||
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
|
||||
const auto elapsed = now - previous_time_point_;
|
||||
previous_time_point_ = now;
|
||||
void BestEffortUpdater::update(int flags) {
|
||||
// Bump the requested target time and set the update requested flag.
|
||||
{
|
||||
std::lock_guard<decltype(update_mutex_)> lock(update_mutex_);
|
||||
has_skipped_ = update_requested_;
|
||||
update_requested_ = true;
|
||||
flags_ |= flags;
|
||||
target_time_ = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||
}
|
||||
update_condition_.notify_one();
|
||||
}
|
||||
|
||||
if(has_previous_time_point_) {
|
||||
// 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 as
|
||||
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
|
||||
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
if(integer_duration > 0) {
|
||||
if(delegate_) {
|
||||
// Cap running at 1/5th of a second, to avoid doing a huge amount of work after any
|
||||
// brief system interruption.
|
||||
const double duration = std::min(static_cast<double>(integer_duration) / 1e9, 0.2);
|
||||
delegate_->update(this, duration, has_skipped_);
|
||||
}
|
||||
has_skipped_ = false;
|
||||
}
|
||||
} else {
|
||||
has_previous_time_point_ = true;
|
||||
}
|
||||
void BestEffortUpdater::update_loop() {
|
||||
while(true) {
|
||||
std::unique_lock<decltype(update_mutex_)> lock(update_mutex_);
|
||||
is_updating_ = false;
|
||||
|
||||
// Allow furthers updates to occur.
|
||||
update_is_ongoing_.clear();
|
||||
});
|
||||
} else {
|
||||
async_task_queue_.enqueue([this]() {
|
||||
has_skipped_ = true;
|
||||
// Wait to be signalled.
|
||||
update_condition_.wait(lock, [this]() -> bool {
|
||||
return update_requested_;
|
||||
});
|
||||
|
||||
// Possibly this signalling really means 'quit'.
|
||||
if(should_quit_) return;
|
||||
|
||||
// Note update started, crib the target time.
|
||||
auto target_time = target_time_;
|
||||
update_requested_ = false;
|
||||
|
||||
// If this was actually the first update request, silently swallow it.
|
||||
if(!has_previous_time_point_) {
|
||||
has_previous_time_point_ = true;
|
||||
previous_time_point_ = target_time;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Release the lock on requesting new updates.
|
||||
is_updating_ = true;
|
||||
const int flags = flags_;
|
||||
flags_ = 0;
|
||||
lock.unlock();
|
||||
|
||||
// Invoke the delegate, if supplied, in order to run.
|
||||
const int64_t integer_duration = std::max(target_time - previous_time_point_, int64_t(0));
|
||||
const auto delegate = delegate_.load();
|
||||
if(delegate) {
|
||||
// Cap running at 1/5th of a second, to avoid doing a huge amount of work after any
|
||||
// brief system interruption.
|
||||
const double duration = std::min(double(integer_duration) / 1e9, 0.2);
|
||||
const double elapsed_duration = delegate->update(this, duration, has_skipped_, flags);
|
||||
|
||||
previous_time_point_ += int64_t(elapsed_duration * 1e9);
|
||||
has_skipped_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BestEffortUpdater::flush() {
|
||||
async_task_queue_.flush();
|
||||
// Spin lock; this is allowed to be slow.
|
||||
while(true) {
|
||||
std::lock_guard<decltype(update_mutex_)> lock(update_mutex_);
|
||||
if(!is_updating_) return;
|
||||
}
|
||||
}
|
||||
|
||||
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
|
||||
async_task_queue_.enqueue([this, delegate]() {
|
||||
delegate_ = delegate;
|
||||
});
|
||||
delegate_.store(delegate);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "AsyncTaskQueue.hpp"
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
|
||||
namespace Concurrency {
|
||||
@@ -31,7 +33,13 @@ class BestEffortUpdater {
|
||||
|
||||
/// A delegate receives timing cues.
|
||||
struct Delegate {
|
||||
virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
|
||||
/*!
|
||||
Instructs the delegate to run for at least @c duration, providing hints as to whether multiple updates were requested before the previous had completed
|
||||
(as @c did_skip_previous_update) and providing the union of any flags supplied to @c update.
|
||||
|
||||
@returns The amount of time actually run for.
|
||||
*/
|
||||
virtual Time::Seconds update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update, int flags) = 0;
|
||||
};
|
||||
|
||||
/// Sets the current delegate.
|
||||
@@ -41,20 +49,32 @@ class BestEffortUpdater {
|
||||
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.
|
||||
*/
|
||||
void update();
|
||||
void update(int flags = 0);
|
||||
|
||||
/// Blocks until any ongoing update is complete.
|
||||
/// Blocks until any ongoing update is complete; may spin.
|
||||
void flush();
|
||||
|
||||
private:
|
||||
std::atomic_flag update_is_ongoing_;
|
||||
AsyncTaskQueue async_task_queue_;
|
||||
std::atomic<bool> should_quit_;
|
||||
std::atomic<bool> is_updating_;
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
|
||||
int64_t target_time_;
|
||||
int flags_ = 0;
|
||||
bool update_requested_;
|
||||
std::mutex update_mutex_;
|
||||
std::condition_variable update_condition_;
|
||||
|
||||
decltype(target_time_) previous_time_point_;
|
||||
bool has_previous_time_point_ = false;
|
||||
bool has_skipped_ = false;
|
||||
std::atomic<bool> has_skipped_ = false;
|
||||
|
||||
Delegate *delegate_ = nullptr;
|
||||
std::atomic<Delegate *>delegate_ = nullptr;
|
||||
|
||||
void update_loop();
|
||||
|
||||
// This is deliberately at the bottom, to ensure it constructs after the various
|
||||
// mutexs, conditions, etc, that it'll depend upon.
|
||||
std::thread update_thread_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -170,11 +170,11 @@ class ConcreteJoystick: public Joystick {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Input> &get_inputs() override final {
|
||||
std::vector<Input> &get_inputs() final {
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
void set_input(const Input &input, bool is_active) override final {
|
||||
void set_input(const Input &input, bool is_active) final {
|
||||
// If this is a digital setting to a digital property, just pass it along.
|
||||
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
|
||||
did_set_input(input, is_active);
|
||||
@@ -193,7 +193,7 @@ class ConcreteJoystick: public Joystick {
|
||||
}
|
||||
}
|
||||
|
||||
void set_input(const Input &input, float value) override final {
|
||||
void set_input(const Input &input, float value) final {
|
||||
// If this is an analogue setting to an analogue property, just pass it along.
|
||||
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
|
||||
did_set_input(input, value);
|
||||
|
||||
@@ -34,24 +34,24 @@ class QuadratureMouse: public Mouse {
|
||||
/*
|
||||
Inputs, to satisfy the Mouse interface.
|
||||
*/
|
||||
void move(int x, int y) override {
|
||||
void move(int x, int y) final {
|
||||
// Accumulate all provided motion.
|
||||
axes_[0] += x;
|
||||
axes_[1] += y;
|
||||
}
|
||||
|
||||
int get_number_of_buttons() override {
|
||||
int get_number_of_buttons() final {
|
||||
return number_of_buttons_;
|
||||
}
|
||||
|
||||
void set_button_pressed(int index, bool is_pressed) override {
|
||||
void set_button_pressed(int index, bool is_pressed) final {
|
||||
if(is_pressed)
|
||||
button_flags_ |= (1 << index);
|
||||
else
|
||||
button_flags_ &= ~(1 << index);
|
||||
}
|
||||
|
||||
void reset_all_buttons() override {
|
||||
void reset_all_buttons() final {
|
||||
button_flags_ = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,9 @@ class AYDeferrer {
|
||||
/// Constructs a new AY instance and sets its clock rate.
|
||||
AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) {
|
||||
speaker_.set_input_rate(1000000);
|
||||
// Per the CPC Wiki:
|
||||
// "A is output to the right, channel C is output left, and channel B is output to both left and right".
|
||||
ay_.set_output_mixing(0.0, 0.5, 1.0, 1.0, 0.5, 0.0);
|
||||
}
|
||||
|
||||
~AYDeferrer() {
|
||||
@@ -153,14 +156,14 @@ class AYDeferrer {
|
||||
}
|
||||
|
||||
/// @returns the AY itself.
|
||||
GI::AY38910::AY38910 &ay() {
|
||||
GI::AY38910::AY38910<true> &ay() {
|
||||
return ay_;
|
||||
}
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
GI::AY38910::AY38910<true> ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<true>> speaker_;
|
||||
HalfCycles cycles_since_update_;
|
||||
};
|
||||
|
||||
@@ -335,6 +338,11 @@ class CRTCBusHandler {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
/// @returns The current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 4.0f;
|
||||
}
|
||||
|
||||
/// Sets the type of display.
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
@@ -627,7 +635,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
}),
|
||||
state_(state) {}
|
||||
|
||||
void did_set_input(const Input &input, bool is_active) override {
|
||||
void did_set_input(const Input &input, bool is_active) final {
|
||||
uint8_t mask = 0;
|
||||
switch(input.type) {
|
||||
default: return;
|
||||
@@ -656,17 +664,15 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
class FDC: public Intel::i8272::i8272 {
|
||||
private:
|
||||
Intel::i8272::BusHandler bus_handler_;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
|
||||
public:
|
||||
FDC() :
|
||||
i8272(bus_handler_, Cycles(8000000)),
|
||||
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
|
||||
set_drive(drive_);
|
||||
FDC() : i8272(bus_handler_, Cycles(8000000)) {
|
||||
emplace_drive(8000000, 300, 1);
|
||||
set_drive(1);
|
||||
}
|
||||
|
||||
void set_motor_on(bool on) {
|
||||
drive_->set_motor_on(on);
|
||||
get_drive().set_motor_on(on);
|
||||
}
|
||||
|
||||
void select_drive(int c) {
|
||||
@@ -674,11 +680,11 @@ class FDC: public Intel::i8272::i8272 {
|
||||
}
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
drive_->set_disk(disk);
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
drive_->set_activity_observer(observer, "Drive 1", true);
|
||||
get_drive().set_activity_observer(observer, "Drive 1", true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1002,26 +1008,31 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
}
|
||||
|
||||
/// A CRTMachine function; sets the destination for video.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
crtc_bus_handler_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
/// A CRTMachine function; returns the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return crtc_bus_handler_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
/// A CRTMachine function; sets the output display type.
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override final {
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
crtc_bus_handler_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
/// @returns the speaker in use.
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return ay_.get_speaker();
|
||||
}
|
||||
|
||||
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
|
||||
void run_for(const Cycles cycles) override final {
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
// If there are any tapes supplied, use the first of them.
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
@@ -1038,70 +1049,70 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
return !media.tapes.empty() || (!media.disks.empty() && has_fdc);
|
||||
}
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
|
||||
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
void type_string(const std::string &string) override final {
|
||||
void type_string(const std::string &string) final {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||
Utility::TypeRecipient::add_typer(string, std::move(mapper));
|
||||
}
|
||||
|
||||
HalfCycles get_typer_delay() override final {
|
||||
HalfCycles get_typer_delay() final {
|
||||
return Cycles(4000000); // Wait 1 second before typing.
|
||||
}
|
||||
|
||||
HalfCycles get_typer_frequency() override final {
|
||||
HalfCycles get_typer_frequency() final {
|
||||
return Cycles(160000); // Type one character per frame.
|
||||
}
|
||||
|
||||
// See header; sets a key as either pressed or released.
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
void set_key_state(uint16_t key, bool isPressed) final {
|
||||
key_state_.set_is_pressed(isPressed, key >> 4, key & 7);
|
||||
}
|
||||
|
||||
// See header; sets all keys to released.
|
||||
void clear_all_keys() override final {
|
||||
void clear_all_keys() final {
|
||||
key_state_.clear_all_keys();
|
||||
}
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
if constexpr (has_fdc) fdc_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return AmstradCPC::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
Configurable::Display display;
|
||||
if(Configurable::get_display(selections_by_option, display)) {
|
||||
set_video_signal_configurable(display);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
// MARK: - Joysticks
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return key_state_.get_joysticks();
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
card_became_just_in_time_ |= !is_every_cycle;
|
||||
}
|
||||
|
||||
void card_did_change_select_constraints(Apple::II::Card *card) override {
|
||||
void card_did_change_select_constraints(Apple::II::Card *card) final {
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
@@ -283,12 +283,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
Input(Input::Fire, 2),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &input, float value) override {
|
||||
void did_set_input(const Input &input, float value) final {
|
||||
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
|
||||
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
|
||||
}
|
||||
|
||||
void did_set_input(const Input &input, bool value) override {
|
||||
void did_set_input(const Input &input, bool value) final {
|
||||
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
|
||||
buttons[input.info.control.index] = value;
|
||||
}
|
||||
@@ -417,16 +417,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
video_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
/// Sets the type of display.
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
video_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
@@ -799,15 +803,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
void run_for(const Cycles cycles) final {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void reset_all_keys() override {
|
||||
void reset_all_keys() final {
|
||||
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
|
||||
}
|
||||
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) override {
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) final {
|
||||
switch(key) {
|
||||
default: break;
|
||||
case Key::F12:
|
||||
@@ -846,38 +850,38 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
Inputs::Keyboard &get_keyboard() override {
|
||||
Inputs::Keyboard &get_keyboard() final {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) override {
|
||||
void type_string(const std::string &string) final {
|
||||
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
|
||||
}
|
||||
|
||||
// MARK:: Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Apple::II::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
Configurable::Display display;
|
||||
if(Configurable::get_display(selections_by_option, display)) {
|
||||
set_video_signal_configurable(display);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
return get_accurate_selections();
|
||||
}
|
||||
|
||||
// MARK: MediaTarget
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.disks.empty()) {
|
||||
auto diskii = diskii_card();
|
||||
if(diskii) diskii->set_disk(media.disks[0], 0);
|
||||
@@ -886,14 +890,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: Activity::Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
for(const auto &card: cards_) {
|
||||
if(card) card->set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: JoystickMachine
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,16 +27,16 @@ class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) override;
|
||||
void run_for(Cycles cycles, int stretches) override;
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
void run_for(Cycles cycles, int stretches) final;
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) override;
|
||||
void set_activity_observer(Activity::Observer *observer) final;
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
Storage::Disk::Drive &get_drive(int drive);
|
||||
|
||||
private:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
|
||||
std::vector<uint8_t> boot_;
|
||||
Apple::DiskII diskii_;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
@@ -47,6 +47,10 @@ void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus VideoBase::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 14.0f;
|
||||
}
|
||||
|
||||
void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
@@ -56,7 +60,7 @@ void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
*/
|
||||
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
|
||||
set_alternative_character_set_ = alternative_character_set;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
deferrer_.defer(Cycles(2), [this, alternative_character_set] {
|
||||
alternative_character_set_ = alternative_character_set;
|
||||
if(alternative_character_set) {
|
||||
character_zones[1].address_mask = 0xff;
|
||||
@@ -74,7 +78,7 @@ bool VideoBase::get_alternative_character_set() {
|
||||
|
||||
void VideoBase::set_80_columns(bool columns_80) {
|
||||
set_columns_80_ = columns_80;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
deferrer_.defer(Cycles(2), [this, columns_80] {
|
||||
columns_80_ = columns_80;
|
||||
});
|
||||
}
|
||||
@@ -101,7 +105,7 @@ bool VideoBase::get_page2() {
|
||||
|
||||
void VideoBase::set_text(bool text) {
|
||||
set_text_ = text;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
deferrer_.defer(Cycles(2), [this, text] {
|
||||
text_ = text;
|
||||
});
|
||||
}
|
||||
@@ -112,7 +116,7 @@ bool VideoBase::get_text() {
|
||||
|
||||
void VideoBase::set_mixed(bool mixed) {
|
||||
set_mixed_ = mixed;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
deferrer_.defer(Cycles(2), [this, mixed] {
|
||||
mixed_ = mixed;
|
||||
});
|
||||
}
|
||||
@@ -123,7 +127,7 @@ bool VideoBase::get_mixed() {
|
||||
|
||||
void VideoBase::set_high_resolution(bool high_resolution) {
|
||||
set_high_resolution_ = high_resolution;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
deferrer_.defer(Cycles(2), [this, high_resolution] {
|
||||
high_resolution_ = high_resolution;
|
||||
});
|
||||
}
|
||||
@@ -134,7 +138,7 @@ bool VideoBase::get_high_resolution() {
|
||||
|
||||
void VideoBase::set_annunciator_3(bool annunciator_3) {
|
||||
set_annunciator_3_ = annunciator_3;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
deferrer_.defer(Cycles(2), [this, annunciator_3] {
|
||||
annunciator_3_ = annunciator_3;
|
||||
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
|
||||
});
|
||||
|
||||
@@ -40,6 +40,9 @@ class VideoBase {
|
||||
/// Sets the scan target.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
@@ -252,14 +255,14 @@ class VideoBase {
|
||||
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
|
||||
|
||||
// Maintain a DeferredQueue for delayed mode switches.
|
||||
DeferredQueue<Cycles> deferrer_;
|
||||
DeferredQueuePerformer<Cycles> deferrer_;
|
||||
};
|
||||
|
||||
template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video(BusHandler &bus_handler) :
|
||||
VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }),
|
||||
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
|
||||
bus_handler_(bus_handler) {}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -35,7 +35,7 @@ void Audio::set_volume(int volume) {
|
||||
posted_volume_ = volume;
|
||||
|
||||
// Post the volume change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
task_queue_.defer([this, volume] () {
|
||||
volume_ = volume;
|
||||
set_volume_multiplier();
|
||||
});
|
||||
@@ -47,7 +47,7 @@ void Audio::set_enabled(bool on) {
|
||||
posted_enable_mask_ = int(on);
|
||||
|
||||
// Post the enabled mask change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
task_queue_.defer([this, on] () {
|
||||
enabled_mask_ = int(on);
|
||||
set_volume_multiplier();
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
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);
|
||||
constexpr static bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
@@ -123,10 +123,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
|
||||
} break;
|
||||
}
|
||||
ram_mask_ = (ram_size >> 1) - 1;
|
||||
rom_mask_ = (rom_size >> 1) - 1;
|
||||
ram_.resize(ram_size >> 1);
|
||||
video_.set_ram(ram_.data(), ram_mask_);
|
||||
ram_mask_ = ram_size - 1;
|
||||
rom_mask_ = rom_size - 1;
|
||||
ram_.resize(ram_size);
|
||||
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
|
||||
|
||||
// Grab a copy of the ROM and convert it into big-endian data.
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
@@ -171,15 +171,19 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
audio_.queue.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
video_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &audio_.speaker;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
void run_for(const Cycles cycles) final {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
@@ -192,11 +196,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
// A null cycle leaves nothing else to do.
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
|
||||
|
||||
// Grab the value on the address bus, at word precision.
|
||||
uint32_t word_address = cycle.active_operation_word_address();
|
||||
// Grab the address.
|
||||
auto address = cycle.host_endian_byte_address();
|
||||
|
||||
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
||||
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
||||
mc68000_.set_is_peripheral_address(address >= 0xe0'0000);
|
||||
|
||||
// All code below deals only with reads and writes — cycles in which a
|
||||
// data select is active. So quit now if this is not the active part of
|
||||
@@ -209,9 +213,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0);
|
||||
|
||||
// Grab the word-precision address being accessed.
|
||||
uint16_t *memory_base = nullptr;
|
||||
uint8_t *memory_base = nullptr;
|
||||
HalfCycles delay;
|
||||
switch(memory_map_[word_address >> 16]) {
|
||||
switch(memory_map_[address >> 17]) {
|
||||
default: assert(false);
|
||||
|
||||
case BusDevice::Unassigned:
|
||||
@@ -222,7 +226,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
if(*cycle.address & 1) {
|
||||
fill_unmapped(cycle);
|
||||
} else {
|
||||
const int register_address = word_address >> 8;
|
||||
const int register_address = address >> 9;
|
||||
|
||||
// VIA accesses are via address 0xefe1fe + register*512,
|
||||
// which at word precision is 0x77f0ff + register*256.
|
||||
@@ -246,7 +250,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
case BusDevice::IWM: {
|
||||
if(*cycle.address & 1) {
|
||||
const int register_address = word_address >> 8;
|
||||
const int register_address = address >> 9;
|
||||
|
||||
// The IWM; this is a purely polled device, so can be run on demand.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
@@ -262,8 +266,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
} return delay;
|
||||
|
||||
case BusDevice::SCSI: {
|
||||
const int register_address = word_address >> 3;
|
||||
const bool dma_acknowledge = word_address & 0x100;
|
||||
const int register_address = address >> 4;
|
||||
const bool dma_acknowledge = address & 0x200;
|
||||
|
||||
// Even accesses = read; odd = write.
|
||||
if(*cycle.address & 1) {
|
||||
@@ -304,7 +308,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
cycle.value->halves.low = 0xff;
|
||||
}
|
||||
} else {
|
||||
const auto read = scc_.read(int(word_address));
|
||||
const auto read = scc_.read(int(address >> 1));
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = read;
|
||||
}
|
||||
@@ -319,10 +323,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
} else {
|
||||
if(*cycle.address & 1) {
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
scc_.write(int(word_address), 0xff);
|
||||
scc_.write(int(address >> 1), 0xff);
|
||||
cycle.value->halves.low = 0xff;
|
||||
} else {
|
||||
scc_.write(int(word_address), cycle.value->halves.low);
|
||||
scc_.write(int(address >> 1), cycle.value->halves.low);
|
||||
}
|
||||
} else {
|
||||
fill_unmapped(cycle);
|
||||
@@ -334,13 +338,13 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
// This is coupled with the Macintosh implementation of video; the magic
|
||||
// constant should probably be factored into the Video class.
|
||||
// It embodies knowledge of the fact that video (and audio) will always
|
||||
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
|
||||
// be fetched from the final $d900 bytes of memory.
|
||||
// (And that ram_mask_ = ram size - 1).
|
||||
if(word_address > ram_mask_ - 0x6c80)
|
||||
if(address > ram_mask_ - 0xd900)
|
||||
update_video();
|
||||
|
||||
memory_base = ram_.data();
|
||||
word_address &= ram_mask_;
|
||||
address &= ram_mask_;
|
||||
|
||||
// Apply a delay due to video contention if applicable; scheme applied:
|
||||
// only every other access slot is available during the period of video
|
||||
@@ -355,7 +359,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
case BusDevice::ROM: {
|
||||
if(!(cycle.operation & Microcycle::Read)) return delay;
|
||||
memory_base = rom_;
|
||||
word_address &= rom_mask_;
|
||||
address &= rom_mask_;
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -365,19 +369,16 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = memory_base[word_address];
|
||||
cycle.value->full = *reinterpret_cast<uint16_t *>(&memory_base[address]);
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
|
||||
cycle.value->halves.low = memory_base[address];
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
memory_base[word_address] = cycle.value->full;
|
||||
*reinterpret_cast<uint16_t *>(&memory_base[address]) = cycle.value->full;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
memory_base[word_address] = uint16_t(
|
||||
(cycle.value->halves.low << cycle.byte_shift()) |
|
||||
(memory_base[word_address] & cycle.untouched_byte_mask())
|
||||
);
|
||||
memory_base[address] = cycle.value->halves.low;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -395,7 +396,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
via_.flush();
|
||||
audio_.queue.perform();
|
||||
|
||||
// Experimental?
|
||||
// This avoids deferring IWM costs indefinitely, until
|
||||
// they become artbitrarily large.
|
||||
iwm_.flush();
|
||||
}
|
||||
|
||||
@@ -454,7 +456,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty())
|
||||
return false;
|
||||
|
||||
@@ -468,7 +470,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
drives_[0].set_disk(media.disks[0]);
|
||||
}
|
||||
|
||||
// TODO: allow this only at machine startup.
|
||||
// TODO: allow this only at machine startup?
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get());
|
||||
if(volume) {
|
||||
@@ -482,11 +484,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
// MARK: Keyboard input.
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
keyboard_.enqueue_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
@@ -494,7 +496,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
// MARK: Interrupt updates.
|
||||
|
||||
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
|
||||
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) final {
|
||||
update_interrupt_input();
|
||||
}
|
||||
|
||||
@@ -511,7 +513,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
iwm_->set_activity_observer(observer);
|
||||
|
||||
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
||||
@@ -520,40 +522,42 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Apple::Macintosh::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
bool quick_boot;
|
||||
if(Configurable::get_quick_boot(selections_by_option, quick_boot)) {
|
||||
if(quick_boot) {
|
||||
// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
|
||||
// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
|
||||
ram_[0x02ae >> 1] = 0x40;
|
||||
ram_[0x02b0 >> 1] = 0x00;
|
||||
ram_[0x02ae] = 0x40;
|
||||
ram_[0x02af] = 0x00;
|
||||
ram_[0x02b0] = 0x00;
|
||||
ram_[0x02b1] = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_boot_selection(selection_set, false);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_boot_selection(selection_set, true);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
private:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
|
||||
scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None;
|
||||
}
|
||||
|
||||
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override {
|
||||
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final {
|
||||
iwm_.flush();
|
||||
drives_[0].set_rotation_speed(speed);
|
||||
drives_[1].set_rotation_speed(speed);
|
||||
@@ -565,11 +569,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
forceinline void fill_unmapped(const Microcycle &cycle) {
|
||||
if(!(cycle.operation & Microcycle::Read)) return;
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
cycle.value->full = 0xffff;
|
||||
} else {
|
||||
cycle.value->halves.low = 0xff;
|
||||
}
|
||||
cycle.set_value16(0xffff);
|
||||
}
|
||||
|
||||
/// Advances all non-CPU components by @c duration half cycles.
|
||||
@@ -652,7 +652,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
}
|
||||
|
||||
Inputs::Mouse &get_mouse() override {
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
@@ -852,8 +852,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
uint32_t ram_mask_ = 0;
|
||||
uint32_t rom_mask_ = 0;
|
||||
uint16_t rom_[64*1024]; // i.e. up to 128kb in size.
|
||||
std::vector<uint16_t> ram_;
|
||||
uint8_t rom_[128*1024];
|
||||
std::vector<uint8_t> ram_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ using namespace Apple::Macintosh;
|
||||
Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
|
||||
audio_(audio),
|
||||
drive_speed_accumulator_(drive_speed_accumulator),
|
||||
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) {
|
||||
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
|
||||
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||
@@ -37,6 +37,10 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 2.0f;
|
||||
}
|
||||
|
||||
void Video::run_for(HalfCycles duration) {
|
||||
// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately.
|
||||
const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;
|
||||
|
||||
@@ -42,6 +42,9 @@ class Video {
|
||||
*/
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*!
|
||||
Produces the next @c duration period of pixels.
|
||||
*/
|
||||
|
||||
@@ -48,7 +48,7 @@ class Joystick: public Inputs::ConcreteJoystick {
|
||||
}),
|
||||
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
switch(digital_input.type) {
|
||||
case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
|
||||
case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
|
||||
@@ -77,12 +77,9 @@ using Target = Analyser::Static::Atari2600::Target;
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public JoystickMachine::Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
public JoystickMachine::Machine {
|
||||
public:
|
||||
ConcreteMachine(const Target &target) {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
|
||||
ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) {
|
||||
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
||||
|
||||
using PagingModel = Target::PagingModel;
|
||||
@@ -122,13 +119,15 @@ class ConcreteMachine:
|
||||
|
||||
joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0));
|
||||
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
|
||||
|
||||
set_is_ntsc(is_ntsc_);
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state) override {
|
||||
void set_switch_is_enabled(Atari2600Switch input, bool state) final {
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
||||
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
||||
@@ -138,7 +137,7 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
bool get_switch_is_enabled(Atari2600Switch input) override {
|
||||
bool get_switch_is_enabled(Atari2600Switch input) final {
|
||||
uint8_t port_input = bus_->mos6532_.get_port_input(1);
|
||||
switch(input) {
|
||||
case Atari2600SwitchReset: return !!(port_input & 0x01);
|
||||
@@ -150,83 +149,62 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void set_reset_switch(bool state) override {
|
||||
void set_reset_switch(bool state) final {
|
||||
bus_->set_reset_line(state);
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
bus_->speaker_.set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->tia_.set_crt_delegate(this);
|
||||
bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_);
|
||||
bus_->tia_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return bus_->tia_.get_scaled_scan_status() / 3.0f;
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &bus_->speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
void run_for(const Cycles cycles) final {
|
||||
bus_->run_for(cycles);
|
||||
bus_->apply_confidence(confidence_counter_);
|
||||
}
|
||||
|
||||
// to satisfy Outputs::CRT::Delegate
|
||||
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) override {
|
||||
const std::size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
|
||||
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
||||
frame_record_pointer_ ++;
|
||||
|
||||
if(frame_record_pointer_ >= 6) {
|
||||
int total_number_of_frames = 0;
|
||||
int total_number_of_unexpected_vertical_syncs = 0;
|
||||
for(std::size_t c = 0; c < number_of_frame_records; c++) {
|
||||
total_number_of_frames += frame_records_[c].number_of_frames;
|
||||
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
||||
}
|
||||
|
||||
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
||||
for(std::size_t c = 0; c < number_of_frame_records; c++) {
|
||||
frame_records_[c].number_of_frames = 0;
|
||||
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
||||
}
|
||||
is_ntsc_ ^= true;
|
||||
|
||||
double clock_rate;
|
||||
if(is_ntsc_) {
|
||||
clock_rate = NTSC_clock_rate;
|
||||
bus_->tia_.set_output_mode(TIA::OutputMode::NTSC);
|
||||
} else {
|
||||
clock_rate = PAL_clock_rate;
|
||||
bus_->tia_.set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->speaker_.set_high_frequency_cutoff(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0)));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
}
|
||||
void flush() {
|
||||
bus_->flush();
|
||||
}
|
||||
|
||||
float get_confidence() override {
|
||||
void register_crt_frequency_mismatch() {
|
||||
is_ntsc_ ^= true;
|
||||
set_is_ntsc(is_ntsc_);
|
||||
}
|
||||
|
||||
float get_confidence() final {
|
||||
return confidence_counter_.get_confidence();
|
||||
}
|
||||
|
||||
private:
|
||||
// the bus
|
||||
// The bus.
|
||||
std::unique_ptr<Bus> bus_;
|
||||
|
||||
// output frame rate tracker
|
||||
struct FrameRecord {
|
||||
int number_of_frames = 0;
|
||||
int number_of_unexpected_vertical_syncs = 0;
|
||||
} frame_records_[4];
|
||||
unsigned int frame_record_pointer_ = 0;
|
||||
// Output frame rate tracker.
|
||||
Outputs::CRT::CRTFrequencyMismatchWarner<ConcreteMachine> frequency_mismatch_warner_;
|
||||
bool is_ntsc_ = true;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
// a confidence counter
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||
|
||||
void set_is_ntsc(bool is_ntsc) {
|
||||
bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL);
|
||||
const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate;
|
||||
bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
|
||||
bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class Bus {
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0;
|
||||
virtual void set_reset_line(bool state) = 0;
|
||||
virtual void flush() = 0;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
PIA mos6532_;
|
||||
|
||||
@@ -39,7 +39,7 @@ template<class T> class Cartridge:
|
||||
// consider doing something less fragile.
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
void run_for(const Cycles cycles) override {
|
||||
// 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;
|
||||
@@ -50,13 +50,13 @@ template<class T> class Cartridge:
|
||||
/*!
|
||||
Adjusts @c confidence_counter according to the results of the most recent run_for.
|
||||
*/
|
||||
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) {
|
||||
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
|
||||
if(cycle_count_.as_integral() < 200) return;
|
||||
if(horizontal_counter_resets_ > 10)
|
||||
confidence_counter.add_miss();
|
||||
}
|
||||
|
||||
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
|
||||
void set_reset_line(bool state) override { m6502_.set_reset_line(state); }
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
@@ -197,7 +197,7 @@ template<class T> class Cartridge:
|
||||
return Cycles(cycles_run_for / 3);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
void flush() override {
|
||||
update_audio();
|
||||
update_video();
|
||||
audio_queue_.perform();
|
||||
|
||||
@@ -142,6 +142,10 @@ void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus TIA::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 2.0f;
|
||||
}
|
||||
|
||||
void TIA::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = int(cycles.as_integral());
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class TIA {
|
||||
|
||||
void set_crt_delegate(Outputs::CRT::Delegate *);
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
@@ -18,21 +18,21 @@ Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue)
|
||||
{}
|
||||
|
||||
void Atari2600::TIASound::set_volume(int channel, uint8_t volume) {
|
||||
audio_queue_.defer([=]() {
|
||||
volume_[channel] = volume & 0xf;
|
||||
audio_queue_.defer([target = &volume_[channel], volume]() {
|
||||
*target = volume & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::TIASound::set_divider(int channel, uint8_t divider) {
|
||||
audio_queue_.defer([=]() {
|
||||
audio_queue_.defer([this, channel, divider]() {
|
||||
divider_[channel] = divider & 0x1f;
|
||||
divider_counter_[channel] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::TIASound::set_control(int channel, uint8_t control) {
|
||||
audio_queue_.defer([=]() {
|
||||
control_[channel] = control & 0xf;
|
||||
audio_queue_.defer([target = &control_[channel], control]() {
|
||||
*target = control & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
|
||||
// To satisfy ::SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
@@ -101,8 +101,10 @@ class ConcreteMachine:
|
||||
|
||||
const bool is_early_tos = true;
|
||||
if(is_early_tos) {
|
||||
rom_start_ = 0xfc0000;
|
||||
for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM;
|
||||
} else {
|
||||
rom_start_ = 0xe00000;
|
||||
for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM;
|
||||
}
|
||||
|
||||
@@ -139,6 +141,10 @@ class ConcreteMachine:
|
||||
video_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
video_->set_display_type(display_type);
|
||||
}
|
||||
@@ -241,7 +247,7 @@ class ConcreteMachine:
|
||||
|
||||
case BusDevice::ROM:
|
||||
memory = rom_.data();
|
||||
address %= rom_.size();
|
||||
address -= rom_start_;
|
||||
break;
|
||||
|
||||
case BusDevice::Floating:
|
||||
@@ -435,7 +441,8 @@ class ConcreteMachine:
|
||||
// Advance the relevant counters.
|
||||
cycles_since_audio_update_ += length;
|
||||
mfp_ += length;
|
||||
dma_ += length;
|
||||
if(dma_clocking_preference_ != ClockingHint::Preference::None)
|
||||
dma_ += length;
|
||||
keyboard_acia_ += length;
|
||||
midi_acia_ += length;
|
||||
bus_phase_ += length;
|
||||
@@ -456,7 +463,7 @@ class ConcreteMachine:
|
||||
mfp_.flush();
|
||||
}
|
||||
|
||||
if(dma_is_realtime_) {
|
||||
if(dma_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
dma_.flush();
|
||||
}
|
||||
|
||||
@@ -465,6 +472,7 @@ class ConcreteMachine:
|
||||
length -= cycles_until_video_event_;
|
||||
video_ += cycles_until_video_event_;
|
||||
cycles_until_video_event_ = video_->get_next_sequence_point();
|
||||
assert(cycles_until_video_event_ > HalfCycles(0));
|
||||
|
||||
mfp_->set_timer_event_input(1, video_->display_enabled());
|
||||
update_interrupt_input();
|
||||
@@ -489,8 +497,8 @@ class ConcreteMachine:
|
||||
JustInTimeActor<Motorola::ACIA::ACIA, 16> midi_acia_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<false>> speaker_;
|
||||
HalfCycles cycles_since_audio_update_;
|
||||
|
||||
JustInTimeActor<DMAController> dma_;
|
||||
@@ -500,6 +508,7 @@ class ConcreteMachine:
|
||||
|
||||
std::vector<uint8_t> ram_;
|
||||
std::vector<uint8_t> rom_;
|
||||
uint32_t rom_start_ = 0;
|
||||
|
||||
enum class BusDevice {
|
||||
/// A mostly RAM page is one that returns ROM for the first 8 bytes, RAM elsewhere.
|
||||
@@ -523,7 +532,7 @@ class ConcreteMachine:
|
||||
bool may_defer_acias_ = true;
|
||||
bool keyboard_needs_clock_ = false;
|
||||
bool mfp_is_realtime_ = false;
|
||||
bool dma_is_realtime_ = false;
|
||||
ClockingHint::Preference dma_clocking_preference_ = ClockingHint::Preference::None;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
|
||||
// This is being called by one of the components; avoid any time flushing here as that's
|
||||
// already dealt with (and, just to be absolutely sure, to avoid recursive mania).
|
||||
@@ -532,7 +541,7 @@ class ConcreteMachine:
|
||||
(midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime);
|
||||
keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None;
|
||||
mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
|
||||
dma_is_realtime_ = dma_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
|
||||
dma_clocking_preference_ = dma_.last_valid()->preferred_clocking();
|
||||
}
|
||||
|
||||
// MARK: - GPIP input.
|
||||
@@ -563,7 +572,7 @@ class ConcreteMachine:
|
||||
GPIP 0: centronics busy
|
||||
*/
|
||||
mfp_->set_port_input(
|
||||
0x80 | // b7: Monochrome monitor detect (1 = is monochrome).
|
||||
0x80 | // b7: Monochrome monitor detect (0 = is monochrome).
|
||||
0x40 | // b6: RS-232 ring indicator.
|
||||
(dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested).
|
||||
((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested).
|
||||
@@ -658,7 +667,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
dma_->set_activity_observer(observer);
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool si
|
||||
}
|
||||
|
||||
void DMAController::set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
fdc_.drives_[drive]->set_disk(disk);
|
||||
fdc_.set_disk(disk, drive);
|
||||
}
|
||||
|
||||
void DMAController::run_for(HalfCycles duration) {
|
||||
@@ -256,6 +256,5 @@ ClockingHint::Preference DMAController::preferred_clocking() {
|
||||
}
|
||||
|
||||
void DMAController::set_activity_observer(Activity::Observer *observer) {
|
||||
fdc_.drives_[0]->set_activity_observer(observer, "Internal", true);
|
||||
fdc_.drives_[1]->set_activity_observer(observer, "External", true);
|
||||
fdc_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
@@ -56,30 +56,36 @@ class DMAController: public WD::WD1770::Delegate, public ClockingHint::Source, p
|
||||
HalfCycles running_time_;
|
||||
struct WD1772: public WD::WD1770 {
|
||||
WD1772(): WD::WD1770(WD::WD1770::P1772) {
|
||||
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
set_drive(drives_[0]);
|
||||
emplace_drives(2, 8000000, 300, 2);
|
||||
set_is_double_density(true); // TODO: is this selectable on the ST?
|
||||
}
|
||||
|
||||
void set_motor_on(bool motor_on) final {
|
||||
drives_[0]->set_motor_on(motor_on);
|
||||
drives_[1]->set_motor_on(motor_on);
|
||||
for_all_drives([motor_on] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_motor_on(motor_on);
|
||||
});
|
||||
}
|
||||
|
||||
void set_floppy_drive_selection(bool drive1, bool drive2, bool side2) {
|
||||
// TODO: handle no drives and/or both drives selected.
|
||||
if(drive1) {
|
||||
set_drive(drives_[0]);
|
||||
} else {
|
||||
set_drive(drives_[1]);
|
||||
}
|
||||
set_drive(
|
||||
(drive1 ? 1 : 0) |
|
||||
(drive2 ? 2 : 0)
|
||||
);
|
||||
|
||||
drives_[0]->set_head(side2);
|
||||
drives_[1]->set_head(side2);
|
||||
for_all_drives([side2] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_head(side2);
|
||||
});
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
get_drive(0).set_activity_observer(observer, "Internal", true);
|
||||
get_drive(1).set_activity_observer(observer, "External", true);
|
||||
}
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
get_drive(drive).set_disk(disk);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
|
||||
} fdc_;
|
||||
|
||||
void wd1770_did_change_output(WD::WD1770 *) final;
|
||||
|
||||
@@ -162,7 +162,7 @@ class IntelligentKeyboard:
|
||||
Input(Input::Fire, 0),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &input, bool is_active) override {
|
||||
void did_set_input(const Input &input, bool is_active) final {
|
||||
uint8_t mask = 0;
|
||||
switch(input.type) {
|
||||
default: return;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#define CYCLE(x) ((x) * 2)
|
||||
|
||||
using namespace Atari::ST;
|
||||
|
||||
namespace {
|
||||
@@ -29,7 +31,7 @@ const struct VerticalParams {
|
||||
} vertical_params[3] = {
|
||||
{63, 263, 313}, // 47 rather than 63 on early machines.
|
||||
{34, 234, 263},
|
||||
{1, 401, 500} // 72 Hz mode: who knows?
|
||||
{34, 434, 500} // Guesswork: (i) nobody ever recommends 72Hz mode for opening the top border, so it's likely to be the same as another mode; (ii) being the same as PAL feels too late.
|
||||
};
|
||||
|
||||
/// @returns The correct @c VerticalParams for output at @c frequency.
|
||||
@@ -37,8 +39,6 @@ const VerticalParams &vertical_parameters(Video::FieldFrequency frequency) {
|
||||
return vertical_params[int(frequency)];
|
||||
}
|
||||
|
||||
|
||||
#define CYCLE(x) ((x) * 2)
|
||||
/*!
|
||||
Defines the horizontal counts at which mode-specific events will occur:
|
||||
horizontal enable being set and being reset, blank being set and reset, and the
|
||||
@@ -57,13 +57,23 @@ const struct HorizontalParams {
|
||||
const int set_blank;
|
||||
const int reset_blank;
|
||||
|
||||
const int length;
|
||||
const int vertical_decision;
|
||||
|
||||
LineLength length;
|
||||
} horizontal_params[3] = {
|
||||
{CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(512)},
|
||||
{CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(508)},
|
||||
{CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(224)} // 72Hz mode doesn't set or reset blank.
|
||||
{CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(502), { CYCLE(512), CYCLE(464), CYCLE(504) }},
|
||||
{CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(502), { CYCLE(508), CYCLE(460), CYCLE(500) }},
|
||||
{CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(214), { CYCLE(224), CYCLE(194), CYCLE(212) }}
|
||||
// 72Hz mode doesn't set or reset blank.
|
||||
};
|
||||
|
||||
// Re: 'vertical_decision':
|
||||
// This is cycle 502 if in 50 or 60 Hz mode; in 70 Hz mode I've put it on cycle 214
|
||||
// in order to be analogous to 50 and 60 Hz mode. I have no idea where it should
|
||||
// actually go.
|
||||
//
|
||||
// Ditto the horizontal sync timings for 72Hz are plucked out of thin air.
|
||||
|
||||
const HorizontalParams &horizontal_parameters(Video::FieldFrequency frequency) {
|
||||
return horizontal_params[int(frequency)];
|
||||
}
|
||||
@@ -79,10 +89,10 @@ struct Checker {
|
||||
assert(horizontal.reset_blank < horizontal.set_enable);
|
||||
assert(horizontal.set_enable < horizontal.reset_enable);
|
||||
assert(horizontal.reset_enable < horizontal.set_blank);
|
||||
assert(horizontal.set_blank+50 < horizontal.length);
|
||||
assert(horizontal.set_blank+50 < horizontal.length.length);
|
||||
} else {
|
||||
assert(horizontal.set_enable < horizontal.reset_enable);
|
||||
assert(horizontal.set_enable+50 <horizontal.length);
|
||||
assert(horizontal.set_enable+50 <horizontal.length.length);
|
||||
}
|
||||
|
||||
// Expected vertical order of events: reset blank, enable display, disable display, enable blank (at least 50 before end of line), end of line
|
||||
@@ -97,12 +107,13 @@ struct Checker {
|
||||
const int de_delay_period = CYCLE(28); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX.
|
||||
const int vsync_x_position = CYCLE(56); // Horizontal cycle on which vertical sync changes happen.
|
||||
|
||||
const int hsync_start = CYCLE(48); // Cycles before end of line when hsync starts.
|
||||
const int hsync_end = CYCLE(8); // Cycles before end of line when hsync ends.
|
||||
const int line_length_latch_position = CYCLE(54);
|
||||
|
||||
const int hsync_delay_period = hsync_end; // Signal hsync at the end of the line.
|
||||
const int hsync_delay_period = CYCLE(8); // Signal hsync at the end of the line.
|
||||
const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same delay as hsync.
|
||||
|
||||
const int load_delay_period = CYCLE(4); // Amount of time after DE that observed DE changes. NB: HACK HERE. This currently incorporates the MFP recognition delay. MUST FIX.
|
||||
|
||||
// "VSYNC starts 104 cycles after the start of the previous line's HSYNC, so that's 4 cycles before DE would be activated. ";
|
||||
// that's an inconsistent statement since it would imply VSYNC at +54, which is 2 cycles before DE in 60Hz mode and 6 before
|
||||
// in 50Hz mode. I've gone with 56, to be four cycles ahead of DE in 50Hz mode.
|
||||
@@ -110,13 +121,13 @@ const int vsync_delay_period = hsync_delay_period; // Signal vsync with the same
|
||||
}
|
||||
|
||||
Video::Video() :
|
||||
deferrer_([=] (HalfCycles duration) { advance(duration); }),
|
||||
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
|
||||
crt_(2048, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
|
||||
// crt_(896, 1, 500, 5, Outputs::Display::InputDataType::Red4Green4Blue4),
|
||||
video_stream_(crt_, palette_) {
|
||||
|
||||
// Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's
|
||||
// usual output height of 200 lines.
|
||||
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 220, 850, 4.0f / 3.0f));
|
||||
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 440, 1700, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void Video::set_ram(uint16_t *ram, size_t size) {
|
||||
@@ -127,52 +138,41 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 4.0f;
|
||||
}
|
||||
|
||||
void Video::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
void Video::run_for(HalfCycles duration) {
|
||||
deferrer_.run_for(duration);
|
||||
}
|
||||
|
||||
void Video::advance(HalfCycles duration) {
|
||||
const auto horizontal_timings = horizontal_parameters(field_frequency_);
|
||||
const auto vertical_timings = vertical_parameters(field_frequency_);
|
||||
int integer_duration = int(duration.as_integral());
|
||||
|
||||
// Effect any changes in visible state out here; they're not relevant in the inner loop.
|
||||
if(!pending_events_.empty()) {
|
||||
auto erase_iterator = pending_events_.begin();
|
||||
int duration_remaining = integer_duration;
|
||||
while(erase_iterator != pending_events_.end()) {
|
||||
erase_iterator->delay -= duration_remaining;
|
||||
if(erase_iterator->delay <= 0) {
|
||||
duration_remaining = -erase_iterator->delay;
|
||||
erase_iterator->apply(public_state_);
|
||||
++erase_iterator;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(erase_iterator != pending_events_.begin()) {
|
||||
pending_events_.erase(pending_events_.begin(), erase_iterator);
|
||||
}
|
||||
}
|
||||
assert(integer_duration >= 0);
|
||||
|
||||
while(integer_duration) {
|
||||
const auto horizontal_timings = horizontal_parameters(field_frequency_);
|
||||
const auto vertical_timings = vertical_parameters(field_frequency_);
|
||||
|
||||
// Determine time to next event; this'll either be one of the ones informally scheduled in here,
|
||||
// or something from the deferral queue.
|
||||
|
||||
// Seed next event to end of line.
|
||||
int next_event = line_length_;
|
||||
int next_event = line_length_.length;
|
||||
|
||||
const int next_deferred_event = deferrer_.time_until_next_action().as<int>();
|
||||
if(next_deferred_event >= 0)
|
||||
next_event = std::min(next_event, next_deferred_event + x_);
|
||||
|
||||
// Check the explicitly-placed events.
|
||||
if(horizontal_timings.reset_blank > x_) next_event = std::min(next_event, horizontal_timings.reset_blank);
|
||||
if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank);
|
||||
if(horizontal_timings.reset_enable > x_) next_event = std::min(next_event, horizontal_timings.reset_enable);
|
||||
if(horizontal_timings.set_enable > x_) next_event = std::min(next_event, horizontal_timings.set_enable);
|
||||
if(next_load_toggle_ > x_) next_event = std::min(next_event, next_load_toggle_);
|
||||
|
||||
// Check for events that are relative to existing latched state.
|
||||
if(line_length_ - hsync_start > x_) next_event = std::min(next_event, line_length_ - hsync_start);
|
||||
if(line_length_ - hsync_end > x_) next_event = std::min(next_event, line_length_ - hsync_end);
|
||||
if(line_length_.hsync_start > x_) next_event = std::min(next_event, line_length_.hsync_start);
|
||||
if(line_length_.hsync_end > x_) next_event = std::min(next_event, line_length_.hsync_end);
|
||||
|
||||
// Also, a vertical sync event might intercede.
|
||||
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < vsync_x_position && next_event >= vsync_x_position) {
|
||||
@@ -185,6 +185,8 @@ void Video::advance(HalfCycles duration) {
|
||||
const bool hsync = horizontal_.sync;
|
||||
const bool vsync = vertical_.sync;
|
||||
|
||||
assert(run_length > 0);
|
||||
|
||||
// Ensure proper fetching irrespective of the output.
|
||||
if(load_) {
|
||||
const int since_load = x_ - load_base_;
|
||||
@@ -192,8 +194,8 @@ void Video::advance(HalfCycles duration) {
|
||||
// There will be pixels this line, subject to the shifter pipeline.
|
||||
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
|
||||
// and during the rest of the window, shift out.
|
||||
int start_column = since_load >> 3;
|
||||
const int end_column = (since_load + run_length) >> 3;
|
||||
int start_column = (since_load - 1) >> 3;
|
||||
const int end_column = (since_load + run_length - 1) >> 3;
|
||||
|
||||
while(start_column != end_column) {
|
||||
data_latch_[data_latch_position_] = ram_[current_address_ & 262143];
|
||||
@@ -210,13 +212,16 @@ void Video::advance(HalfCycles duration) {
|
||||
} else if(!load_) {
|
||||
video_stream_.output(run_length, VideoStream::OutputMode::Pixels);
|
||||
} else {
|
||||
const int since_load = x_ - load_base_;
|
||||
const int start = x_ - load_base_;
|
||||
const int end = start + run_length;
|
||||
|
||||
// There will be pixels this line, subject to the shifter pipeline.
|
||||
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
|
||||
// and during the rest of the window, shift out.
|
||||
int start_column = since_load >> 3;
|
||||
const int end_column = (since_load + run_length) >> 3;
|
||||
int start_column = start >> 3;
|
||||
const int end_column = end >> 3;
|
||||
const int start_offset = start & 7;
|
||||
const int end_offset = end & 7;
|
||||
|
||||
// Rules obeyed below:
|
||||
//
|
||||
@@ -225,35 +230,40 @@ void Video::advance(HalfCycles duration) {
|
||||
// was reloaded by the fetch depends on the FIFO.
|
||||
|
||||
if(start_column == end_column) {
|
||||
if(!start_offset) {
|
||||
push_latched_data();
|
||||
}
|
||||
video_stream_.output(run_length, VideoStream::OutputMode::Pixels);
|
||||
} else {
|
||||
// Continue the current column if partway across.
|
||||
if(since_load&7) {
|
||||
if(start_offset) {
|
||||
// If at least one column boundary is crossed, complete this column.
|
||||
video_stream_.output(8 - (since_load & 7), VideoStream::OutputMode::Pixels);
|
||||
video_stream_.output(8 - start_offset, VideoStream::OutputMode::Pixels);
|
||||
++start_column; // This starts a new column, so latch a new word.
|
||||
push_latched_data();
|
||||
}
|
||||
|
||||
// Run for all columns that have their starts in this time period.
|
||||
int complete_columns = end_column - start_column;
|
||||
while(complete_columns--) {
|
||||
video_stream_.output(8, VideoStream::OutputMode::Pixels);
|
||||
push_latched_data();
|
||||
video_stream_.output(8, VideoStream::OutputMode::Pixels);
|
||||
}
|
||||
|
||||
// Output the start of the next column, if necessary.
|
||||
if((since_load + run_length) & 7) {
|
||||
video_stream_.output((since_load + run_length) & 7, VideoStream::OutputMode::Pixels);
|
||||
if(end_offset) {
|
||||
push_latched_data();
|
||||
video_stream_.output(end_offset, VideoStream::OutputMode::Pixels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for whether line length should have been latched during this run.
|
||||
if(x_ <= CYCLE(54) && (x_ + run_length) > CYCLE(54)) line_length_ = horizontal_timings.length;
|
||||
if(x_ < line_length_latch_position && (x_ + run_length) >= line_length_latch_position) {
|
||||
line_length_ = horizontal_timings.length;
|
||||
}
|
||||
|
||||
// Make a decision about vertical state on cycle 502.
|
||||
if(x_ <= CYCLE(502) && (x_ + run_length) > CYCLE(502)) {
|
||||
// Make a decision about vertical state on the appropriate cycle.
|
||||
if(x_ < horizontal_timings.vertical_decision && (x_ + run_length) >= horizontal_timings.vertical_decision) {
|
||||
next_y_ = y_ + 1;
|
||||
next_vertical_ = vertical_;
|
||||
next_vertical_.sync_schedule = VerticalState::SyncSchedule::None;
|
||||
@@ -278,23 +288,17 @@ void Video::advance(HalfCycles duration) {
|
||||
|
||||
// Apply the next event.
|
||||
x_ += run_length;
|
||||
assert(integer_duration >= run_length);
|
||||
integer_duration -= run_length;
|
||||
deferrer_.advance(HalfCycles(run_length));
|
||||
|
||||
// Check horizontal events; the first six are guaranteed to occur separately.
|
||||
if(horizontal_timings.reset_blank == x_) horizontal_.blank = false;
|
||||
else if(horizontal_timings.set_blank == x_) horizontal_.blank = true;
|
||||
else if(horizontal_timings.reset_enable == x_) horizontal_.enable = false;
|
||||
else if(horizontal_timings.set_enable == x_) horizontal_.enable = true;
|
||||
else if(line_length_ - hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; }
|
||||
else if(line_length_ - hsync_end == x_) horizontal_.sync = false;
|
||||
|
||||
// next_load_toggle_ is less predictable; test separately because it may coincide
|
||||
// with one of the above tests.
|
||||
if(next_load_toggle_ == x_) {
|
||||
next_load_toggle_ = -1;
|
||||
load_ ^= true;
|
||||
load_base_ = x_;
|
||||
}
|
||||
else if(line_length_.hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; }
|
||||
else if(line_length_.hsync_end == x_) horizontal_.sync = false;
|
||||
|
||||
// Check vertical events.
|
||||
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == vsync_x_position) {
|
||||
@@ -306,7 +310,7 @@ void Video::advance(HalfCycles duration) {
|
||||
|
||||
// Check whether the terminating event was end-of-line; if so then advance
|
||||
// the vertical bits of state.
|
||||
if(x_ == line_length_) {
|
||||
if(x_ == line_length_.length) {
|
||||
x_ = 0;
|
||||
vertical_ = next_vertical_;
|
||||
y_ = next_y_;
|
||||
@@ -329,21 +333,30 @@ void Video::advance(HalfCycles duration) {
|
||||
// Chuck any deferred output changes into the queue.
|
||||
const bool next_display_enable = vertical_.enable && horizontal_.enable;
|
||||
if(display_enable != next_display_enable) {
|
||||
// Schedule change in outwardly-visible DE line.
|
||||
add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable);
|
||||
// Schedule change in load line.
|
||||
deferrer_.defer(load_delay_period, [this, next_display_enable] {
|
||||
this->load_ = next_display_enable;
|
||||
this->load_base_ = this->x_;
|
||||
});
|
||||
|
||||
// Schedule change in inwardly-visible effect.
|
||||
next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles
|
||||
// Schedule change in outwardly-visible DE line.
|
||||
deferrer_.defer(de_delay_period, [this, next_display_enable] {
|
||||
this->public_state_.display_enable = next_display_enable;
|
||||
});
|
||||
}
|
||||
|
||||
if(horizontal_.sync != hsync) {
|
||||
// Schedule change in outwardly-visible hsync line.
|
||||
add_event(hsync_delay_period - integer_duration, horizontal_.sync ? Event::Type::SetHsync : Event::Type::ResetHsync);
|
||||
deferrer_.defer(hsync_delay_period, [this, next_horizontal_sync = horizontal_.sync] {
|
||||
this->public_state_.hsync = next_horizontal_sync;
|
||||
});
|
||||
}
|
||||
|
||||
if(vertical_.sync != vsync) {
|
||||
// Schedule change in outwardly-visible hsync line.
|
||||
add_event(vsync_delay_period - integer_duration, vertical_.sync ? Event::Type::SetVsync : Event::Type::ResetVsync);
|
||||
deferrer_.defer(vsync_delay_period, [this, next_vertical_sync = vertical_.sync] {
|
||||
this->public_state_.vsync = next_vertical_sync;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,11 +409,12 @@ HalfCycles Video::get_next_sequence_point() {
|
||||
|
||||
const auto horizontal_timings = horizontal_parameters(field_frequency_);
|
||||
|
||||
int event_time = line_length_; // Worst case: report end of line.
|
||||
int event_time = line_length_.length; // Worst case: report end of line.
|
||||
|
||||
// If any events are pending, give the first of those the chance to be next.
|
||||
if(!pending_events_.empty()) {
|
||||
event_time = std::min(event_time, x_ + pending_events_.front().delay);
|
||||
const auto next_deferred_item = deferrer_.time_until_next_action();
|
||||
if(next_deferred_item != HalfCycles(-1)) {
|
||||
event_time = std::min(event_time, x_ + next_deferred_item.as<int>());
|
||||
}
|
||||
|
||||
// If this is a vertically-enabled line, check for the display enable boundaries, + the standard delay.
|
||||
@@ -419,11 +433,17 @@ HalfCycles Video::get_next_sequence_point() {
|
||||
}
|
||||
|
||||
// Test for beginning and end of horizontal sync.
|
||||
if(x_ < line_length_ - hsync_start + hsync_delay_period) {
|
||||
event_time = std::min(line_length_ - hsync_start + hsync_delay_period, event_time);
|
||||
if(x_ < line_length_.hsync_start + hsync_delay_period) {
|
||||
event_time = std::min(line_length_.hsync_start + hsync_delay_period, event_time);
|
||||
}
|
||||
if(x_ < line_length_.hsync_end + hsync_delay_period) {
|
||||
event_time = std::min(line_length_.hsync_end + hsync_delay_period, event_time);
|
||||
}
|
||||
|
||||
// Also factor in the line length latching time.
|
||||
if(x_ < line_length_latch_position) {
|
||||
event_time = std::min(line_length_latch_position, event_time);
|
||||
}
|
||||
/* Hereby assumed: hsync end will be communicated at end of line: */
|
||||
static_assert(hsync_end == hsync_delay_period);
|
||||
|
||||
// It wasn't any of those, just supply end of line. That's when the static_assert above assumes a visible hsync transition.
|
||||
return HalfCycles(event_time - x_);
|
||||
@@ -465,7 +485,7 @@ void Video::write(int address, uint16_t value) {
|
||||
// Sync mode and pixel mode.
|
||||
case 0x05:
|
||||
// Writes to sync mode have a one-cycle delay in effect.
|
||||
deferrer_.defer(HalfCycles(2), [=] {
|
||||
deferrer_.defer(HalfCycles(2), [this, value] {
|
||||
sync_mode_ = value;
|
||||
update_output_mode();
|
||||
});
|
||||
@@ -555,12 +575,12 @@ void Video::VideoStream::generate(int duration, OutputMode mode, bool is_termina
|
||||
if(mode != OutputMode::Pixels) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case OutputMode::Sync: crt_.output_sync(duration_); break;
|
||||
case OutputMode::Blank: crt_.output_blank(duration_); break;
|
||||
case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_); break;
|
||||
case OutputMode::Sync: crt_.output_sync(duration_*2); break;
|
||||
case OutputMode::Blank: crt_.output_blank(duration_*2); break;
|
||||
case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_*2); break;
|
||||
}
|
||||
|
||||
// Reseed duration
|
||||
// Reseed duration.
|
||||
duration_ = duration;
|
||||
|
||||
// The shifter should keep running, so throw away the proper amount of content.
|
||||
@@ -610,7 +630,7 @@ void Video::VideoStream::flush_border() {
|
||||
// Output colour 0 for the entirety of duration_ (or black, if this is 1bpp mode).
|
||||
uint16_t *const colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = (bpp_ != OutputBpp::One) ? palette_[0] : 0;
|
||||
crt_.output_level(duration_);
|
||||
crt_.output_level(duration_*2);
|
||||
|
||||
duration_ = 0;
|
||||
}
|
||||
@@ -711,7 +731,7 @@ void Video::VideoStream::output_pixels(int duration) {
|
||||
}
|
||||
|
||||
// Check whether the limit has been reached.
|
||||
if(pixel_pointer_ == allocation_size) {
|
||||
if(pixel_pointer_ >= allocation_size - 32) {
|
||||
flush_pixels();
|
||||
}
|
||||
}
|
||||
@@ -721,12 +741,12 @@ void Video::VideoStream::output_pixels(int duration) {
|
||||
if(pixels) {
|
||||
int leftover_duration = pixels;
|
||||
switch(bpp_) {
|
||||
case OutputBpp::One: leftover_duration >>= 1; break;
|
||||
default: break;
|
||||
case OutputBpp::Four: leftover_duration <<= 1; break;
|
||||
default: leftover_duration >>= 1; break;
|
||||
case OutputBpp::Two: break;
|
||||
case OutputBpp::Four: leftover_duration <<= 1; break;
|
||||
}
|
||||
shift(leftover_duration);
|
||||
crt_.output_data(leftover_duration);
|
||||
crt_.output_data(leftover_duration*2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,9 +754,9 @@ void Video::VideoStream::flush_pixels() {
|
||||
// Flush only if there's something to flush.
|
||||
if(pixel_pointer_) {
|
||||
switch(bpp_) {
|
||||
case OutputBpp::One: crt_.output_data(pixel_pointer_ >> 1, size_t(pixel_pointer_)); break;
|
||||
default: crt_.output_data(pixel_pointer_); break;
|
||||
case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break;
|
||||
case OutputBpp::One: crt_.output_data(pixel_pointer_); break;
|
||||
default: crt_.output_data(pixel_pointer_ << 1, size_t(pixel_pointer_)); break;
|
||||
case OutputBpp::Four: crt_.output_data(pixel_pointer_ << 2, size_t(pixel_pointer_)); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,7 +777,13 @@ void Video::VideoStream::set_bpp(OutputBpp bpp) {
|
||||
}
|
||||
|
||||
void Video::VideoStream::load(uint64_t value) {
|
||||
output_shifter_ = value;
|
||||
// In 1bpp mode, a 0 bit is white and a 1 bit is black.
|
||||
// Invert the input so that the 'just output the border colour
|
||||
// when the shifter is empty' optimisation works.
|
||||
if(bpp_ == OutputBpp::One)
|
||||
output_shifter_ = ~value;
|
||||
else
|
||||
output_shifter_ = value;
|
||||
}
|
||||
|
||||
// MARK: - Range observer.
|
||||
|
||||
@@ -21,6 +21,12 @@ class VideoTester;
|
||||
namespace Atari {
|
||||
namespace ST {
|
||||
|
||||
struct LineLength {
|
||||
int length = 1024;
|
||||
int hsync_start = 1024;
|
||||
int hsync_end = 1024;
|
||||
};
|
||||
|
||||
/*!
|
||||
Models a combination of the parts of the GLUE, MMU and Shifter that in net
|
||||
form the video subsystem of the Atari ST. So not accurate to a real chip, but
|
||||
@@ -40,6 +46,9 @@ class Video {
|
||||
*/
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*!
|
||||
Sets the type of output.
|
||||
*/
|
||||
@@ -112,7 +121,6 @@ class Video {
|
||||
Range get_memory_access_range();
|
||||
|
||||
private:
|
||||
void advance(HalfCycles duration);
|
||||
DeferredQueue<HalfCycles> deferrer_;
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
@@ -125,10 +133,8 @@ class Video {
|
||||
int current_address_ = 0;
|
||||
|
||||
uint16_t *ram_ = nullptr;
|
||||
uint16_t line_buffer_[256];
|
||||
|
||||
int x_ = 0, y_ = 0, next_y_ = 0;
|
||||
int next_load_toggle_ = -1;
|
||||
bool load_ = false;
|
||||
int load_base_ = 0;
|
||||
|
||||
@@ -160,7 +166,7 @@ class Video {
|
||||
} sync_schedule = SyncSchedule::None;
|
||||
bool sync = false;
|
||||
} vertical_, next_vertical_;
|
||||
int line_length_ = 1024;
|
||||
LineLength line_length_;
|
||||
|
||||
int data_latch_position_ = 0;
|
||||
int data_latch_read_position_ = 0;
|
||||
@@ -234,59 +240,6 @@ class Video {
|
||||
bool vsync = false;
|
||||
} public_state_;
|
||||
|
||||
struct Event {
|
||||
int delay;
|
||||
enum class Type {
|
||||
SetDisplayEnable, ResetDisplayEnable,
|
||||
SetHsync, ResetHsync,
|
||||
SetVsync, ResetVsync,
|
||||
} type;
|
||||
|
||||
Event(Type type, int delay) : delay(delay), type(type) {}
|
||||
|
||||
void apply(PublicState &state) {
|
||||
apply(type, state);
|
||||
}
|
||||
|
||||
static void apply(Type type, PublicState &state) {
|
||||
switch(type) {
|
||||
default:
|
||||
case Type::SetDisplayEnable: state.display_enable = true; break;
|
||||
case Type::ResetDisplayEnable: state.display_enable = false; break;
|
||||
case Type::SetHsync: state.hsync = true; break;
|
||||
case Type::ResetHsync: state.hsync = false; break;
|
||||
case Type::SetVsync: state.vsync = true; break;
|
||||
case Type::ResetVsync: state.vsync = false; break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Event> pending_events_;
|
||||
void add_event(int delay, Event::Type type) {
|
||||
// Apply immediately if there's no delay (or a negative delay).
|
||||
if(delay <= 0) {
|
||||
Event::apply(type, public_state_);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!pending_events_.empty()) {
|
||||
// Otherwise enqueue, having subtracted the delay for any preceding events,
|
||||
// and subtracting from the subsequent, if any.
|
||||
auto insertion_point = pending_events_.begin();
|
||||
while(insertion_point != pending_events_.end() && insertion_point->delay < delay) {
|
||||
delay -= insertion_point->delay;
|
||||
++insertion_point;
|
||||
}
|
||||
if(insertion_point != pending_events_.end()) {
|
||||
insertion_point->delay -= delay;
|
||||
}
|
||||
|
||||
pending_events_.emplace(insertion_point, type, delay);
|
||||
} else {
|
||||
pending_events_.emplace_back(type, delay);
|
||||
}
|
||||
}
|
||||
|
||||
friend class ::VideoTester;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "../Configurable/StandardOptions.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
// TODO: rename.
|
||||
@@ -37,6 +38,13 @@ class Machine {
|
||||
*/
|
||||
virtual void set_scan_target(Outputs::Display::ScanTarget *scan_target) = 0;
|
||||
|
||||
/*!
|
||||
@returns The current scan status.
|
||||
*/
|
||||
virtual Outputs::Display::ScanStatus get_scan_status() const {
|
||||
return get_scaled_scan_status() / float(clock_rate_);
|
||||
}
|
||||
|
||||
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
|
||||
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
|
||||
|
||||
@@ -46,11 +54,89 @@ class Machine {
|
||||
|
||||
/// Runs the machine for @c duration seconds.
|
||||
virtual void run_for(Time::Seconds duration) {
|
||||
const double cycles = (duration * clock_rate_) + clock_conversion_error_;
|
||||
const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
|
||||
clock_conversion_error_ = std::fmod(cycles, 1.0);
|
||||
run_for(Cycles(static_cast<int>(cycles)));
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
|
||||
emulated machine to run 50% faster than a real machine. This speed-up is an emulation
|
||||
fiction: it will apply across the system, including to the CRT.
|
||||
*/
|
||||
virtual void set_speed_multiplier(double multiplier) {
|
||||
speed_multiplier_ = multiplier;
|
||||
auto speaker = get_speaker();
|
||||
if(speaker) {
|
||||
speaker->set_input_rate_multiplier(float(multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The current speed multiplier.
|
||||
*/
|
||||
virtual double get_speed_multiplier() {
|
||||
return speed_multiplier_;
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for the machine for at least @c duration seconds, and then until @c condition is true.
|
||||
|
||||
@returns The amount of time run for.
|
||||
*/
|
||||
Time::Seconds run_until(Time::Seconds minimum_duration, std::function<bool()> condition) {
|
||||
Time::Seconds total_runtime = minimum_duration;
|
||||
run_for(minimum_duration);
|
||||
while(!condition()) {
|
||||
// Advance in increments of one 500th of a second until the condition
|
||||
// is true; that's 1/10th of a 50Hz frame, but more like 1/8.33 of a
|
||||
// 60Hz frame. Though most machines aren't exactly 50Hz or 60Hz, and some
|
||||
// are arbitrary other refresh rates. So those observations are merely
|
||||
// for scale.
|
||||
run_for(0.002);
|
||||
total_runtime += 0.002;
|
||||
}
|
||||
return total_runtime;
|
||||
}
|
||||
|
||||
enum MachineEvent: int {
|
||||
/// At least one new packet of audio has been delivered to the spaker's delegate.
|
||||
NewSpeakerSamplesGenerated = 1 << 0,
|
||||
|
||||
/// The next vertical retrace has begun.
|
||||
VerticalSync = 1 << 1,
|
||||
};
|
||||
|
||||
/*!
|
||||
Runs for at least @c duration seconds, and then every one of the @c events has occurred at least once since this
|
||||
call to @c run_until_event.
|
||||
|
||||
@param events A bitmask comprised of @c MachineEvent flags.
|
||||
@returns The amount of time run for.
|
||||
*/
|
||||
Time::Seconds run_until(Time::Seconds minimum_duration, int events) {
|
||||
// Tie up a wait-for-samples, if requested.
|
||||
const Outputs::Speaker::Speaker *speaker = nullptr;
|
||||
int sample_sets = 0;
|
||||
if(events & MachineEvent::NewSpeakerSamplesGenerated) {
|
||||
speaker = get_speaker();
|
||||
if(!speaker) events &= ~MachineEvent::NewSpeakerSamplesGenerated;
|
||||
sample_sets = speaker->completed_sample_sets();
|
||||
}
|
||||
|
||||
int retraces = 0;
|
||||
if(events & MachineEvent::VerticalSync) {
|
||||
retraces = get_scan_status().hsync_count;
|
||||
}
|
||||
|
||||
// Run until all requested events are satisfied.
|
||||
return run_until(minimum_duration, [=]() {
|
||||
return
|
||||
(!(events & MachineEvent::NewSpeakerSamplesGenerated) || (sample_sets != speaker->completed_sample_sets())) &&
|
||||
(!(events & MachineEvent::VerticalSync) || (retraces != get_scan_status().hsync_count));
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
@@ -61,6 +147,15 @@ class Machine {
|
||||
return clock_rate_;
|
||||
}
|
||||
|
||||
virtual Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||
// This deliberately sets up an infinite loop if the user hasn't
|
||||
// overridden at least one of this or get_scan_status.
|
||||
//
|
||||
// Most likely you want to override this, and let the base class
|
||||
// throw in a divide-by-clock-rate at the end for you.
|
||||
return get_scan_status();
|
||||
}
|
||||
|
||||
/*!
|
||||
Maps from Configurable::Display to Outputs::Display::VideoSignal and calls
|
||||
@c set_display_type with the result.
|
||||
@@ -90,9 +185,11 @@ class Machine {
|
||||
*/
|
||||
virtual void set_display_type(Outputs::Display::DisplayType display_type) {}
|
||||
|
||||
|
||||
private:
|
||||
double clock_rate_ = 1.0;
|
||||
double clock_conversion_error_ = 0.0;
|
||||
double speed_multiplier_ = 1.0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class Joystick: public Inputs::ConcreteJoystick {
|
||||
Input('9'), Input('*'), Input('#'),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
|
||||
@@ -177,23 +177,27 @@ class ConcreteMachine:
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
vdp_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return vdp_->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
vdp_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
@@ -358,30 +362,30 @@ class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
float get_confidence() override {
|
||||
float get_confidence() final {
|
||||
if(pc_zero_accesses_ > 1) return 0.0f;
|
||||
return confidence_counter_.get_confidence();
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Coleco::Vision::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
Configurable::Display display;
|
||||
if(Configurable::get_display(selections_by_option, display)) {
|
||||
set_video_signal_configurable(display);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::SVideo);
|
||||
return selection_set;
|
||||
@@ -401,9 +405,9 @@ class ConcreteMachine:
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910>> speaker_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
|
||||
|
||||
std::vector<uint8_t> bios_;
|
||||
std::vector<uint8_t> cartridge_;
|
||||
|
||||
@@ -19,7 +19,6 @@ using namespace Commodore::C1540;
|
||||
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
Storage::Disk::Controller(1000000),
|
||||
m6502_(*this),
|
||||
drive_(new Storage::Disk::Drive(1000000, 300, 2)),
|
||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||
serial_port_(new SerialPort),
|
||||
drive_VIA_(drive_VIA_port_handler_),
|
||||
@@ -37,7 +36,8 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
|
||||
// attach the only drive there is
|
||||
set_drive(drive_);
|
||||
emplace_drive(1000000, 300, 2);
|
||||
set_drive(1);
|
||||
|
||||
std::string device_name;
|
||||
uint32_t crc = 0;
|
||||
@@ -103,21 +103,21 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
drive_->set_disk(disk);
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
void Machine::run_for(const Cycles cycles) {
|
||||
m6502_.run_for(cycles);
|
||||
|
||||
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
|
||||
drive_->set_motor_on(drive_motor);
|
||||
const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
|
||||
get_drive().set_motor_on(drive_motor);
|
||||
if(drive_motor)
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
}
|
||||
|
||||
void MachineBase::set_activity_observer(Activity::Observer *observer) {
|
||||
drive_VIA_.bus_handler().set_activity_observer(observer);
|
||||
drive_->set_activity_observer(observer, "Drive", false);
|
||||
get_drive().set_activity_observer(observer, "Drive", false);
|
||||
}
|
||||
|
||||
// MARK: - 6522 delegate
|
||||
@@ -154,7 +154,7 @@ void MachineBase::process_index_hole() {}
|
||||
// MARK: - Drive VIA delegate
|
||||
|
||||
void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) {
|
||||
drive_->step(Storage::Disk::HeadPosition(direction, 2));
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction, 2));
|
||||
}
|
||||
|
||||
void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) {
|
||||
|
||||
@@ -144,7 +144,6 @@ class MachineBase:
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
@@ -259,7 +259,7 @@ class Joystick: public Inputs::ConcreteJoystick {
|
||||
user_port_via_port_handler_(user_port_via_port_handler),
|
||||
keyboard_via_port_handler_(keyboard_via_port_handler) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
JoystickInput mapped_input;
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
@@ -397,9 +397,10 @@ class ConcreteMachine:
|
||||
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
|
||||
memset(mos6560_bus_handler_.video_memory_map, 0, sizeof(mos6560_bus_handler_.video_memory_map));
|
||||
|
||||
#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);
|
||||
#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.
|
||||
set_ram(0x0000, 0x0400);
|
||||
@@ -453,7 +454,7 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_->set_tape(media.tapes.front());
|
||||
}
|
||||
@@ -477,18 +478,18 @@ class ConcreteMachine:
|
||||
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
if(key != KeyRestore)
|
||||
keyboard_via_port_handler_->set_key_state(key, is_pressed);
|
||||
else
|
||||
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
void clear_all_keys() final {
|
||||
keyboard_via_port_handler_->clear_all_keys();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
@@ -618,45 +619,49 @@ class ConcreteMachine:
|
||||
mos6560_.flush();
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
void run_for(const Cycles cycles) final {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
mos6560_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override final {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return mos6560_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
mos6560_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return mos6560_.get_speaker();
|
||||
}
|
||||
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) final {
|
||||
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
|
||||
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) override final {
|
||||
void type_string(const std::string &string) final {
|
||||
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>());
|
||||
}
|
||||
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final {
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) final {
|
||||
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
|
||||
}
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Commodore::Vic20::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
allow_fast_tape_hack_ = quickload;
|
||||
@@ -669,27 +674,27 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
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_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
|
||||
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
if(c1540_) c1540_->set_activity_observer(observer);
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ class ConcreteMachine:
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
void set_key_state(uint16_t key, bool isPressed) final {
|
||||
if(key == KeyBreak) {
|
||||
m6502_.set_reset_line(isPressed);
|
||||
} else {
|
||||
@@ -125,12 +125,12 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
void clear_all_keys() final {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
if(is_holding_shift_) set_key_state(KeyShift, true);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_.set_tape(media.tapes.front());
|
||||
}
|
||||
@@ -379,49 +379,53 @@ class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
video_output_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_output_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
video_output_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
void run_for(const Cycles cycles) final {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void tape_did_change_interrupt_status(Tape *tape) override final {
|
||||
void tape_did_change_interrupt_status(Tape *tape) final {
|
||||
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
HalfCycles get_typer_delay() override final {
|
||||
HalfCycles get_typer_delay() final {
|
||||
return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting
|
||||
}
|
||||
|
||||
HalfCycles get_typer_frequency() override final {
|
||||
HalfCycles get_typer_frequency() final {
|
||||
return Cycles(625*128*2); // accept a new character every two frames
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) override final {
|
||||
void type_string(const std::string &string) final {
|
||||
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>());
|
||||
}
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Electron::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
allow_fast_tape_hack_ = quickload;
|
||||
@@ -434,14 +438,14 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
@@ -449,7 +453,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
activity_observer_ = observer;
|
||||
if(activity_observer_) {
|
||||
activity_observer_->register_led(caps_led);
|
||||
|
||||
@@ -34,7 +34,7 @@ enum Key: uint16_t {
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
using namespace Electron;
|
||||
|
||||
Plus3::Plus3() : WD1770(P1770) {
|
||||
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
emplace_drives(2, 8000000, 300, 2);
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
drives_[drive]->set_disk(disk);
|
||||
get_drive(drive).set_disk(disk);
|
||||
}
|
||||
|
||||
void Plus3::set_control_register(uint8_t control) {
|
||||
@@ -33,16 +32,15 @@ void Plus3::set_control_register(uint8_t control) {
|
||||
|
||||
void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
||||
if(changes&3) {
|
||||
switch(control&3) {
|
||||
case 0: selected_drive_ = -1; set_drive(nullptr); break;
|
||||
default: selected_drive_ = 0; set_drive(drives_[0]); break;
|
||||
case 2: selected_drive_ = 1; set_drive(drives_[1]); break;
|
||||
}
|
||||
set_drive(control&3);
|
||||
}
|
||||
|
||||
// Select the side on both drives at once.
|
||||
if(changes & 0x04) {
|
||||
drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||
drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||
get_drive(0).set_head((control & 0x04) ? 1 : 0);
|
||||
get_drive(1).set_head((control & 0x04) ? 1 : 0);
|
||||
}
|
||||
|
||||
if(changes & 0x08) set_is_double_density(!(control & 0x08));
|
||||
}
|
||||
|
||||
@@ -53,9 +51,7 @@ void Plus3::set_motor_on(bool on) {
|
||||
}
|
||||
|
||||
void Plus3::set_activity_observer(Activity::Observer *observer) {
|
||||
size_t index = 0;
|
||||
for(const auto &drive: drives_) {
|
||||
drive->set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
|
||||
++index;
|
||||
}
|
||||
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
|
||||
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ class Plus3 : public WD::WD1770 {
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
|
||||
int selected_drive_ = 0;
|
||||
uint8_t last_control_ = 0;
|
||||
|
||||
void set_motor_on(bool on);
|
||||
|
||||
@@ -36,13 +36,13 @@ void SoundGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
}
|
||||
|
||||
void SoundGenerator::set_divider(uint8_t divider) {
|
||||
audio_queue_.defer([=]() {
|
||||
audio_queue_.defer([this, divider]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void SoundGenerator::set_is_enabled(bool is_enabled) {
|
||||
audio_queue_.defer([=]() {
|
||||
audio_queue_.defer([this, is_enabled]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ class SoundGenerator: public ::Outputs::Speaker::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);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
@@ -56,6 +56,10 @@ void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / float(crt_cycles_multiplier);
|
||||
}
|
||||
|
||||
void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ class VideoOutput {
|
||||
/// Sets the destination for output.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler {
|
||||
ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
|
||||
map_(map), slot_(slot) {}
|
||||
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
|
||||
switch(address >> 11) {
|
||||
default:
|
||||
if(pc_is_outside_bios) confidence_counter_.add_miss();
|
||||
@@ -39,7 +39,7 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler {
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::string debug_type() override {
|
||||
virtual std::string debug_type() final {
|
||||
return "A16";
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler {
|
||||
ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
|
||||
map_(map), slot_(slot) {}
|
||||
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
|
||||
switch(address >> 11) {
|
||||
default:
|
||||
if(pc_is_outside_bios) confidence_counter_.add_miss();
|
||||
@@ -51,7 +51,7 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler {
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::string debug_type() override {
|
||||
virtual std::string debug_type() final {
|
||||
return "A8";
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class KonamiROMSlotHandler: public ROMSlotHandler {
|
||||
KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) :
|
||||
map_(map), slot_(slot) {}
|
||||
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
|
||||
switch(address >> 13) {
|
||||
default:
|
||||
if(pc_is_outside_bios) confidence_counter_.add_miss();
|
||||
@@ -45,7 +45,7 @@ class KonamiROMSlotHandler: public ROMSlotHandler {
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::string debug_type() override {
|
||||
virtual std::string debug_type() final {
|
||||
return "K";
|
||||
}
|
||||
private:
|
||||
|
||||
@@ -20,7 +20,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
|
||||
KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) :
|
||||
map_(map), slot_(slot), scc_(scc) {}
|
||||
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final {
|
||||
switch(address >> 11) {
|
||||
default:
|
||||
if(pc_is_outside_bios) confidence_counter_.add_miss();
|
||||
@@ -66,7 +66,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t read(uint16_t address) override {
|
||||
uint8_t read(uint16_t address) final {
|
||||
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
|
||||
confidence_counter_.add_hit();
|
||||
return scc_.read(address);
|
||||
@@ -75,7 +75,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
virtual std::string debug_type() override {
|
||||
virtual std::string debug_type() final {
|
||||
return "KSCC";
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ using namespace MSX;
|
||||
DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
|
||||
WD1770(P1793),
|
||||
rom_(rom) {
|
||||
drives_[0] = std::make_shared<Storage::Disk::Drive>(8000000, 300, 2);
|
||||
drives_[1] = std::make_shared<Storage::Disk::Drive>(8000000, 300, 2);
|
||||
emplace_drives(2, 8000000, 300, 2);
|
||||
set_is_double_density(true);
|
||||
}
|
||||
|
||||
@@ -23,18 +22,19 @@ void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) {
|
||||
case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb:
|
||||
WD::WD1770::write(address, value);
|
||||
break;
|
||||
case 0x7ffc:
|
||||
selected_head_ = value & 1;
|
||||
drives_[0]->set_head(selected_head_);
|
||||
drives_[1]->set_head(selected_head_);
|
||||
break;
|
||||
case 0x7ffc: {
|
||||
const int selected_head = value & 1;
|
||||
for_all_drives([selected_head] (Storage::Disk::Drive &drive, size_t index) {
|
||||
drive.set_head(selected_head);
|
||||
});
|
||||
} break;
|
||||
case 0x7ffd: {
|
||||
selected_drive_ = value & 1;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
set_drive(1 << (value & 1));
|
||||
|
||||
bool drive_motor = !!(value & 0x80);
|
||||
drives_[0]->set_motor_on(drive_motor);
|
||||
drives_[1]->set_motor_on(drive_motor);
|
||||
const bool drive_motor = value & 0x80;
|
||||
for_all_drives([drive_motor] (Storage::Disk::Drive &drive, size_t index) {
|
||||
drive.set_motor_on(drive_motor);
|
||||
});
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ void DiskROM::run_for(HalfCycles half_cycles) {
|
||||
}
|
||||
|
||||
void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
drives_[drive]->set_disk(disk);
|
||||
get_drive(drive).set_disk(disk);
|
||||
}
|
||||
|
||||
void DiskROM::set_head_load_request(bool head_load) {
|
||||
@@ -68,9 +68,7 @@ void DiskROM::set_head_load_request(bool head_load) {
|
||||
}
|
||||
|
||||
void DiskROM::set_activity_observer(Activity::Observer *observer) {
|
||||
size_t c = 1;
|
||||
for(auto &drive: drives_) {
|
||||
drive->set_activity_observer(observer, "Drive " + std::to_string(c), true);
|
||||
++c;
|
||||
}
|
||||
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
|
||||
drive.set_activity_observer(observer, "Drive " + std::to_string(index), true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
|
||||
public:
|
||||
DiskROM(const std::vector<uint8_t> &rom);
|
||||
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override;
|
||||
uint8_t read(uint16_t address) override;
|
||||
void run_for(HalfCycles half_cycles) override;
|
||||
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final;
|
||||
uint8_t read(uint16_t address) final;
|
||||
void run_for(HalfCycles half_cycles) final;
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
@@ -36,11 +36,8 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
|
||||
const std::vector<uint8_t> &rom_;
|
||||
|
||||
long int controller_cycles_ = 0;
|
||||
size_t selected_drive_ = 0;
|
||||
int selected_head_ = 0;
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 2> drives_;
|
||||
|
||||
void set_head_load_request(bool head_load) override;
|
||||
void set_head_load_request(bool head_load) final;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ class AYPortHandler: public GI::AY38910::PortHandler {
|
||||
Input(Input::Fire, 1),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &input, bool is_active) override {
|
||||
void did_set_input(const Input &input, bool is_active) final {
|
||||
uint8_t mask = 0;
|
||||
switch(input.type) {
|
||||
default: return;
|
||||
@@ -278,23 +278,27 @@ class ConcreteMachine:
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
vdp_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return vdp_->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
vdp_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
float get_confidence() override {
|
||||
float get_confidence() final {
|
||||
if(performed_unmapped_access_ || pc_zero_accesses_ > 1) return 0.0f;
|
||||
if(memory_slots_[1].handler) {
|
||||
return memory_slots_[1].handler->get_confidence();
|
||||
@@ -302,14 +306,14 @@ class ConcreteMachine:
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
std::string debug_type() override {
|
||||
std::string debug_type() final {
|
||||
if(memory_slots_[1].handler) {
|
||||
return "MSX:" + memory_slots_[1].handler->debug_type();
|
||||
}
|
||||
return "MSX";
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.cartridges.empty()) {
|
||||
const auto &segment = media.cartridges.front()->get_segments().front();
|
||||
memory_slots_[1].source = segment.data;
|
||||
@@ -356,7 +360,7 @@ class ConcreteMachine:
|
||||
return true;
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) override final {
|
||||
void type_string(const std::string &string) final {
|
||||
std::transform(
|
||||
string.begin(),
|
||||
string.end(),
|
||||
@@ -366,7 +370,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: MSX::MemoryMap
|
||||
void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) override {
|
||||
void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) final {
|
||||
assert(!(destination_address & 8191));
|
||||
assert(!(length & 8191));
|
||||
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
|
||||
@@ -381,7 +385,7 @@ class ConcreteMachine:
|
||||
page_memory(paged_memory_);
|
||||
}
|
||||
|
||||
void unmap(int slot, uint16_t destination_address, std::size_t length) override {
|
||||
void unmap(int slot, uint16_t destination_address, std::size_t length) final {
|
||||
assert(!(destination_address & 8191));
|
||||
assert(!(length & 8191));
|
||||
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
|
||||
@@ -624,26 +628,26 @@ class ConcreteMachine:
|
||||
return key_states_[selected_key_line_];
|
||||
}
|
||||
|
||||
void clear_all_keys() override {
|
||||
void clear_all_keys() final {
|
||||
std::memset(key_states_, 0xff, sizeof(key_states_));
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
int mask = 1 << (key & 7);
|
||||
int line = key >> 4;
|
||||
if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask;
|
||||
}
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return MSX::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
allow_fast_tape_ = quickload;
|
||||
@@ -656,14 +660,14 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
@@ -671,13 +675,13 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Sleeper
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
// MARK: - Activity::Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
DiskROM *disk_rom = get_disk_rom();
|
||||
if(disk_rom) {
|
||||
disk_rom->set_activity_observer(observer);
|
||||
@@ -686,7 +690,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Joysticks
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return ay_port_handler_.get_joysticks();
|
||||
}
|
||||
|
||||
@@ -756,11 +760,11 @@ class ConcreteMachine:
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Konami::SCC scc_;
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC>> speaker_;
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC>> speaker_;
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
bool tape_player_is_sleeping_ = false;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#define LOG_PREFIX "[SMS] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Sega/Target.hpp"
|
||||
@@ -54,7 +56,7 @@ class Joystick: public Inputs::ConcreteJoystick {
|
||||
Input(Input::Fire, 1)
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
|
||||
@@ -168,7 +170,7 @@ class ConcreteMachine:
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
vdp_->set_tv_standard(
|
||||
(region_ == Target::Region::Europe) ?
|
||||
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
|
||||
@@ -177,15 +179,19 @@ class ConcreteMachine:
|
||||
vdp_->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return vdp_->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
vdp_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
@@ -340,16 +346,16 @@ class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
// MARK: - Keyboard (i.e. the pause and reset buttons).
|
||||
Inputs::Keyboard &get_keyboard() override {
|
||||
Inputs::Keyboard &get_keyboard() final {
|
||||
return keyboard_;
|
||||
}
|
||||
|
||||
void keyboard_did_change_key(Inputs::Keyboard *, Inputs::Keyboard::Key key, bool is_pressed) override {
|
||||
void keyboard_did_change_key(Inputs::Keyboard *, Inputs::Keyboard::Key key, bool is_pressed) final {
|
||||
if(key == Inputs::Keyboard::Key::Enter) {
|
||||
pause_is_pressed_ = is_pressed;
|
||||
} else if(key == Inputs::Keyboard::Key::Escape) {
|
||||
@@ -357,28 +363,28 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_keys(Inputs::Keyboard *) override {
|
||||
void reset_all_keys(Inputs::Keyboard *) final {
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Sega::MasterSystem::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
Configurable::Display display;
|
||||
if(Configurable::get_display(selections_by_option, display)) {
|
||||
set_video_signal_configurable(display);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
return selection_set;
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
|
||||
using namespace Oric;
|
||||
|
||||
BD500::BD500() : DiskController(P1793, 9000000) {
|
||||
/*
|
||||
Notes on the code below: the Byte Drive 500 isn't well documented; this implementation is based on
|
||||
experimentation without access to real hardware as documented in http://forum.defence-force.org/viewtopic.php?f=25&t=2055
|
||||
and with additional guidance from iss of Oricutron (amongst other things) elsewhere,
|
||||
e.g. http://forum.defence-force.org/viewtopic.php?f=22&t=1698&start=105#p21468
|
||||
*/
|
||||
|
||||
BD500::BD500() : DiskController(P1793, 9000000, Storage::Disk::Drive::ReadyType::ShugartModifiedRDY) {
|
||||
disable_basic_rom_ = true;
|
||||
select_paged_item();
|
||||
set_is_double_density(true);
|
||||
set_drive(1);
|
||||
}
|
||||
|
||||
void BD500::write(int address, uint8_t value) {
|
||||
@@ -23,6 +31,18 @@ void BD500::write(int address, uint8_t value) {
|
||||
// if(address == 0x320) printf("Command %02x\n", value);
|
||||
WD::WD1770::write(address, value);
|
||||
}
|
||||
|
||||
if(address == 0x031a) {
|
||||
// Drive select; kudos to iss of Oricutron for figuring this one out;
|
||||
// cf. http://forum.defence-force.org/viewtopic.php?f=25&p=21409#p21393
|
||||
switch(value & 0xe0) {
|
||||
default: set_drive(0); break;
|
||||
case 0x20: set_drive(1); break;
|
||||
case 0x40: set_drive(2); break;
|
||||
case 0x80: set_drive(4); break;
|
||||
case 0xc0: set_drive(8); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t BD500::read(int address) {
|
||||
@@ -44,9 +64,10 @@ void BD500::access(int address) {
|
||||
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
|
||||
return;
|
||||
|
||||
case 0x310: enable_overlay_ram_ = true; break;
|
||||
case 0x311: disable_basic_rom_ = true; break;
|
||||
case 0x313: enable_overlay_ram_ = false; break;
|
||||
case 0x317: disable_basic_rom_ = false; break; // Could be 0x311.
|
||||
case 0x314: enable_overlay_ram_ = true; break;
|
||||
case 0x317: disable_basic_rom_ = false; break;
|
||||
|
||||
default:
|
||||
// printf("Switch %04x???\n", address);
|
||||
@@ -56,73 +77,19 @@ void BD500::access(int address) {
|
||||
select_paged_item();
|
||||
}
|
||||
|
||||
/*
|
||||
The following was used when trying to find appropriate soft switch locations. It is preserved
|
||||
as the values I have above are unlikely to be wholly correct and further research might be
|
||||
desirable.
|
||||
|
||||
void BD500::access(int address) {
|
||||
// 0,1,4,5,10,11 -> 64kb Atmos
|
||||
// 2,3,9 -> 56kb Atmos.
|
||||
// Broken: 6, 7, 8
|
||||
|
||||
int order = 5;
|
||||
int commands[4];
|
||||
std::vector<int> available_commands = {0, 1, 2, 3};
|
||||
const int modulos[] = {6, 2, 1, 1};
|
||||
|
||||
for(int c = 0; c < 4; ++c) {
|
||||
const int index = order / modulos[c];
|
||||
commands[c] = available_commands[size_t(index)];
|
||||
available_commands.erase(available_commands.begin() + index);
|
||||
order %= modulos[c];
|
||||
}
|
||||
|
||||
|
||||
// Determine whether to perform a command.
|
||||
int index = -1;
|
||||
switch(address) {
|
||||
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
|
||||
return;
|
||||
|
||||
case 0x310: index = 0; break;
|
||||
case 0x313: index = 1; break;
|
||||
case 0x314: index = 2; break;
|
||||
case 0x317: index = 3; break;
|
||||
|
||||
default:
|
||||
printf("Switch %04x???\n", address);
|
||||
break;
|
||||
}
|
||||
|
||||
select_paged_item();
|
||||
|
||||
if(index >= 0) {
|
||||
switch(commands[index]) {
|
||||
case 0: enable_overlay_ram_ = true; break; // +RAM
|
||||
case 1: disable_basic_rom_ = false; break; // -rom
|
||||
case 2: disable_basic_rom_ = true; break; // +rom
|
||||
case 3: enable_overlay_ram_ = false; break; // -RAM
|
||||
|
||||
}
|
||||
select_paged_item();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void BD500::set_head_load_request(bool head_load) {
|
||||
// Turn all motors on or off; if off then unload the head instantly.
|
||||
is_loading_head_ |= head_load;
|
||||
for(auto &drive : drives_) {
|
||||
if(drive) drive->set_motor_on(head_load);
|
||||
}
|
||||
for_all_drives([head_load] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_motor_on(head_load);
|
||||
});
|
||||
if(!head_load) set_head_loaded(false);
|
||||
}
|
||||
|
||||
void BD500::run_for(const Cycles cycles) {
|
||||
// If a head load is in progress and the selected drive is now ready,
|
||||
// declare head loaded.
|
||||
if(is_loading_head_ && drives_[selected_drive_] && drives_[selected_drive_]->get_is_ready()) {
|
||||
if(is_loading_head_ && get_drive().get_is_ready()) {
|
||||
set_head_loaded(true);
|
||||
is_loading_head_ = false;
|
||||
}
|
||||
|
||||
@@ -13,16 +13,14 @@ namespace Oric {
|
||||
|
||||
class DiskController: public WD::WD1770 {
|
||||
public:
|
||||
DiskController(WD::WD1770::Personality personality, int clock_rate) :
|
||||
WD::WD1770(personality), clock_rate_(clock_rate) {}
|
||||
DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) :
|
||||
WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) {
|
||||
emplace_drives(4, clock_rate_, 300, 2, ready_type_);
|
||||
// TODO: don't assume four drives?
|
||||
}
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
|
||||
const size_t drive = size_t(d);
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive] = std::make_unique<Storage::Disk::Drive>(clock_rate_, 300, 2);
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
get_drive(size_t(d)).set_disk(disk);
|
||||
}
|
||||
|
||||
enum class PagedItem {
|
||||
@@ -44,14 +42,6 @@ class DiskController: public WD::WD1770 {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
||||
size_t selected_drive_ = 0;
|
||||
void select_drive(size_t drive) {
|
||||
if(drive != selected_drive_) {
|
||||
selected_drive_ = drive;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
}
|
||||
}
|
||||
Delegate *delegate_ = nullptr;
|
||||
|
||||
bool enable_overlay_ram_ = false;
|
||||
@@ -67,6 +57,7 @@ class DiskController: public WD::WD1770 {
|
||||
private:
|
||||
PagedItem paged_item_ = PagedItem::DiskROM;
|
||||
int clock_rate_;
|
||||
Storage::Disk::Drive::ReadyType ready_type_;
|
||||
|
||||
inline void set_paged_item(PagedItem item) {
|
||||
if(paged_item_ == item) return;
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace Oric;
|
||||
|
||||
// NB: there's some controversy here on WD1770 versus WD1772, but between those two I think
|
||||
// the only difference is stepping rates, and it says 1770 on the schematic I'm looking at.
|
||||
Jasmin::Jasmin() : DiskController(P1770, 8000000) {
|
||||
Jasmin::Jasmin() : DiskController(P1770, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
|
||||
set_is_double_density(true);
|
||||
select_paged_item();
|
||||
}
|
||||
@@ -20,11 +20,12 @@ Jasmin::Jasmin() : DiskController(P1770, 8000000) {
|
||||
void Jasmin::write(int address, uint8_t value) {
|
||||
switch(address) {
|
||||
// Set side.
|
||||
case 0x3f8:
|
||||
for(auto &drive : drives_) {
|
||||
if(drive) drive->set_head(value & 1);
|
||||
}
|
||||
break;
|
||||
case 0x3f8: {
|
||||
const int head = value & 1;
|
||||
for_all_drives([head] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_head(head);
|
||||
});
|
||||
} break;
|
||||
|
||||
case 0x3f9:
|
||||
/* TODO: reset. */
|
||||
@@ -43,9 +44,19 @@ void Jasmin::write(int address, uint8_t value) {
|
||||
break;
|
||||
|
||||
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
|
||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
|
||||
select_drive(size_t(address - 0x3fc));
|
||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
||||
// Updated selected drives mask.
|
||||
const uint8_t mask = uint8_t(1 << (address - 0x3fc));
|
||||
selected_drives_ = (selected_drives_ & ~mask) | (mask * (value & 1));
|
||||
|
||||
// Select drive.
|
||||
set_drive(selected_drives_);
|
||||
|
||||
// Update motor inputs: apply the motor only to selected drives.
|
||||
// Which may or may not be correct.
|
||||
for_all_drives([mask, this] (Storage::Disk::Drive &drive, size_t index) {
|
||||
const uint8_t shift_mask = uint8_t(1 << index);
|
||||
drive.set_motor_on( (mask & shift_mask) ? motor_on_ : false );
|
||||
});
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -55,9 +66,9 @@ void Jasmin::write(int address, uint8_t value) {
|
||||
|
||||
void Jasmin::set_motor_on(bool on) {
|
||||
motor_on_ = on;
|
||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
||||
get_drive().set_motor_on(motor_on_);
|
||||
if(observer_) {
|
||||
observer_->set_led_status("Microdisc", on);
|
||||
observer_->set_led_status("Jasmin", on);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class Jasmin: public DiskController {
|
||||
private:
|
||||
void set_motor_on(bool on) final;
|
||||
bool motor_on_ = false;
|
||||
uint8_t selected_drives_ = 0;
|
||||
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace {
|
||||
const Cycles::IntType head_load_request_counter_target = 7653333;
|
||||
}
|
||||
|
||||
Microdisc::Microdisc() : DiskController(P1793, 8000000) {
|
||||
Microdisc::Microdisc() : DiskController(P1793, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
@@ -33,16 +33,15 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
|
||||
|
||||
// b65: drive select
|
||||
if((changes >> 5)&3) {
|
||||
selected_drive_ = (control >> 5)&3;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
set_drive(1 << (control >> 5)&3);
|
||||
}
|
||||
|
||||
// b4: side select
|
||||
if(changes & 0x10) {
|
||||
const int head = (control & 0x10) ? 1 : 0;
|
||||
for(auto &drive : drives_) {
|
||||
if(drive) drive->set_head(head);
|
||||
}
|
||||
for_all_drives([head] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_head(head);
|
||||
});
|
||||
}
|
||||
|
||||
// b3: double density select (0 = double)
|
||||
@@ -86,9 +85,9 @@ void Microdisc::set_head_load_request(bool head_load) {
|
||||
|
||||
// The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive
|
||||
// the current head load request state.
|
||||
for(auto &drive : drives_) {
|
||||
if(drive) drive->set_motor_on(head_load);
|
||||
}
|
||||
for_all_drives([head_load] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_motor_on(head_load);
|
||||
});
|
||||
|
||||
// A request to load the head results in a delay until the head is confirmed loaded. This delay is handled
|
||||
// in ::run_for. A request to unload the head results in an instant answer that the head is unloaded.
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
namespace Oric {
|
||||
|
||||
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
|
||||
using AY = GI::AY38910::AY38910<false>;
|
||||
using Speaker = Outputs::Speaker::LowpassSpeaker<AY>;
|
||||
|
||||
enum ROM {
|
||||
BASIC10 = 0, BASIC11, Microdisc, Colour
|
||||
@@ -146,7 +148,7 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
*/
|
||||
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, GI::AY38910::AY38910 &ay8910, Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
||||
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
||||
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
|
||||
|
||||
/*!
|
||||
@@ -209,8 +211,8 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
HalfCycles cycles_since_ay_update_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
GI::AY38910::AY38910 &ay8910_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker_;
|
||||
AY &ay8910_;
|
||||
Speaker &speaker_;
|
||||
TapePlayer &tape_player_;
|
||||
Keyboard &keyboard_;
|
||||
};
|
||||
@@ -556,6 +558,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
video_output_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_output_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
video_output_.set_display_type(display_type);
|
||||
}
|
||||
@@ -604,20 +610,20 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
|
||||
// WD::WD1770::Delegate
|
||||
void wd1770_did_change_output(WD::WD1770 *wd1770) override final {
|
||||
void wd1770_did_change_output(WD::WD1770 *wd1770) final {
|
||||
set_interrupt_line();
|
||||
}
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return Oric::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
set_use_fast_tape_hack(quickload);
|
||||
@@ -629,21 +635,21 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
@@ -661,7 +667,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
}
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final {
|
||||
diskii_clocking_preference_ = diskii_.preferred_clocking();
|
||||
}
|
||||
|
||||
@@ -687,8 +693,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
VideoOutput video_output_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay8910_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
GI::AY38910::AY38910<false> ay8910_;
|
||||
Speaker speaker_;
|
||||
|
||||
// Inputs
|
||||
Oric::KeyboardMapper keyboard_mapper_;
|
||||
@@ -743,7 +749,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
|
||||
// Keys that aren't read by polling.
|
||||
void perform_special_key(Oric::Key key) override {
|
||||
void perform_special_key(Oric::Key key) final {
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
|
||||
@@ -26,12 +26,27 @@ namespace {
|
||||
VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
ram_(memory),
|
||||
crt_(64*6, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1),
|
||||
frequency_mismatch_warner_(*this),
|
||||
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
|
||||
counter_period_(PAL50Period) {
|
||||
crt_.set_visible_area(crt_.get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
crt_.set_phase_linked_luminance_offset(-1.0f / 8.0f);
|
||||
data_type_ = Outputs::Display::InputDataType::Red1Green1Blue1;
|
||||
crt_.set_input_data_type(data_type_);
|
||||
crt_.set_delegate(&frequency_mismatch_warner_);
|
||||
update_crt_frequency();
|
||||
}
|
||||
|
||||
void VideoOutput::register_crt_frequency_mismatch() {
|
||||
crt_is_60Hz_ ^= true;
|
||||
update_crt_frequency();
|
||||
}
|
||||
|
||||
void VideoOutput::update_crt_frequency() {
|
||||
// Set the proper frequency...
|
||||
crt_.set_new_display_type(64*6, crt_is_60Hz_ ? Outputs::Display::Type::PAL60 : Outputs::Display::Type::PAL50);
|
||||
|
||||
// ... but also pick an appropriate crop rectangle.
|
||||
crt_.set_visible_area(crt_.get_rect_for_area(crt_is_60Hz_ ? 26 : 54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
@@ -56,6 +71,10 @@ void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 6.0f;
|
||||
}
|
||||
|
||||
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||
for(std::size_t c = 0; c < 8; c++) {
|
||||
colour_forms_[c] = 0;
|
||||
@@ -88,8 +107,8 @@ void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||
}
|
||||
|
||||
void VideoOutput::run_for(const Cycles cycles) {
|
||||
// Vertical: 0-39: pixels; otherwise blank; 48-53 sync, 54-56 colour burst
|
||||
// Horizontal: 0-223: pixels; otherwise blank; 256-259 sync
|
||||
// Horizontal: 0-39: pixels; otherwise blank; 48-53 sync, 54-56 colour burst.
|
||||
// Vertical: 0-223: pixels; otherwise blank; 256-259 (50Hz) or 234-238 (60Hz) sync.
|
||||
|
||||
#define clamp(action) \
|
||||
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
|
||||
|
||||
@@ -27,22 +27,29 @@ class VideoOutput {
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
void set_display_type(Outputs::Display::DisplayType display_type);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
void register_crt_frequency_mismatch();
|
||||
|
||||
private:
|
||||
uint8_t *ram_;
|
||||
Outputs::CRT::CRT crt_;
|
||||
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
|
||||
bool crt_is_60Hz_ = false;
|
||||
|
||||
// Counters and limits
|
||||
void update_crt_frequency();
|
||||
|
||||
// Counters and limits.
|
||||
int counter_ = 0, frame_counter_ = 0;
|
||||
int v_sync_start_position_, v_sync_end_position_, counter_period_;
|
||||
|
||||
// Output target and device
|
||||
uint8_t *rgb_pixel_target_;
|
||||
uint32_t *composite_pixel_target_;
|
||||
// Output target and device.
|
||||
uint8_t *rgb_pixel_target_ = nullptr;
|
||||
uint32_t *composite_pixel_target_ = nullptr;
|
||||
uint32_t colour_forms_[8];
|
||||
Outputs::Display::InputDataType data_type_;
|
||||
|
||||
// Registers
|
||||
// Registers.
|
||||
uint8_t ink_, paper_;
|
||||
|
||||
int character_set_base_address_ = 0xb400;
|
||||
|
||||
@@ -25,35 +25,35 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
|
||||
return *this;
|
||||
}
|
||||
|
||||
Activity::Source *activity_source() override {
|
||||
Activity::Source *activity_source() final {
|
||||
return get<Activity::Source>();
|
||||
}
|
||||
|
||||
MediaTarget::Machine *media_target() override {
|
||||
MediaTarget::Machine *media_target() final {
|
||||
return get<MediaTarget::Machine>();
|
||||
}
|
||||
|
||||
CRTMachine::Machine *crt_machine() override {
|
||||
CRTMachine::Machine *crt_machine() final {
|
||||
return get<CRTMachine::Machine>();
|
||||
}
|
||||
|
||||
JoystickMachine::Machine *joystick_machine() override {
|
||||
JoystickMachine::Machine *joystick_machine() final {
|
||||
return get<JoystickMachine::Machine>();
|
||||
}
|
||||
|
||||
KeyboardMachine::Machine *keyboard_machine() override {
|
||||
KeyboardMachine::Machine *keyboard_machine() final {
|
||||
return get<KeyboardMachine::Machine>();
|
||||
}
|
||||
|
||||
MouseMachine::Machine *mouse_machine() override {
|
||||
MouseMachine::Machine *mouse_machine() final {
|
||||
return get<MouseMachine::Machine>();
|
||||
}
|
||||
|
||||
Configurable::Device *configurable_device() override {
|
||||
Configurable::Device *configurable_device() final {
|
||||
return get<Configurable::Device>();
|
||||
}
|
||||
|
||||
void *raw_pointer() override {
|
||||
void *raw_pointer() final {
|
||||
return get();
|
||||
}
|
||||
|
||||
|
||||
@@ -109,3 +109,7 @@ void Video::output_byte(uint8_t byte) {
|
||||
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() / 2.0f;
|
||||
}
|
||||
|
||||
@@ -36,12 +36,16 @@ class Video {
|
||||
|
||||
/// Sets the current sync output.
|
||||
void set_sync(bool sync);
|
||||
|
||||
/// Causes @c byte to be serialised into pixels and output over the next four cycles.
|
||||
void output_byte(uint8_t byte);
|
||||
|
||||
/// Sets the scan target.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
private:
|
||||
bool sync_ = false;
|
||||
uint8_t *line_data_ = nullptr;
|
||||
|
||||
@@ -314,19 +314,23 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
video_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return video_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return is_zx81 ? &speaker_ : nullptr;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
void run_for(const Cycles cycles) final {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
}
|
||||
@@ -335,19 +339,19 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
return !media.tapes.empty();
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) override final {
|
||||
void type_string(const std::string &string) final {
|
||||
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>(is_zx81));
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
if(is_pressed)
|
||||
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
|
||||
else
|
||||
key_states_[key >> 8] |= static_cast<uint8_t>(key);
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
void clear_all_keys() final {
|
||||
memset(key_states_, 0xff, 8);
|
||||
}
|
||||
|
||||
@@ -360,28 +364,28 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void set_tape_is_playing(bool is_playing) override final {
|
||||
void set_tape_is_playing(bool is_playing) final {
|
||||
tape_player_.set_motor_control(is_playing);
|
||||
}
|
||||
|
||||
bool get_tape_is_playing() override final {
|
||||
bool get_tape_is_playing() final {
|
||||
return tape_player_.get_motor_control();
|
||||
}
|
||||
|
||||
// MARK: - Typer timing
|
||||
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
|
||||
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
|
||||
HalfCycles get_typer_delay() final { return Cycles(7000000); }
|
||||
HalfCycles get_typer_frequency() final { return Cycles(390000); }
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||
return ZX8081::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||
bool quickload;
|
||||
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
||||
allow_fast_tape_hack_ = quickload;
|
||||
@@ -394,14 +398,14 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet get_accurate_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, false);
|
||||
Configurable::append_automatic_tape_motor_control_selection(selection_set, false);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_load_tape_selection(selection_set, true);
|
||||
Configurable::append_automatic_tape_motor_control_selection(selection_set, true);
|
||||
@@ -463,8 +467,9 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
|
||||
// MARK: - Audio
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
using AY = GI::AY38910::AY38910<false>;
|
||||
AY ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<AY> speaker_;
|
||||
HalfCycles time_since_ay_update_;
|
||||
inline void ay_set_register(uint8_t value) {
|
||||
update_audio();
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
//
|
||||
// Factors.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Factors_hpp
|
||||
#define Factors_hpp
|
||||
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
namespace NumberTheory {
|
||||
/*!
|
||||
@returns The greatest common divisor of @c a and @c b.
|
||||
*/
|
||||
template<class T> T greatest_common_divisor(T a, T b) {
|
||||
#if __cplusplus > 201402L
|
||||
return std::gcd(a, b);
|
||||
#else
|
||||
if(a < b) {
|
||||
std::swap(a, b);
|
||||
}
|
||||
|
||||
while(1) {
|
||||
if(!a) return b;
|
||||
if(!b) return a;
|
||||
|
||||
T remainder = a%b;
|
||||
a = b;
|
||||
b = remainder;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The least common multiple of @c a and @c b computed indirectly via the greatest
|
||||
common divisor.
|
||||
*/
|
||||
template<class T> T least_common_multiple(T a, T b) {
|
||||
if(a == b) return a;
|
||||
|
||||
T gcd = greatest_common_divisor<T>(a, b);
|
||||
return (a / gcd) * (b / gcd) * gcd;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Factors_hpp */
|
||||
@@ -15,19 +15,19 @@
|
||||
namespace CRC {
|
||||
|
||||
/*! Provides a class capable of generating a CRC from source data. */
|
||||
template <typename T, T reset_value, T xor_output, bool reflect_input, bool reflect_output> class Generator {
|
||||
template <typename IntType, IntType reset_value, IntType output_xor, bool reflect_input, bool reflect_output> class Generator {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a CRC16 that will compute the CRC16 specified by the supplied
|
||||
@c polynomial and @c reset_value.
|
||||
*/
|
||||
Generator(T polynomial): value_(reset_value) {
|
||||
const T top_bit = T(~(T(~0) >> 1));
|
||||
Generator(IntType polynomial): value_(reset_value) {
|
||||
const IntType top_bit = IntType(~(IntType(~0) >> 1));
|
||||
for(int c = 0; c < 256; c++) {
|
||||
T shift_value = static_cast<T>(c << multibyte_shift);
|
||||
IntType shift_value = IntType(c << multibyte_shift);
|
||||
for(int b = 0; b < 8; b++) {
|
||||
T exclusive_or = (shift_value&top_bit) ? polynomial : 0;
|
||||
shift_value = static_cast<T>(shift_value << 1) ^ exclusive_or;
|
||||
IntType exclusive_or = (shift_value&top_bit) ? polynomial : 0;
|
||||
shift_value = IntType(shift_value << 1) ^ exclusive_or;
|
||||
}
|
||||
xor_table[c] = shift_value;
|
||||
}
|
||||
@@ -38,17 +38,17 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
|
||||
|
||||
/// Updates the CRC to include @c byte.
|
||||
void add(uint8_t byte) {
|
||||
if(reflect_input) byte = reverse_byte(byte);
|
||||
value_ = static_cast<T>((value_ << 8) ^ xor_table[(value_ >> multibyte_shift) ^ byte]);
|
||||
if constexpr (reflect_input) byte = reverse_byte(byte);
|
||||
value_ = IntType((value_ << 8) ^ xor_table[(value_ >> multibyte_shift) ^ byte]);
|
||||
}
|
||||
|
||||
/// @returns The current value of the CRC.
|
||||
inline T get_value() const {
|
||||
T result = value_^xor_output;
|
||||
if(reflect_output) {
|
||||
T reflected_output = 0;
|
||||
for(std::size_t c = 0; c < sizeof(T); ++c) {
|
||||
reflected_output = T(reflected_output << 8) | T(reverse_byte(result & 0xff));
|
||||
inline IntType get_value() const {
|
||||
IntType result = value_ ^ output_xor;
|
||||
if constexpr (reflect_output) {
|
||||
IntType reflected_output = 0;
|
||||
for(std::size_t c = 0; c < sizeof(IntType); ++c) {
|
||||
reflected_output = IntType(reflected_output << 8) | IntType(reverse_byte(result & 0xff));
|
||||
result >>= 8;
|
||||
}
|
||||
return reflected_output;
|
||||
@@ -57,7 +57,7 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
|
||||
}
|
||||
|
||||
/// Sets the current value of the CRC.
|
||||
inline void set_value(T value) { value_ = value; }
|
||||
inline void set_value(IntType value) { value_ = value; }
|
||||
|
||||
/*!
|
||||
A compound for:
|
||||
@@ -66,16 +66,30 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
|
||||
[add all data from @c data]
|
||||
get_value()
|
||||
*/
|
||||
T compute_crc(const std::vector<uint8_t> &data) {
|
||||
template <typename Collection> IntType compute_crc(const Collection &data) {
|
||||
return compute_crc(data.begin(), data.end());
|
||||
}
|
||||
|
||||
/*!
|
||||
A compound for:
|
||||
|
||||
reset()
|
||||
[add all data from @c begin to @c end]
|
||||
get_value()
|
||||
*/
|
||||
template <typename Iterator> IntType compute_crc(Iterator begin, Iterator end) {
|
||||
reset();
|
||||
for(const auto &byte: data) add(byte);
|
||||
while(begin != end) {
|
||||
add(*begin);
|
||||
++begin;
|
||||
}
|
||||
return get_value();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int multibyte_shift = (sizeof(T) * 8) - 8;
|
||||
T xor_table[256];
|
||||
T value_;
|
||||
static constexpr int multibyte_shift = (sizeof(IntType) * 8) - 8;
|
||||
IntType xor_table[256];
|
||||
IntType value_;
|
||||
|
||||
constexpr uint8_t reverse_byte(uint8_t byte) const {
|
||||
return
|
||||
67
Numeric/LFSR.hpp
Normal file
67
Numeric/LFSR.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// LFSR.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/01/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LFSR_h
|
||||
#define LFSR_h
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
template <typename IntType> struct LSFRPolynomial {};
|
||||
|
||||
// The following were taken 'at random' from https://users.ece.cmu.edu/~koopman/lfsr/index.html
|
||||
template <> struct LSFRPolynomial<uint64_t> {
|
||||
static constexpr uint64_t value = 0x80000000000019E2;
|
||||
};
|
||||
|
||||
template <> struct LSFRPolynomial<uint32_t> {
|
||||
static constexpr uint32_t value = 0x80000C34;
|
||||
};
|
||||
|
||||
template <> struct LSFRPolynomial<uint16_t> {
|
||||
static constexpr uint16_t value = 0x853E;
|
||||
};
|
||||
|
||||
template <> struct LSFRPolynomial<uint8_t> {
|
||||
static constexpr uint8_t value = 0xAF;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a linear-feedback shift register with a random initial state; if no polynomial is supplied
|
||||
then one will be picked that is guaranteed to give the maximal number of LFSR states that can fit
|
||||
in the specified int type.
|
||||
*/
|
||||
template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntType>::value> class LFSR {
|
||||
public:
|
||||
LFSR() {
|
||||
// Randomise the value, ensuring it doesn't end up being 0.
|
||||
while(!value_) {
|
||||
uint8_t *value_byte = reinterpret_cast<uint8_t *>(&value_);
|
||||
for(size_t c = 0; c < sizeof(IntType); ++c) {
|
||||
*value_byte = uint8_t(uint64_t(rand()) * 255 / RAND_MAX);
|
||||
++value_byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the LSFR, returning either an @c IntType of value @c 1 or @c 0,
|
||||
determining the bit that was just shifted out.
|
||||
*/
|
||||
IntType next() {
|
||||
const auto result = value_ & 1;
|
||||
value_ = (value_ >> 1) ^ (result * polynomial);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
IntType value_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LFSR_h */
|
||||
@@ -158,6 +158,9 @@
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
|
||||
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
|
||||
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; };
|
||||
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
|
||||
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
|
||||
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */; };
|
||||
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
|
||||
@@ -809,7 +812,6 @@
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
|
||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
|
||||
4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
|
||||
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
|
||||
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
|
||||
4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
|
||||
@@ -1000,6 +1002,8 @@
|
||||
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
|
||||
4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = "<group>"; };
|
||||
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
|
||||
4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSHighPrecisionTimer.h; sourceTree = "<group>"; };
|
||||
4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSHighPrecisionTimer.m; sourceTree = "<group>"; };
|
||||
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
|
||||
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
|
||||
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
|
||||
@@ -1125,6 +1129,7 @@
|
||||
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
|
||||
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
|
||||
4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
|
||||
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanSynchroniser.hpp; sourceTree = "<group>"; };
|
||||
4B65085F22F4CF8D009C1100 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = "<group>"; };
|
||||
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = "<group>"; };
|
||||
@@ -1185,6 +1190,8 @@
|
||||
4B7BA03523CEB86000B98D9E /* BD500.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BD500.cpp; path = Oric/BD500.cpp; sourceTree = "<group>"; };
|
||||
4B7BA03623CEB86000B98D9E /* BD500.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BD500.hpp; path = Oric/BD500.hpp; sourceTree = "<group>"; };
|
||||
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskController.hpp; path = Oric/DiskController.hpp; sourceTree = "<group>"; };
|
||||
4B7BA03E23D55E7900B98D9E /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRC.hpp; sourceTree = "<group>"; };
|
||||
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LFSR.hpp; sourceTree = "<group>"; };
|
||||
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
|
||||
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
|
||||
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
@@ -1590,7 +1597,6 @@
|
||||
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MacintoshIMG.hpp; sourceTree = "<group>"; };
|
||||
4BB4BFB722A4372E0069048D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
|
||||
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
|
||||
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
|
||||
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreGCR.cpp; path = Encodings/CommodoreGCR.cpp; sourceTree = "<group>"; };
|
||||
@@ -1683,8 +1689,6 @@
|
||||
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
|
||||
4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTargetGLSLFragments.cpp; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
|
||||
4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; };
|
||||
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
@@ -1740,7 +1744,6 @@
|
||||
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
|
||||
4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ScanTarget.hpp; path = ../../Outputs/ScanTarget.hpp; sourceTree = "<group>"; };
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
|
||||
4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = "<group>"; };
|
||||
4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AllRAMProcessor.hpp; sourceTree = "<group>"; };
|
||||
4BFCA1251ECBE33200AC40C1 /* TestMachineZ80.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachineZ80.h; sourceTree = "<group>"; };
|
||||
@@ -2061,6 +2064,15 @@
|
||||
path = Utility;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2BF19323E10F0000C3AD60 /* High Precision Timer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */,
|
||||
4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */,
|
||||
);
|
||||
path = "High Precision Timer";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2E2D9E1C3A070900138695 /* Electron */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2593,6 +2605,16 @@
|
||||
path = Coleco;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B7BA03C23D55E7900B98D9E /* Numeric */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
|
||||
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
|
||||
);
|
||||
name = Numeric;
|
||||
path = ../../Numeric;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B7F188B2154825D00388727 /* MasterSystem */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3181,15 +3203,6 @@
|
||||
path = Macintosh;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3217,7 +3230,7 @@
|
||||
4B055A761FAE78210060FFFF /* Frameworks */,
|
||||
4B86E2581F8C628F006FAA45 /* Inputs */,
|
||||
4BB73EDC1B587CA500552FC2 /* Machines */,
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */,
|
||||
4B7BA03C23D55E7900B98D9E /* Numeric */,
|
||||
4B366DFD1B5C165F0026627B /* Outputs */,
|
||||
4BB73EDD1B587CA500552FC2 /* Processors */,
|
||||
4BB73E9F1B587A5100552FC2 /* Products */,
|
||||
@@ -3254,13 +3267,13 @@
|
||||
4B2A538F1D117D36003C6002 /* Audio */,
|
||||
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
||||
4B55CE551C3B7D360093A61B /* Documents */,
|
||||
4B2BF19323E10F0000C3AD60 /* High Precision Timer */,
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4B2A53921D117D36003C6002 /* Machine */,
|
||||
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||
);
|
||||
path = "Clock Signal";
|
||||
@@ -3608,15 +3621,6 @@
|
||||
name = 1770;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD5F1961D1352A000631CD1 /* Updater */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */,
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */,
|
||||
);
|
||||
name = Updater;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD67DC8209BE4D600AB2146 /* DiskII */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3723,6 +3727,7 @@
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4B80214322EE7C3E00068002 /* JustInTime.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
@@ -3836,16 +3841,19 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 1100;
|
||||
LastUpgradeCheck = 1130;
|
||||
ORGANIZATIONNAME = "Thomas Harte";
|
||||
TargetAttributes = {
|
||||
4B055A691FAE763F0060FFFF = {
|
||||
CreatedOnToolsVersion = 9.1;
|
||||
DevelopmentTeam = CP2SKEB3XT;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
4BB73E9D1B587A5100552FC2 = {
|
||||
CreatedOnToolsVersion = 7.0;
|
||||
DevelopmentTeam = CP2SKEB3XT;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
@@ -3854,11 +3862,14 @@
|
||||
};
|
||||
4BB73EB11B587A5100552FC2 = {
|
||||
CreatedOnToolsVersion = 7.0;
|
||||
DevelopmentTeam = CP2SKEB3XT;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 4BB73E9D1B587A5100552FC2;
|
||||
};
|
||||
4BB73EBC1B587A5100552FC2 = {
|
||||
CreatedOnToolsVersion = 7.0;
|
||||
DevelopmentTeam = CP2SKEB3XT;
|
||||
LastSwiftMigration = 1020;
|
||||
TestTargetID = 4BB73E9D1B587A5100552FC2;
|
||||
};
|
||||
@@ -4250,6 +4261,7 @@
|
||||
4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */,
|
||||
4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */,
|
||||
4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */,
|
||||
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */,
|
||||
4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */,
|
||||
4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */,
|
||||
4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */,
|
||||
@@ -4301,6 +4313,7 @@
|
||||
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */,
|
||||
4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */,
|
||||
4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */,
|
||||
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */,
|
||||
4B0ACC2723775819008902D0 /* AtariST.cpp in Sources */,
|
||||
4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */,
|
||||
4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */,
|
||||
@@ -4436,6 +4449,7 @@
|
||||
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */,
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,
|
||||
4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */,
|
||||
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */,
|
||||
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */,
|
||||
4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
|
||||
@@ -4468,7 +4482,6 @@
|
||||
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
|
||||
4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
|
||||
4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */,
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
|
||||
4B894532201967B4007DE474 /* 6502.cpp in Sources */,
|
||||
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */,
|
||||
4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
|
||||
@@ -4905,6 +4918,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
@@ -4925,6 +4939,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
@@ -5053,8 +5068,11 @@
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
@@ -5071,12 +5089,14 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-Wreorder",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -5095,8 +5115,11 @@
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
@@ -5115,12 +5138,14 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-Wreorder",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -5132,11 +5157,17 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -5150,12 +5181,18 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 2;
|
||||
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clock Signal.app/Contents/MacOS/Clock Signal";
|
||||
@@ -5166,6 +5203,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests";
|
||||
@@ -5180,6 +5218,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user