mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
321 Commits
2018-05-24
...
2018-09-09
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab02f82470 | ||
|
|
1e3318816c | ||
|
|
3a3dec92c7 | ||
|
|
5a5fc1ae1a | ||
|
|
8d79a1e381 | ||
|
|
d70f5da94e | ||
|
|
05d4274019 | ||
|
|
afeec09902 | ||
|
|
0526ac2ee2 | ||
|
|
6725ee2190 | ||
|
|
8b661fb90f | ||
|
|
dab7d3db1b | ||
|
|
1cba3d48d9 | ||
|
|
d53b38ec7e | ||
|
|
5d0f47eda2 | ||
|
|
2e04c4442c | ||
|
|
f639cdc8ad | ||
|
|
71ec7624ca | ||
|
|
0599d9602e | ||
|
|
234bef2a88 | ||
|
|
adb574e1cd | ||
|
|
1f491e764e | ||
|
|
114a43a662 | ||
|
|
5547c39c91 | ||
|
|
97a89aaf4d | ||
|
|
61e46399dc | ||
|
|
e802f6ecc2 | ||
|
|
4209f0e044 | ||
|
|
33576aa2c4 | ||
|
|
17bf1a64bf | ||
|
|
f8d46f8f3d | ||
|
|
8787d85e64 | ||
|
|
7f0f17f435 | ||
|
|
0e7f54f375 | ||
|
|
b3bdfa9f46 | ||
|
|
592ec69d36 | ||
|
|
60e00ddd02 | ||
|
|
6806193dc2 | ||
|
|
c35dca783f | ||
|
|
901e0d65b9 | ||
|
|
ddf45a0010 | ||
|
|
1eca4463b3 | ||
|
|
be01203cc1 | ||
|
|
4d1d19a464 | ||
|
|
760817eb3b | ||
|
|
cb47575860 | ||
|
|
434d184503 | ||
|
|
7374c665e8 | ||
|
|
10c930a59d | ||
|
|
60ab6f0c2a | ||
|
|
a13eb351da | ||
|
|
4b91910fab | ||
|
|
f46d52364c | ||
|
|
878c63dcd2 | ||
|
|
261fb3d4f8 | ||
|
|
b63e0cff72 | ||
|
|
5d6e479338 | ||
|
|
90094529a5 | ||
|
|
aed4c0539e | ||
|
|
8b50ab2593 | ||
|
|
95164b79c9 | ||
|
|
6f838fe190 | ||
|
|
bb680b40d8 | ||
|
|
e3f6da6994 | ||
|
|
e46bde35f5 | ||
|
|
32338bea4d | ||
|
|
5c881bd19d | ||
|
|
1a44ef0469 | ||
|
|
ebce9a2e51 | ||
|
|
633af4d404 | ||
|
|
76a73c835c | ||
|
|
c1d1c451ef | ||
|
|
3be30d8c71 | ||
|
|
d4c1244485 | ||
|
|
c61b9dca17 | ||
|
|
39bf682016 | ||
|
|
60ac9b49ea | ||
|
|
a8bb18e2cf | ||
|
|
1852786609 | ||
|
|
31df8c7e91 | ||
|
|
832939f5b7 | ||
|
|
c2d9e1ec81 | ||
|
|
673b915ee8 | ||
|
|
032a62dfff | ||
|
|
f2d78182a3 | ||
|
|
de68e70246 | ||
|
|
e07447eb9a | ||
|
|
5cdeb58571 | ||
|
|
ce14cc8677 | ||
|
|
bcd0479074 | ||
|
|
d72dd8c4ff | ||
|
|
f7ce86fef8 | ||
|
|
55f2fccf5e | ||
|
|
c939a274be | ||
|
|
101fb5d7bf | ||
|
|
3c51e335c3 | ||
|
|
33ea90678c | ||
|
|
11ae2c64ba | ||
|
|
26624d7652 | ||
|
|
85fb4773b0 | ||
|
|
099d66804e | ||
|
|
086596c28e | ||
|
|
3aeb4213fe | ||
|
|
558b96bc05 | ||
|
|
e97cc40a2c | ||
|
|
94503ed771 | ||
|
|
c4f86cc324 | ||
|
|
70c4d6b9b3 | ||
|
|
78c7137427 | ||
|
|
74a2f717b3 | ||
|
|
98bb5bd9f1 | ||
|
|
c91eaaf8da | ||
|
|
a36f37d240 | ||
|
|
c773d3501a | ||
|
|
5810f9b3f9 | ||
|
|
3f56683342 | ||
|
|
16ccbdefd6 | ||
|
|
a533d09fe7 | ||
|
|
e9aaa5bbdf | ||
|
|
ecb26e3281 | ||
|
|
5aa0b17720 | ||
|
|
632b37ecec | ||
|
|
c905de2e40 | ||
|
|
bc2afe69e1 | ||
|
|
894998b163 | ||
|
|
51192d8397 | ||
|
|
3c33ccd730 | ||
|
|
3e35109d63 | ||
|
|
99c770eab4 | ||
|
|
34aa78b7ce | ||
|
|
8cca9c2055 | ||
|
|
85ce21c79f | ||
|
|
d19d949b9c | ||
|
|
1cb3713b84 | ||
|
|
689850d698 | ||
|
|
c572a52049 | ||
|
|
41765e00c4 | ||
|
|
080aa0acc5 | ||
|
|
5e7c46a72a | ||
|
|
5f2b9b2d5a | ||
|
|
5c4506a9db | ||
|
|
55a6431fb3 | ||
|
|
ede2696a77 | ||
|
|
59b9e39022 | ||
|
|
6b2970f2f2 | ||
|
|
6a73fe7d65 | ||
|
|
1362906f94 | ||
|
|
8f4042c4bb | ||
|
|
c05b6397b0 | ||
|
|
8d18808efe | ||
|
|
09950d9414 | ||
|
|
badbbdf155 | ||
|
|
2832792fed | ||
|
|
efa45b9504 | ||
|
|
523749edf8 | ||
|
|
5a0499e8a7 | ||
|
|
258c8b5900 | ||
|
|
24b861f056 | ||
|
|
29f7f4d432 | ||
|
|
21080a1149 | ||
|
|
1d068fd09b | ||
|
|
92065813ef | ||
|
|
3e9ef6b8cb | ||
|
|
c9451a5382 | ||
|
|
2be3b027db | ||
|
|
e339d169c5 | ||
|
|
87001f86ee | ||
|
|
58484e8f37 | ||
|
|
94f68f9d55 | ||
|
|
3f6944de54 | ||
|
|
00cb4d26b3 | ||
|
|
774d8668bf | ||
|
|
8503589828 | ||
|
|
0f95ef2059 | ||
|
|
efd812cf22 | ||
|
|
736e14c83e | ||
|
|
57f161e64c | ||
|
|
0897210969 | ||
|
|
7e58a44771 | ||
|
|
e8f847d288 | ||
|
|
a0f817108e | ||
|
|
3862fdb44c | ||
|
|
3e2d271566 | ||
|
|
c97c5fa03a | ||
|
|
fa63f7ffc3 | ||
|
|
bfccadd356 | ||
|
|
5b3512f1df | ||
|
|
6e34e60f8a | ||
|
|
a391d0f4ae | ||
|
|
abc5c50b2e | ||
|
|
1fcb461c42 | ||
|
|
abca38a548 | ||
|
|
b4be2cd063 | ||
|
|
2d83eeb9c4 | ||
|
|
4d9e897cc3 | ||
|
|
be664b5695 | ||
|
|
c3751066b7 | ||
|
|
77feee8197 | ||
|
|
f75af3b45e | ||
|
|
1471a35bb8 | ||
|
|
555c2a4377 | ||
|
|
16bef0dcd5 | ||
|
|
cd464fc7de | ||
|
|
5b88207477 | ||
|
|
df8c896193 | ||
|
|
5d3e1f7084 | ||
|
|
59f8eeb05a | ||
|
|
0b14850467 | ||
|
|
f72e260915 | ||
|
|
640a84d456 | ||
|
|
04f6cb1750 | ||
|
|
87d688b7e3 | ||
|
|
26141a59b0 | ||
|
|
a93f8103ad | ||
|
|
4a3d7c338a | ||
|
|
55ab305dbf | ||
|
|
e48ba89721 | ||
|
|
9bb55b6b61 | ||
|
|
c33308bdc5 | ||
|
|
44a33941bf | ||
|
|
cc34cd2133 | ||
|
|
52c9f9e89e | ||
|
|
2363deb19c | ||
|
|
1c6af279b2 | ||
|
|
6e96275e1c | ||
|
|
9968342a11 | ||
|
|
c248ecde48 | ||
|
|
370952ab33 | ||
|
|
154c89e041 | ||
|
|
d45f1a793d | ||
|
|
9800951f18 | ||
|
|
17251997c2 | ||
|
|
5ab4cfee84 | ||
|
|
a9eb0d02c6 | ||
|
|
1f8b69a5b0 | ||
|
|
8b83f58d7a | ||
|
|
9a91ae38c1 | ||
|
|
ad57caed5e | ||
|
|
283ed8dbae | ||
|
|
acb74185d5 | ||
|
|
7a5d16ccf8 | ||
|
|
adca862166 | ||
|
|
1bdc718527 | ||
|
|
685a80f95b | ||
|
|
62eef8cb40 | ||
|
|
6ed3a49fe1 | ||
|
|
17702bfb89 | ||
|
|
292e02702a | ||
|
|
5a56d8a5d0 | ||
|
|
3da1d5700c | ||
|
|
d437e06e15 | ||
|
|
6a3702a5c7 | ||
|
|
83a654540a | ||
|
|
678bd93c52 | ||
|
|
1bf0c1891a | ||
|
|
b899a22c7d | ||
|
|
1bd6bbca8d | ||
|
|
14a2e470e4 | ||
|
|
41dcf1de42 | ||
|
|
0c65385c82 | ||
|
|
4aaf43150a | ||
|
|
f05ee525cb | ||
|
|
1172c4fd97 | ||
|
|
15deef50c8 | ||
|
|
7728adfc5a | ||
|
|
eff67f2250 | ||
|
|
64e3cf5de2 | ||
|
|
31a6d620e8 | ||
|
|
dfd37e7dec | ||
|
|
8d8f244bf5 | ||
|
|
037b4802db | ||
|
|
51da21b844 | ||
|
|
0be19d8de7 | ||
|
|
f26e4734b3 | ||
|
|
f1b430338e | ||
|
|
2954373115 | ||
|
|
42d21ea3a9 | ||
|
|
7d761f145f | ||
|
|
27657fcde0 | ||
|
|
3ea2a4ccb8 | ||
|
|
a1c60152d4 | ||
|
|
69da00fcfb | ||
|
|
c4108efc5c | ||
|
|
d576ff1172 | ||
|
|
af54666c23 | ||
|
|
e0b75b6e3d | ||
|
|
12c59ede09 | ||
|
|
b4d0d4fff6 | ||
|
|
a694844190 | ||
|
|
28f2d331a8 | ||
|
|
dde9b73a22 | ||
|
|
fb4bb21bf6 | ||
|
|
744c35b617 | ||
|
|
9ac21a4e71 | ||
|
|
94359e9c75 | ||
|
|
076fa55651 | ||
|
|
d380595ad4 | ||
|
|
d84b8700a3 | ||
|
|
80b281d9f1 | ||
|
|
69dc3cc4d8 | ||
|
|
1a9cea050e | ||
|
|
0833412df9 | ||
|
|
35e84ff1a8 | ||
|
|
8dd7c6ef23 | ||
|
|
a26ab7086d | ||
|
|
b2464598d0 | ||
|
|
6812a001d8 | ||
|
|
6c16754a6b | ||
|
|
75f9e3caeb | ||
|
|
ad5afe21ee | ||
|
|
8a566cc1dd | ||
|
|
928aab13dc | ||
|
|
f3fe711542 | ||
|
|
db8d8d8404 | ||
|
|
6220ccb5d3 | ||
|
|
20843305dd | ||
|
|
8f6c0f6a8d | ||
|
|
ede2df7e70 | ||
|
|
d45231c1a8 | ||
|
|
772812b35f | ||
|
|
f443fd44b5 |
6
.editorconfig
Normal file
6
.editorconfig
Normal file
@@ -0,0 +1,6 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -24,13 +24,13 @@ namespace Activity {
|
||||
class Observer {
|
||||
public:
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led(const std::string &name) = 0;
|
||||
virtual void register_led(const std::string &name) {}
|
||||
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
virtual void register_drive(const std::string &name) = 0;
|
||||
virtual void register_drive(const std::string &name) {}
|
||||
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
virtual void set_led_status(const std::string &name, bool lit) = 0;
|
||||
virtual void set_led_status(const std::string &name, bool lit) {}
|
||||
|
||||
enum class DriveEvent {
|
||||
StepNormal,
|
||||
@@ -39,11 +39,10 @@ class Observer {
|
||||
};
|
||||
|
||||
/// Informs the receiver that the named event just occurred for the drive with name @c name.
|
||||
virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0;
|
||||
virtual void announce_drive_event(const std::string &name, DriveEvent event) {}
|
||||
|
||||
/// Informs the receiver of the motor-on status of the drive with name @c name.
|
||||
virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0;
|
||||
|
||||
virtual void set_drive_motor_status(const std::string &name, bool is_on) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// MultiConfigurationTarget.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiConfigurationTarget.hpp"
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
|
||||
if(configuration_target) targets_.push_back(configuration_target);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
|
||||
}
|
||||
|
||||
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {
|
||||
bool inserted = false;
|
||||
for(const auto &target : targets_) {
|
||||
inserted |= target->insert_media(media);
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// MultiConfigurationTarget.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiConfigurationTarget_hpp
|
||||
#define MultiConfigurationTarget_hpp
|
||||
|
||||
#include "../../../../Machines/ConfigurationTarget.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the configuration target interface to multiple machines.
|
||||
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
|
||||
public:
|
||||
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
|
||||
void configure_as_target(const Analyser::Static::Target *target) override;
|
||||
bool insert_media(const Analyser::Static::Media &media) override;
|
||||
|
||||
private:
|
||||
std::vector<ConfigurationTarget::Machine *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiConfigurationTarget_hpp */
|
||||
@@ -25,14 +25,14 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DigitalInput> get_inputs() override {
|
||||
std::vector<DigitalInput> inputs;
|
||||
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<DigitalInput> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
std::vector<Input> &get_inputs() override {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
for(const auto &input: joystick_inputs) {
|
||||
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
|
||||
inputs.push_back(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,11 +40,18 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
|
||||
void set_input(const Input &digital_input, bool is_active) override {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_digital_input(digital_input, is_active);
|
||||
joystick->set_input(digital_input, is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void set_input(const Input &digital_input, float value) override {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->set_input(digital_input, value);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_inputs() override {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
joystick->reset_all_inputs();
|
||||
@@ -52,6 +59,7 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs;
|
||||
std::vector<Inputs::Joystick *> joysticks_;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// MultiMediaTarget.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiMediaTarget.hpp"
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
MediaTarget::Machine *media_target = machine->media_target();
|
||||
if(media_target) targets_.push_back(media_target);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
|
||||
bool inserted = false;
|
||||
for(const auto &target : targets_) {
|
||||
inserted |= target->insert_media(media);
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// MultiMediaTarget.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiMediaTarget_hpp
|
||||
#define MultiMediaTarget_hpp
|
||||
|
||||
#include "../../../../Machines/MediaTarget.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the media target interface to multiple machines.
|
||||
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiMediaTarget: public MediaTarget::Machine {
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard MediaTarget::Machine interface; see there for documentation.
|
||||
bool insert_media(const Analyser::Static::Media &media) override;
|
||||
|
||||
private:
|
||||
std::vector<MediaTarget::Machine *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMediaTarget_hpp */
|
||||
@@ -15,10 +15,10 @@ using namespace Analyser::Dynamic;
|
||||
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
|
||||
machines_(std::move(machines)),
|
||||
configurable_(machines_),
|
||||
configuration_target_(machines_),
|
||||
crt_machine_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines),
|
||||
keyboard_machine_(machines_) {
|
||||
keyboard_machine_(machines_),
|
||||
media_target_(machines_) {
|
||||
crt_machine_.set_delegate(this);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ Activity::Source *MultiMachine::activity_source() {
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
|
||||
MediaTarget::Machine *MultiMachine::media_target() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->configuration_target();
|
||||
return machines_.front()->media_target();
|
||||
} else {
|
||||
return &configuration_target_;
|
||||
return &media_target_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
#include "../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "Implementation/MultiConfigurable.hpp"
|
||||
#include "Implementation/MultiConfigurationTarget.hpp"
|
||||
#include "Implementation/MultiCRTMachine.hpp"
|
||||
#include "Implementation/MultiJoystickMachine.hpp"
|
||||
#include "Implementation/MultiKeyboardMachine.hpp"
|
||||
#include "Implementation/MultiMediaTarget.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -51,11 +51,11 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
|
||||
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
|
||||
|
||||
Activity::Source *activity_source() override;
|
||||
ConfigurationTarget::Machine *configuration_target() override;
|
||||
Configurable::Device *configurable_device() override;
|
||||
CRTMachine::Machine *crt_machine() override;
|
||||
JoystickMachine::Machine *joystick_machine() override;
|
||||
KeyboardMachine::Machine *keyboard_machine() override;
|
||||
Configurable::Device *configurable_device() override;
|
||||
MediaTarget::Machine *media_target() override;
|
||||
void *raw_pointer() override;
|
||||
|
||||
private:
|
||||
@@ -65,10 +65,10 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
|
||||
std::mutex machines_mutex_;
|
||||
|
||||
MultiConfigurable configurable_;
|
||||
MultiConfigurationTarget configuration_target_;
|
||||
MultiCRTMachine crt_machine_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
|
||||
void pick_first();
|
||||
bool has_picked_ = false;
|
||||
|
||||
@@ -69,13 +69,13 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// if there are any tapes, attempt to get data from the first
|
||||
if(media.tapes.size() > 0) {
|
||||
if(!media.tapes.empty()) {
|
||||
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
||||
std::vector<File> files = GetFiles(tape);
|
||||
tape->reset();
|
||||
|
||||
// continue if there are any files
|
||||
if(files.size()) {
|
||||
if(!files.empty()) {
|
||||
bool is_basic = true;
|
||||
|
||||
// protected files are always for *RUNning only
|
||||
@@ -103,7 +103,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
}
|
||||
}
|
||||
|
||||
if(media.disks.size() > 0) {
|
||||
if(!media.disks.empty()) {
|
||||
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
|
||||
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
|
||||
@@ -18,7 +18,9 @@ namespace AppleII {
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
II,
|
||||
IIplus
|
||||
IIplus,
|
||||
IIe,
|
||||
EnhancedIIe
|
||||
};
|
||||
enum class DiskController {
|
||||
None,
|
||||
@@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
ThirteenSector
|
||||
};
|
||||
|
||||
Model model = Model::IIplus;
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
|
||||
// only one mapped item is allowed
|
||||
if(segments.size() != 1) continue;
|
||||
|
||||
// which must be 8, 12, 16, 24 or 32 kb in size
|
||||
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
||||
const std::size_t data_size = segment.data.size();
|
||||
const std::size_t overflow = data_size&8191;
|
||||
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
|
||||
if(data_size < 8192) continue;
|
||||
|
||||
// the two bytes that will be first must be 0xaa and 0x55, either way around
|
||||
auto *start = &segment.data[0];
|
||||
@@ -34,19 +29,24 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
if(start[0] == start[1]) continue;
|
||||
|
||||
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
|
||||
if(!overflow) {
|
||||
coleco_cartridges.push_back(cartridge);
|
||||
|
||||
// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if
|
||||
// this image is within a short distance of 32kb.
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
|
||||
size_t target_size;
|
||||
if(data_size >= 32*1024 && data_size < 32*1024 + 512) {
|
||||
target_size = 32 * 1024;
|
||||
} else {
|
||||
// Size down to a multiple of 8kb and apply the start address.
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
|
||||
std::vector<uint8_t> truncated_data;
|
||||
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(0x8000, truncated_data);
|
||||
|
||||
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
target_size = data_size + ((8192 - (data_size & 8191)) & 8191);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> truncated_data;
|
||||
truncated_data = segment.data;
|
||||
truncated_data.resize(target_size);
|
||||
output_segments.emplace_back(0x8000, truncated_data);
|
||||
|
||||
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
}
|
||||
|
||||
return coleco_cartridges;
|
||||
|
||||
@@ -111,7 +111,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
for(auto &tape : media.tapes) {
|
||||
std::vector<File> tape_files = GetFiles(tape);
|
||||
tape->reset();
|
||||
if(tape_files.size()) {
|
||||
if(!tape_files.empty()) {
|
||||
for(const auto &file : tape_files) {
|
||||
if(file.data_type == File::MachineCode) {
|
||||
std::vector<uint16_t> entry_points = {file.starting_address};
|
||||
|
||||
81
ClockReceiver/ClockDeferrer.hpp
Normal file
81
ClockReceiver/ClockDeferrer.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// ClockDeferrer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/08/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockDeferrer_h
|
||||
#define ClockDeferrer_h
|
||||
|
||||
#include <vector>
|
||||
|
||||
/*!
|
||||
A ClockDeferrer maintains a list of ordered actions and the times at which
|
||||
they should happen, and divides a total execution period up into the portions
|
||||
that occur between those actions, triggering each action when it is reached.
|
||||
*/
|
||||
template <typename TimeUnit> class ClockDeferrer {
|
||||
public:
|
||||
/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
|
||||
ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Schedules @c action to occur in @c delay units of time.
|
||||
|
||||
Actions must be scheduled in the order they will occur. It is undefined behaviour
|
||||
to schedule them out of order.
|
||||
*/
|
||||
void defer(TimeUnit delay, const std::function<void(void)> &action) {
|
||||
pending_actions_.emplace_back(delay, action);
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
||||
The constructor-supplied target will be called with one or more periods that add up to @c length;
|
||||
any scheduled actions will be called between periods.
|
||||
*/
|
||||
void run_for(TimeUnit length) {
|
||||
// If there are no pending actions, just run for the entire length.
|
||||
// This should be the normal branch.
|
||||
if(pending_actions_.empty()) {
|
||||
target_(length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide the time to run according to the pending actions.
|
||||
while(length > TimeUnit(0)) {
|
||||
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
|
||||
target_(next_period);
|
||||
length -= next_period;
|
||||
|
||||
off_t performances = 0;
|
||||
for(auto &action: pending_actions_) {
|
||||
action.delay -= next_period;
|
||||
if(!action.delay) {
|
||||
action.action();
|
||||
++performances;
|
||||
}
|
||||
}
|
||||
if(performances) {
|
||||
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
TimeUnit delay;
|
||||
std::function<void(void)> action;
|
||||
|
||||
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
|
||||
};
|
||||
std::vector<DeferredAction> pending_actions_;
|
||||
};
|
||||
|
||||
#endif /* ClockDeferrer_h */
|
||||
@@ -52,79 +52,79 @@
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
inline WrappedInt(int l) : length_(l) {}
|
||||
inline WrappedInt() : length_(0) {}
|
||||
constexpr WrappedInt(int l) : length_(l) {}
|
||||
constexpr WrappedInt() : length_(0) {}
|
||||
|
||||
inline T &operator =(const T &rhs) {
|
||||
T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T &operator +=(const T &rhs) {
|
||||
T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator -=(const T &rhs) {
|
||||
T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++() {
|
||||
T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++(int) {
|
||||
T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --() {
|
||||
T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --(int) {
|
||||
T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator %=(const T &rhs) {
|
||||
T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator &=(const T &rhs) {
|
||||
T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
inline T operator -() const { return T(- length_); }
|
||||
constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
inline bool operator !() const { return !length_; }
|
||||
constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
inline int as_int() const { return length_; }
|
||||
constexpr int as_int() const { return length_; }
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
inline T divide(const T &divisor) {
|
||||
T divide(const T &divisor) {
|
||||
T result(length_ / divisor.length_);
|
||||
length_ %= divisor.length_;
|
||||
return result;
|
||||
@@ -134,7 +134,7 @@ template <class T> class WrappedInt {
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
inline T flush() {
|
||||
T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@@ -150,34 +150,34 @@ template <class T> class WrappedInt {
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
inline Cycles() : WrappedInt<Cycles>() {}
|
||||
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
inline Cycles cycles() {
|
||||
constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
inline Cycles flush_cycles() {
|
||||
Cycles flush_cycles() {
|
||||
Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
|
||||
inline HalfCycles flush() {
|
||||
HalfCycles flush() {
|
||||
HalfCycles result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@@ -187,7 +187,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
inline Cycles divide_cycles(const Cycles &divisor) {
|
||||
Cycles divide_cycles(const Cycles &divisor) {
|
||||
HalfCycles half_divisor = HalfCycles(divisor);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
@@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
using T::run_for;
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
|
||||
88
ClockReceiver/ClockingHintSource.hpp
Normal file
88
ClockReceiver/ClockingHintSource.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// ClockingHintSource.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/08/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockingHintSource_hpp
|
||||
#define ClockingHintSource_hpp
|
||||
|
||||
namespace ClockingHint {
|
||||
|
||||
enum class Preference {
|
||||
/// The component doesn't currently require a clock signal.
|
||||
None,
|
||||
/// The component can be clocked only immediate prior to (explicit) accesses.
|
||||
JustInTime,
|
||||
/// The component require real-time clocking.
|
||||
RealTime
|
||||
};
|
||||
|
||||
class Source;
|
||||
|
||||
struct Observer {
|
||||
/// Called to inform an observer that the component @c component has changed its clocking requirements.
|
||||
virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
An clocking hint source is any component that can provide hints as to the type of
|
||||
clocking required for accurate emulation. A disk controller is an archetypal example.
|
||||
|
||||
Types of clocking are:
|
||||
|
||||
- none:
|
||||
a component that acts and reacts to direct contact but does not have a state that autonomously evolves.
|
||||
E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command.
|
||||
|
||||
- just-in-time:
|
||||
a component that has an evolving state but can receive clock updates only immediately before a
|
||||
direct contact. This is possibly the most common kind of component.
|
||||
|
||||
- real-time:
|
||||
a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example
|
||||
so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of
|
||||
the machine rather than waiting to be called upon, or because there's some other non-obvious relationship
|
||||
at play.
|
||||
|
||||
A clocking hint source can signal changes in preferred clocking to an observer.
|
||||
|
||||
This is intended to allow for performance improvements to machines with components that can be messaged selectively.
|
||||
The observer callout is virtual so the intended use case is that a machine holds a component that might go through
|
||||
periods of different clocking requirements.
|
||||
|
||||
Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that
|
||||
the saved or deferred ::run_fors add up to a substantial amount.
|
||||
|
||||
The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency.
|
||||
*/
|
||||
class Source {
|
||||
public:
|
||||
/// Registers @c observer as the new clocking observer.
|
||||
void set_clocking_hint_observer(Observer *observer) {
|
||||
observer_ = observer;
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() = 0;
|
||||
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Provided for subclasses; call this whenever the clocking preference might have changed.
|
||||
This will notify the observer if there is one.
|
||||
*/
|
||||
void update_clocking_observer() {
|
||||
if(!observer_) return;
|
||||
observer_->set_component_prefers_clocking(this, preferred_clocking());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockingHintSource_h */
|
||||
@@ -1,60 +0,0 @@
|
||||
//
|
||||
// Sleeper.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/08/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Sleeper_hpp
|
||||
#define Sleeper_hpp
|
||||
|
||||
/*!
|
||||
A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing
|
||||
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
|
||||
|
||||
A sleeper will signal sleeps and wakes to an observer.
|
||||
|
||||
This is intended to allow for performance improvements to machines with components that can sleep. The observer
|
||||
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
|
||||
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
|
||||
the saved ::run_fors add up to a substantial amount.
|
||||
|
||||
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
|
||||
not a command.
|
||||
*/
|
||||
class Sleeper {
|
||||
public:
|
||||
Sleeper() : sleep_observer_(nullptr) {}
|
||||
|
||||
class SleepObserver {
|
||||
public:
|
||||
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
|
||||
virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0;
|
||||
};
|
||||
|
||||
/// Registers @c observer as the new sleep observer;
|
||||
void set_sleep_observer(SleepObserver *observer) {
|
||||
sleep_observer_ = observer;
|
||||
}
|
||||
|
||||
/// @returns @c true if the component is currently sleeping; @c false otherwise.
|
||||
virtual bool is_sleeping() = 0;
|
||||
|
||||
protected:
|
||||
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
|
||||
SleepObserver *sleep_observer_;
|
||||
|
||||
/*!
|
||||
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
|
||||
if one exists.
|
||||
|
||||
@c is_sleeping will be called only if there is an observer.
|
||||
*/
|
||||
void update_sleep_observer() {
|
||||
if(!sleep_observer_) return;
|
||||
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* Sleeper_h */
|
||||
@@ -7,7 +7,9 @@
|
||||
//
|
||||
|
||||
#include "1770.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace WD;
|
||||
|
||||
@@ -25,10 +27,10 @@ void WD1770::set_register(int address, uint8_t value) {
|
||||
if((value&0xf0) == 0xd0) {
|
||||
if(value == 0xd0) {
|
||||
// Force interrupt **immediately**.
|
||||
printf("Force interrupt immediately\n");
|
||||
LOG("Force interrupt immediately");
|
||||
posit_event(static_cast<int>(Event1770::ForceInterrupt));
|
||||
} else {
|
||||
printf("!!!TODO: force interrupt!!!\n");
|
||||
ERROR("!!!TODO: force interrupt!!!");
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
});
|
||||
@@ -193,7 +195,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
// Wait for a new command, branch to the appropriate handler.
|
||||
case 0:
|
||||
wait_for_command:
|
||||
printf("Idle...\n");
|
||||
LOG("Idle...");
|
||||
set_data_mode(DataMode::Scanning);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
@@ -209,7 +211,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
status.interrupt_request = false;
|
||||
});
|
||||
|
||||
printf("Starting %02x\n", command_);
|
||||
LOG("Starting " << std::hex << command_ << std::endl);
|
||||
|
||||
if(!(command_ & 0x80)) goto begin_type_1;
|
||||
if(!(command_ & 0x40)) goto begin_type_2;
|
||||
@@ -327,7 +329,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
|
||||
if(header_[0] == track_) {
|
||||
printf("Reached track %d\n", track_);
|
||||
LOG("Reached track " << std::dec << track_);
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = false;
|
||||
});
|
||||
@@ -396,20 +398,20 @@ void WD1770::posit_event(int new_event_type) {
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5) {
|
||||
printf("Failed to find sector %d\n", sector_);
|
||||
LOG("Failed to find sector " << std::dec << sector_);
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
if(distance_into_section_ == 7) {
|
||||
printf("Considering %d/%d\n", header_[0], header_[2]);
|
||||
LOG("Considering " << std::dec << header_[0] << "/" << header_[2]);
|
||||
set_data_mode(DataMode::Scanning);
|
||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||
printf("Found %d/%d\n", header_[0], header_[2]);
|
||||
LOG("Found " << std::dec << header_[0] << "/" << header_[2]);
|
||||
if(get_crc_generator().get_value()) {
|
||||
printf("CRC error; back to searching\n");
|
||||
LOG("CRC error; back to searching");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -465,7 +467,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ == 2) {
|
||||
if(get_crc_generator().get_value()) {
|
||||
printf("CRC error; terminating\n");
|
||||
LOG("CRC error; terminating");
|
||||
update_status([this] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -476,7 +478,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
printf("Finished reading sector %d\n", sector_);
|
||||
LOG("Finished reading sector " << std::dec << sector_);
|
||||
goto wait_for_command;
|
||||
}
|
||||
goto type2_check_crc;
|
||||
@@ -558,7 +560,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
printf("Wrote sector %d\n", sector_);
|
||||
LOG("Wrote sector " << std::dec << sector_);
|
||||
goto wait_for_command;
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
|
||||
/// Runs the controller for @c number_of_cycles cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Disk::Controller::run_for;
|
||||
|
||||
enum Flag: uint8_t {
|
||||
NotReady = 0x80,
|
||||
|
||||
@@ -69,7 +69,7 @@ template <class BusHandler> class MOS6560 {
|
||||
speaker_(audio_generator_)
|
||||
{
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
|
||||
|
||||
@@ -125,10 +125,10 @@ template <class BusHandler> class MOS6560 {
|
||||
19, 86, 123, 59,
|
||||
};
|
||||
const uint8_t ntsc_chrominances[16] = {
|
||||
255, 255, 7, 71,
|
||||
25, 86, 48, 112,
|
||||
0, 119, 7, 71,
|
||||
25, 86, 48, 112,
|
||||
255, 255, 121, 57,
|
||||
103, 42, 80, 16,
|
||||
0, 9, 121, 57,
|
||||
103, 42, 80, 16,
|
||||
};
|
||||
const uint8_t *chrominances;
|
||||
Outputs::CRT::DisplayType display_type;
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
//
|
||||
|
||||
#include "i8272.hpp"
|
||||
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Intel::i8272;
|
||||
|
||||
@@ -83,8 +82,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||
ClockingHint::Preference i8272::preferred_clocking() {
|
||||
const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
|
||||
if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
|
||||
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
void i8272::run_for(Cycles cycles) {
|
||||
@@ -113,7 +114,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
while(steps--) {
|
||||
// Perform a step.
|
||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
||||
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position));
|
||||
select_drive(c);
|
||||
get_drive().step(Storage::Disk::HeadPosition(direction));
|
||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||
@@ -159,7 +160,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
if(is_sleeping_) update_clocking_observer();
|
||||
}
|
||||
|
||||
void i8272::set_register(int address, uint8_t value) {
|
||||
@@ -198,7 +199,7 @@ uint8_t i8272::get_register(int address) {
|
||||
|
||||
#define MS_TO_CYCLES(x) x * 8000
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
|
||||
|
||||
#define PASTE(x, y) x##y
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
@@ -257,7 +258,7 @@ uint8_t i8272::get_register(int address) {
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_sleep_observer(); \
|
||||
update_clocking_observer(); \
|
||||
} \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||
}
|
||||
@@ -384,17 +385,17 @@ void i8272::posit_event(int event_type) {
|
||||
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
|
||||
// values in the internal registers.
|
||||
index_hole_limit_ = 2;
|
||||
// printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_);
|
||||
// LOG("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
|
||||
find_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
// Two index holes have passed wihout finding the header sought.
|
||||
// printf("Not found\n");
|
||||
// LOG("Not found");
|
||||
SetNoData();
|
||||
goto abort;
|
||||
}
|
||||
index_hole_count_ = 0;
|
||||
// printf("Header\n");
|
||||
// LOG("Header");
|
||||
READ_HEADER();
|
||||
if(index_hole_count_) {
|
||||
// This implies an index hole was sighted within the header. Error out.
|
||||
@@ -405,11 +406,11 @@ void i8272::posit_event(int event_type) {
|
||||
// This implies a CRC error in the header; mark as such but continue.
|
||||
SetDataError();
|
||||
}
|
||||
// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
// LOG("Considering << PADHEX(2) << header_[0] << " " << header_[1] << " " << header_[2] << " " << header_[3] << " [" << get_crc_generator().get_value() << "]");
|
||||
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
|
||||
|
||||
// Branch to whatever is supposed to happen next
|
||||
// printf("Proceeding\n");
|
||||
// LOG("Proceeding");
|
||||
switch(command_[0] & 0x1f) {
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
@@ -423,7 +424,13 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
// Performs the read data or read deleted data command.
|
||||
read_data:
|
||||
printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
LOG(PADHEX(2) << "Read [deleted] data ["
|
||||
<< static_cast<int>(command_[2]) << " "
|
||||
<< static_cast<int>(command_[3]) << " "
|
||||
<< static_cast<int>(command_[4]) << " "
|
||||
<< static_cast<int>(command_[5]) << " ... "
|
||||
<< static_cast<int>(command_[6]) << " "
|
||||
<< static_cast<int>(command_[8]) << "]");
|
||||
read_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
@@ -507,7 +514,13 @@ void i8272::posit_event(int event_type) {
|
||||
goto post_st012chrn;
|
||||
|
||||
write_data:
|
||||
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
LOG(PADHEX(2) << "Write [deleted] data ["
|
||||
<< static_cast<int>(command_[2]) << " "
|
||||
<< static_cast<int>(command_[3]) << " "
|
||||
<< static_cast<int>(command_[4]) << " "
|
||||
<< static_cast<int>(command_[5]) << " ... "
|
||||
<< static_cast<int>(command_[6]) << " "
|
||||
<< static_cast<int>(command_[8]) << "]");
|
||||
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
@@ -542,7 +555,7 @@ void i8272::posit_event(int event_type) {
|
||||
goto write_loop;
|
||||
}
|
||||
|
||||
printf("Wrote %d bytes\n", distance_into_section_);
|
||||
LOG("Wrote " << PADDEC(0) << distance_into_section_ << " bytes");
|
||||
write_crc();
|
||||
expects_input_ = false;
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
@@ -558,7 +571,7 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs the read ID command.
|
||||
read_id:
|
||||
// Establishes the drive and head being addressed, and whether in double density mode.
|
||||
printf("Read ID [%02x %02x]\n", command_[0], command_[1]);
|
||||
LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]");
|
||||
|
||||
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
||||
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
||||
@@ -580,7 +593,11 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
// Performs read track.
|
||||
read_track:
|
||||
printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
|
||||
LOG(PADHEX(2) << "Read track ["
|
||||
<< static_cast<int>(command_[2]) << " "
|
||||
<< static_cast<int>(command_[3]) << " "
|
||||
<< static_cast<int>(command_[4]) << " "
|
||||
<< static_cast<int>(command_[5]) << "]");
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
@@ -621,7 +638,7 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
printf("Format track\n");
|
||||
LOG("Format track");
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort;
|
||||
@@ -665,7 +682,12 @@ void i8272::posit_event(int event_type) {
|
||||
break;
|
||||
}
|
||||
|
||||
printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
LOG(PADHEX(2) << "W:"
|
||||
<< static_cast<int>(header_[0]) << " "
|
||||
<< static_cast<int>(header_[1]) << " "
|
||||
<< static_cast<int>(header_[2]) << " "
|
||||
<< static_cast<int>(header_[3]) << ", "
|
||||
<< get_crc_generator().get_value());
|
||||
write_crc();
|
||||
|
||||
// Write the sector body.
|
||||
@@ -697,15 +719,15 @@ void i8272::posit_event(int event_type) {
|
||||
goto post_st012chrn;
|
||||
|
||||
scan_low:
|
||||
printf("Scan low unimplemented!!\n");
|
||||
ERROR("Scan low unimplemented!!");
|
||||
goto wait_for_command;
|
||||
|
||||
scan_low_or_equal:
|
||||
printf("Scan low or equal unimplemented!!\n");
|
||||
ERROR("Scan low or equal unimplemented!!");
|
||||
goto wait_for_command;
|
||||
|
||||
scan_high_or_equal:
|
||||
printf("Scan high or equal unimplemented!!\n");
|
||||
ERROR("Scan high or equal unimplemented!!");
|
||||
goto wait_for_command;
|
||||
|
||||
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
|
||||
@@ -720,7 +742,7 @@ void i8272::posit_event(int event_type) {
|
||||
if(drives_[drive].phase != Drive::Seeking) {
|
||||
drives_seeking_++;
|
||||
is_sleeping_ = false;
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
||||
@@ -736,11 +758,11 @@ void i8272::posit_event(int event_type) {
|
||||
// up in run_for understands to mean 'keep going until track 0 is active').
|
||||
if(command_.size() > 2) {
|
||||
drives_[drive].target_head_position = command_[2];
|
||||
printf("Seek to %02x\n", command_[2]);
|
||||
LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
|
||||
} else {
|
||||
drives_[drive].target_head_position = -1;
|
||||
drives_[drive].head_position = 0;
|
||||
printf("Recalibrate\n");
|
||||
LOG("Recalibrate");
|
||||
}
|
||||
|
||||
// Check whether any steps are even needed; if not then mark as completed already.
|
||||
@@ -753,7 +775,7 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
// Performs sense interrupt status.
|
||||
sense_interrupt_status:
|
||||
printf("Sense interrupt status\n");
|
||||
LOG("Sense interrupt status");
|
||||
{
|
||||
// Find the first drive that is in the CompletedSeeking state.
|
||||
int found_drive = -1;
|
||||
@@ -781,7 +803,7 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs specify.
|
||||
specify:
|
||||
// Just store the values, and terminate the command.
|
||||
printf("Specify\n");
|
||||
LOG("Specify");
|
||||
step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
|
||||
head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms
|
||||
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
|
||||
@@ -792,7 +814,7 @@ void i8272::posit_event(int event_type) {
|
||||
goto wait_for_command;
|
||||
|
||||
sense_drive_status:
|
||||
printf("Sense drive status\n");
|
||||
LOG("Sense drive status");
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
select_drive(drive);
|
||||
@@ -831,11 +853,11 @@ void i8272::posit_event(int event_type) {
|
||||
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
|
||||
// last thing in it will be returned first.
|
||||
post_result:
|
||||
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
||||
LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
|
||||
for(std::size_t c = 0; c < result_stack_.size(); c++) {
|
||||
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
||||
LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
|
||||
}
|
||||
printf("\n");
|
||||
LOGNBR(std::endl);
|
||||
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
is_executing_ = false;
|
||||
|
||||
@@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
@@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type);
|
||||
void posit_event(int type) override;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
@@ -496,17 +496,25 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
// Paint sprites and check for collisions.
|
||||
// Paint sprites and check for collisions, but only if at least one sprite is active
|
||||
// on this line.
|
||||
if(sprite_set.active_sprite_slot) {
|
||||
int sprite_pixels_left = pixels_left;
|
||||
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||
|
||||
const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
while(sprite_pixels_left--) {
|
||||
// sprite_colour is the colour that's going to reach the display after sprite logic has been
|
||||
// applied; by default assume that nothing is going to be drawn.
|
||||
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
|
||||
|
||||
// The sprite_mask is used to keep track of whether two sprites have both sought to output
|
||||
// a pixel at the same location, and to feed that into the status register's sprite
|
||||
// collision bit.
|
||||
int sprite_mask = 0;
|
||||
|
||||
int c = sprite_set.active_sprite_slot;
|
||||
while(c--) {
|
||||
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
|
||||
@@ -517,15 +525,24 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
} else if(sprite.shift_position < 32) {
|
||||
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
|
||||
mask = (mask >> 7) & 1;
|
||||
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
|
||||
sprite_mask |= mask;
|
||||
sprite.shift_position += shift_advance;
|
||||
|
||||
mask &= colour_masks[sprite.info[3]&15];
|
||||
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
|
||||
// Ignore the right half of whatever was collected if sprites are not in 16x16 mode.
|
||||
if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) {
|
||||
// If any previous sprite has been painted in this column and this sprite
|
||||
// has this pixel set, set the sprite collision status bit.
|
||||
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
|
||||
sprite_mask |= mask;
|
||||
|
||||
// Check that the sprite colour is not transparent
|
||||
mask &= colour_masks[sprite.info[3]&15];
|
||||
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
|
||||
}
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
// Output whichever sprite colour was on top.
|
||||
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
|
||||
output_column_++;
|
||||
}
|
||||
|
||||
@@ -225,14 +225,10 @@ uint8_t AY38910::get_register_value() {
|
||||
};
|
||||
|
||||
if(selected_register_ > 15) return 0xff;
|
||||
switch(selected_register_) {
|
||||
default: return registers_[selected_register_] & register_masks[selected_register_];
|
||||
case 14: return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0];
|
||||
case 15: return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1];
|
||||
}
|
||||
return registers_[selected_register_] & register_masks[selected_register_];
|
||||
}
|
||||
|
||||
// MARK: - Port handling
|
||||
// MARK: - Port querying
|
||||
|
||||
uint8_t AY38910::get_port_output(bool port_b) {
|
||||
return registers_[port_b ? 15 : 14];
|
||||
@@ -250,11 +246,16 @@ void AY38910::set_data_input(uint8_t r) {
|
||||
}
|
||||
|
||||
uint8_t AY38910::get_data_output() {
|
||||
if(control_state_ == Read && selected_register_ >= 14) {
|
||||
if(port_handler_) {
|
||||
return port_handler_->get_port_input(selected_register_ == 15);
|
||||
} else {
|
||||
return 0xff;
|
||||
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
|
||||
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
|
||||
// value returned to the CPU when reading it is the and of the output value and any input.
|
||||
// If it's defined as input then you just get the input.
|
||||
const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff;
|
||||
|
||||
switch(selected_register_) {
|
||||
default: break;
|
||||
case 14: return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff);
|
||||
case 15: return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff);
|
||||
}
|
||||
}
|
||||
return data_output_;
|
||||
@@ -276,8 +277,10 @@ void AY38910::set_control_lines(ControlLines control_lines) {
|
||||
}
|
||||
|
||||
void AY38910::update_bus() {
|
||||
// Assume no output, unless this turns out to be a read.
|
||||
data_output_ = 0xff;
|
||||
switch(control_state_) {
|
||||
default: break;
|
||||
default: break;
|
||||
case LatchAddress: select_register(data_input_); break;
|
||||
case Write: set_register_value(data_input_); break;
|
||||
case Read: data_output_ = get_register_value(); break;
|
||||
|
||||
@@ -92,9 +92,8 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
int selected_register_ = 0;
|
||||
uint8_t registers_[16];
|
||||
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint8_t port_inputs_[2];
|
||||
|
||||
int master_divider_ = 0;
|
||||
|
||||
|
||||
@@ -19,12 +19,13 @@ namespace {
|
||||
const uint8_t input_flux = 0x1;
|
||||
}
|
||||
|
||||
DiskII::DiskII() :
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{{2045454, 300, 1}, {2045454, 300, 1}}
|
||||
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
|
||||
{
|
||||
drives_[0].set_sleep_observer(this);
|
||||
drives_[1].set_sleep_observer(this);
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
drives_[active_drive_].set_event_delegate(this);
|
||||
}
|
||||
|
||||
@@ -72,72 +73,90 @@ void DiskII::select_drive(int drive) {
|
||||
drives_[active_drive_].set_motor_on(motor_is_enabled_);
|
||||
}
|
||||
|
||||
// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.
|
||||
|
||||
void DiskII::run_for(const Cycles cycles) {
|
||||
if(is_sleeping()) return;
|
||||
if(preferred_clocking() == ClockingHint::Preference::None) return;
|
||||
|
||||
if(!controller_can_sleep_) {
|
||||
int integer_cycles = cycles.as_int();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
inputs_ |= input_flux;
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
|
||||
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
|
||||
|
||||
case 0xa: // shift right, bringing in write protected status
|
||||
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
|
||||
|
||||
// If the controller is in the sense write protect loop but the register will never change,
|
||||
// short circuit further work and return now.
|
||||
if(shift_register_ == is_write_protected() ? 0xff : 0x00) {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
|
||||
set_controller_can_sleep();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
|
||||
// Currently writing?
|
||||
if(inputs_&input_mode) {
|
||||
// state_ & 0x80 should be the current level sent to the disk;
|
||||
// therefore transitions in that bit should become flux transitions
|
||||
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
|
||||
}
|
||||
|
||||
// TODO: surely there's a less heavyweight solution than this?
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
|
||||
int integer_cycles = cycles.as_int();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
if(flux_duration_) {
|
||||
--flux_duration_;
|
||||
if(!flux_duration_) inputs_ |= input_flux;
|
||||
}
|
||||
} else {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles);
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles);
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
|
||||
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
|
||||
|
||||
case 0xa: // shift right, bringing in write protected status
|
||||
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
|
||||
|
||||
// If the controller is in the sense write protect loop but the register will never change,
|
||||
// short circuit further work and return now.
|
||||
if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
|
||||
decide_clocking_preference();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
|
||||
// Currently writing?
|
||||
if(inputs_&input_mode) {
|
||||
// state_ & 0x80 should be the current level sent to the disk;
|
||||
// therefore transitions in that bit should become flux transitions
|
||||
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
|
||||
}
|
||||
|
||||
// TODO: surely there's a less heavyweight solution than inline updates?
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
|
||||
}
|
||||
|
||||
set_controller_can_sleep();
|
||||
// Per comp.sys.apple2.programmer there is a delay between the controller
|
||||
// motor switch being flipped and the drive motor actually switching off.
|
||||
// This models that, accepting overrun as a risk.
|
||||
if(motor_off_time_ >= 0) {
|
||||
motor_off_time_ -= cycles.as_int();
|
||||
if(motor_off_time_ < 0) {
|
||||
set_control(Control::Motor, false);
|
||||
}
|
||||
}
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
void DiskII::set_controller_can_sleep() {
|
||||
// Permit the controller to sleep if it's in sense write protect mode, and the shift register
|
||||
// has already filled with the result of shifting eight times.
|
||||
bool controller_could_sleep = controller_can_sleep_;
|
||||
controller_can_sleep_ =
|
||||
(
|
||||
(inputs_ == input_flux) &&
|
||||
!motor_is_enabled_ &&
|
||||
!shift_register_
|
||||
) ||
|
||||
(
|
||||
(inputs_ == (input_command | input_flux)) &&
|
||||
(shift_register_ == (is_write_protected() ? 0xff : 0x00))
|
||||
);
|
||||
if(controller_could_sleep != controller_can_sleep_)
|
||||
update_sleep_observer();
|
||||
void DiskII::decide_clocking_preference() {
|
||||
ClockingHint::Preference prior_preference = clocking_preference_;
|
||||
|
||||
// If in read mode, clocking is either:
|
||||
//
|
||||
// just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
|
||||
// none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
|
||||
if(!(inputs_ & ~input_flux)) {
|
||||
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// If in writing mode, clocking is real time.
|
||||
if(inputs_ & input_mode) {
|
||||
clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that
|
||||
// corresponds to the current write protect status. Otherwise it is none.
|
||||
if((inputs_ & ~input_flux) == input_command) {
|
||||
clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// Announce a change if there was one.
|
||||
if(prior_preference != clocking_preference_)
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool DiskII::is_write_protected() {
|
||||
@@ -195,18 +214,19 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
|
||||
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
|
||||
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
|
||||
inputs_ &= ~input_flux;
|
||||
set_controller_can_sleep();
|
||||
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
|
||||
decide_clocking_preference();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
drive_is_sleeping_[0] = drives_[0].is_sleeping();
|
||||
drive_is_sleeping_[1] = drives_[1].is_sleeping();
|
||||
update_sleep_observer();
|
||||
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
|
||||
drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
bool DiskII::is_sleeping() {
|
||||
return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1];
|
||||
ClockingHint::Preference DiskII::preferred_clocking() {
|
||||
return clocking_preference_;
|
||||
}
|
||||
|
||||
void DiskII::set_data_input(uint8_t input) {
|
||||
@@ -227,9 +247,12 @@ int DiskII::read_address(int address) {
|
||||
|
||||
case 0x8:
|
||||
shift_register_ = 0;
|
||||
set_control(Control::Motor, false);
|
||||
motor_off_time_ = clock_rate_;
|
||||
break;
|
||||
case 0x9:
|
||||
set_control(Control::Motor, true);
|
||||
motor_off_time_ = -1;
|
||||
break;
|
||||
case 0x9: set_control(Control::Motor, true); break;
|
||||
|
||||
case 0xa: select_drive(0); break;
|
||||
case 0xb: select_drive(1); break;
|
||||
@@ -243,11 +266,11 @@ int DiskII::read_address(int address) {
|
||||
break;
|
||||
case 0xf:
|
||||
if(!(inputs_ & input_mode))
|
||||
drives_[active_drive_].begin_writing(Storage::Time(1, 2045454), false);
|
||||
drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
|
||||
inputs_ |= input_mode;
|
||||
break;
|
||||
}
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
return (address & 1) ? 0xff : shift_register_;
|
||||
}
|
||||
|
||||
@@ -255,3 +278,7 @@ void DiskII::set_activity_observer(Activity::Observer *observer) {
|
||||
drives_[0].set_activity_observer(observer, "Drive 1", true);
|
||||
drives_[1].set_activity_observer(observer, "Drive 2", true);
|
||||
}
|
||||
|
||||
Storage::Disk::Drive &DiskII::get_drive(int index) {
|
||||
return drives_[index];
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#define DiskII_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
@@ -28,10 +28,10 @@ namespace Apple {
|
||||
*/
|
||||
class DiskII:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public Sleeper::SleepObserver,
|
||||
public Sleeper {
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
DiskII();
|
||||
DiskII(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
void set_data_input(uint8_t input);
|
||||
@@ -76,11 +76,15 @@ class DiskII:
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
|
||||
// As per Sleeper.
|
||||
bool is_sleeping() override;
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
// Returns the Storage::Disk::Drive in use for drive @c index.
|
||||
// *NOT FOR HARDWARE EMULATION USAGE*.
|
||||
Storage::Disk::Drive &get_drive(int index);
|
||||
|
||||
private:
|
||||
enum class Control {
|
||||
P0, P1, P2, P3,
|
||||
@@ -95,7 +99,9 @@ class DiskII:
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Track::Event &event) override;
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
|
||||
|
||||
const int clock_rate_ = 0;
|
||||
|
||||
uint8_t state_ = 0;
|
||||
uint8_t inputs_ = 0;
|
||||
@@ -103,18 +109,20 @@ class DiskII:
|
||||
|
||||
int stepper_mask_ = 0;
|
||||
int stepper_position_ = 0;
|
||||
int motor_off_time_ = -1;
|
||||
|
||||
bool is_write_protected();
|
||||
std::array<uint8_t, 256> state_machine_;
|
||||
Storage::Disk::Drive drives_[2];
|
||||
bool drive_is_sleeping_[2];
|
||||
bool controller_can_sleep_ = false;
|
||||
int active_drive_ = 0;
|
||||
bool motor_is_enabled_ = false;
|
||||
|
||||
void set_controller_can_sleep();
|
||||
void decide_clocking_preference();
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
uint8_t data_input_ = 0;
|
||||
int flux_duration_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ struct BooleanSelection: public Selection {
|
||||
|
||||
struct ListSelection: public Selection {
|
||||
std::string value;
|
||||
|
||||
|
||||
ListSelection *list_selection();
|
||||
BooleanSelection *boolean_selection();
|
||||
ListSelection(const std::string value) : value(value) {}
|
||||
|
||||
@@ -21,28 +21,73 @@ class Joystick {
|
||||
public:
|
||||
virtual ~Joystick() {}
|
||||
|
||||
struct DigitalInput {
|
||||
/*!
|
||||
Defines a single input, any individually-measured thing — a fire button or
|
||||
other digital control, an analogue axis, or a button with a symbol on it.
|
||||
*/
|
||||
struct Input {
|
||||
/// Defines the broad type of the input.
|
||||
enum Type {
|
||||
Up, Down, Left, Right, Fire,
|
||||
// Half-axis inputs.
|
||||
Up, Down, Left, Right,
|
||||
// Full-axis inputs.
|
||||
Horizontal, Vertical,
|
||||
// Fire buttons.
|
||||
Fire,
|
||||
// Other labelled keys.
|
||||
Key
|
||||
} type;
|
||||
union {
|
||||
};
|
||||
const Type type;
|
||||
|
||||
bool is_digital_axis() const {
|
||||
return type < Type::Horizontal;
|
||||
}
|
||||
bool is_analogue_axis() const {
|
||||
return type >= Type::Horizontal && type < Type::Fire;
|
||||
}
|
||||
bool is_axis() const {
|
||||
return type < Type::Fire;
|
||||
}
|
||||
bool is_button() const {
|
||||
return type >= Type::Fire;
|
||||
}
|
||||
|
||||
enum Precision {
|
||||
Analogue, Digital
|
||||
};
|
||||
Precision precision() const {
|
||||
return is_analogue_axis() ? Precision::Analogue : Precision::Digital;
|
||||
}
|
||||
|
||||
/*!
|
||||
Holds extra information pertaining to the input.
|
||||
|
||||
@c Type::Key inputs declare the symbol printed on them.
|
||||
|
||||
All other types of input have an associated index, indicating whether they
|
||||
are the zeroth, first, second, third, etc of those things. E.g. a joystick
|
||||
may have two fire buttons, which will be buttons 0 and 1.
|
||||
*/
|
||||
union Info {
|
||||
struct {
|
||||
int index;
|
||||
size_t index;
|
||||
} control;
|
||||
struct {
|
||||
wchar_t symbol;
|
||||
} key;
|
||||
} info;
|
||||
};
|
||||
Info info;
|
||||
// TODO: Find a way to make the above safely const; may mean not using a union.
|
||||
|
||||
DigitalInput(Type type, int index = 0) : type(type) {
|
||||
Input(Type type, size_t index = 0) :
|
||||
type(type) {
|
||||
info.control.index = index;
|
||||
}
|
||||
DigitalInput(wchar_t symbol) : type(Key) {
|
||||
Input(wchar_t symbol) : type(Key) {
|
||||
info.key.symbol = symbol;
|
||||
}
|
||||
|
||||
bool operator == (const DigitalInput &rhs) {
|
||||
bool operator == (const Input &rhs) {
|
||||
if(rhs.type != type) return false;
|
||||
if(rhs.type == Key) {
|
||||
return rhs.info.key.symbol == info.key.symbol;
|
||||
@@ -52,15 +97,139 @@ class Joystick {
|
||||
}
|
||||
};
|
||||
|
||||
virtual std::vector<DigitalInput> get_inputs() = 0;
|
||||
/// @returns The list of all inputs defined on this joystick.
|
||||
virtual std::vector<Input> &get_inputs() = 0;
|
||||
|
||||
// Host interface.
|
||||
virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0;
|
||||
/*!
|
||||
Sets the digital value of @c input. This may have direct effect or
|
||||
influence an analogue value; e.g. if the caller declares that ::Left is
|
||||
active but this joystick has only an analogue horizontal axis, this will
|
||||
cause a change to that analogue value.
|
||||
*/
|
||||
virtual void set_input(const Input &input, bool is_active) = 0;
|
||||
|
||||
/*!
|
||||
Sets the analogue value of @c input. If the input is actually digital,
|
||||
or if there is a digital input with a corresponding meaning (e.g. ::Left
|
||||
versus the horizontal axis), this may cause a digital input to be set.
|
||||
|
||||
@c value should be in the range [0.0, 1.0].
|
||||
*/
|
||||
virtual void set_input(const Input &input, float value) = 0;
|
||||
|
||||
/*!
|
||||
Sets all inputs to their resting state.
|
||||
*/
|
||||
virtual void reset_all_inputs() {
|
||||
for(const auto &input: get_inputs()) {
|
||||
set_digital_input(input, false);
|
||||
if(input.precision() == Input::Precision::Digital)
|
||||
set_input(input, false);
|
||||
else
|
||||
set_input(input, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the number of input fire buttons.
|
||||
|
||||
This is cached by default, but it's virtual so overridable.
|
||||
*/
|
||||
virtual int get_number_of_fire_buttons() {
|
||||
if(number_of_buttons_ >= 0) return number_of_buttons_;
|
||||
|
||||
number_of_buttons_ = 0;
|
||||
for(const auto &input: get_inputs()) {
|
||||
if(input.type == Input::Type::Fire) ++number_of_buttons_;
|
||||
}
|
||||
return number_of_buttons_;
|
||||
}
|
||||
|
||||
private:
|
||||
int number_of_buttons_ = -1;
|
||||
};
|
||||
|
||||
/*!
|
||||
ConcreteJoystick is the class that it's expected most machines will actually subclass;
|
||||
it accepts a set of Inputs at construction and thereby is able to provide the
|
||||
promised analogue <-> digital mapping of Joystick.
|
||||
*/
|
||||
class ConcreteJoystick: public Joystick {
|
||||
public:
|
||||
ConcreteJoystick(const std::vector<Input> &inputs) : inputs_(inputs) {
|
||||
// Size and populate stick_types_, which is used for digital <-> analogue conversion.
|
||||
for(const auto &input: inputs_) {
|
||||
const bool is_digital_axis = input.is_digital_axis();
|
||||
const bool is_analogue_axis = input.is_analogue_axis();
|
||||
if(is_digital_axis || is_analogue_axis) {
|
||||
const size_t required_size = static_cast<size_t>(input.info.control.index+1);
|
||||
if(stick_types_.size() < required_size) {
|
||||
stick_types_.resize(required_size);
|
||||
}
|
||||
stick_types_[static_cast<size_t>(input.info.control.index)] = is_digital_axis ? StickType::Digital : StickType::Analogue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Input> &get_inputs() override final {
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
void set_input(const Input &input, bool is_active) override final {
|
||||
// If this is a digital setting to a digital property, just pass it along.
|
||||
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
|
||||
did_set_input(input, is_active);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise this is logically to an analogue axis; for now just use some
|
||||
// convenient hard-coded values. TODO: make these a function of time.
|
||||
using Type = Joystick::Input::Type;
|
||||
switch(input.type) {
|
||||
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
|
||||
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break;
|
||||
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break;
|
||||
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break;
|
||||
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f); break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_input(const Input &input, float value) override final {
|
||||
// If this is an analogue setting to an analogue property, just pass it along.
|
||||
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
|
||||
did_set_input(input, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
|
||||
using Type = Joystick::Input::Type;
|
||||
switch(input.type) {
|
||||
default: did_set_input(input, value > 0.5f); break;
|
||||
case Type::Horizontal:
|
||||
did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
|
||||
did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);
|
||||
break;
|
||||
case Type::Vertical:
|
||||
did_set_input(Input(Type::Up, input.info.control.index), value <= 0.25f);
|
||||
did_set_input(Input(Type::Down, input.info.control.index), value >= 0.75f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void did_set_input(const Input &input, float value) {
|
||||
}
|
||||
|
||||
virtual void did_set_input(const Input &input, bool value) {
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Input> inputs_;
|
||||
|
||||
enum class StickType {
|
||||
Digital,
|
||||
Analogue
|
||||
};
|
||||
std::vector<StickType> stick_types_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
@@ -37,12 +38,11 @@
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
enum ROMType: int {
|
||||
OS464 = 0, BASIC464,
|
||||
OS664, BASIC664,
|
||||
OS6128, BASIC6128,
|
||||
AMSDOS
|
||||
};
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
return Configurable::standard_options(
|
||||
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite)
|
||||
);
|
||||
}
|
||||
|
||||
/*!
|
||||
Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
|
||||
@@ -192,36 +192,51 @@ class CRTCBusHandler {
|
||||
}
|
||||
|
||||
bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6);
|
||||
bool is_colour_burst = (cycles_into_hsync_ >= 7 && cycles_into_hsync_ < 11);
|
||||
|
||||
// Sync is taken to override pixels, and is combined as a simple OR.
|
||||
bool is_sync = is_hsync || state.vsync;
|
||||
bool is_blank = !is_sync && state.hsync;
|
||||
|
||||
OutputMode output_mode;
|
||||
if(is_sync) {
|
||||
output_mode = OutputMode::Sync;
|
||||
} else if(is_colour_burst) {
|
||||
output_mode = OutputMode::ColourBurst;
|
||||
} else if(is_blank) {
|
||||
output_mode = OutputMode::Blank;
|
||||
} else if(state.display_enable) {
|
||||
output_mode = OutputMode::Pixels;
|
||||
} else {
|
||||
output_mode = OutputMode::Border;
|
||||
}
|
||||
|
||||
// If a transition between sync/border/pixels just occurred, flush whatever was
|
||||
// in progress to the CRT and reset counting.
|
||||
if(state.display_enable != was_enabled_ || is_sync != was_sync_) {
|
||||
if(was_sync_) {
|
||||
crt_->output_sync(cycles_ * 16);
|
||||
} else {
|
||||
if(was_enabled_) {
|
||||
if(cycles_) {
|
||||
if(output_mode != previous_output_mode_) {
|
||||
if(cycles_) {
|
||||
switch(previous_output_mode_) {
|
||||
default:
|
||||
case OutputMode::Blank: crt_->output_blank(cycles_ * 16); break;
|
||||
case OutputMode::Sync: crt_->output_sync(cycles_ * 16); break;
|
||||
case OutputMode::Border: output_border(cycles_); break;
|
||||
case OutputMode::ColourBurst: crt_->output_default_colour_burst(cycles_ * 16); break;
|
||||
case OutputMode::Pixels:
|
||||
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
|
||||
pixel_pointer_ = pixel_data_ = nullptr;
|
||||
}
|
||||
} else {
|
||||
output_border(cycles_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cycles_ = 0;
|
||||
was_sync_ = is_sync;
|
||||
was_enabled_ = state.display_enable;
|
||||
previous_output_mode_ = output_mode;
|
||||
}
|
||||
|
||||
// increment cycles since state changed
|
||||
cycles_++;
|
||||
|
||||
// collect some more pixels if output is ongoing
|
||||
if(!is_sync && state.display_enable) {
|
||||
if(previous_output_mode_ == OutputMode::Pixels) {
|
||||
if(!pixel_data_) {
|
||||
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
|
||||
}
|
||||
@@ -317,7 +332,7 @@ class CRTCBusHandler {
|
||||
"uint sample = texture(texID, coordinate).r;"
|
||||
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
|
||||
"}");
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
|
||||
}
|
||||
|
||||
@@ -349,7 +364,7 @@ class CRTCBusHandler {
|
||||
if(pen_ & 16) {
|
||||
// If border is[/was] currently being output, flush what should have been
|
||||
// drawn in the old colour.
|
||||
if(!was_sync_ && !was_enabled_) {
|
||||
if(previous_output_mode_ == OutputMode::Border) {
|
||||
output_border(cycles_);
|
||||
cycles_ = 0;
|
||||
}
|
||||
@@ -506,9 +521,16 @@ class CRTCBusHandler {
|
||||
return mapping[colour];
|
||||
}
|
||||
|
||||
enum class OutputMode {
|
||||
Sync,
|
||||
Blank,
|
||||
ColourBurst,
|
||||
Border,
|
||||
Pixels
|
||||
} previous_output_mode_ = OutputMode::Sync;
|
||||
unsigned int cycles_ = 0;
|
||||
|
||||
bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
|
||||
bool was_hsync_ = false, was_vsync_ = false;
|
||||
int cycles_into_hsync_ = 0;
|
||||
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
@@ -537,24 +559,28 @@ class CRTCBusHandler {
|
||||
|
||||
/*!
|
||||
Holds and vends the current keyboard state, acting as the AY's port handler.
|
||||
Also owns the joysticks.
|
||||
*/
|
||||
class KeyboardState: public GI::AY38910::PortHandler {
|
||||
public:
|
||||
KeyboardState() : rows_{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} {}
|
||||
KeyboardState() {
|
||||
joysticks_.emplace_back(new Joystick(rows_[9]));
|
||||
joysticks_.emplace_back(new Joystick(joy2_state_));
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the row currently being reported to the AY.
|
||||
*/
|
||||
void set_row(int row) {
|
||||
row_ = row;
|
||||
row_ = static_cast<size_t>(row);
|
||||
}
|
||||
|
||||
/*!
|
||||
Reports the state of the currently-selected row as Port A to the AY.
|
||||
*/
|
||||
uint8_t get_port_input(bool port_b) {
|
||||
if(!port_b && row_ < 10) {
|
||||
return rows_[row_];
|
||||
if(!port_b && row_ < sizeof(rows_)) {
|
||||
return (row_ == 6) ? rows_[row_] & joy2_state_ : rows_[row_];
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
@@ -565,7 +591,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
*/
|
||||
void set_is_pressed(bool is_pressed, int line, int key) {
|
||||
int mask = 1 << key;
|
||||
assert(line < 10);
|
||||
assert(static_cast<size_t>(line) < sizeof(rows_));
|
||||
if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask;
|
||||
}
|
||||
|
||||
@@ -573,12 +599,52 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
Sets all keys as currently unpressed.
|
||||
*/
|
||||
void clear_all_keys() {
|
||||
memset(rows_, 0xff, 10);
|
||||
memset(rows_, 0xff, sizeof(rows_));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t rows_[10];
|
||||
int row_;
|
||||
uint8_t joy2_state_ = 0xff;
|
||||
uint8_t rows_[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
size_t row_ = 0;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick(uint8_t &state) :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
}),
|
||||
state_(state) {}
|
||||
|
||||
void did_set_input(const Input &input, bool is_active) override {
|
||||
uint8_t mask = 0;
|
||||
switch(input.type) {
|
||||
default: return;
|
||||
case Input::Up: mask = 0x01; break;
|
||||
case Input::Down: mask = 0x02; break;
|
||||
case Input::Left: mask = 0x04; break;
|
||||
case Input::Right: mask = 0x08; break;
|
||||
case Input::Fire:
|
||||
if(input.info.control.index >= 2) return;
|
||||
mask = input.info.control.index ? 0x20 : 0x10;
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_active) state_ &= ~mask; else state_ |= mask;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t &state_;
|
||||
};
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -688,17 +754,19 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
/*!
|
||||
The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80.
|
||||
*/
|
||||
class ConcreteMachine:
|
||||
template <bool has_fdc> class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Configurable::Device,
|
||||
public JoystickMachine::Machine,
|
||||
public Machine,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
z80_(*this),
|
||||
crtc_bus_handler_(ram_, interrupt_timer_),
|
||||
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
||||
@@ -714,13 +782,62 @@ class ConcreteMachine:
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
|
||||
// register this class as the sleep observer for the FDC and tape
|
||||
fdc_.set_sleep_observer(this);
|
||||
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||
|
||||
tape_player_.set_sleep_observer(this);
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
fdc_.set_clocking_hint_observer(this);
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
|
||||
// install the keyboard state class as the AY port handler
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
|
||||
// construct the list of necessary ROMs
|
||||
std::vector<std::string> required_roms = {"amsdos.rom"};
|
||||
std::string model_number;
|
||||
switch(target.model) {
|
||||
default:
|
||||
model_number = "6128";
|
||||
has_128k_ = true;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
|
||||
model_number = "464";
|
||||
has_128k_ = false;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
|
||||
model_number = "664";
|
||||
has_128k_ = false;
|
||||
break;
|
||||
}
|
||||
required_roms.push_back("os" + model_number + ".rom");
|
||||
required_roms.push_back("basic" + model_number + ".rom");
|
||||
|
||||
// fetch and verify the ROMs
|
||||
const auto roms = rom_fetcher("AmstradCPC", required_roms);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) throw ROMMachine::Error::MissingROMs;
|
||||
roms_[static_cast<int>(index)] = std::move(*data);
|
||||
roms_[static_cast<int>(index)].resize(16384);
|
||||
}
|
||||
|
||||
// Establish default memory map
|
||||
upper_rom_is_paged_ = true;
|
||||
upper_rom_ = ROMType::BASIC;
|
||||
|
||||
write_pointers_[0] = &ram_[0];
|
||||
write_pointers_[1] = &ram_[16384];
|
||||
write_pointers_[2] = &ram_[32768];
|
||||
write_pointers_[3] = &ram_[49152];
|
||||
|
||||
read_pointers_[0] = roms_[ROMType::OS].data();
|
||||
read_pointers_[1] = write_pointers_[1];
|
||||
read_pointers_[2] = write_pointers_[2];
|
||||
read_pointers_[3] = roms_[upper_rom_].data();
|
||||
|
||||
// Type whatever is required.
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
/// The entry point for performing a partial Z80 machine cycle.
|
||||
@@ -749,7 +866,7 @@ class ConcreteMachine:
|
||||
ay_.run_for(cycle.length);
|
||||
|
||||
// Clock the FDC, if connected, using a lazy scale by two
|
||||
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||
time_since_fdc_update_ += cycle.length;
|
||||
|
||||
// Update typing activity
|
||||
if(typer_) typer_->run_for(cycle.length);
|
||||
@@ -775,8 +892,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// Check for an upper ROM selection
|
||||
if(has_fdc_ && !(address&0x2000)) {
|
||||
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : rom_model_ + 1;
|
||||
if(has_fdc && !(address&0x2000)) {
|
||||
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
|
||||
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
|
||||
}
|
||||
|
||||
@@ -795,12 +912,14 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
if(has_fdc && (address & 0x580) == 0x100) {
|
||||
flush_fdc();
|
||||
fdc_.set_register(address & 1, *cycle.value);
|
||||
}
|
||||
|
||||
// Check for a disk motor access
|
||||
if(has_fdc_ && !(address & 0x580)) {
|
||||
if(has_fdc && !(address & 0x580)) {
|
||||
flush_fdc();
|
||||
fdc_.set_motor_on(!!(*cycle.value));
|
||||
}
|
||||
break;
|
||||
@@ -814,7 +933,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
if(has_fdc && (address & 0x580) == 0x100) {
|
||||
flush_fdc();
|
||||
*cycle.value &= fdc_.get_register(address & 1);
|
||||
}
|
||||
|
||||
@@ -858,6 +978,7 @@ class ConcreteMachine:
|
||||
// Just flush the AY.
|
||||
ay_.update();
|
||||
ay_.flush();
|
||||
flush_fdc();
|
||||
}
|
||||
|
||||
/// A CRTMachine function; indicates that outputs should be created now.
|
||||
@@ -885,50 +1006,6 @@ class ConcreteMachine:
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target);
|
||||
|
||||
switch(cpc_target->model) {
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
|
||||
rom_model_ = ROMType::OS464;
|
||||
has_128k_ = false;
|
||||
has_fdc_ = false;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
|
||||
rom_model_ = ROMType::OS664;
|
||||
has_128k_ = false;
|
||||
has_fdc_ = true;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC6128:
|
||||
rom_model_ = ROMType::OS6128;
|
||||
has_128k_ = true;
|
||||
has_fdc_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Establish default memory map
|
||||
upper_rom_is_paged_ = true;
|
||||
upper_rom_ = rom_model_ + 1;
|
||||
|
||||
write_pointers_[0] = &ram_[0];
|
||||
write_pointers_[1] = &ram_[16384];
|
||||
write_pointers_[2] = &ram_[32768];
|
||||
write_pointers_[3] = &ram_[49152];
|
||||
|
||||
read_pointers_[0] = roms_[rom_model_].data();
|
||||
read_pointers_[1] = write_pointers_[1];
|
||||
read_pointers_[2] = write_pointers_[2];
|
||||
read_pointers_[3] = roms_[upper_rom_].data();
|
||||
|
||||
// Type whatever is required.
|
||||
if(!cpc_target->loading_command.empty()) {
|
||||
type_string(cpc_target->loading_command);
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
// If there are any tapes supplied, use the first of them.
|
||||
if(!media.tapes.empty()) {
|
||||
@@ -943,37 +1020,15 @@ class ConcreteMachine:
|
||||
if(c == 4) break;
|
||||
}
|
||||
|
||||
return !media.tapes.empty() || (!media.disks.empty() && has_fdc_);
|
||||
return !media.tapes.empty() || (!media.disks.empty() && has_fdc);
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
auto roms = roms_with_names(
|
||||
"AmstradCPC",
|
||||
{
|
||||
"os464.rom", "basic464.rom",
|
||||
"os664.rom", "basic664.rom",
|
||||
"os6128.rom", "basic6128.rom",
|
||||
"amsdos.rom"
|
||||
});
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) return false;
|
||||
roms_[static_cast<int>(index)] = std::move(*data);
|
||||
roms_[static_cast<int>(index)].resize(16384);
|
||||
}
|
||||
|
||||
return true;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
|
||||
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
|
||||
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
|
||||
// MARK: - Keyboard
|
||||
void type_string(const std::string &string) override final {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
|
||||
Utility::TypeRecipient::add_typer(string, std::move(mapper));
|
||||
@@ -1003,9 +1058,37 @@ class ConcreteMachine:
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
if(has_fdc_) fdc_.set_activity_observer(observer);
|
||||
if(has_fdc) fdc_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
return AmstradCPC::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
Configurable::Display display;
|
||||
if(Configurable::get_display(selections_by_option, display)) {
|
||||
set_video_signal_configurable(display);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
// MARK: - Joysticks
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return key_state_.get_joysticks();
|
||||
}
|
||||
|
||||
private:
|
||||
inline void write_to_gate_array(uint8_t value) {
|
||||
@@ -1014,7 +1097,7 @@ class ConcreteMachine:
|
||||
case 1: crtc_bus_handler_.set_colour(value & 0x1f); break;
|
||||
case 2:
|
||||
// Perform ROM paging.
|
||||
read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data();
|
||||
read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[ROMType::OS].data();
|
||||
|
||||
upper_rom_is_paged_ = !(value & 8);
|
||||
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3];
|
||||
@@ -1063,6 +1146,14 @@ class ConcreteMachine:
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
FDC fdc_;
|
||||
HalfCycles time_since_fdc_update_;
|
||||
void flush_fdc() {
|
||||
// Clock the FDC, if connected, using a lazy scale by two
|
||||
if(has_fdc && !fdc_is_sleeping_) {
|
||||
fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
|
||||
}
|
||||
time_since_fdc_update_ = HalfCycles(0);
|
||||
}
|
||||
|
||||
InterruptTimer interrupt_timer_;
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
@@ -1073,13 +1164,16 @@ class ConcreteMachine:
|
||||
|
||||
uint8_t ram_[128 * 1024];
|
||||
|
||||
std::vector<uint8_t> roms_[7];
|
||||
int rom_model_;
|
||||
bool has_fdc_, fdc_is_sleeping_;
|
||||
bool fdc_is_sleeping_;
|
||||
bool tape_player_is_sleeping_;
|
||||
bool has_128k_;
|
||||
|
||||
enum ROMType: int {
|
||||
AMSDOS = 0, OS = 1, BASIC = 2
|
||||
};
|
||||
std::vector<uint8_t> roms_[3];
|
||||
bool upper_rom_is_paged_;
|
||||
int upper_rom_;
|
||||
ROMType upper_rom_;
|
||||
|
||||
uint8_t *ram_pages_[4];
|
||||
uint8_t *read_pointers_[4];
|
||||
@@ -1094,8 +1188,13 @@ class ConcreteMachine:
|
||||
using namespace AmstradCPC;
|
||||
|
||||
// See header; constructs and returns an instance of the Amstrad CPC.
|
||||
Machine *Machine::AmstradCPC() {
|
||||
return new AmstradCPC::ConcreteMachine;
|
||||
Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::AmstradCPC::Target;
|
||||
const Target *const cpc_target = dynamic_cast<const Target *>(target);
|
||||
switch(cpc_target->model) {
|
||||
default: return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher);
|
||||
case Target::Model::CPC464: return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -9,8 +9,18 @@
|
||||
#ifndef AmstradCPC_hpp
|
||||
#define AmstradCPC_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
/// @returns The options available for an Amstrad CPC.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
/*!
|
||||
Models an Amstrad CPC.
|
||||
*/
|
||||
@@ -19,7 +29,7 @@ class Machine {
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Amstrad CPC.
|
||||
static Machine *AmstradCPC();
|
||||
static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
#include "AppleII.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/StringSerialiser.hpp"
|
||||
@@ -19,12 +20,14 @@
|
||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "DiskIICard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -32,31 +35,35 @@
|
||||
|
||||
namespace {
|
||||
|
||||
class ConcreteMachine:
|
||||
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
|
||||
|
||||
template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public AppleII::Machine,
|
||||
public Activity::Source,
|
||||
public JoystickMachine::Machine,
|
||||
public AppleII::Card::Delegate {
|
||||
private:
|
||||
struct VideoBusHandler : public AppleII::Video::BusHandler {
|
||||
public:
|
||||
VideoBusHandler(uint8_t *ram) : ram_(ram) {}
|
||||
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
|
||||
|
||||
uint8_t perform_read(uint16_t address) {
|
||||
return ram_[address];
|
||||
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
|
||||
memcpy(base_target, &ram_[address], count);
|
||||
memcpy(auxiliary_target, &aux_ram_[address], count);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *ram_;
|
||||
uint8_t *ram_, *aux_ram_;
|
||||
};
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
VideoBusHandler video_bus_handler_;
|
||||
std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_;
|
||||
std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie()>> video_;
|
||||
int cycles_into_current_line_ = 0;
|
||||
Cycles cycles_since_video_update_;
|
||||
|
||||
@@ -76,17 +83,24 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
uint8_t ram_[65536], aux_ram_[65536];
|
||||
std::vector<uint8_t> apple2_rom_, apple2plus_rom_, rom_;
|
||||
std::vector<uint8_t> rom_;
|
||||
std::vector<uint8_t> character_rom_;
|
||||
uint8_t keyboard_input_ = 0x00;
|
||||
bool key_is_down_ = false;
|
||||
|
||||
uint8_t get_keyboard_input() {
|
||||
if(string_serialiser_) {
|
||||
return string_serialiser_->head() | 0x80;
|
||||
} else {
|
||||
return keyboard_input_;
|
||||
}
|
||||
}
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
|
||||
ROMMachine::ROMFetcher rom_fetcher_;
|
||||
|
||||
// MARK: - Cards
|
||||
std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
|
||||
Cycles cycles_since_card_update_;
|
||||
@@ -121,11 +135,48 @@ class ConcreteMachine:
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
// MARK: - Memory Map
|
||||
struct MemoryBlock {
|
||||
uint8_t *read_pointer = nullptr;
|
||||
uint8_t *write_pointer = nullptr;
|
||||
} memory_blocks_[4]; // The IO page isn't included.
|
||||
AppleII::DiskIICard *diskii_card() {
|
||||
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
|
||||
}
|
||||
|
||||
// MARK: - Memory Map.
|
||||
|
||||
/*
|
||||
The Apple II's paging mechanisms are byzantine to say the least. Painful is
|
||||
another appropriate adjective.
|
||||
|
||||
On a II and II+ there are five distinct zones of memory:
|
||||
|
||||
0000 to c000 : the main block of RAM
|
||||
c000 to d000 : the IO area, including card ROMs
|
||||
d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card
|
||||
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
|
||||
|
||||
On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
|
||||
|
||||
0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it
|
||||
0400 to 0800 : the text screen, can be configured to write to auxiliary RAM
|
||||
2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM
|
||||
c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area
|
||||
c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest
|
||||
c800 to d000 : can contain ROM separately from the region below c800
|
||||
|
||||
If dealt with as individual blocks in the inner loop, that would therefore imply mapping
|
||||
an address to one of 13 potential pageable zones. So I've gone reductive and surrendered
|
||||
to paging every 6502 page of memory independently. It makes the paging events more expensive,
|
||||
but hopefully more clear.
|
||||
*/
|
||||
uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
|
||||
uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write.
|
||||
void page(int start, int end, uint8_t *read, uint8_t *write) {
|
||||
for(int position = start; position < end; ++position) {
|
||||
read_pages_[position] = read;
|
||||
if(read) read += 256;
|
||||
|
||||
write_pages_[position] = write;
|
||||
if(write) write += 256;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - The language card.
|
||||
struct {
|
||||
@@ -136,29 +187,128 @@ class ConcreteMachine:
|
||||
} language_card_;
|
||||
bool has_language_card_ = true;
|
||||
void set_language_card_paging() {
|
||||
if(has_language_card_ && !language_card_.write) {
|
||||
memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
|
||||
memory_blocks_[3].write_pointer = &ram_[56*1024];
|
||||
} else {
|
||||
memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr;
|
||||
uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_;
|
||||
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
|
||||
|
||||
page(0xd0, 0xe0,
|
||||
language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom,
|
||||
language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]);
|
||||
|
||||
page(0xe0, 0x100,
|
||||
language_card_.read ? &ram[0xe000] : &rom[0x1000],
|
||||
language_card_.write ? nullptr : &ram[0xe000]);
|
||||
}
|
||||
|
||||
// MARK - The IIe's ROM controls.
|
||||
bool internal_CX_rom_ = false;
|
||||
bool slot_C3_rom_ = false;
|
||||
bool internal_c8_rom_ = false;
|
||||
|
||||
void set_card_paging() {
|
||||
page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr);
|
||||
|
||||
if(!internal_CX_rom_) {
|
||||
if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100];
|
||||
}
|
||||
|
||||
if(has_language_card_ && language_card_.read) {
|
||||
memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
|
||||
memory_blocks_[3].read_pointer = &ram_[56*1024];
|
||||
page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
|
||||
}
|
||||
|
||||
// MARK - The IIe's auxiliary RAM controls.
|
||||
bool alternative_zero_page_ = false;
|
||||
void set_zero_page_paging() {
|
||||
if(alternative_zero_page_) {
|
||||
read_pages_[0] = aux_ram_;
|
||||
} else {
|
||||
memory_blocks_[2].read_pointer = rom_.data();
|
||||
memory_blocks_[3].read_pointer = rom_.data() + 0x1000;
|
||||
read_pages_[0] = ram_;
|
||||
}
|
||||
read_pages_[1] = read_pages_[0] + 256;
|
||||
write_pages_[0] = read_pages_[0];
|
||||
write_pages_[1] = read_pages_[1];
|
||||
}
|
||||
|
||||
bool read_auxiliary_memory_ = false;
|
||||
bool write_auxiliary_memory_ = false;
|
||||
void set_main_paging() {
|
||||
page(0x02, 0xc0,
|
||||
read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
|
||||
write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
|
||||
|
||||
if(video_ && video_->get_80_store()) {
|
||||
bool use_aux_ram = video_->get_page2();
|
||||
page(0x04, 0x08,
|
||||
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
|
||||
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
|
||||
|
||||
if(video_->get_high_resolution()) {
|
||||
page(0x20, 0x40,
|
||||
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
|
||||
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK - typing
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
||||
|
||||
// MARK - joysticks
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Horizontal),
|
||||
Input(Input::Vertical),
|
||||
|
||||
// The Apple II offers three buttons between two joysticks;
|
||||
// this emulator puts three buttons on each joystick and
|
||||
// combines them.
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
Input(Input::Fire, 2),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &input, float value) override {
|
||||
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
|
||||
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
|
||||
}
|
||||
|
||||
void did_set_input(const Input &input, bool value) override {
|
||||
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
|
||||
buttons[input.info.control.index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool buttons[3] = {false, false, false};
|
||||
float axes[2] = {0.5f, 0.5f};
|
||||
};
|
||||
|
||||
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
|
||||
// to begin a charge and discharge cycle **if they are not already charging**.
|
||||
// The greater the analogue input, the faster they will charge and therefore the sooner
|
||||
// they will discharge.
|
||||
//
|
||||
// This emulator models that with analogue_charge_ being essentially the amount of time,
|
||||
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
|
||||
// inputs were already partially charged then they gain a bias in analogue_biases_.
|
||||
//
|
||||
// It's a little indirect, but it means only having to increment the one value in the
|
||||
// main loop.
|
||||
float analogue_charge_ = 0.0f;
|
||||
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
bool analogue_channel_is_discharged(size_t channel) {
|
||||
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
|
||||
}
|
||||
|
||||
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
|
||||
bool open_apple_is_pressed_ = false;
|
||||
bool closed_apple_is_pressed_ = false;
|
||||
|
||||
public:
|
||||
ConcreteMachine():
|
||||
m6502_(*this),
|
||||
video_bus_handler_(ram_),
|
||||
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
|
||||
m6502_(*this),
|
||||
video_bus_handler_(ram_, aux_ram_),
|
||||
audio_toggle_(audio_queue_),
|
||||
speaker_(audio_toggle_) {
|
||||
// The system's master clock rate.
|
||||
@@ -180,6 +330,66 @@ class ConcreteMachine:
|
||||
|
||||
// Also, start with randomised memory contents.
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
|
||||
|
||||
// Add a couple of joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
// Pick the required ROMs.
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
std::vector<std::string> rom_names;
|
||||
size_t rom_size = 12*1024;
|
||||
switch(target.model) {
|
||||
default:
|
||||
rom_names.push_back("apple2-character.rom");
|
||||
rom_names.push_back("apple2o.rom");
|
||||
break;
|
||||
case Target::Model::IIplus:
|
||||
rom_names.push_back("apple2-character.rom");
|
||||
rom_names.push_back("apple2.rom");
|
||||
break;
|
||||
case Target::Model::IIe:
|
||||
rom_size += 3840;
|
||||
rom_names.push_back("apple2eu-character.rom");
|
||||
rom_names.push_back("apple2eu.rom");
|
||||
break;
|
||||
case Target::Model::EnhancedIIe:
|
||||
rom_size += 3840;
|
||||
rom_names.push_back("apple2e-character.rom");
|
||||
rom_names.push_back("apple2e.rom");
|
||||
break;
|
||||
}
|
||||
const auto roms = rom_fetcher("AppleII", rom_names);
|
||||
|
||||
if(!roms[0] || !roms[1]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
rom_ = std::move(*roms[1]);
|
||||
if(rom_.size() > rom_size) {
|
||||
rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
|
||||
}
|
||||
|
||||
character_rom_ = std::move(*roms[0]);
|
||||
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
}
|
||||
|
||||
// Set up the default memory blocks. On a II or II+ these values will never change.
|
||||
// On a IIe they'll be affected by selection of auxiliary RAM.
|
||||
set_main_paging();
|
||||
set_zero_page_paging();
|
||||
|
||||
// Set the whole card area to initially backed by nothing.
|
||||
page(0xc0, 0xd0, nullptr, nullptr);
|
||||
|
||||
// Set proper values for the language card/ROM area.
|
||||
set_language_card_paging();
|
||||
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -187,7 +397,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_));
|
||||
video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie()>(video_bus_handler_));
|
||||
video_->set_character_rom(character_rom_);
|
||||
}
|
||||
|
||||
@@ -203,7 +413,7 @@ class ConcreteMachine:
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) {
|
||||
++ cycles_since_video_update_;
|
||||
++ cycles_since_card_update_;
|
||||
cycles_since_audio_update_ += Cycles(7);
|
||||
@@ -221,30 +431,23 @@ class ConcreteMachine:
|
||||
++ stretched_cycles_since_card_update_;
|
||||
}
|
||||
|
||||
/*
|
||||
There are five distinct zones of memory on an Apple II:
|
||||
|
||||
0000 to 0200 : the zero and stack pages, which can be paged independently on a IIe
|
||||
0200 to c000 : the main block of RAM, which can be paged on a IIe
|
||||
c000 to d000 : the IO area, including card ROMs
|
||||
d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card
|
||||
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
|
||||
*/
|
||||
MemoryBlock *block = nullptr;
|
||||
if(address < 0x200) block = &memory_blocks_[0];
|
||||
else if(address < 0xc000) {
|
||||
if(address < 0x6000 && !isReadOperation(operation)) update_video();
|
||||
block = &memory_blocks_[1];
|
||||
address -= 0x200;
|
||||
}
|
||||
else if(address < 0xd000) block = nullptr;
|
||||
else if(address < 0xe000) {block = &memory_blocks_[2]; address -= 0xd000; }
|
||||
else { block = &memory_blocks_[3]; address -= 0xe000; }
|
||||
|
||||
bool has_updated_cards = false;
|
||||
if(block) {
|
||||
if(isReadOperation(operation)) *value = block->read_pointer[address];
|
||||
else if(block->write_pointer) block->write_pointer[address] = *value;
|
||||
if(read_pages_[address >> 8]) {
|
||||
if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff];
|
||||
else {
|
||||
if(address >= 0x200 && address < 0x6000) update_video();
|
||||
if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value;
|
||||
}
|
||||
|
||||
if(is_iie() && address >= 0xc300 && address < 0xd000) {
|
||||
bool internal_c8_rom = internal_c8_rom_;
|
||||
internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_;
|
||||
internal_c8_rom &= (address != 0xcfff);
|
||||
if(internal_c8_rom != internal_c8_rom_) {
|
||||
internal_c8_rom_ = internal_c8_rom;
|
||||
set_card_paging();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assume a vapour read unless it turns out otherwise; this is a little
|
||||
// wasteful but works for now.
|
||||
@@ -271,27 +474,171 @@ class ConcreteMachine:
|
||||
default: break;
|
||||
|
||||
case 0xc000:
|
||||
if(string_serialiser_) {
|
||||
*value = string_serialiser_->head() | 0x80;
|
||||
} else {
|
||||
*value = keyboard_input_;
|
||||
*value = get_keyboard_input();
|
||||
break;
|
||||
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
|
||||
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
|
||||
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
|
||||
break;
|
||||
|
||||
case 0xc061: // Switch input 0.
|
||||
*value &= 0x7f;
|
||||
if(
|
||||
static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] ||
|
||||
(is_iie() && open_apple_is_pressed_)
|
||||
)
|
||||
*value |= 0x80;
|
||||
break;
|
||||
case 0xc062: // Switch input 1.
|
||||
*value &= 0x7f;
|
||||
if(
|
||||
static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] ||
|
||||
(is_iie() && closed_apple_is_pressed_)
|
||||
)
|
||||
*value |= 0x80;
|
||||
break;
|
||||
case 0xc063: // Switch input 2.
|
||||
*value &= 0x7f;
|
||||
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[2] || static_cast<Joystick *>(joysticks_[1].get())->buttons[0])
|
||||
*value |= 0x80;
|
||||
break;
|
||||
|
||||
case 0xc064: // Analogue input 0.
|
||||
case 0xc065: // Analogue input 1.
|
||||
case 0xc066: // Analogue input 2.
|
||||
case 0xc067: { // Analogue input 3.
|
||||
const size_t input = address - 0xc064;
|
||||
*value &= 0x7f;
|
||||
if(!analogue_channel_is_discharged(input)) {
|
||||
*value |= 0x80;
|
||||
}
|
||||
} break;
|
||||
|
||||
// The IIe-only state reads follow...
|
||||
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
|
||||
case 0xc011: IIeSwitchRead(language_card_.bank1); break;
|
||||
case 0xc012: IIeSwitchRead(language_card_.read); break;
|
||||
case 0xc013: IIeSwitchRead(read_auxiliary_memory_); break;
|
||||
case 0xc014: IIeSwitchRead(write_auxiliary_memory_); break;
|
||||
case 0xc015: IIeSwitchRead(internal_CX_rom_); break;
|
||||
case 0xc016: IIeSwitchRead(alternative_zero_page_); break;
|
||||
case 0xc017: IIeSwitchRead(slot_C3_rom_); break;
|
||||
case 0xc018: IIeSwitchRead(video_->get_80_store()); break;
|
||||
case 0xc019: IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_)); break;
|
||||
case 0xc01a: IIeSwitchRead(video_->get_text()); break;
|
||||
case 0xc01b: IIeSwitchRead(video_->get_mixed()); break;
|
||||
case 0xc01c: IIeSwitchRead(video_->get_page2()); break;
|
||||
case 0xc01d: IIeSwitchRead(video_->get_high_resolution()); break;
|
||||
case 0xc01e: IIeSwitchRead(video_->get_alternative_character_set()); break;
|
||||
case 0xc01f: IIeSwitchRead(video_->get_80_columns()); break;
|
||||
#undef IIeSwitchRead
|
||||
|
||||
case 0xc07f:
|
||||
if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Write-only switches.
|
||||
// Write-only switches. All IIe as currently implemented.
|
||||
if(is_iie()) {
|
||||
switch(address) {
|
||||
default: break;
|
||||
|
||||
case 0xc000:
|
||||
case 0xc001:
|
||||
update_video();
|
||||
video_->set_80_store(!!(address&1));
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc002:
|
||||
case 0xc003:
|
||||
read_auxiliary_memory_ = !!(address&1);
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc004:
|
||||
case 0xc005:
|
||||
write_auxiliary_memory_ = !!(address&1);
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc006:
|
||||
case 0xc007:
|
||||
internal_CX_rom_ = !!(address&1);
|
||||
set_card_paging();
|
||||
break;
|
||||
|
||||
case 0xc008:
|
||||
case 0xc009:
|
||||
// The alternative zero page setting affects both bank 0 and any RAM
|
||||
// that's paged as though it were on a language card.
|
||||
alternative_zero_page_ = !!(address&1);
|
||||
set_zero_page_paging();
|
||||
set_language_card_paging();
|
||||
break;
|
||||
|
||||
case 0xc00a:
|
||||
case 0xc00b:
|
||||
slot_C3_rom_ = !!(address&1);
|
||||
set_card_paging();
|
||||
break;
|
||||
|
||||
case 0xc00c:
|
||||
case 0xc00d:
|
||||
update_video();
|
||||
video_->set_80_columns(!!(address&1));
|
||||
break;
|
||||
|
||||
case 0xc00e:
|
||||
case 0xc00f:
|
||||
update_video();
|
||||
video_->set_alternative_character_set(!!(address&1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* Read-write switches. */
|
||||
case 0xc050: update_video(); video_->set_graphics_mode(); break;
|
||||
case 0xc051: update_video(); video_->set_text_mode(); break;
|
||||
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
|
||||
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
|
||||
case 0xc054: update_video(); video_->set_video_page(0); break;
|
||||
case 0xc055: update_video(); video_->set_video_page(1); break;
|
||||
case 0xc056: update_video(); video_->set_low_resolution(); break;
|
||||
case 0xc057: update_video(); video_->set_high_resolution(); break;
|
||||
case 0xc070: { // Permit analogue inputs that are currently discharged to begin a charge cycle.
|
||||
// Ensure those that were still charging retain that state.
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
if(analogue_channel_is_discharged(c)) {
|
||||
analogue_biases_[c] = 0.0f;
|
||||
} else {
|
||||
analogue_biases_[c] += analogue_charge_;
|
||||
}
|
||||
}
|
||||
analogue_charge_ = 0.0f;
|
||||
} break;
|
||||
|
||||
/* Switches triggered by reading or writing. */
|
||||
case 0xc050:
|
||||
case 0xc051:
|
||||
update_video();
|
||||
video_->set_text(!!(address&1));
|
||||
break;
|
||||
case 0xc052: update_video(); video_->set_mixed(false); break;
|
||||
case 0xc053: update_video(); video_->set_mixed(true); break;
|
||||
case 0xc054:
|
||||
case 0xc055:
|
||||
update_video();
|
||||
video_->set_page2(!!(address&1));
|
||||
set_main_paging();
|
||||
break;
|
||||
case 0xc056:
|
||||
case 0xc057:
|
||||
update_video();
|
||||
video_->set_high_resolution(!!(address&1));
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc05e:
|
||||
case 0xc05f:
|
||||
if(is_iie()) {
|
||||
update_video();
|
||||
video_->set_annunciator_3(!(address&1));
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc010:
|
||||
keyboard_input_ &= 0x7f;
|
||||
@@ -299,9 +646,15 @@ class ConcreteMachine:
|
||||
if(!string_serialiser_->advance())
|
||||
string_serialiser_.reset();
|
||||
}
|
||||
|
||||
// On the IIe, reading C010 returns additional key info.
|
||||
if(is_iie() && isReadOperation(operation)) {
|
||||
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc030:
|
||||
case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037:
|
||||
case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f:
|
||||
update_audio();
|
||||
audio_toggle_.set_output(!audio_toggle_.get_output());
|
||||
break;
|
||||
@@ -330,6 +683,7 @@ class ConcreteMachine:
|
||||
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
|
||||
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
|
||||
|
||||
// Apply whatever the net effect of all that is to the memory map.
|
||||
set_language_card_paging();
|
||||
break;
|
||||
}
|
||||
@@ -338,7 +692,7 @@ class ConcreteMachine:
|
||||
Communication with cards follows.
|
||||
*/
|
||||
|
||||
if(address >= 0xc090 && address < 0xc800) {
|
||||
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) {
|
||||
// If this is a card access, figure out which card is at play before determining
|
||||
// the totality of who needs messaging.
|
||||
size_t card_number = 0;
|
||||
@@ -363,7 +717,7 @@ class ConcreteMachine:
|
||||
// If the selected card is a just-in-time card, update the just-in-time cards,
|
||||
// and then message it specifically.
|
||||
const bool is_read = isReadOperation(operation);
|
||||
AppleII::Card *const target = cards_[card_number].get();
|
||||
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
|
||||
if(target && !is_every_cycle_card(target)) {
|
||||
update_just_in_time_cards();
|
||||
target->perform_bus_operation(select, is_read, address, value);
|
||||
@@ -390,6 +744,9 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
// Update analogue charge level.
|
||||
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
|
||||
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
@@ -400,49 +757,50 @@ class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
auto roms = roms_with_names(
|
||||
"AppleII",
|
||||
{
|
||||
"apple2o.rom",
|
||||
"apple2.rom",
|
||||
"apple2-character.rom"
|
||||
});
|
||||
|
||||
if(!roms[0] || !roms[1] || !roms[2]) return false;
|
||||
|
||||
apple2_rom_ = std::move(*roms[0]);
|
||||
apple2plus_rom_ = std::move(*roms[1]);
|
||||
|
||||
character_rom_ = std::move(*roms[2]);
|
||||
|
||||
rom_fetcher_ = roms_with_names;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void reset_all_keys() override {
|
||||
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
|
||||
}
|
||||
|
||||
void set_key_pressed(Key key, char value, bool is_pressed) override {
|
||||
if(key == Key::F12) {
|
||||
m6502_.set_reset_line(is_pressed);
|
||||
switch(key) {
|
||||
default: break;
|
||||
case Key::F12:
|
||||
m6502_.set_reset_line(is_pressed);
|
||||
return;
|
||||
case Key::LeftOption:
|
||||
open_apple_is_pressed_ = is_pressed;
|
||||
return;
|
||||
case Key::RightOption:
|
||||
closed_apple_is_pressed_ = is_pressed;
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_pressed) {
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
if(!value) {
|
||||
switch(key) {
|
||||
case Key::Left: value = 8; break;
|
||||
case Key::Right: value = 21; break;
|
||||
case Key::Down: value = 10; break;
|
||||
default: break;
|
||||
}
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
if(!value) {
|
||||
switch(key) {
|
||||
case Key::Left: value = 0x08; break;
|
||||
case Key::Right: value = 0x15; break;
|
||||
case Key::Down: value = 0x0a; break;
|
||||
case Key::Up: value = 0x0b; break;
|
||||
case Key::BackSpace: value = 0x7f; break;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80);
|
||||
// Prior to the IIe, the keyboard could produce uppercase only.
|
||||
if(!is_iie()) value = static_cast<char>(toupper(value));
|
||||
|
||||
if(is_pressed) {
|
||||
keyboard_input_ = static_cast<uint8_t>(value | 0x80);
|
||||
key_is_down_ = true;
|
||||
} else {
|
||||
if((keyboard_input_ & 0x7f) == value) {
|
||||
key_is_down_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,32 +812,11 @@ class ConcreteMachine:
|
||||
string_serialiser_.reset(new Utility::StringSerialiser(string, true));
|
||||
}
|
||||
|
||||
// MARK: ConfigurationTarget
|
||||
void configure_as_target(const Analyser::Static::Target *target) override {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
auto *const apple_target = dynamic_cast<const Target *>(target);
|
||||
|
||||
if(apple_target->disk_controller != Target::DiskController::None) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new AppleII::DiskIICard(rom_fetcher_, apple_target->disk_controller == Target::DiskController::SixteenSector));
|
||||
}
|
||||
|
||||
rom_ = (apple_target->model == Target::Model::II) ? apple2_rom_ : apple2plus_rom_;
|
||||
if(rom_.size() > 12*1024) {
|
||||
rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024);
|
||||
}
|
||||
|
||||
// Set up the default memory blocks.
|
||||
memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_;
|
||||
memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200];
|
||||
set_language_card_paging();
|
||||
|
||||
insert_media(apple_target->media);
|
||||
}
|
||||
|
||||
// MARK: MediaTarget
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(!media.disks.empty() && cards_[5]) {
|
||||
dynamic_cast<AppleII::DiskIICard *>(cards_[5].get())->set_disk(media.disks[0], 0);
|
||||
if(!media.disks.empty()) {
|
||||
auto diskii = diskii_card();
|
||||
if(diskii) diskii->set_disk(media.disks[0], 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -490,14 +827,27 @@ class ConcreteMachine:
|
||||
if(card) card->set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: JoystickMachine
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace AppleII;
|
||||
|
||||
Machine *Machine::AppleII() {
|
||||
return new ConcreteMachine;
|
||||
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
const Target *const appleii_target = dynamic_cast<const Target *>(target);
|
||||
switch(appleii_target->model) {
|
||||
default: return nullptr;
|
||||
case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher);
|
||||
case Target::Model::IIplus: return new ConcreteMachine<Target::Model::IIplus>(*appleii_target, rom_fetcher);
|
||||
case Target::Model::IIe: return new ConcreteMachine<Target::Model::IIe>(*appleii_target, rom_fetcher);
|
||||
case Target::Model::EnhancedIIe: return new ConcreteMachine<Target::Model::EnhancedIIe>(*appleii_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
#ifndef AppleII_hpp
|
||||
#define AppleII_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
class Machine {
|
||||
@@ -16,7 +23,7 @@ class Machine {
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an AppleII.
|
||||
static Machine *AppleII();
|
||||
static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
using namespace AppleII;
|
||||
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) {
|
||||
auto roms = rom_fetcher(
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
|
||||
const auto roms = rom_fetcher(
|
||||
"DiskII",
|
||||
{
|
||||
is_16_sector ? "boot-16.rom" : "boot-13.rom",
|
||||
@@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec
|
||||
boot_ = std::move(*roms[0]);
|
||||
diskii_.set_state_machine(*roms[1]);
|
||||
set_select_constraints(None);
|
||||
diskii_.set_sleep_observer(this);
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) {
|
||||
@@ -41,7 +41,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
|
||||
}
|
||||
|
||||
void DiskIICard::run_for(Cycles cycles, int stretches) {
|
||||
if(diskii_is_sleeping_) return;
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
|
||||
diskii_.run_for(Cycles(cycles.as_int() * 2));
|
||||
}
|
||||
|
||||
@@ -53,7 +53,11 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
|
||||
diskii_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
diskii_is_sleeping_ = is_sleeping;
|
||||
set_select_constraints(is_sleeping ? (IO | Device) : 0);
|
||||
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
diskii_clocking_preference_ = preference;
|
||||
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
|
||||
}
|
||||
|
||||
Storage::Disk::Drive &DiskIICard::get_drive(int drive) {
|
||||
return diskii_.get_drive(drive);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
class DiskIICard: public Card, public Sleeper::SleepObserver {
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
|
||||
|
||||
@@ -32,12 +32,13 @@ class DiskIICard: public Card, public Sleeper::SleepObserver {
|
||||
void set_activity_observer(Activity::Observer *observer) override;
|
||||
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
Storage::Disk::Drive &get_drive(int drive);
|
||||
|
||||
private:
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
std::vector<uint8_t> boot_;
|
||||
Apple::DiskII diskii_;
|
||||
bool diskii_is_sleeping_ = false;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,122 +10,330 @@
|
||||
|
||||
using namespace AppleII::Video;
|
||||
|
||||
namespace {
|
||||
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)),
|
||||
is_iie_(is_iie),
|
||||
deferrer_(std::move(target)) {
|
||||
|
||||
struct ScaledByteFiller {
|
||||
ScaledByteFiller() {
|
||||
VideoBase::setup_tables();
|
||||
}
|
||||
} throwaway;
|
||||
|
||||
}
|
||||
|
||||
VideoBase::VideoBase() :
|
||||
crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) {
|
||||
|
||||
// Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte.
|
||||
// Set a composite sampling function that assumes one byte per pixel input, and
|
||||
// accepts any non-zero value as being fully on, zero being fully off.
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue >>= int(icoordinate.x) % 7;"
|
||||
"return float(texValue & 1u);"
|
||||
"return clamp(texture(sampler, coordinate).r, 0.0, 0.7);"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(7.0f);
|
||||
|
||||
// Show only the centre 75% of the TV frame.
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f));
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f));
|
||||
crt_->set_immediate_default_phase(0.0f);
|
||||
|
||||
character_zones[0].xor_mask = 0;
|
||||
character_zones[0].address_mask = 0x3f;
|
||||
character_zones[1].xor_mask = 0;
|
||||
character_zones[1].address_mask = 0x3f;
|
||||
character_zones[2].xor_mask = 0;
|
||||
character_zones[2].address_mask = 0x3f;
|
||||
character_zones[3].xor_mask = 0;
|
||||
character_zones[3].address_mask = 0x3f;
|
||||
|
||||
if(is_iie) {
|
||||
character_zones[0].xor_mask =
|
||||
character_zones[2].xor_mask =
|
||||
character_zones[3].xor_mask = 0xff;
|
||||
character_zones[2].address_mask =
|
||||
character_zones[3].address_mask = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *VideoBase::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
uint16_t VideoBase::scaled_byte[256];
|
||||
uint16_t VideoBase::low_resolution_patterns[2][16];
|
||||
|
||||
void VideoBase::setup_tables() {
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const uint16_t value =
|
||||
((c & 0x01) ? 0x0003 : 0x0000) |
|
||||
((c & 0x02) ? 0x000c : 0x0000) |
|
||||
((c & 0x04) ? 0x0030 : 0x0000) |
|
||||
((c & 0x08) ? 0x0140 : 0x0000) |
|
||||
((c & 0x10) ? 0x0600 : 0x0000) |
|
||||
((c & 0x20) ? 0x1800 : 0x0000) |
|
||||
((c & 0x40) ? 0x6000 : 0x0000);
|
||||
|
||||
uint8_t *const table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
|
||||
table_entry[0] = static_cast<uint8_t>(value & 0xff);
|
||||
table_entry[1] = static_cast<uint8_t>(value >> 8);
|
||||
}
|
||||
for(int c = 128; c < 256; ++c) {
|
||||
uint8_t *const source_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c & 0x7f]);
|
||||
uint8_t *const destination_table_entry = reinterpret_cast<uint8_t *>(&scaled_byte[c]);
|
||||
|
||||
destination_table_entry[0] = static_cast<uint8_t>(source_table_entry[0] << 1);
|
||||
destination_table_entry[1] = static_cast<uint8_t>((source_table_entry[1] << 1) | (source_table_entry[0] >> 6));
|
||||
}
|
||||
|
||||
for(int c = 0; c < 16; ++c) {
|
||||
// Produce the whole 28-bit pattern that would cover two columns.
|
||||
const int reversed_c = ((c&0x1) ? 0x8 : 0x0) | ((c&0x2) ? 0x4 : 0x0) | ((c&0x4) ? 0x2 : 0x0) | ((c&0x8) ? 0x1 : 0x0);
|
||||
int pattern = 0;
|
||||
for(int l = 0; l < 7; ++l) {
|
||||
pattern <<= 4;
|
||||
pattern |= reversed_c;
|
||||
/*
|
||||
Rote setters and getters.
|
||||
*/
|
||||
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
|
||||
set_alternative_character_set_ = alternative_character_set;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
alternative_character_set_ = alternative_character_set;
|
||||
if(alternative_character_set) {
|
||||
character_zones[1].address_mask = 0xff;
|
||||
character_zones[1].xor_mask = 0;
|
||||
} else {
|
||||
character_zones[1].address_mask = 0x3f;
|
||||
character_zones[1].xor_mask = flash_mask();
|
||||
}
|
||||
|
||||
// Pack that 28-bit pattern into the appropriate look-up tables.
|
||||
uint8_t *const left_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[0][c]);
|
||||
uint8_t *const right_entry = reinterpret_cast<uint8_t *>(&low_resolution_patterns[1][c]);
|
||||
left_entry[0] = static_cast<uint8_t>(pattern);;
|
||||
left_entry[1] = static_cast<uint8_t>(pattern >> 7);
|
||||
right_entry[0] = static_cast<uint8_t>(pattern >> 14);
|
||||
right_entry[1] = static_cast<uint8_t>(pattern >> 21);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VideoBase::set_graphics_mode() {
|
||||
use_graphics_mode_ = true;
|
||||
bool VideoBase::get_alternative_character_set() {
|
||||
return set_alternative_character_set_;
|
||||
}
|
||||
|
||||
void VideoBase::set_text_mode() {
|
||||
use_graphics_mode_ = false;
|
||||
void VideoBase::set_80_columns(bool columns_80) {
|
||||
set_columns_80_ = columns_80;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
columns_80_ = columns_80;
|
||||
});
|
||||
}
|
||||
|
||||
void VideoBase::set_mixed_mode(bool mixed_mode) {
|
||||
mixed_mode_ = mixed_mode;
|
||||
bool VideoBase::get_80_columns() {
|
||||
return set_columns_80_;
|
||||
}
|
||||
|
||||
void VideoBase::set_video_page(int page) {
|
||||
video_page_ = page;
|
||||
void VideoBase::set_80_store(bool store_80) {
|
||||
set_store_80_ = store_80_ = store_80;
|
||||
}
|
||||
|
||||
void VideoBase::set_low_resolution() {
|
||||
graphics_mode_ = GraphicsMode::LowRes;
|
||||
bool VideoBase::get_80_store() {
|
||||
return set_store_80_;
|
||||
}
|
||||
|
||||
void VideoBase::set_high_resolution() {
|
||||
graphics_mode_ = GraphicsMode::HighRes;
|
||||
void VideoBase::set_page2(bool page2) {
|
||||
set_page2_ = page2_ = page2;
|
||||
}
|
||||
|
||||
bool VideoBase::get_page2() {
|
||||
return set_page2_;
|
||||
}
|
||||
|
||||
void VideoBase::set_text(bool text) {
|
||||
set_text_ = text;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
text_ = text;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_text() {
|
||||
return set_text_;
|
||||
}
|
||||
|
||||
void VideoBase::set_mixed(bool mixed) {
|
||||
set_mixed_ = mixed;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
mixed_ = mixed;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_mixed() {
|
||||
return set_mixed_;
|
||||
}
|
||||
|
||||
void VideoBase::set_high_resolution(bool high_resolution) {
|
||||
set_high_resolution_ = high_resolution;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
high_resolution_ = high_resolution;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_high_resolution() {
|
||||
return set_high_resolution_;
|
||||
}
|
||||
|
||||
void VideoBase::set_annunciator_3(bool annunciator_3) {
|
||||
set_annunciator_3_ = annunciator_3;
|
||||
deferrer_.defer(Cycles(2), [=] {
|
||||
annunciator_3_ = annunciator_3;
|
||||
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
|
||||
});
|
||||
}
|
||||
|
||||
bool VideoBase::get_annunciator_3() {
|
||||
return set_annunciator_3_;
|
||||
}
|
||||
|
||||
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
|
||||
character_rom_ = character_rom;
|
||||
|
||||
// Bytes in the character ROM are stored in reverse bit order. Reverse them
|
||||
// ahead of time so as to be able to use the same scaling table as for
|
||||
// high-resolution graphics.
|
||||
for(auto &byte : character_rom_) {
|
||||
byte =
|
||||
((byte & 0x40) ? 0x01 : 0x00) |
|
||||
((byte & 0x20) ? 0x02 : 0x00) |
|
||||
((byte & 0x10) ? 0x04 : 0x00) |
|
||||
((byte & 0x08) ? 0x08 : 0x00) |
|
||||
((byte & 0x04) ? 0x10 : 0x00) |
|
||||
((byte & 0x02) ? 0x20 : 0x00) |
|
||||
((byte & 0x01) ? 0x40 : 0x00) |
|
||||
(byte & 0x80);
|
||||
// Flip all character contents based on the second line of the $ graphic.
|
||||
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
|
||||
for(auto &graphic : character_rom_) {
|
||||
graphic =
|
||||
((graphic & 0x01) ? 0x40 : 0x00) |
|
||||
((graphic & 0x02) ? 0x20 : 0x00) |
|
||||
((graphic & 0x04) ? 0x10 : 0x00) |
|
||||
((graphic & 0x08) ? 0x08 : 0x00) |
|
||||
((graphic & 0x10) ? 0x04 : 0x00) |
|
||||
((graphic & 0x20) ? 0x02 : 0x00) |
|
||||
((graphic & 0x40) ? 0x01 : 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
const int character = source[c] & character_zones[source[c] >> 6].address_mask;
|
||||
const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask;
|
||||
const std::size_t character_address = static_cast<std::size_t>(character << 3) + pixel_row;
|
||||
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
|
||||
|
||||
// The character ROM is output MSB to LSB rather than LSB to MSB.
|
||||
target[0] = target[1] = character_pattern & 0x40;
|
||||
target[2] = target[3] = character_pattern & 0x20;
|
||||
target[4] = target[5] = character_pattern & 0x10;
|
||||
target[6] = target[7] = character_pattern & 0x08;
|
||||
target[8] = target[9] = character_pattern & 0x04;
|
||||
target[10] = target[11] = character_pattern & 0x02;
|
||||
target[12] = target[13] = character_pattern & 0x01;
|
||||
graphics_carry_ = character_pattern & 0x01;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, size_t pixel_row) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
const std::size_t character_addresses[2] = {
|
||||
static_cast<std::size_t>(
|
||||
(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3
|
||||
) + pixel_row,
|
||||
static_cast<std::size_t>(
|
||||
(source[c] & character_zones[source[c] >> 6].address_mask) << 3
|
||||
) + pixel_row
|
||||
};
|
||||
|
||||
const uint8_t character_patterns[2] = {
|
||||
static_cast<uint8_t>(
|
||||
character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask
|
||||
),
|
||||
static_cast<uint8_t>(
|
||||
character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask
|
||||
)
|
||||
};
|
||||
|
||||
// The character ROM is output MSB to LSB rather than LSB to MSB.
|
||||
target[0] = character_patterns[0] & 0x40;
|
||||
target[1] = character_patterns[0] & 0x20;
|
||||
target[2] = character_patterns[0] & 0x10;
|
||||
target[3] = character_patterns[0] & 0x08;
|
||||
target[4] = character_patterns[0] & 0x04;
|
||||
target[5] = character_patterns[0] & 0x02;
|
||||
target[6] = character_patterns[0] & 0x01;
|
||||
target[7] = character_patterns[1] & 0x40;
|
||||
target[8] = character_patterns[1] & 0x20;
|
||||
target[9] = character_patterns[1] & 0x10;
|
||||
target[10] = character_patterns[1] & 0x08;
|
||||
target[11] = character_patterns[1] & 0x04;
|
||||
target[12] = character_patterns[1] & 0x02;
|
||||
target[13] = character_patterns[1] & 0x01;
|
||||
graphics_carry_ = character_patterns[1] & 0x01;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
|
||||
const int row_shift = row&4;
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
|
||||
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
|
||||
if((column + static_cast<int>(c))&1) {
|
||||
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4;
|
||||
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8;
|
||||
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1;
|
||||
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 8;
|
||||
} else {
|
||||
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1;
|
||||
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2;
|
||||
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4;
|
||||
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 2;
|
||||
}
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_fat_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
|
||||
const int row_shift = row&4;
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
// Fat low-resolution mode appears not to do anything to try to make odd and
|
||||
// even columns compatible.
|
||||
target[0] = target[1] = target[8] = target[9] = (source[c] >> row_shift) & 1;
|
||||
target[2] = target[3] = target[10] = target[11] = (source[c] >> row_shift) & 2;
|
||||
target[4] = target[5] = target[12] = target[13] = (source[c] >> row_shift) & 4;
|
||||
target[6] = target[7] = (source[c] >> row_shift) & 8;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 4;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, int column, int row) const {
|
||||
const int row_shift = row&4;
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
if((column + static_cast<int>(c))&1) {
|
||||
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2;
|
||||
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4;
|
||||
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8;
|
||||
target[3] = (auxiliary_source[c] >> row_shift) & 1;
|
||||
|
||||
target[8] = target[12] = (source[c] >> row_shift) & 4;
|
||||
target[9] = target[13] = (source[c] >> row_shift) & 8;
|
||||
target[10] = (source[c] >> row_shift) & 1;
|
||||
target[7] = target[11] = (source[c] >> row_shift) & 2;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 8;
|
||||
} else {
|
||||
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8;
|
||||
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1;
|
||||
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2;
|
||||
target[3] = (auxiliary_source[c] >> row_shift) & 4;
|
||||
|
||||
target[8] = target[12] = (source[c] >> row_shift) & 1;
|
||||
target[9] = target[13] = (source[c] >> row_shift) & 2;
|
||||
target[10] = (source[c] >> row_shift) & 4;
|
||||
target[7] = target[11] = (source[c] >> row_shift) & 8;
|
||||
graphics_carry_ = (source[c] >> row_shift) & 2;
|
||||
}
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_high_resolution(uint8_t *target, const uint8_t *const source, size_t length) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
|
||||
// If there is a delay, the previous output level is held to bridge the gap.
|
||||
// Delays may be ignored on a IIe if Annunciator 3 is set; that's the state that
|
||||
// high_resolution_mask_ models.
|
||||
if(source[c] & high_resolution_mask_ & 0x80) {
|
||||
target[0] = graphics_carry_;
|
||||
target[1] = target[2] = source[c] & 0x01;
|
||||
target[3] = target[4] = source[c] & 0x02;
|
||||
target[5] = target[6] = source[c] & 0x04;
|
||||
target[7] = target[8] = source[c] & 0x08;
|
||||
target[9] = target[10] = source[c] & 0x10;
|
||||
target[11] = target[12] = source[c] & 0x20;
|
||||
target[13] = source[c] & 0x40;
|
||||
} else {
|
||||
target[0] = target[1] = source[c] & 0x01;
|
||||
target[2] = target[3] = source[c] & 0x02;
|
||||
target[4] = target[5] = source[c] & 0x04;
|
||||
target[6] = target[7] = source[c] & 0x08;
|
||||
target[8] = target[9] = source[c] & 0x10;
|
||||
target[10] = target[11] = source[c] & 0x20;
|
||||
target[12] = target[13] = source[c] & 0x40;
|
||||
}
|
||||
graphics_carry_ = source[c] & 0x40;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBase::output_double_high_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length) const {
|
||||
for(size_t c = 0; c < length; ++c) {
|
||||
target[0] = auxiliary_source[c] & 0x01;
|
||||
target[1] = auxiliary_source[c] & 0x02;
|
||||
target[2] = auxiliary_source[c] & 0x04;
|
||||
target[3] = auxiliary_source[c] & 0x08;
|
||||
target[4] = auxiliary_source[c] & 0x10;
|
||||
target[5] = auxiliary_source[c] & 0x20;
|
||||
target[6] = auxiliary_source[c] & 0x40;
|
||||
target[7] = source[c] & 0x01;
|
||||
target[8] = source[c] & 0x02;
|
||||
target[9] = source[c] & 0x04;
|
||||
target[10] = source[c] & 0x08;
|
||||
target[11] = source[c] & 0x10;
|
||||
target[12] = source[c] & 0x20;
|
||||
target[13] = source[c] & 0x40;
|
||||
|
||||
graphics_carry_ = auxiliary_source[c] & 0x40;
|
||||
target += 14;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockDeferrer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
@@ -19,26 +21,126 @@ namespace Video {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
uint8_t perform_read(uint16_t address) {
|
||||
return 0xff;
|
||||
/*!
|
||||
Requests fetching of the @c count bytes starting from @c address.
|
||||
|
||||
The handler should write the values from base memory to @c base_target, and those
|
||||
from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory,
|
||||
it needn't write anything to auxiliary_target.
|
||||
*/
|
||||
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
|
||||
}
|
||||
};
|
||||
|
||||
class VideoBase {
|
||||
public:
|
||||
VideoBase();
|
||||
static void setup_tables();
|
||||
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
|
||||
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
// Inputs for the various soft switches.
|
||||
void set_graphics_mode();
|
||||
void set_text_mode();
|
||||
void set_mixed_mode(bool);
|
||||
void set_video_page(int);
|
||||
void set_low_resolution();
|
||||
void set_high_resolution();
|
||||
/*
|
||||
Descriptions for the setters below are taken verbatim from
|
||||
the Apple IIe Technical Reference. Addresses are the conventional
|
||||
locations within the Apple II memory map. Only those which affect
|
||||
video output are implemented here.
|
||||
|
||||
Those registers which don't exist on a II/II+ are marked.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
|
||||
|
||||
* Off: display text using primary character set.
|
||||
* On: display text using alternate character set.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_alternative_character_set(bool);
|
||||
bool get_alternative_character_set();
|
||||
|
||||
/*!
|
||||
Setter for 80COL ($C00C/$C00D; triggers on write only).
|
||||
|
||||
* Off: display 40 columns.
|
||||
* On: display 80 columns.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_columns(bool);
|
||||
bool get_80_columns();
|
||||
|
||||
/*!
|
||||
Setter for 80STORE ($C000/$C001; triggers on write only).
|
||||
|
||||
* Off: cause PAGE2 to select auxiliary RAM.
|
||||
* On: cause PAGE2 to switch main RAM areas.
|
||||
|
||||
Doesn't exist on a II/II+.
|
||||
*/
|
||||
void set_80_store(bool);
|
||||
bool get_80_store();
|
||||
|
||||
/*!
|
||||
Setter for PAGE2 ($C054/$C055; triggers on read or write).
|
||||
|
||||
* Off: select Page 1.
|
||||
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
|
||||
|
||||
80STORE doesn't exist on a II/II+; therefore this always selects
|
||||
either Page 1 or Page 2 on those machines.
|
||||
*/
|
||||
void set_page2(bool);
|
||||
bool get_page2();
|
||||
|
||||
/*!
|
||||
Setter for TEXT ($C050/$C051; triggers on read or write).
|
||||
|
||||
* Off: display graphics or, if MIXED on, mixed.
|
||||
* On: display text.
|
||||
*/
|
||||
void set_text(bool);
|
||||
bool get_text();
|
||||
|
||||
/*!
|
||||
Setter for MIXED ($C052/$C053; triggers on read or write).
|
||||
|
||||
* Off: display only text or only graphics.
|
||||
* On: if TEXT off, display text and graphics.
|
||||
*/
|
||||
void set_mixed(bool);
|
||||
bool get_mixed();
|
||||
|
||||
/*!
|
||||
Setter for HIRES ($C056/$C057; triggers on read or write).
|
||||
|
||||
* Off: if TEXT off, display low-resolution graphics.
|
||||
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
|
||||
|
||||
DHIRES doesn't exist on a II/II+; therefore this always selects
|
||||
either high- or low-resolution graphics on those machines.
|
||||
|
||||
Despite Apple's documentation, the IIe also supports double low-resolution
|
||||
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
|
||||
low-resolution graphics.
|
||||
*/
|
||||
void set_high_resolution(bool);
|
||||
bool get_high_resolution();
|
||||
|
||||
/*!
|
||||
Setter for annunciator 3.
|
||||
|
||||
* On: turn on annunciator 3.
|
||||
* Off: turn off annunciator 3.
|
||||
|
||||
This exists on both the II/II+ and the IIe, but has no effect on
|
||||
video on the older machines. It's intended to be used on the IIe
|
||||
to confirm double-high resolution mode but has side effects in
|
||||
selecting mixed mode output and discarding high-resolution
|
||||
delay bits.
|
||||
*/
|
||||
void set_annunciator_3(bool);
|
||||
bool get_annunciator_3();
|
||||
|
||||
// Setup for text mode.
|
||||
void set_character_rom(const std::vector<uint8_t> &);
|
||||
@@ -46,167 +148,121 @@ class VideoBase {
|
||||
protected:
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
int video_page_ = 0;
|
||||
// State affecting output video stream generation.
|
||||
uint8_t *pixel_pointer_ = nullptr;
|
||||
|
||||
// State affecting logical state.
|
||||
int row_ = 0, column_ = 0, flash_ = 0;
|
||||
uint16_t *pixel_pointer_ = nullptr;
|
||||
uint8_t flash_mask() {
|
||||
return static_cast<uint8_t>((flash_ / flash_length) * 0xff);
|
||||
}
|
||||
|
||||
// Enumerates all Apple II and IIe display modes.
|
||||
enum class GraphicsMode {
|
||||
Text = 0,
|
||||
DoubleText,
|
||||
HighRes,
|
||||
DoubleHighRes,
|
||||
LowRes,
|
||||
DoubleLowRes,
|
||||
FatLowRes
|
||||
};
|
||||
bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; }
|
||||
bool is_double_mode(GraphicsMode m) { return !!(static_cast<int>(m)&1); }
|
||||
|
||||
// Various soft-switch values.
|
||||
bool alternative_character_set_ = false, set_alternative_character_set_ = false;
|
||||
bool columns_80_ = false, set_columns_80_ = false;
|
||||
bool store_80_ = false, set_store_80_ = false;
|
||||
bool page2_ = false, set_page2_ = false;
|
||||
bool text_ = true, set_text_ = true;
|
||||
bool mixed_ = false, set_mixed_ = false;
|
||||
bool high_resolution_ = false, set_high_resolution_ = false;
|
||||
bool annunciator_3_ = false, set_annunciator_3_ = false;
|
||||
|
||||
// Graphics carry is the final level output in a fetch window;
|
||||
// it carries on into the next if it's high resolution with
|
||||
// the delay bit set.
|
||||
mutable uint8_t graphics_carry_ = 0;
|
||||
bool was_double_ = false;
|
||||
uint8_t high_resolution_mask_ = 0xff;
|
||||
|
||||
// This holds a copy of the character ROM. The regular character
|
||||
// set is assumed to be in the first 64*8 bytes; the alternative
|
||||
// is in the 128*8 bytes after that.
|
||||
std::vector<uint8_t> character_rom_;
|
||||
|
||||
enum class GraphicsMode {
|
||||
LowRes,
|
||||
HighRes,
|
||||
Text
|
||||
} graphics_mode_ = GraphicsMode::LowRes;
|
||||
bool use_graphics_mode_ = false;
|
||||
bool mixed_mode_ = false;
|
||||
uint16_t graphics_carry_ = 0;
|
||||
// Memory is fetched ahead of time into this array;
|
||||
// this permits the correct delay between fetching
|
||||
// without having to worry about a rolling buffer.
|
||||
std::array<uint8_t, 40> base_stream_;
|
||||
std::array<uint8_t, 40> auxiliary_stream_;
|
||||
|
||||
static uint16_t scaled_byte[256];
|
||||
static uint16_t low_resolution_patterns[2][16];
|
||||
bool is_iie_ = false;
|
||||
static const int flash_length = 8406;
|
||||
|
||||
// Describes the current text mode mapping from in-memory character index
|
||||
// to output character.
|
||||
struct CharacterMapping {
|
||||
uint8_t address_mask;
|
||||
uint8_t xor_mask;
|
||||
};
|
||||
CharacterMapping character_zones[4];
|
||||
|
||||
/*!
|
||||
Outputs 40-column text to @c target, using @c length bytes from @c source.
|
||||
*/
|
||||
void output_text(uint8_t *target, const uint8_t *source, size_t length, size_t pixel_row) const;
|
||||
|
||||
/*!
|
||||
Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source.
|
||||
*/
|
||||
void output_double_text(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, size_t pixel_row) const;
|
||||
|
||||
/*!
|
||||
Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source.
|
||||
*/
|
||||
void output_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
|
||||
|
||||
/*!
|
||||
Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source.
|
||||
*/
|
||||
void output_double_low_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, int column, int row) const;
|
||||
|
||||
/*!
|
||||
Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source.
|
||||
*/
|
||||
void output_high_resolution(uint8_t *target, const uint8_t *source, size_t length) const;
|
||||
|
||||
/*!
|
||||
Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source.
|
||||
*/
|
||||
void output_double_high_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length) const;
|
||||
|
||||
/*!
|
||||
Outputs 40-column "fat low resolution" graphics to @c target, drawing @c length columns from @c source.
|
||||
|
||||
Fat low-resolution mode is like regular low-resolution mode except that data is shifted out on the 7M
|
||||
clock rather than the 14M.
|
||||
*/
|
||||
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
|
||||
|
||||
// Maintain a ClockDeferrer for delayed mode switches.
|
||||
ClockDeferrer<Cycles> deferrer_;
|
||||
};
|
||||
|
||||
template <class BusHandler> class Video: public VideoBase {
|
||||
template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
public:
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video(BusHandler &bus_handler) :
|
||||
VideoBase(),
|
||||
VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }),
|
||||
bus_handler_(bus_handler) {}
|
||||
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of every
|
||||
line.
|
||||
Runs video for @c cycles.
|
||||
*/
|
||||
void run_for(const Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
const int first_sync_line = 220; // A complete guess. Information needed.
|
||||
const int first_sync_column = 49; // Also a guess.
|
||||
|
||||
int int_cycles = cycles.as_int();
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
|
||||
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
|
||||
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 7);
|
||||
} else {
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
|
||||
|
||||
// The first 40 columns are submitted to the CRT only upon completion;
|
||||
// they'll be either graphics or blank, depending on which side we are
|
||||
// of line 192.
|
||||
if(column_ < 40) {
|
||||
if(row_ < 192) {
|
||||
if(!column_) {
|
||||
pixel_pointer_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(80, 2));
|
||||
graphics_carry_ = 0;
|
||||
}
|
||||
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int character_row = row_ >> 3;
|
||||
const int pixel_row = row_ & 7;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
|
||||
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
|
||||
const int row_shift = (row_&4);
|
||||
|
||||
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
|
||||
switch(pixel_mode) {
|
||||
case GraphicsMode::Text: {
|
||||
const uint8_t inverses[] = {
|
||||
0xff,
|
||||
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
|
||||
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
|
||||
|
||||
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
|
||||
pixel_pointer_[c] = scaled_byte[character_pattern & 0x7f];
|
||||
}
|
||||
} break;
|
||||
|
||||
case GraphicsMode::LowRes:
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
|
||||
pixel_pointer_[c] = low_resolution_patterns[c&1][(character >> row_shift)&0xf];
|
||||
}
|
||||
break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
for(int c = column_; c < pixel_end; ++c) {
|
||||
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
|
||||
pixel_pointer_[c] = scaled_byte[graphic];
|
||||
if(graphic & 0x80) {
|
||||
reinterpret_cast<uint8_t *>(&pixel_pointer_[c])[0] |= graphics_carry_;
|
||||
}
|
||||
graphics_carry_ = (graphic >> 6) & 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(ending_column >= 40) {
|
||||
crt_->output_data(280, 80);
|
||||
}
|
||||
} else {
|
||||
if(ending_column >= 40) {
|
||||
crt_->output_blank(280);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
const int first_blank_start = std::max(40, column_);
|
||||
const int first_blank_end = std::min(first_sync_column, ending_column);
|
||||
if(first_blank_end > first_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 7);
|
||||
}
|
||||
|
||||
const int sync_start = std::max(first_sync_column, column_);
|
||||
const int sync_end = std::min(first_sync_column + 4, ending_column);
|
||||
if(sync_end > sync_start) {
|
||||
crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 7);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
|
||||
const int colour_burst_start = std::max(first_sync_column + 4, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + 7, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 7);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + 7, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + 4, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 7);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised.
|
||||
crt_->output_blank(1);
|
||||
}
|
||||
}
|
||||
void run_for(Cycles cycles) {
|
||||
deferrer_.run_for(cycles);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -244,22 +300,294 @@ template <class BusHandler> class Video: public VideoBase {
|
||||
|
||||
// Calculate the address and return the value.
|
||||
uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25);
|
||||
return bus_handler_.perform_read(read_address);
|
||||
uint8_t value, aux_value;
|
||||
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
|
||||
*/
|
||||
bool get_is_vertical_blank(Cycles offset) {
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
int mapped_column = column_ + offset.as_int();
|
||||
|
||||
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
|
||||
// (so what was column 0 is now column 25).
|
||||
mapped_column += 25;
|
||||
|
||||
// Apply carry into the row counter and test it for location.
|
||||
int mapped_row = row_ + (mapped_column / 65);
|
||||
return (mapped_row % 262) >= 192;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
Advances time by @c cycles; expects to be fed by the CPU clock.
|
||||
Implicitly adds an extra half a colour clock at the end of
|
||||
line.
|
||||
*/
|
||||
void advance(Cycles cycles) {
|
||||
/*
|
||||
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
|
||||
row 0 is the first row with pixels in it.
|
||||
|
||||
A frame is oriented around 65 cycles across, 262 lines down.
|
||||
*/
|
||||
static const int first_sync_line = 220; // A complete guess. Information needed.
|
||||
static const int first_sync_column = 49; // Also a guess.
|
||||
static const int sync_length = 4; // One of the two likely candidates.
|
||||
|
||||
int int_cycles = cycles.as_int();
|
||||
while(int_cycles) {
|
||||
const int cycles_this_line = std::min(65 - column_, int_cycles);
|
||||
const int ending_column = column_ + cycles_this_line;
|
||||
|
||||
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
|
||||
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
|
||||
// pulses (and hencce keep hsync approximately where it should be during vsync).
|
||||
const int blank_start = std::max(first_sync_column - sync_length, column_);
|
||||
const int blank_end = std::min(first_sync_column, ending_column);
|
||||
if(blank_end > blank_start) {
|
||||
if(blank_start > column_) {
|
||||
crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
|
||||
}
|
||||
crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
|
||||
if(blank_end < ending_column) {
|
||||
crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
|
||||
}
|
||||
} else {
|
||||
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
|
||||
}
|
||||
} else {
|
||||
const GraphicsMode line_mode = graphics_mode(row_);
|
||||
|
||||
// Determine whether there's any fetching to do. Fetching occurs during the first
|
||||
// 40 columns of rows prior to 192.
|
||||
if(row_ < 192 && column_ < 40) {
|
||||
const int character_row = row_ >> 3;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
// Grab the memory contents that'll be needed momentarily.
|
||||
const int fetch_end = std::min(40, ending_column);
|
||||
uint16_t fetch_address;
|
||||
switch(line_mode) {
|
||||
default:
|
||||
case GraphicsMode::Text:
|
||||
case GraphicsMode::DoubleText:
|
||||
case GraphicsMode::LowRes:
|
||||
case GraphicsMode::FatLowRes:
|
||||
case GraphicsMode::DoubleLowRes: {
|
||||
const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
|
||||
fetch_address = static_cast<uint16_t>(text_address + column_);
|
||||
} break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
fetch_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
|
||||
break;
|
||||
}
|
||||
|
||||
bus_handler_.perform_read(
|
||||
fetch_address,
|
||||
static_cast<size_t>(fetch_end - column_),
|
||||
&base_stream_[static_cast<size_t>(column_)],
|
||||
&auxiliary_stream_[static_cast<size_t>(column_)]);
|
||||
// TODO: should character modes be mapped to character pixel outputs here?
|
||||
}
|
||||
|
||||
if(row_ < 192) {
|
||||
// The pixel area is the first 40.5 columns; base contents
|
||||
// remain where they would naturally be but auxiliary
|
||||
// graphics appear to the left of that.
|
||||
if(!column_) {
|
||||
pixel_pointer_ = crt_->allocate_write_area(568);
|
||||
graphics_carry_ = 0;
|
||||
was_double_ = true;
|
||||
}
|
||||
|
||||
if(column_ < 40) {
|
||||
const int pixel_start = std::max(0, column_);
|
||||
const int pixel_end = std::min(40, ending_column);
|
||||
const int pixel_row = row_ & 7;
|
||||
|
||||
const bool is_double = Video::is_double_mode(line_mode);
|
||||
if(!is_double && was_double_) {
|
||||
pixel_pointer_[pixel_start*14 + 0] =
|
||||
pixel_pointer_[pixel_start*14 + 1] =
|
||||
pixel_pointer_[pixel_start*14 + 2] =
|
||||
pixel_pointer_[pixel_start*14 + 3] =
|
||||
pixel_pointer_[pixel_start*14 + 4] =
|
||||
pixel_pointer_[pixel_start*14 + 5] =
|
||||
pixel_pointer_[pixel_start*14 + 6] = 0;
|
||||
}
|
||||
was_double_ = is_double;
|
||||
|
||||
switch(line_mode) {
|
||||
case GraphicsMode::Text:
|
||||
output_text(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
static_cast<size_t>(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleText:
|
||||
output_double_text(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
static_cast<size_t>(pixel_row));
|
||||
break;
|
||||
|
||||
case GraphicsMode::LowRes:
|
||||
output_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::FatLowRes:
|
||||
output_fat_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleLowRes:
|
||||
output_double_low_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start),
|
||||
pixel_start,
|
||||
pixel_row);
|
||||
break;
|
||||
|
||||
case GraphicsMode::HighRes:
|
||||
output_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14 + 7],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
case GraphicsMode::DoubleHighRes:
|
||||
output_double_high_resolution(
|
||||
&pixel_pointer_[pixel_start * 14],
|
||||
&base_stream_[static_cast<size_t>(pixel_start)],
|
||||
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
|
||||
static_cast<size_t>(pixel_end - pixel_start));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if(pixel_end == 40) {
|
||||
if(was_double_) {
|
||||
pixel_pointer_[563] =
|
||||
pixel_pointer_[564] =
|
||||
pixel_pointer_[565] =
|
||||
pixel_pointer_[566] =
|
||||
pixel_pointer_[567] = 0;
|
||||
} else {
|
||||
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
|
||||
pixel_pointer_[567] = graphics_carry_;
|
||||
else
|
||||
pixel_pointer_[567] = 0;
|
||||
}
|
||||
|
||||
crt_->output_data(568, 568);
|
||||
pixel_pointer_ = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(column_ < 40 && ending_column >= 40) {
|
||||
crt_->output_blank(568);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The left border, sync, right border pattern doesn't depend on whether
|
||||
there were pixels this row and is output as soon as it is known.
|
||||
*/
|
||||
|
||||
if(column_ < first_sync_column && ending_column >= first_sync_column) {
|
||||
crt_->output_blank((first_sync_column - 41)*14 - 1);
|
||||
}
|
||||
|
||||
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
|
||||
crt_->output_sync(sync_length*14);
|
||||
}
|
||||
|
||||
int second_blank_start;
|
||||
if(!is_text_mode(graphics_mode(row_+1))) {
|
||||
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
||||
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
||||
if(colour_burst_end > colour_burst_start) {
|
||||
crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192);
|
||||
}
|
||||
|
||||
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
|
||||
} else {
|
||||
second_blank_start = std::max(first_sync_column + sync_length, column_);
|
||||
}
|
||||
|
||||
if(ending_column > second_blank_start) {
|
||||
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
|
||||
}
|
||||
}
|
||||
|
||||
int_cycles -= cycles_this_line;
|
||||
column_ = (column_ + cycles_this_line) % 65;
|
||||
if(!column_) {
|
||||
row_ = (row_ + 1) % 262;
|
||||
flash_ = (flash_ + 1) % (2 * flash_length);
|
||||
if(!alternative_character_set_) {
|
||||
character_zones[1].xor_mask = flash_mask();
|
||||
}
|
||||
|
||||
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
|
||||
// count explicitly but is promised.
|
||||
crt_->output_blank(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsMode graphics_mode(int row) {
|
||||
if(
|
||||
text_ ||
|
||||
(mixed_ && row >= 160 && row < 192)
|
||||
) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
|
||||
if(high_resolution_) {
|
||||
return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
|
||||
} else {
|
||||
if(columns_80_) return GraphicsMode::DoubleLowRes;
|
||||
if(annunciator_3_) return GraphicsMode::FatLowRes;
|
||||
return GraphicsMode::LowRes;
|
||||
}
|
||||
}
|
||||
|
||||
int video_page() {
|
||||
return (store_80_ || !page2_) ? 0 : 1;
|
||||
}
|
||||
|
||||
uint16_t get_row_address(int row) {
|
||||
const int character_row = row >> 3;
|
||||
const int pixel_row = row & 7;
|
||||
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
|
||||
|
||||
GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text;
|
||||
return (pixel_mode == GraphicsMode::HighRes) ?
|
||||
static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
|
||||
static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
|
||||
const GraphicsMode pixel_mode = graphics_mode(row);
|
||||
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
|
||||
static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
|
||||
static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
|
||||
}
|
||||
|
||||
const int flash_length = 8406;
|
||||
BusHandler &bus_handler_;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
|
||||
@@ -37,30 +36,27 @@ namespace {
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
class Joystick: public Inputs::Joystick {
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire)
|
||||
}),
|
||||
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
|
||||
|
||||
std::vector<DigitalInput> get_inputs() override {
|
||||
return {
|
||||
DigitalInput(DigitalInput::Up),
|
||||
DigitalInput(DigitalInput::Down),
|
||||
DigitalInput(DigitalInput::Left),
|
||||
DigitalInput(DigitalInput::Right),
|
||||
DigitalInput(DigitalInput::Fire)
|
||||
};
|
||||
}
|
||||
|
||||
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
switch(digital_input.type) {
|
||||
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
|
||||
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
|
||||
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
|
||||
case DigitalInput::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
|
||||
case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
|
||||
case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
|
||||
case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
|
||||
case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
|
||||
|
||||
// TODO: latching
|
||||
case DigitalInput::Fire:
|
||||
case Input::Fire:
|
||||
if(is_active)
|
||||
bus_->tia_input_value_[fire_tia_input_] &= ~0x80;
|
||||
else
|
||||
@@ -79,24 +75,16 @@ class Joystick: public Inputs::Joystick {
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public JoystickMachine::Machine,
|
||||
public Outputs::CRT::Delegate {
|
||||
public:
|
||||
ConcreteMachine() {
|
||||
ConcreteMachine(const Analyser::Static::Atari::Target &target) {
|
||||
set_clock_rate(NTSC_clock_rate);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
close_output();
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target *target) override {
|
||||
auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target);
|
||||
const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data;
|
||||
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
|
||||
|
||||
using PagingModel = Analyser::Static::Atari::Target::PagingModel;
|
||||
switch(atari_target->paging_model) {
|
||||
switch(target.paging_model) {
|
||||
case PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
|
||||
case PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
|
||||
case PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
|
||||
@@ -108,21 +96,21 @@ class ConcreteMachine:
|
||||
case PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
|
||||
|
||||
case PagingModel::Atari8k:
|
||||
if(atari_target->uses_superchip) {
|
||||
if(target.uses_superchip) {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
|
||||
}
|
||||
break;
|
||||
case PagingModel::Atari16k:
|
||||
if(atari_target->uses_superchip) {
|
||||
if(target.uses_superchip) {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
|
||||
}
|
||||
break;
|
||||
case PagingModel::Atari32k:
|
||||
if(atari_target->uses_superchip) {
|
||||
if(target.uses_superchip) {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
|
||||
} else {
|
||||
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
|
||||
@@ -134,8 +122,8 @@ class ConcreteMachine:
|
||||
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
return false;
|
||||
~ConcreteMachine() {
|
||||
close_output();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
@@ -257,8 +245,10 @@ class ConcreteMachine:
|
||||
|
||||
using namespace Atari2600;
|
||||
|
||||
Machine *Machine::Atari2600() {
|
||||
return new Atari2600::ConcreteMachine;
|
||||
Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Atari::Target;
|
||||
const Target *const atari_target = dynamic_cast<const Target *>(target);
|
||||
return new Atari2600::ConcreteMachine(*atari_target);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#ifndef Atari2600_cpp
|
||||
#define Atari2600_cpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
#include "Atari2600Inputs.h"
|
||||
|
||||
namespace Atari2600 {
|
||||
@@ -21,7 +25,7 @@ class Machine {
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Atari 2600 on the heap.
|
||||
static Machine *Atari2600();
|
||||
static Machine *Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
|
||||
/// Sets the switch @c input to @c state.
|
||||
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
|
||||
|
||||
@@ -204,7 +204,7 @@ template<class T> class Cartridge:
|
||||
}
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
|
||||
std::vector<uint8_t> rom_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -124,19 +124,19 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
|
||||
|
||||
if(output_mode == OutputMode::NTSC) {
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
"uint iPhase = (c >> 4);"
|
||||
|
||||
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
|
||||
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset));"
|
||||
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));"
|
||||
"}");
|
||||
display_type = Outputs::CRT::DisplayType::NTSC60;
|
||||
} else {
|
||||
crt_->set_svideo_sampling_function(
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
|
||||
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint c = texture(texID, coordinate).r;"
|
||||
"uint y = c & 14u;"
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace CRTMachine {
|
||||
that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate
|
||||
should that clock rate change.
|
||||
*/
|
||||
class Machine: public ROMMachine::Machine {
|
||||
class Machine {
|
||||
public:
|
||||
/*!
|
||||
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "../../Components/AY38910/AY38910.hpp" // For the Super Game Module.
|
||||
#include "../../Components/SN76489/SN76489.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
|
||||
@@ -32,30 +31,29 @@ const int sn76489_divider = 2;
|
||||
namespace Coleco {
|
||||
namespace Vision {
|
||||
|
||||
class Joystick: public Inputs::Joystick {
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
std::vector<DigitalInput> get_inputs() override {
|
||||
return {
|
||||
DigitalInput(DigitalInput::Up),
|
||||
DigitalInput(DigitalInput::Down),
|
||||
DigitalInput(DigitalInput::Left),
|
||||
DigitalInput(DigitalInput::Right),
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
|
||||
DigitalInput(DigitalInput::Fire, 0),
|
||||
DigitalInput(DigitalInput::Fire, 1),
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
|
||||
DigitalInput('0'), DigitalInput('1'), DigitalInput('2'),
|
||||
DigitalInput('3'), DigitalInput('4'), DigitalInput('5'),
|
||||
DigitalInput('6'), DigitalInput('7'), DigitalInput('8'),
|
||||
DigitalInput('9'), DigitalInput('*'), DigitalInput('#'),
|
||||
};
|
||||
}
|
||||
Input('0'), Input('1'), Input('2'),
|
||||
Input('3'), Input('4'), Input('5'),
|
||||
Input('6'), Input('7'), Input('8'),
|
||||
Input('9'), Input('*'), Input('#'),
|
||||
}) {}
|
||||
|
||||
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
|
||||
case DigitalInput::Key:
|
||||
case Input::Key:
|
||||
if(!is_active) keypad_ |= 0xf;
|
||||
else {
|
||||
uint8_t mask = 0xf;
|
||||
@@ -78,11 +76,11 @@ class Joystick: public Inputs::Joystick {
|
||||
}
|
||||
break;
|
||||
|
||||
case DigitalInput::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
|
||||
case DigitalInput::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
|
||||
case DigitalInput::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
|
||||
case DigitalInput::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
|
||||
case DigitalInput::Fire:
|
||||
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
|
||||
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
|
||||
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
|
||||
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
|
||||
case Input::Fire:
|
||||
switch(digital_input.info.control.index) {
|
||||
default: break;
|
||||
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
|
||||
@@ -109,11 +107,10 @@ class ConcreteMachine:
|
||||
public Machine,
|
||||
public CPU::Z80::BusHandler,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public JoystickMachine::Machine {
|
||||
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
z80_(*this),
|
||||
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
|
||||
ay_(audio_queue_),
|
||||
@@ -123,6 +120,36 @@ class ConcreteMachine:
|
||||
set_clock_rate(3579545);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
const auto roms = rom_fetcher(
|
||||
"ColecoVision",
|
||||
{ "coleco.rom" });
|
||||
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
bios_ = *roms[0];
|
||||
bios_.resize(8192);
|
||||
|
||||
if(!target.media.cartridges.empty()) {
|
||||
const auto &segment = target.media.cartridges.front()->get_segments().front();
|
||||
cartridge_ = segment.data;
|
||||
if(cartridge_.size() >= 32768)
|
||||
cartridge_address_limit_ = 0xffff;
|
||||
else
|
||||
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
|
||||
|
||||
if(cartridge_.size() > 32768) {
|
||||
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
|
||||
cartridge_pages_[1] = cartridge_.data();
|
||||
is_megacart_ = true;
|
||||
} else {
|
||||
cartridge_pages_[0] = cartridge_.data();
|
||||
cartridge_pages_[1] = cartridge_.data() + 16384;
|
||||
is_megacart_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -154,48 +181,6 @@ class ConcreteMachine:
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target *target) override {
|
||||
// Insert the media.
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(!media.cartridges.empty()) {
|
||||
const auto &segment = media.cartridges.front()->get_segments().front();
|
||||
cartridge_ = segment.data;
|
||||
if(cartridge_.size() >= 32768)
|
||||
cartridge_address_limit_ = 0xffff;
|
||||
else
|
||||
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
|
||||
|
||||
if(cartridge_.size() > 32768) {
|
||||
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
|
||||
cartridge_pages_[1] = cartridge_.data();
|
||||
is_megacart_ = true;
|
||||
} else {
|
||||
cartridge_pages_[0] = cartridge_.data();
|
||||
cartridge_pages_[1] = cartridge_.data() + 16384;
|
||||
is_megacart_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
auto roms = roms_with_names(
|
||||
"ColecoVision",
|
||||
{ "coleco.rom" });
|
||||
|
||||
if(!roms[0]) return false;
|
||||
|
||||
bios_ = *roms[0];
|
||||
bios_.resize(8192);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
// The SN76489 will use its ready line to trigger the Z80's wait for three
|
||||
@@ -409,8 +394,8 @@ class ConcreteMachine:
|
||||
|
||||
using namespace Coleco::Vision;
|
||||
|
||||
Machine *Machine::ColecoVision() {
|
||||
return new ConcreteMachine;
|
||||
Machine *Machine::ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
return new ConcreteMachine(*target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -9,13 +9,16 @@
|
||||
#ifndef ColecoVision_hpp
|
||||
#define ColecoVision_hpp
|
||||
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace Coleco {
|
||||
namespace Vision {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
static Machine *ColecoVision();
|
||||
static Machine *ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,25 @@
|
||||
#ifndef Commodore1540_hpp
|
||||
#define Commodore1540_hpp
|
||||
|
||||
namespace Commodore {
|
||||
namespace C1540 {
|
||||
|
||||
/// Defines the type of drive this 1540 hardware is configured as.
|
||||
enum class Personality {
|
||||
C1540,
|
||||
C1541
|
||||
};
|
||||
|
||||
/*
|
||||
Implementation note: this is defined up here so that it precedes
|
||||
C1540Base.hpp below. The alternative option was to factor it out,
|
||||
but the whole point of the C1540.hpp/C1540Base.hpp split is supposed
|
||||
to be to create a single file of public interface.
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "../SerialBus.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
@@ -20,18 +39,9 @@ namespace C1540 {
|
||||
/*!
|
||||
Provides an emulation of the C1540.
|
||||
*/
|
||||
class Machine: public MachineBase, public ROMMachine::Machine {
|
||||
class Machine: public MachineBase {
|
||||
public:
|
||||
enum Personality {
|
||||
C1540,
|
||||
C1541
|
||||
};
|
||||
Machine(Personality p);
|
||||
|
||||
/*!
|
||||
Sets the source for this drive's ROM image.
|
||||
*/
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names);
|
||||
Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
|
||||
/*!
|
||||
Sets the serial bus to which this drive should attach itself.
|
||||
@@ -43,9 +53,6 @@ class Machine: public MachineBase, public ROMMachine::Machine {
|
||||
|
||||
/// Inserts @c disk into the drive.
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
|
||||
|
||||
private:
|
||||
Personality personality_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
MachineBase::MachineBase() :
|
||||
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
Storage::Disk::Controller(1000000),
|
||||
m6502_(*this),
|
||||
drive_(new Storage::Disk::Drive(1000000, 300, 2)),
|
||||
@@ -38,9 +38,22 @@ MachineBase::MachineBase() :
|
||||
|
||||
// attach the only drive there is
|
||||
set_drive(drive_);
|
||||
|
||||
std::string rom_name;
|
||||
switch(personality) {
|
||||
case Personality::C1540: rom_name = "1540.bin"; break;
|
||||
case Personality::C1541: rom_name = "1541.bin"; break;
|
||||
}
|
||||
|
||||
auto roms = rom_fetcher("Commodore1540", {rom_name});
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
|
||||
}
|
||||
|
||||
Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {}
|
||||
Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
MachineBase(personality, rom_fetcher) {}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
@@ -82,19 +95,6 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
bool Machine::set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) {
|
||||
std::string rom_name;
|
||||
switch(personality_) {
|
||||
case Personality::C1540: rom_name = "1540.bin"; break;
|
||||
case Personality::C1541: rom_name = "1541.bin"; break;
|
||||
}
|
||||
|
||||
auto roms = roms_with_names("Commodore1540", {rom_name});
|
||||
if(!roms[0]) return false;
|
||||
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
drive_->set_disk(disk);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "../../../../Storage/Disk/Controller/DiskController.hpp"
|
||||
|
||||
#include "../C1540.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace C1540 {
|
||||
|
||||
@@ -125,7 +127,7 @@ class MachineBase:
|
||||
public Storage::Disk::Controller {
|
||||
|
||||
public:
|
||||
MachineBase();
|
||||
MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
@@ -141,7 +143,7 @@ class MachineBase:
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
protected:
|
||||
CPU::MOS6502::Processor<MachineBase, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include "../../../Activity/Source.hpp"
|
||||
#include "../../ConfigurationTarget.hpp"
|
||||
#include "../../MediaTarget.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../JoystickMachine.hpp"
|
||||
@@ -62,18 +62,6 @@ enum JoystickInput {
|
||||
Fire = 0x20
|
||||
};
|
||||
|
||||
enum ROM {
|
||||
CharactersDanish = 0,
|
||||
CharactersEnglish,
|
||||
CharactersJapanese,
|
||||
CharactersSwedish,
|
||||
KernelDanish,
|
||||
KernelJapanese,
|
||||
KernelNTSC,
|
||||
KernelPAL,
|
||||
KernelSwedish
|
||||
};
|
||||
|
||||
/*!
|
||||
Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder;
|
||||
sensing the presence or absence of a tape and controlling the tape motor; and reading the current
|
||||
@@ -257,31 +245,28 @@ class Vic6560BusHandler {
|
||||
/*!
|
||||
Interfaces a joystick to the two VIAs.
|
||||
*/
|
||||
class Joystick: public Inputs::Joystick {
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire)
|
||||
}),
|
||||
user_port_via_port_handler_(user_port_via_port_handler),
|
||||
keyboard_via_port_handler_(keyboard_via_port_handler) {}
|
||||
|
||||
std::vector<DigitalInput> get_inputs() override {
|
||||
return {
|
||||
DigitalInput(DigitalInput::Up),
|
||||
DigitalInput(DigitalInput::Down),
|
||||
DigitalInput(DigitalInput::Left),
|
||||
DigitalInput(DigitalInput::Right),
|
||||
DigitalInput(DigitalInput::Fire)
|
||||
};
|
||||
}
|
||||
|
||||
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
|
||||
void did_set_input(const Input &digital_input, bool is_active) override {
|
||||
JoystickInput mapped_input;
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
case DigitalInput::Up: mapped_input = Up; break;
|
||||
case DigitalInput::Down: mapped_input = Down; break;
|
||||
case DigitalInput::Left: mapped_input = Left; break;
|
||||
case DigitalInput::Right: mapped_input = Right; break;
|
||||
case DigitalInput::Fire: mapped_input = Fire; break;
|
||||
case Input::Up: mapped_input = Up; break;
|
||||
case Input::Down: mapped_input = Down; break;
|
||||
case Input::Left: mapped_input = Left; break;
|
||||
case Input::Right: mapped_input = Right; break;
|
||||
case Input::Fire: mapped_input = Fire; break;
|
||||
}
|
||||
|
||||
user_port_via_port_handler_.set_joystick_state(mapped_input, is_active);
|
||||
@@ -295,7 +280,7 @@ class Joystick: public Inputs::Joystick {
|
||||
|
||||
class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public JoystickMachine::Machine,
|
||||
public Configurable::Device,
|
||||
@@ -304,10 +289,10 @@ class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Machine,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
user_port_via_port_handler_(new UserPortVIA),
|
||||
keyboard_via_port_handler_(new KeyboardVIA),
|
||||
@@ -331,131 +316,72 @@ class ConcreteMachine:
|
||||
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
tape_->set_sleep_observer(this);
|
||||
tape_->set_clocking_hint_observer(this);
|
||||
|
||||
// install a joystick
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
rom_fetcher_ = roms_with_names;
|
||||
|
||||
auto roms = roms_with_names(
|
||||
"Vic20",
|
||||
{
|
||||
"characters-danish.bin",
|
||||
"characters-english.bin",
|
||||
"characters-japanese.bin",
|
||||
"characters-swedish.bin",
|
||||
"kernel-danish.bin",
|
||||
"kernel-japanese.bin",
|
||||
"kernel-ntsc.bin",
|
||||
"kernel-pal.bin",
|
||||
"kernel-swedish.bin",
|
||||
"basic.bin"
|
||||
});
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) return false;
|
||||
if(index < 9) roms_[index] = std::move(*data); else basic_rom_ = std::move(*data);
|
||||
std::vector<std::string> rom_names = { "basic.bin" };
|
||||
switch(target.region) {
|
||||
default:
|
||||
rom_names.push_back("characters-english.bin");
|
||||
rom_names.push_back("kernel-pal.bin");
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::American:
|
||||
rom_names.push_back("characters-english.bin");
|
||||
rom_names.push_back("kernel-ntsc.bin");
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Danish:
|
||||
rom_names.push_back("characters-danish.bin");
|
||||
rom_names.push_back("kernel-danish.bin");
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Japanese:
|
||||
rom_names.push_back("characters-japanese.bin");
|
||||
rom_names.push_back("kernel-japanese.bin");
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Swedish:
|
||||
rom_names.push_back("characters-swedish.bin");
|
||||
rom_names.push_back("kernel-japanese.bin");
|
||||
break;
|
||||
}
|
||||
|
||||
const auto roms = rom_fetcher("Vic20", rom_names);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
}
|
||||
|
||||
basic_rom_ = std::move(*roms[0]);
|
||||
character_rom_ = std::move(*roms[1]);
|
||||
kernel_rom_ = std::move(*roms[2]);
|
||||
|
||||
// Characters ROMs should be 4kb.
|
||||
for(std::size_t index = 0; index < 4; ++index) roms_[index].resize(4096);
|
||||
character_rom_.resize(4096);
|
||||
// Kernel ROMs and the BASIC ROM should be 8kb.
|
||||
for(std::size_t index = 4; index < roms.size(); ++index) roms_[index].resize(8192);
|
||||
kernel_rom_.resize(8192);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target);
|
||||
|
||||
if(!commodore_target_.loading_command.empty()) {
|
||||
type_string(commodore_target_.loading_command);
|
||||
}
|
||||
|
||||
if(commodore_target_.has_c1540) {
|
||||
if(target.has_c1540) {
|
||||
// construct the 1540
|
||||
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540));
|
||||
c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Personality::C1540, rom_fetcher));
|
||||
|
||||
// attach it to the serial bus
|
||||
c1540_->set_serial_bus(serial_bus_);
|
||||
|
||||
// give it a means to obtain its ROM
|
||||
c1540_->set_rom_fetcher(rom_fetcher_);
|
||||
|
||||
// give it a little warm up
|
||||
c1540_->run_for(Cycles(2000000));
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_->set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
if(!media.disks.empty() && c1540_) {
|
||||
c1540_->set_disk(media.disks.front());
|
||||
}
|
||||
|
||||
if(!media.cartridges.empty()) {
|
||||
rom_address_ = 0xa000;
|
||||
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
|
||||
rom_length_ = static_cast<uint16_t>(rom_image.size());
|
||||
|
||||
rom_ = rom_image;
|
||||
rom_.resize(0x2000);
|
||||
}
|
||||
|
||||
set_use_fast_tape();
|
||||
|
||||
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
if(key != KeyRestore)
|
||||
keyboard_via_port_handler_->set_key_state(key, is_pressed);
|
||||
else
|
||||
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
keyboard_via_port_handler_->clear_all_keys();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
void set_ntsc_6560() {
|
||||
set_clock_rate(1022727);
|
||||
if(mos6560_) {
|
||||
mos6560_->set_output_mode(MOS::MOS6560::OutputMode::NTSC);
|
||||
mos6560_->set_clock_rate(1022727);
|
||||
}
|
||||
}
|
||||
|
||||
void set_pal_6560() {
|
||||
set_clock_rate(1108404);
|
||||
if(mos6560_) {
|
||||
mos6560_->set_output_mode(MOS::MOS6560::OutputMode::PAL);
|
||||
mos6560_->set_clock_rate(1108404);
|
||||
}
|
||||
}
|
||||
|
||||
void set_memory_map(Analyser::Static::Commodore::Target::MemoryModel memory_model, Analyser::Static::Commodore::Target::Region region) {
|
||||
// Determine PAL/NTSC
|
||||
if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) {
|
||||
if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) {
|
||||
// NTSC
|
||||
set_ntsc_6560();
|
||||
set_clock_rate(1022727);
|
||||
output_mode_ = MOS::MOS6560::OutputMode::NTSC;
|
||||
} else {
|
||||
// PAL
|
||||
set_pal_6560();
|
||||
set_clock_rate(1108404);
|
||||
output_mode_ = MOS::MOS6560::OutputMode::PAL;
|
||||
}
|
||||
|
||||
// Initialise the memory maps as all pointing to nothing
|
||||
@@ -468,7 +394,7 @@ class ConcreteMachine:
|
||||
write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length);
|
||||
|
||||
// Add 6502-visible RAM as requested
|
||||
switch(memory_model) {
|
||||
switch(target.memory_model) {
|
||||
case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded:
|
||||
// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000.
|
||||
set_ram(0x0000, 0x0400);
|
||||
@@ -515,39 +441,53 @@ class ConcreteMachine:
|
||||
write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size()));
|
||||
|
||||
// install the system ROM
|
||||
ROM character_rom;
|
||||
ROM kernel_rom;
|
||||
switch(region) {
|
||||
default:
|
||||
character_rom = CharactersEnglish;
|
||||
kernel_rom = KernelPAL;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::American:
|
||||
character_rom = CharactersEnglish;
|
||||
kernel_rom = KernelNTSC;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Danish:
|
||||
character_rom = CharactersDanish;
|
||||
kernel_rom = KernelDanish;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Japanese:
|
||||
character_rom = CharactersJapanese;
|
||||
kernel_rom = KernelJapanese;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Swedish:
|
||||
character_rom = CharactersSwedish;
|
||||
kernel_rom = KernelSwedish;
|
||||
break;
|
||||
write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, static_cast<uint16_t>(character_rom_.size()));
|
||||
write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, static_cast<uint16_t>(character_rom_.size()));
|
||||
write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, static_cast<uint16_t>(kernel_rom_.size()));
|
||||
|
||||
insert_media(target.media);
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_->set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
write_to_map(processor_read_memory_map_, roms_[character_rom].data(), 0x8000, static_cast<uint16_t>(roms_[character_rom].size()));
|
||||
write_to_map(mos6560_bus_handler_.video_memory_map, roms_[character_rom].data(), 0x0000, static_cast<uint16_t>(roms_[character_rom].size()));
|
||||
write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size()));
|
||||
if(!media.disks.empty() && c1540_) {
|
||||
c1540_->set_disk(media.disks.front());
|
||||
}
|
||||
|
||||
// install the inserted ROM if there is one
|
||||
if(!rom_.empty()) {
|
||||
if(!media.cartridges.empty()) {
|
||||
rom_address_ = 0xa000;
|
||||
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
|
||||
rom_length_ = static_cast<uint16_t>(rom_image.size());
|
||||
|
||||
rom_ = rom_image;
|
||||
rom_.resize(0x2000);
|
||||
write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_);
|
||||
}
|
||||
|
||||
set_use_fast_tape();
|
||||
|
||||
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
if(key != KeyRestore)
|
||||
keyboard_via_port_handler_->set_key_state(key, is_pressed);
|
||||
else
|
||||
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() override final {
|
||||
keyboard_via_port_handler_->clear_all_keys();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
@@ -683,8 +623,8 @@ class ConcreteMachine:
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_));
|
||||
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
|
||||
set_memory_map(commodore_target_.memory_model, commodore_target_.region);
|
||||
mos6560_->set_output_mode(output_mode_);
|
||||
mos6560_->set_clock_rate(get_clock_rate());
|
||||
}
|
||||
|
||||
void close_output() override final {
|
||||
@@ -749,8 +689,8 @@ class ConcreteMachine:
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
|
||||
tape_is_sleeping_ = is_sleeping;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
@@ -763,11 +703,7 @@ class ConcreteMachine:
|
||||
void update_video() {
|
||||
mos6560_->run_for(cycles_since_mos6560_update_.flush());
|
||||
}
|
||||
Analyser::Static::Commodore::Target commodore_target_;
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
|
||||
std::vector<uint8_t> roms_[9];
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
std::vector<uint8_t> character_rom_;
|
||||
std::vector<uint8_t> basic_rom_;
|
||||
@@ -778,8 +714,6 @@ class ConcreteMachine:
|
||||
uint8_t ram_[0x8000];
|
||||
uint8_t colour_ram_[0x0400];
|
||||
|
||||
std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_;
|
||||
|
||||
uint8_t *processor_read_memory_map_[64];
|
||||
uint8_t *processor_write_memory_map_[64];
|
||||
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
|
||||
@@ -797,6 +731,7 @@ class ConcreteMachine:
|
||||
|
||||
Cycles cycles_since_mos6560_update_;
|
||||
Vic6560BusHandler mos6560_bus_handler_;
|
||||
MOS::MOS6560::OutputMode output_mode_;
|
||||
std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
|
||||
@@ -825,8 +760,10 @@ class ConcreteMachine:
|
||||
|
||||
using namespace Commodore::Vic20;
|
||||
|
||||
Machine *Machine::Vic20() {
|
||||
return new Vic20::ConcreteMachine;
|
||||
Machine *Machine::Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Commodore::Target;
|
||||
const Target *const commodore_target = dynamic_cast<const Target *>(target);
|
||||
return new Vic20::ConcreteMachine(*commodore_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
#define Vic20_hpp
|
||||
|
||||
#include "../../../Configurable/Configurable.hpp"
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Commodore {
|
||||
namespace Vic20 {
|
||||
@@ -22,7 +27,7 @@ class Machine {
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns a Vic-20.
|
||||
static Machine *Vic20();
|
||||
static Machine *Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// ConfigurationTarget.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ConfigurationTarget_hpp
|
||||
#define ConfigurationTarget_hpp
|
||||
|
||||
#include "../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../Configurable/Configurable.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ConfigurationTarget {
|
||||
|
||||
/*!
|
||||
A ConfigurationTarget::Machine is anything that can accept a Analyser::Static::Target
|
||||
and configure itself appropriately, or accept a list of media subsequently to insert.
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
/// Instructs the machine to configure itself as described by @c target and insert the included media.
|
||||
virtual void configure_as_target(const Analyser::Static::Target *target) = 0;
|
||||
|
||||
/*!
|
||||
Requests that the machine insert @c media as a modification to current state
|
||||
|
||||
@returns @c true if any media was inserted; @c false otherwise.
|
||||
*/
|
||||
virtual bool insert_media(const Analyser::Static::Media &media) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ConfigurationTarget_h */
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include "../Configurable/Configurable.hpp"
|
||||
#include "../Activity/Source.hpp"
|
||||
#include "ConfigurationTarget.hpp"
|
||||
#include "MediaTarget.hpp"
|
||||
#include "CRTMachine.hpp"
|
||||
#include "JoystickMachine.hpp"
|
||||
#include "KeyboardMachine.hpp"
|
||||
@@ -27,11 +27,11 @@ struct DynamicMachine {
|
||||
virtual ~DynamicMachine() {}
|
||||
|
||||
virtual Activity::Source *activity_source() = 0;
|
||||
virtual ConfigurationTarget::Machine *configuration_target() = 0;
|
||||
virtual Configurable::Device *configurable_device() = 0;
|
||||
virtual CRTMachine::Machine *crt_machine() = 0;
|
||||
virtual JoystickMachine::Machine *joystick_machine() = 0;
|
||||
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
|
||||
virtual Configurable::Device *configurable_device() = 0;
|
||||
virtual MediaTarget::Machine *media_target() = 0;
|
||||
|
||||
/*!
|
||||
Provides a raw pointer to the underlying machine if and only if this dynamic machine really is
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "Electron.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
@@ -41,7 +41,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
@@ -49,10 +49,10 @@ class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
sound_generator_(audio_queue_),
|
||||
speaker_(sound_generator_) {
|
||||
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
sound_generator_(audio_queue_),
|
||||
speaker_(sound_generator_) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
@@ -61,60 +61,53 @@ class ConcreteMachine:
|
||||
set_clock_rate(2000000);
|
||||
|
||||
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||
|
||||
std::vector<std::string> rom_names = {"basic.rom", "os.rom"};
|
||||
if(target.has_adfs) {
|
||||
rom_names.push_back("ADFS-E00_1.rom");
|
||||
rom_names.push_back("ADFS-E00_2.rom");
|
||||
}
|
||||
const size_t dfs_rom_position = rom_names.size();
|
||||
if(target.has_dfs) {
|
||||
rom_names.push_back("DFS-1770-2.20.rom");
|
||||
}
|
||||
const auto roms = rom_fetcher("Electron", rom_names);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
}
|
||||
set_rom(ROM::BASIC, *roms[0], false);
|
||||
set_rom(ROM::OS, *roms[1], false);
|
||||
|
||||
if(target.has_dfs || target.has_adfs) {
|
||||
plus3_.reset(new Plus3);
|
||||
|
||||
if(target.has_dfs) {
|
||||
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
|
||||
}
|
||||
if(target.has_adfs) {
|
||||
set_rom(ROM::Slot4, *roms[2], true);
|
||||
set_rom(ROM::Slot5, *roms[3], true);
|
||||
}
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
|
||||
if(target.should_shift_restart) {
|
||||
shift_restart_counter_ = 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final {
|
||||
uint8_t *target = nullptr;
|
||||
switch(slot) {
|
||||
case ROMSlotDFS: dfs_ = data; return;
|
||||
case ROMSlotADFS1: adfs1_ = data; return;
|
||||
case ROMSlotADFS2: adfs2_ = data; return;
|
||||
|
||||
case ROMSlotOS: target = os_; break;
|
||||
default:
|
||||
target = roms_[slot];
|
||||
rom_write_masks_[slot] = is_writeable;
|
||||
break;
|
||||
}
|
||||
|
||||
// Copy in, with mirroring.
|
||||
std::size_t rom_ptr = 0;
|
||||
while(rom_ptr < 16384) {
|
||||
std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size());
|
||||
std::memcpy(&target[rom_ptr], data.data(), size_to_copy);
|
||||
rom_ptr += size_to_copy;
|
||||
}
|
||||
rom_inserted_[slot] = true;
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
auto roms = roms_with_names(
|
||||
"Electron",
|
||||
{
|
||||
"DFS-1770-2.20.rom",
|
||||
"ADFS-E00_1.rom", "ADFS-E00_2.rom",
|
||||
"basic.rom", "os.rom"
|
||||
});
|
||||
ROMSlot slots[] = {
|
||||
ROMSlotDFS,
|
||||
ROMSlotADFS1, ROMSlotADFS2,
|
||||
ROMSlotBASIC, ROMSlotOS
|
||||
};
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) return false;
|
||||
set_rom(slots[index], *data, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) override final {
|
||||
if(key == KeyBreak) {
|
||||
m6502_.set_reset_line(isPressed);
|
||||
@@ -131,32 +124,6 @@ class ConcreteMachine:
|
||||
if(is_holding_shift_) set_key_state(KeyShift, true);
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target);
|
||||
|
||||
if(!acorn_target->loading_command.empty()) {
|
||||
type_string(acorn_target->loading_command);
|
||||
}
|
||||
|
||||
if(acorn_target->should_shift_restart) {
|
||||
shift_restart_counter_ = 1000000;
|
||||
}
|
||||
|
||||
if(acorn_target->has_dfs || acorn_target->has_adfs) {
|
||||
plus3_.reset(new Plus3);
|
||||
|
||||
if(acorn_target->has_dfs) {
|
||||
set_rom(ROMSlot0, dfs_, true);
|
||||
}
|
||||
if(acorn_target->has_adfs) {
|
||||
set_rom(ROMSlot4, adfs1_, true);
|
||||
set_rom(ROMSlot5, adfs2_, true);
|
||||
}
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_.set_tape(media.tapes.front());
|
||||
@@ -167,11 +134,11 @@ class ConcreteMachine:
|
||||
plus3_->set_disk(media.disks.front(), 0);
|
||||
}
|
||||
|
||||
ROMSlot slot = ROMSlot12;
|
||||
ROM slot = ROM::Slot12;
|
||||
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
|
||||
const ROMSlot first_slot_tried = slot;
|
||||
while(rom_inserted_[slot]) {
|
||||
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
|
||||
const ROM first_slot_tried = slot;
|
||||
while(rom_inserted_[static_cast<int>(slot)]) {
|
||||
slot = static_cast<ROM>((static_cast<int>(slot) + 1) & 15);
|
||||
if(slot == first_slot_tried) return false;
|
||||
}
|
||||
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||
@@ -258,7 +225,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// latch the paged ROM in case external hardware is being emulated
|
||||
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
|
||||
active_rom_ = *value & 0xf;
|
||||
|
||||
// apply the ULA's test
|
||||
if(*value & 0x08) {
|
||||
@@ -363,7 +330,7 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
if(basic_is_active_) {
|
||||
*value &= roms_[ROMSlotBASIC][address & 16383];
|
||||
*value &= roms_[static_cast<int>(ROM::BASIC)][address & 16383];
|
||||
}
|
||||
} else if(rom_write_masks_[active_rom_]) {
|
||||
roms_[active_rom_][address & 16383] = *value;
|
||||
@@ -480,15 +447,64 @@ class ConcreteMachine:
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
activity_observer_ = observer;
|
||||
if(activity_observer_) {
|
||||
activity_observer_->register_led(caps_led);
|
||||
activity_observer_->set_led_status(caps_led, caps_led_state_);
|
||||
|
||||
if(plus3_) {
|
||||
plus3_->set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ROM {
|
||||
Slot0 = 0,
|
||||
Slot1, Slot2, Slot3,
|
||||
Slot4, Slot5, Slot6, Slot7,
|
||||
|
||||
Keyboard = 8, Slot9,
|
||||
BASIC = 10, Slot11,
|
||||
|
||||
Slot12, Slot13, Slot14, Slot15,
|
||||
|
||||
OS, DFS,
|
||||
ADFS1, ADFS2
|
||||
};
|
||||
|
||||
/*!
|
||||
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
|
||||
is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
|
||||
*/
|
||||
void set_rom(ROM slot, const std::vector<uint8_t> &data, bool is_writeable) {
|
||||
uint8_t *target = nullptr;
|
||||
switch(slot) {
|
||||
case ROM::DFS: dfs_ = data; return;
|
||||
case ROM::ADFS1: adfs1_ = data; return;
|
||||
case ROM::ADFS2: adfs2_ = data; return;
|
||||
|
||||
case ROM::OS: target = os_; break;
|
||||
default:
|
||||
target = roms_[static_cast<int>(slot)];
|
||||
rom_write_masks_[static_cast<int>(slot)] = is_writeable;
|
||||
break;
|
||||
}
|
||||
|
||||
// Copy in, with mirroring.
|
||||
std::size_t rom_ptr = 0;
|
||||
while(rom_ptr < 16384) {
|
||||
std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size());
|
||||
std::memcpy(&target[rom_ptr], data.data(), size_to_copy);
|
||||
rom_ptr += size_to_copy;
|
||||
}
|
||||
|
||||
if(static_cast<int>(slot) < 16)
|
||||
rom_inserted_[static_cast<int>(slot)] = true;
|
||||
}
|
||||
|
||||
// MARK: - Work deferral updates.
|
||||
inline void update_display() {
|
||||
if(cycles_since_display_update_ > 0) {
|
||||
@@ -525,7 +541,7 @@ class ConcreteMachine:
|
||||
m6502_.set_irq_line(interrupt_status_ & 1);
|
||||
}
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
// Things that directly constitute the memory map.
|
||||
uint8_t roms_[16][16384];
|
||||
@@ -535,7 +551,7 @@ class ConcreteMachine:
|
||||
std::vector<uint8_t> dfs_, adfs1_, adfs2_;
|
||||
|
||||
// Paging
|
||||
ROMSlot active_rom_ = ROMSlot::ROMSlot0;
|
||||
int active_rom_ = static_cast<int>(ROM::Slot0);
|
||||
bool keyboard_is_active_ = false;
|
||||
bool basic_is_active_ = false;
|
||||
|
||||
@@ -585,8 +601,10 @@ class ConcreteMachine:
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
Machine *Machine::Electron() {
|
||||
return new Electron::ConcreteMachine;
|
||||
Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Acorn::Target;
|
||||
const Target *const acorn_target = dynamic_cast<const Target *>(target);
|
||||
return new Electron::ConcreteMachine(*acorn_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -10,26 +10,15 @@
|
||||
#define Electron_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Electron {
|
||||
|
||||
enum ROMSlot: uint8_t {
|
||||
ROMSlot0 = 0,
|
||||
ROMSlot1, ROMSlot2, ROMSlot3,
|
||||
ROMSlot4, ROMSlot5, ROMSlot6, ROMSlot7,
|
||||
|
||||
ROMSlotKeyboard = 8, ROMSlot9,
|
||||
ROMSlotBASIC = 10, ROMSlot11,
|
||||
|
||||
ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15,
|
||||
|
||||
ROMSlotOS, ROMSlotDFS,
|
||||
ROMSlotADFS1, ROMSlotADFS2
|
||||
};
|
||||
|
||||
/// @returns The options available for an Electron.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
@@ -44,13 +33,7 @@ class Machine {
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Electron.
|
||||
static Machine *Electron();
|
||||
|
||||
/*!
|
||||
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
|
||||
is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
|
||||
*/
|
||||
virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0;
|
||||
static Machine *Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,14 +11,12 @@
|
||||
using namespace Electron;
|
||||
|
||||
Plus3::Plus3() : WD1770(P1770) {
|
||||
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
set_control_register(last_control_, 0xff);
|
||||
}
|
||||
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
drives_[drive]->set_disk(disk);
|
||||
}
|
||||
|
||||
@@ -42,8 +40,8 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) {
|
||||
}
|
||||
}
|
||||
if(changes & 0x04) {
|
||||
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||
drives_[0]->set_head((control & 0x04) ? 1 : 0);
|
||||
drives_[1]->set_head((control & 0x04) ? 1 : 0);
|
||||
}
|
||||
if(changes & 0x08) set_is_double_density(!(control & 0x08));
|
||||
}
|
||||
@@ -55,6 +53,9 @@ void Plus3::set_motor_on(bool on) {
|
||||
}
|
||||
|
||||
void Plus3::set_activity_observer(Activity::Observer *observer) {
|
||||
drives_[0]->set_activity_observer(observer, "Drive 1", true);
|
||||
drives_[1]->set_activity_observer(observer, "Drive 2", true);
|
||||
size_t index = 0;
|
||||
for(const auto &drive: drives_) {
|
||||
drive->set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,18 @@ class Plus3 : public WD::WD1770 {
|
||||
public:
|
||||
Plus3();
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
|
||||
void set_control_register(uint8_t control);
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
std::shared_ptr<Storage::Disk::Drive> drives_[2];
|
||||
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
|
||||
int selected_drive_ = 0;
|
||||
uint8_t last_control_ = 0;
|
||||
|
||||
void set_motor_on(bool on);
|
||||
std::string drive_name(size_t drive);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
|
||||
std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender);
|
||||
crt_->set_bookender(std::move(bookender));
|
||||
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
// MARK: - CRT getter
|
||||
@@ -404,7 +404,7 @@ unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
|
||||
if(current_line >= output_position_line) {
|
||||
// Get the number of lines since then if still in the same frame.
|
||||
int lines_since_output_position = current_line - output_position_line;
|
||||
|
||||
|
||||
// Therefore get the character row at the proposed time, modulo 10.
|
||||
implied_row = (current_character_row_ + lines_since_output_position) % 10;
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,8 @@ using namespace MSX;
|
||||
DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
|
||||
WD1770(P1793),
|
||||
rom_(rom) {
|
||||
drives_[0].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
drives_[1].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
set_is_double_density(true);
|
||||
}
|
||||
|
||||
@@ -23,16 +25,16 @@ void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) {
|
||||
break;
|
||||
case 0x7ffc:
|
||||
selected_head_ = value & 1;
|
||||
if(drives_[0]) drives_[0]->set_head(selected_head_);
|
||||
if(drives_[1]) drives_[1]->set_head(selected_head_);
|
||||
drives_[0]->set_head(selected_head_);
|
||||
drives_[1]->set_head(selected_head_);
|
||||
break;
|
||||
case 0x7ffd: {
|
||||
selected_drive_ = value & 1;
|
||||
set_drive(drives_[selected_drive_]);
|
||||
|
||||
bool drive_motor = !!(value & 0x80);
|
||||
if(drives_[0]) drives_[0]->set_motor_on(drive_motor);
|
||||
if(drives_[1]) drives_[1]->set_motor_on(drive_motor);
|
||||
drives_[0]->set_motor_on(drive_motor);
|
||||
drives_[1]->set_motor_on(drive_motor);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -57,12 +59,6 @@ void DiskROM::run_for(HalfCycles half_cycles) {
|
||||
}
|
||||
|
||||
void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
drives_[drive]->set_head(selected_head_);
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
drives_[drive]->set_activity_observer(observer_, drive_name(drive), true);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
}
|
||||
|
||||
@@ -72,14 +68,9 @@ void DiskROM::set_head_load_request(bool head_load) {
|
||||
}
|
||||
|
||||
void DiskROM::set_activity_observer(Activity::Observer *observer) {
|
||||
size_t c = 0;
|
||||
observer_ = observer;
|
||||
size_t c = 1;
|
||||
for(auto &drive: drives_) {
|
||||
if(drive) drive->set_activity_observer(observer, drive_name(c), true);
|
||||
drive->set_activity_observer(observer, "Drive " + std::to_string(c), true);
|
||||
++c;
|
||||
}
|
||||
}
|
||||
|
||||
std::string DiskROM::drive_name(size_t index) {
|
||||
return "Drive " + std::to_string(index);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 2> drives_;
|
||||
|
||||
void set_head_load_request(bool head_load) override;
|
||||
std::string drive_name(size_t index);
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||
@@ -54,13 +55,19 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
|
||||
class AYPortHandler: public GI::AY38910::PortHandler {
|
||||
public:
|
||||
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {}
|
||||
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
}
|
||||
|
||||
void set_port_output(bool port_b, uint8_t value) {
|
||||
if(port_b) {
|
||||
// Bits 0-3: touchpad handshaking (?)
|
||||
// Bit 4-5: monostable timer pulses
|
||||
|
||||
// Bit 6: joystick select
|
||||
selected_joystick_ = (value >> 6) & 1;
|
||||
|
||||
// Bit 7: code LED, if any
|
||||
}
|
||||
}
|
||||
@@ -69,29 +76,75 @@ class AYPortHandler: public GI::AY38910::PortHandler {
|
||||
if(!port_b) {
|
||||
// Bits 0-5: Joystick (up, down, left, right, A, B)
|
||||
// Bit 6: keyboard switch (not universal)
|
||||
|
||||
// Bit 7: tape input
|
||||
return 0x7f | (tape_player_.get_input() ? 0x00 : 0x80);
|
||||
return
|
||||
(static_cast<Joystick *>(joysticks_[selected_joystick_].get())->get_state() & 0x3f) |
|
||||
0x40 |
|
||||
(tape_player_.get_input() ? 0x00 : 0x80);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
private:
|
||||
Storage::Tape::BinaryTapePlayer &tape_player_;
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
size_t selected_joystick_ = 0;
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire, 0),
|
||||
Input(Input::Fire, 1),
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &input, bool is_active) override {
|
||||
uint8_t mask = 0;
|
||||
switch(input.type) {
|
||||
default: return;
|
||||
case Input::Up: mask = 0x01; break;
|
||||
case Input::Down: mask = 0x02; break;
|
||||
case Input::Left: mask = 0x04; break;
|
||||
case Input::Right: mask = 0x08; break;
|
||||
case Input::Fire:
|
||||
if(input.info.control.index >= 2) return;
|
||||
mask = input.info.control.index ? 0x20 : 0x10;
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_active) state_ &= ~mask; else state_ |= mask;
|
||||
}
|
||||
|
||||
uint8_t get_state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t state_ = 0xff;
|
||||
};
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public CPU::Z80::BusHandler,
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public JoystickMachine::Machine,
|
||||
public MemoryMap,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine():
|
||||
ConcreteMachine(const Analyser::Static::MSX::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
|
||||
z80_(*this),
|
||||
i8255_(i8255_port_handler_),
|
||||
ay_(audio_queue_),
|
||||
@@ -108,10 +161,55 @@ class ConcreteMachine:
|
||||
|
||||
ay_.set_port_handler(&ay_port_handler_);
|
||||
speaker_.set_input_rate(3579545.0f / 2.0f);
|
||||
tape_player_.set_sleep_observer(this);
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
|
||||
// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC.
|
||||
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
|
||||
|
||||
// Fetch the necessary ROMs.
|
||||
std::vector<std::string> rom_names = {"msx.rom"};
|
||||
if(target.has_disk_drive) {
|
||||
rom_names.push_back("disk.rom");
|
||||
}
|
||||
const auto roms = rom_fetcher("MSX", rom_names);
|
||||
|
||||
if(!roms[0] || (target.has_disk_drive && !roms[1])) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
memory_slots_[0].source = std::move(*roms[0]);
|
||||
memory_slots_[0].source.resize(32768);
|
||||
|
||||
for(size_t c = 0; c < 8; ++c) {
|
||||
for(size_t slot = 0; slot < 3; ++slot) {
|
||||
memory_slots_[slot].read_pointers[c] = unpopulated_;
|
||||
memory_slots_[slot].write_pointers[c] = scratch_;
|
||||
}
|
||||
|
||||
memory_slots_[3].read_pointers[c] =
|
||||
memory_slots_[3].write_pointers[c] = &ram_[c * 8192];
|
||||
}
|
||||
|
||||
map(0, 0, 0, 32768);
|
||||
page_memory(0);
|
||||
|
||||
// Add a disk cartridge if any disks were supplied.
|
||||
if(target.has_disk_drive) {
|
||||
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
|
||||
memory_slots_[2].source = std::move(*roms[1]);
|
||||
memory_slots_[2].source.resize(16384);
|
||||
|
||||
map(2, 0, 0x4000, 0x2000);
|
||||
unmap(2, 0x6000, 0x2000);
|
||||
}
|
||||
|
||||
// Insert the media.
|
||||
insert_media(target.media);
|
||||
|
||||
// Type whatever has been requested.
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -152,25 +250,6 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target *target) override {
|
||||
auto *const msx_target = dynamic_cast<const Analyser::Static::MSX::Target *>(target);
|
||||
|
||||
// Add a disk cartridge if any disks were supplied.
|
||||
if(msx_target->has_disk_drive) {
|
||||
map(2, 0, 0x4000, 0x2000);
|
||||
unmap(2, 0x6000, 0x2000);
|
||||
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
|
||||
}
|
||||
|
||||
// Insert the media.
|
||||
insert_media(target->media);
|
||||
|
||||
// Type whatever has been requested.
|
||||
if(!msx_target->loading_command.empty()) {
|
||||
type_string(msx_target->loading_command);
|
||||
}
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(!media.cartridges.empty()) {
|
||||
const auto &segment = media.cartridges.front()->get_segments().front();
|
||||
@@ -467,39 +546,6 @@ class ConcreteMachine:
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
auto roms = roms_with_names(
|
||||
"MSX",
|
||||
{
|
||||
"msx.rom",
|
||||
"disk.rom"
|
||||
});
|
||||
|
||||
if(!roms[0] || !roms[1]) return false;
|
||||
|
||||
memory_slots_[0].source = std::move(*roms[0]);
|
||||
memory_slots_[0].source.resize(32768);
|
||||
|
||||
memory_slots_[2].source = std::move(*roms[1]);
|
||||
memory_slots_[2].source.resize(16384);
|
||||
|
||||
for(size_t c = 0; c < 8; ++c) {
|
||||
for(size_t slot = 0; slot < 3; ++slot) {
|
||||
memory_slots_[slot].read_pointers[c] = unpopulated_;
|
||||
memory_slots_[slot].write_pointers[c] = scratch_;
|
||||
}
|
||||
|
||||
memory_slots_[3].read_pointers[c] =
|
||||
memory_slots_[3].write_pointers[c] = &ram_[c * 8192];
|
||||
}
|
||||
|
||||
map(0, 0, 0, 32768);
|
||||
page_memory(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_keyboard_line(int line) {
|
||||
selected_key_line_ = line;
|
||||
}
|
||||
@@ -555,8 +601,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Sleeper
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
@@ -568,6 +614,11 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Joysticks
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return ay_port_handler_.get_joysticks();
|
||||
}
|
||||
|
||||
private:
|
||||
DiskROM *get_disk_rom() {
|
||||
return dynamic_cast<DiskROM *>(memory_slots_[2].handler.get());
|
||||
@@ -683,8 +734,10 @@ class ConcreteMachine:
|
||||
|
||||
using namespace MSX;
|
||||
|
||||
Machine *Machine::MSX() {
|
||||
return new ConcreteMachine;
|
||||
Machine *Machine::MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::MSX::Target;
|
||||
const Target *const msx_target = dynamic_cast<const Target *>(target);
|
||||
return new ConcreteMachine(*msx_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -10,17 +10,22 @@
|
||||
#define MSX_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace MSX {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
static Machine *MSX();
|
||||
static Machine *MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
}
|
||||
|
||||
#endif /* MSX_hpp */
|
||||
|
||||
34
Machines/MediaTarget.hpp
Normal file
34
Machines/MediaTarget.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// MediaTarget.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/09/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MediaTarget_hpp
|
||||
#define MediaTarget_hpp
|
||||
|
||||
#include "../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../Configurable/Configurable.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace MediaTarget {
|
||||
|
||||
/*!
|
||||
A MediaTarget::Machine is anything that can accept new media while running.
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
/*!
|
||||
Requests that the machine insert @c media as a modification to current state
|
||||
|
||||
@returns @c true if any media was inserted; @c false otherwise.
|
||||
*/
|
||||
virtual bool insert_media(const Analyser::Static::Media &media) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MediaTarget_hpp */
|
||||
@@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 {
|
||||
bool get_interrupt_request_line();
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
using WD::WD1770::run_for;
|
||||
|
||||
enum PagingFlags {
|
||||
/// Indicates that the BASIC ROM should be disabled; if this is set then either
|
||||
@@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 {
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
void set_head_load_request(bool head_load);
|
||||
void set_head_load_request(bool head_load) override;
|
||||
bool get_drive_is_ready();
|
||||
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
||||
size_t selected_drive_;
|
||||
bool irq_enable_ = false;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
@@ -193,7 +193,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
|
||||
template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
@@ -201,36 +201,29 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source,
|
||||
public Machine {
|
||||
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Oric::Target *target) :
|
||||
ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
rom_type_(target ? target->rom : Analyser::Static::Oric::Target::ROM::BASIC10),
|
||||
ay8910_(audio_queue_),
|
||||
speaker_(ay8910_),
|
||||
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
|
||||
via_(via_port_handler_) {
|
||||
via_(via_port_handler_),
|
||||
diskii_(2000000) {
|
||||
set_clock_rate(1000000);
|
||||
via_port_handler_.set_interrupt_delegate(this);
|
||||
tape_player_.set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
|
||||
if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) {
|
||||
diskii_.set_sleep_observer(this);
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
std::vector<std::string> rom_names = {"colour.rom"};
|
||||
switch(rom_type_) {
|
||||
switch(target.rom) {
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom"); break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom"); break;
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom"); break;
|
||||
@@ -238,15 +231,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Microdisc: rom_names.push_back("microdisc.rom"); break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: rom_names.push_back("8dos.rom"); break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: rom_names.push_back("8dos.rom"); break;
|
||||
}
|
||||
|
||||
auto roms = roms_with_names("Oric", rom_names);
|
||||
const auto roms = rom_fetcher("Oric", rom_names);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
if(!roms[index]) return false;
|
||||
if(!roms[index]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
colour_rom_ = std::move(*roms[0]);
|
||||
rom_ = std::move(*roms[1]);
|
||||
|
||||
@@ -260,8 +255,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
pravetz_rom_ = std::move(*roms[2]);
|
||||
pravetz_rom_.resize(512);
|
||||
|
||||
auto state_machine_rom = roms_with_names("DiskII", {"state-machine-16.rom"});
|
||||
if(!state_machine_rom[0]) return false;
|
||||
auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"});
|
||||
if(!state_machine_rom[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
diskii_.set_state_machine(*state_machine_rom[0]);
|
||||
} break;
|
||||
}
|
||||
@@ -270,9 +267,35 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
rom_.resize(16384);
|
||||
paged_rom_ = rom_.data();
|
||||
|
||||
if(video_output_) video_output_->set_colour_rom(colour_rom_);
|
||||
switch(target.disk_interface) {
|
||||
default: break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
|
||||
switch(target.rom) {
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10:
|
||||
tape_get_byte_address_ = 0xe630;
|
||||
tape_speed_address_ = 0x67;
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11:
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz:
|
||||
tape_get_byte_address_ = 0xe6c9;
|
||||
tape_speed_address_ = 0x024d;
|
||||
break;
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
@@ -291,41 +314,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
use_fast_tape_hack_ = activate;
|
||||
}
|
||||
|
||||
// to satisfy ConfigurationTarget::Machine
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target);
|
||||
|
||||
switch(oric_target->disk_interface) {
|
||||
default: break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!oric_target->loading_command.empty()) {
|
||||
type_string(oric_target->loading_command);
|
||||
}
|
||||
|
||||
switch(rom_type_) {
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10:
|
||||
tape_get_byte_address_ = 0xe630;
|
||||
tape_speed_address_ = 0x67;
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11:
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz:
|
||||
tape_get_byte_address_ = 0xe6c9;
|
||||
tape_speed_address_ = 0x024d;
|
||||
break;
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
bool inserted = false;
|
||||
|
||||
if(media.tapes.size()) {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
inserted = true;
|
||||
}
|
||||
@@ -410,6 +402,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flush_diskii();
|
||||
const int disk_value = diskii_.read_address(address);
|
||||
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value);
|
||||
}
|
||||
@@ -444,9 +437,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
microdisc_.run_for(Cycles(8));
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
||||
if(!diskii_is_sleeping_) {
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
diskii_.set_data_input(*value);
|
||||
diskii_.run_for(Cycles(2));
|
||||
} else {
|
||||
cycles_since_diskii_update_ += Cycles(2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -457,6 +452,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_port_handler_.flush();
|
||||
flush_diskii();
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
@@ -571,18 +567,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
|
||||
diskii_is_sleeping_ = diskii_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
|
||||
diskii_clocking_preference_ = diskii_.preferred_clocking();
|
||||
}
|
||||
|
||||
private:
|
||||
const uint16_t basic_invisible_ram_top_ = 0xffff;
|
||||
const uint16_t basic_visible_ram_top_ = 0xbfff;
|
||||
|
||||
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
// RAM and ROM
|
||||
Analyser::Static::Oric::Target::ROM rom_type_;
|
||||
std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_;
|
||||
uint8_t ram_[65536];
|
||||
Cycles cycles_since_video_update_;
|
||||
@@ -617,9 +612,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
// the Pravetz/Disk II, if in use
|
||||
Apple::DiskII diskii_;
|
||||
Cycles cycles_since_diskii_update_;
|
||||
void flush_diskii() {
|
||||
diskii_.run_for(cycles_since_diskii_update_.flush());
|
||||
}
|
||||
std::vector<uint8_t> pravetz_rom_;
|
||||
std::size_t pravetz_rom_base_pointer_ = 0;
|
||||
bool diskii_is_sleeping_ = false;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
// Overlay RAM
|
||||
uint16_t ram_top_ = basic_visible_ram_top_;
|
||||
@@ -641,13 +640,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
using namespace Oric;
|
||||
|
||||
Machine *Machine::Oric(const Analyser::Static::Target *target_hint) {
|
||||
Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint);
|
||||
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
|
||||
switch(oric_target->disk_interface) {
|
||||
default: return new ConcreteMachine<DiskInterface::None>(oric_target);
|
||||
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(oric_target);
|
||||
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(oric_target);
|
||||
default: return new ConcreteMachine<DiskInterface::None>(*oric_target, rom_fetcher);
|
||||
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
|
||||
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
@@ -25,7 +26,7 @@ class Machine {
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Oric.
|
||||
static Machine *Oric(const Analyser::Static::Target *target_hint);
|
||||
static Machine *Oric(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
|
||||
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
|
||||
|
||||
set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
crt_->set_visible_area(crt_->get_rect_for_area(54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) {
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
namespace ROMMachine {
|
||||
|
||||
/*!
|
||||
Defines the signature for a function that must be supplied by the host environment in order to give machines
|
||||
a route for fetching any system ROMs they might need.
|
||||
|
||||
The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects
|
||||
to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the
|
||||
ROM from @c names that corresponds by index, or else are the nullptr
|
||||
*/
|
||||
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher;
|
||||
|
||||
struct Machine {
|
||||
/*!
|
||||
Provides the machine with a way to obtain such ROMs as it needs.
|
||||
*/
|
||||
virtual bool set_rom_fetcher(const ROMFetcher &rom_with_name) { return true; }
|
||||
enum class Error {
|
||||
MissingROMs
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,36 +25,36 @@ namespace {
|
||||
|
||||
::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) {
|
||||
error = Machine::Error::None;
|
||||
::Machine::DynamicMachine *machine = nullptr;
|
||||
switch(target->machine) {
|
||||
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
|
||||
case Analyser::Machine::AppleII: machine = new Machine::TypedDynamicMachine<AppleII::Machine>(AppleII::Machine::AppleII()); break;
|
||||
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
|
||||
case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break;
|
||||
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
|
||||
case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); break;
|
||||
case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric(target)); break;
|
||||
case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20()); break;
|
||||
case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target)); break;
|
||||
|
||||
default:
|
||||
error = Machine::Error::UnknownMachine;
|
||||
return nullptr;
|
||||
}
|
||||
Machine::DynamicMachine *machine = nullptr;
|
||||
try {
|
||||
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<name::Machine>(name::Machine::m(target, rom_fetcher)); break;
|
||||
#define Bind(m) BindD(m, m)
|
||||
switch(target->machine) {
|
||||
Bind(AmstradCPC)
|
||||
Bind(AppleII)
|
||||
Bind(Atari2600)
|
||||
BindD(Coleco::Vision, ColecoVision)
|
||||
Bind(Electron)
|
||||
Bind(MSX)
|
||||
Bind(Oric)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
Bind(ZX8081)
|
||||
|
||||
// TODO: this shouldn't depend on CRT machine's inclusion of ROM machine.
|
||||
CRTMachine::Machine *crt_machine = machine->crt_machine();
|
||||
if(crt_machine) {
|
||||
if(!machine->crt_machine()->set_rom_fetcher(rom_fetcher)) {
|
||||
delete machine;
|
||||
error = Machine::Error::MissingROM;
|
||||
default:
|
||||
error = Machine::Error::UnknownMachine;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
|
||||
if(configuration_target) {
|
||||
machine->configuration_target()->configure_as_target(target);
|
||||
#undef Bind
|
||||
} catch(ROMMachine::Error construction_error) {
|
||||
switch(construction_error) {
|
||||
case ROMMachine::Error::MissingROMs:
|
||||
error = Machine::Error::MissingROM;
|
||||
break;
|
||||
default:
|
||||
error = Machine::Error::UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return machine;
|
||||
@@ -129,6 +129,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() {
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
|
||||
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Machine {
|
||||
|
||||
enum class Error {
|
||||
None,
|
||||
UnknownError,
|
||||
UnknownMachine,
|
||||
MissingROM,
|
||||
NoTargets
|
||||
|
||||
@@ -29,8 +29,8 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
|
||||
return get<Activity::Source>();
|
||||
}
|
||||
|
||||
ConfigurationTarget::Machine *configuration_target() override {
|
||||
return get<ConfigurationTarget::Machine>();
|
||||
MediaTarget::Machine *media_target() override {
|
||||
return get<MediaTarget::Machine>();
|
||||
}
|
||||
|
||||
CRTMachine::Machine *crt_machine() override {
|
||||
|
||||
@@ -16,25 +16,20 @@ namespace {
|
||||
The number of bytes of PCM data to allocate at once; if/when more are required,
|
||||
the class will simply allocate another batch.
|
||||
*/
|
||||
const std::size_t StandardAllocationSize = 40;
|
||||
|
||||
/// The amount of time a byte takes to output.
|
||||
const std::size_t HalfCyclesPerByte = 8;
|
||||
const std::size_t StandardAllocationSize = 320;
|
||||
|
||||
}
|
||||
|
||||
Video::Video() :
|
||||
crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) {
|
||||
|
||||
// Set a composite sampling function that assumes 1bpp input.
|
||||
// Set a composite sampling function that assumes two-level input; either a byte is 0, which is black,
|
||||
// or it is non-zero, which is white.
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
"uint texValue = texture(sampler, coordinate).r;"
|
||||
"texValue <<= int(icoordinate.x) & 7;"
|
||||
"return float(texValue & 128u);"
|
||||
"return texture(sampler, coordinate).r;"
|
||||
"}");
|
||||
crt_->set_integer_coordinate_multiplier(8.0f);
|
||||
|
||||
// Show only the centre 80% of the TV frame.
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
@@ -43,7 +38,7 @@ Video::Video() :
|
||||
|
||||
void Video::run_for(const HalfCycles half_cycles) {
|
||||
// Just keep a running total of the amount of time that remains owed to the CRT.
|
||||
cycles_since_update_ += static_cast<unsigned int>(half_cycles.as_int());
|
||||
time_since_update_ += half_cycles;
|
||||
}
|
||||
|
||||
void Video::flush() {
|
||||
@@ -53,29 +48,29 @@ void Video::flush() {
|
||||
void Video::flush(bool next_sync) {
|
||||
if(sync_) {
|
||||
// If in sync, that takes priority. Output the proper amount of sync.
|
||||
crt_->output_sync(cycles_since_update_);
|
||||
crt_->output_sync(static_cast<unsigned int>(time_since_update_.as_int()));
|
||||
} else {
|
||||
// If not presently in sync, then...
|
||||
|
||||
if(line_data_) {
|
||||
// If there is output data queued, output it either if it's being interrupted by
|
||||
// sync, or if we're past its end anyway. Otherwise let it be.
|
||||
unsigned int data_length = static_cast<unsigned int>(line_data_pointer_ - line_data_) * HalfCyclesPerByte;
|
||||
if(data_length < cycles_since_update_ || next_sync) {
|
||||
unsigned int output_length = std::min(data_length, cycles_since_update_);
|
||||
crt_->output_data(output_length, output_length / HalfCyclesPerByte);
|
||||
int data_length = static_cast<int>(line_data_pointer_ - line_data_);
|
||||
if(data_length < time_since_update_.as_int() || next_sync) {
|
||||
auto output_length = std::min(data_length, time_since_update_.as_int());
|
||||
crt_->output_data(static_cast<unsigned int>(output_length), static_cast<unsigned int>(output_length));
|
||||
line_data_pointer_ = line_data_ = nullptr;
|
||||
cycles_since_update_ -= output_length;
|
||||
time_since_update_ -= HalfCycles(output_length);
|
||||
} else return;
|
||||
}
|
||||
|
||||
// Any pending pixels being dealt with, pad with the white level.
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
|
||||
if(colour_pointer) *colour_pointer = 0xff;
|
||||
crt_->output_level(cycles_since_update_);
|
||||
crt_->output_level(static_cast<unsigned int>(time_since_update_.as_int()));
|
||||
}
|
||||
|
||||
cycles_since_update_ = 0;
|
||||
time_since_update_ = 0;
|
||||
}
|
||||
|
||||
void Video::set_sync(bool sync) {
|
||||
@@ -101,14 +96,19 @@ void Video::output_byte(uint8_t byte) {
|
||||
if(line_data_) {
|
||||
// If the buffer is full, output it now and obtain a new one
|
||||
if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
|
||||
crt_->output_data(StandardAllocationSize * HalfCyclesPerByte, StandardAllocationSize);
|
||||
cycles_since_update_ -= StandardAllocationSize * HalfCyclesPerByte;
|
||||
crt_->output_data(StandardAllocationSize, StandardAllocationSize);
|
||||
time_since_update_ -= StandardAllocationSize;
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
|
||||
if(!line_data_) return;
|
||||
}
|
||||
|
||||
line_data_pointer_[0] = byte;
|
||||
line_data_pointer_ ++;
|
||||
// Convert to one-byte-per-pixel where any non-zero value will act as white.
|
||||
uint8_t mask = 0x80;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
line_data_pointer_[c] = byte & mask;
|
||||
mask >>= 1;
|
||||
}
|
||||
line_data_pointer_ += 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class Video {
|
||||
bool sync_ = false;
|
||||
uint8_t *line_data_ = nullptr;
|
||||
uint8_t *line_data_pointer_ = nullptr;
|
||||
unsigned int cycles_since_update_ = 0;
|
||||
HalfCycles time_since_update_ = 0;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
void flush(bool next_sync);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "ZX8081.hpp"
|
||||
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
|
||||
@@ -59,14 +59,14 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
|
||||
template<bool is_zx81> class ConcreteMachine:
|
||||
public CRTMachine::Machine,
|
||||
public ConfigurationTarget::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
z80_(*this),
|
||||
tape_player_(ZX8081ClockRate),
|
||||
ay_(audio_queue_),
|
||||
@@ -74,6 +74,55 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
set_clock_rate(ZX8081ClockRate);
|
||||
speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f);
|
||||
clear_all_keys();
|
||||
|
||||
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
|
||||
const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" });
|
||||
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
|
||||
|
||||
rom_ = std::move(*roms[0]);
|
||||
rom_.resize(use_zx81_rom ? 8192 : 4096);
|
||||
|
||||
if(is_zx81) {
|
||||
tape_trap_address_ = 0x37c;
|
||||
tape_return_address_ = 0x380;
|
||||
vsync_start_ = HalfCycles(32);
|
||||
vsync_end_ = HalfCycles(64);
|
||||
automatic_tape_motor_start_address_ = 0x0340;
|
||||
automatic_tape_motor_end_address_ = 0x03c3;
|
||||
} else {
|
||||
tape_trap_address_ = 0x220;
|
||||
tape_return_address_ = 0x248;
|
||||
vsync_start_ = HalfCycles(26);
|
||||
vsync_end_ = HalfCycles(66);
|
||||
automatic_tape_motor_start_address_ = 0x0206;
|
||||
automatic_tape_motor_end_address_ = 0x024d;
|
||||
}
|
||||
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
|
||||
|
||||
switch(target.memory_model) {
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
|
||||
ram_.resize(1024);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 1023;
|
||||
break;
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
|
||||
ram_.resize(16384);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 16383;
|
||||
break;
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
|
||||
ram_.resize(65536);
|
||||
ram_base_ = 8192;
|
||||
ram_mask_ = 65535;
|
||||
break;
|
||||
}
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -105,7 +154,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
video_->run_for(cycle.length);
|
||||
}
|
||||
|
||||
if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207));
|
||||
if(is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207));
|
||||
if(!tape_advance_delay_) {
|
||||
tape_player_.run_for(cycle.length);
|
||||
} else {
|
||||
@@ -129,7 +178,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
set_vsync(false);
|
||||
}
|
||||
if(!(address & 2)) nmi_is_enabled_ = false;
|
||||
if(!(address & 1)) nmi_is_enabled_ = is_zx81_;
|
||||
if(!(address & 1)) nmi_is_enabled_ = is_zx81;
|
||||
|
||||
// The below emulates the ZonX AY expansion device.
|
||||
if(is_zx81) {
|
||||
@@ -157,7 +206,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
|
||||
// The below emulates the ZonX AY expansion device.
|
||||
if(is_zx81) {
|
||||
if((address&0xef) == 0x0f) {
|
||||
if((address&0xef) == 0xcf) {
|
||||
value &= ay_read_data();
|
||||
}
|
||||
}
|
||||
@@ -281,54 +330,6 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
void configure_as_target(const Analyser::Static::Target *target) override final {
|
||||
auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
|
||||
is_zx81_ = zx8081_target->is_ZX81;
|
||||
if(is_zx81_) {
|
||||
rom_ = zx81_rom_;
|
||||
tape_trap_address_ = 0x37c;
|
||||
tape_return_address_ = 0x380;
|
||||
vsync_start_ = HalfCycles(32);
|
||||
vsync_end_ = HalfCycles(64);
|
||||
automatic_tape_motor_start_address_ = 0x0340;
|
||||
automatic_tape_motor_end_address_ = 0x03c3;
|
||||
} else {
|
||||
rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_;
|
||||
tape_trap_address_ = 0x220;
|
||||
tape_return_address_ = 0x248;
|
||||
vsync_start_ = HalfCycles(26);
|
||||
vsync_end_ = HalfCycles(66);
|
||||
automatic_tape_motor_start_address_ = 0x0206;
|
||||
automatic_tape_motor_end_address_ = 0x024d;
|
||||
}
|
||||
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
|
||||
|
||||
switch(zx8081_target->memory_model) {
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded:
|
||||
ram_.resize(1024);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 1023;
|
||||
break;
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB:
|
||||
ram_.resize(16384);
|
||||
ram_base_ = 16384;
|
||||
ram_mask_ = 16383;
|
||||
break;
|
||||
case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB:
|
||||
ram_.resize(65536);
|
||||
ram_base_ = 8192;
|
||||
ram_mask_ = 65535;
|
||||
break;
|
||||
}
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
if(!zx8081_target->loading_command.empty()) {
|
||||
type_string(zx8081_target->loading_command);
|
||||
}
|
||||
|
||||
insert_media(target->media);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
@@ -339,30 +340,10 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) override final {
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_));
|
||||
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81));
|
||||
Utility::TypeRecipient::add_typer(string, std::move(mapper));
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override {
|
||||
const auto roms = roms_with_names(
|
||||
"ZX8081",
|
||||
{
|
||||
"zx80.rom", "zx81.rom",
|
||||
});
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
if(!roms[index]) return false;
|
||||
}
|
||||
|
||||
zx80_rom_ = std::move(*roms[0]);
|
||||
zx81_rom_ = std::move(*roms[1]);
|
||||
zx80_rom_.resize(4096);
|
||||
zx81_rom_.resize(8192);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
||||
if(is_pressed)
|
||||
@@ -436,7 +417,6 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
|
||||
|
||||
std::unique_ptr<Video> video_;
|
||||
std::vector<uint8_t> zx81_rom_, zx80_rom_;
|
||||
|
||||
uint16_t tape_trap_address_, tape_return_address_;
|
||||
uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_;
|
||||
@@ -456,7 +436,6 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
Storage::Tape::ZX8081::Parser parser_;
|
||||
|
||||
bool is_zx81_;
|
||||
bool nmi_is_enabled_ = false;
|
||||
|
||||
HalfCycles vsync_start_, vsync_end_;
|
||||
@@ -522,14 +501,12 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
using namespace ZX8081;
|
||||
|
||||
// See header; constructs and returns an instance of the ZX80 or 81.
|
||||
Machine *Machine::ZX8081(const Analyser::Static::Target *target_hint) {
|
||||
const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint);
|
||||
Machine *Machine::ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
const Analyser::Static::ZX8081::Target *const zx_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target);
|
||||
|
||||
// Instantiate the correct type of machine.
|
||||
if(hint->is_ZX81)
|
||||
return new ZX8081::ConcreteMachine<true>();
|
||||
else
|
||||
return new ZX8081::ConcreteMachine<false>();
|
||||
if(zx_target->is_ZX81) return new ZX8081::ConcreteMachine<true>(*zx_target, rom_fetcher);
|
||||
else return new ZX8081::ConcreteMachine<false>(*zx_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace ZX8081 {
|
||||
|
||||
@@ -21,7 +22,7 @@ class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
static Machine *ZX8081(const Analyser::Static::Target *target_hint);
|
||||
static Machine *ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
|
||||
virtual void set_tape_is_playing(bool is_playing) = 0;
|
||||
virtual bool get_tape_is_playing() = 0;
|
||||
|
||||
@@ -95,14 +95,14 @@ template <typename T, T reset_value, T xor_output, bool reflect_input, bool refl
|
||||
those used by the FM and MFM disk encodings.
|
||||
*/
|
||||
struct CCITT: public Generator<uint16_t, 0xffff, 0x0000, false, false> {
|
||||
CCITT() : Generator(0x1021) {}
|
||||
CCITT(): Generator(0x1021) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a generator of "standard 32-bit" CRCs.
|
||||
*/
|
||||
struct CRC32: public Generator<uint32_t, 0xffffffff, 0xffffffff, true, true> {
|
||||
CRC32() : Generator(0x04c11db7) {}
|
||||
CRC32(): Generator(0x04c11db7) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||
@@ -43,7 +44,6 @@
|
||||
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
|
||||
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
|
||||
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
|
||||
4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; };
|
||||
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
||||
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; };
|
||||
4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
||||
@@ -115,6 +115,7 @@
|
||||
4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; };
|
||||
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
|
||||
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
|
||||
4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B08A56720D72BEF0016CE5A /* Activity.xib */; };
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
|
||||
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
|
||||
@@ -124,7 +125,6 @@
|
||||
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
|
||||
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; };
|
||||
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */; };
|
||||
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */; };
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */; };
|
||||
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
|
||||
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */; };
|
||||
@@ -185,7 +185,6 @@
|
||||
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
|
||||
4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; };
|
||||
4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; };
|
||||
4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */; };
|
||||
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
||||
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518751F75E91800926311 /* PCMTrack.cpp */; };
|
||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
|
||||
@@ -214,6 +213,8 @@
|
||||
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; };
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
|
||||
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */; };
|
||||
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B55DD8120DF06680043F2E5 /* MachinePicker.xib */; };
|
||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; };
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
|
||||
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
|
||||
@@ -309,8 +310,6 @@
|
||||
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
|
||||
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
|
||||
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */; };
|
||||
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */; };
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
|
||||
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
|
||||
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; };
|
||||
@@ -597,8 +596,8 @@
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
|
||||
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
|
||||
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
|
||||
4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; };
|
||||
4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; };
|
||||
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
|
||||
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
|
||||
4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; };
|
||||
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; };
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
|
||||
@@ -607,14 +606,18 @@
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
|
||||
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
||||
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
|
||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
||||
4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; };
|
||||
4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; };
|
||||
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; };
|
||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
||||
@@ -644,6 +647,7 @@
|
||||
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
|
||||
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
|
||||
@@ -687,6 +691,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
|
||||
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
|
||||
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
|
||||
4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; };
|
||||
@@ -704,6 +709,7 @@
|
||||
4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
|
||||
4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
|
||||
4B08A2791EE3957B008B7065 /* TestMachine+ForSubclassEyesOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestMachine+ForSubclassEyesOnly.h"; sourceTree = "<group>"; };
|
||||
4B08A56820D72BEF0016CE5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Activity.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
@@ -716,7 +722,6 @@
|
||||
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = "<group>"; };
|
||||
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; };
|
||||
4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; };
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMPatchedTrackTests.mm; sourceTree = "<group>"; };
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
|
||||
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; };
|
||||
@@ -823,8 +828,6 @@
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
|
||||
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
|
||||
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
|
||||
4B4518731F75E91800926311 /* PCMSegment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMSegment.cpp; sourceTree = "<group>"; };
|
||||
4B4518741F75E91800926311 /* PCMSegment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMSegment.hpp; sourceTree = "<group>"; };
|
||||
4B4518751F75E91800926311 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
|
||||
@@ -883,6 +886,8 @@
|
||||
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
|
||||
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
|
||||
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
|
||||
4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
|
||||
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; };
|
||||
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; };
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
|
||||
@@ -1002,6 +1007,7 @@
|
||||
4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; };
|
||||
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = "<group>"; };
|
||||
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
|
||||
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; };
|
||||
@@ -1024,12 +1030,10 @@
|
||||
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
|
||||
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BA141BC2072E8A400A31EC9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
|
||||
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
|
||||
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
|
||||
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
|
||||
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = "<group>"; };
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; };
|
||||
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; };
|
||||
4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; };
|
||||
@@ -1037,7 +1041,7 @@
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
|
||||
4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; };
|
||||
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; };
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; };
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = "<group>"; };
|
||||
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
|
||||
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
|
||||
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
|
||||
@@ -1326,8 +1330,8 @@
|
||||
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
|
||||
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
|
||||
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; };
|
||||
4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiConfigurationTarget.hpp; sourceTree = "<group>"; };
|
||||
4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiConfigurationTarget.cpp; sourceTree = "<group>"; };
|
||||
4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; };
|
||||
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
|
||||
4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; };
|
||||
4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; };
|
||||
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; };
|
||||
@@ -1345,6 +1349,8 @@
|
||||
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
|
||||
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; };
|
||||
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
|
||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
|
||||
4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; };
|
||||
4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; };
|
||||
@@ -1353,10 +1359,13 @@
|
||||
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
|
||||
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
|
||||
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; };
|
||||
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
|
||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
||||
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
|
||||
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||
4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Rectangle.cpp; sourceTree = "<group>"; };
|
||||
4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Rectangle.hpp; sourceTree = "<group>"; };
|
||||
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; };
|
||||
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; };
|
||||
4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; };
|
||||
@@ -1377,6 +1386,7 @@
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
|
||||
4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; };
|
||||
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
@@ -1425,6 +1435,7 @@
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6E1D72496600532C7B /* PRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PRG.hpp; sourceTree = "<group>"; };
|
||||
4BEEE6BC20DC72EA003723BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/CompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
|
||||
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
|
||||
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1537,14 +1548,15 @@
|
||||
4B1414631B588A1100E04248 /* Test Binaries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */,
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
|
||||
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */,
|
||||
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */,
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */,
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */,
|
||||
4BE9A6B21EDE294200CBCB47 /* Zexall */,
|
||||
4BBF49B41ED2881600AB3669 /* FUSE */,
|
||||
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */,
|
||||
4BE9A6B21EDE294200CBCB47 /* Zexall */,
|
||||
);
|
||||
name = "Test Binaries";
|
||||
sourceTree = "<group>";
|
||||
@@ -1770,6 +1782,7 @@
|
||||
children = (
|
||||
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
||||
4BD060A41FE49D3C006E14BE /* Speaker */,
|
||||
4BD601A920D89F2A00CBCE57 /* Log.hpp */,
|
||||
);
|
||||
name = Outputs;
|
||||
sourceTree = "<group>";
|
||||
@@ -1841,12 +1854,10 @@
|
||||
4B4518701F75E91800926311 /* Track */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */,
|
||||
4B4518731F75E91800926311 /* PCMSegment.cpp */,
|
||||
4B4518751F75E91800926311 /* PCMTrack.cpp */,
|
||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */,
|
||||
4B4518771F75E91800926311 /* UnformattedTrack.cpp */,
|
||||
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */,
|
||||
4B4518741F75E91800926311 /* PCMSegment.hpp */,
|
||||
4B4518761F75E91800926311 /* PCMTrack.hpp */,
|
||||
4B4518881F75ECB100926311 /* Track.hpp */,
|
||||
@@ -1986,7 +1997,10 @@
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
|
||||
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
|
||||
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
|
||||
4B08A56720D72BEF0016CE5A /* Activity.xib */,
|
||||
4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */,
|
||||
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
|
||||
4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */,
|
||||
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
|
||||
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
|
||||
@@ -2005,6 +2019,15 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B55DD7F20DF06680043F2E5 /* MachinePicker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */,
|
||||
4B55DD8120DF06680043F2E5 /* MachinePicker.xib */,
|
||||
);
|
||||
path = MachinePicker;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2384,16 +2407,6 @@
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BA141C02072E8B300A31EC9 /* MachinePicker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BA141BE2072E8AF00A31EC9 /* MachinePicker.swift */,
|
||||
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */,
|
||||
);
|
||||
name = MachinePicker;
|
||||
path = "New Group";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2754,6 +2767,7 @@
|
||||
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||
@@ -2763,7 +2777,7 @@
|
||||
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
||||
4B55CE551C3B7D360093A61B /* Documents */,
|
||||
4B2A53921D117D36003C6002 /* Machine */,
|
||||
4BA141C02072E8B300A31EC9 /* MachinePicker */,
|
||||
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||
@@ -2779,7 +2793,6 @@
|
||||
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||
4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||
@@ -2819,7 +2832,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */,
|
||||
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */,
|
||||
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */,
|
||||
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */,
|
||||
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */,
|
||||
4B7041271F92C26900735E45 /* JoystickMachine.hpp */,
|
||||
@@ -2857,16 +2870,16 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */,
|
||||
4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */,
|
||||
4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */,
|
||||
4B1B88C6202E469300B67DFF /* MultiJoystickMachine.cpp */,
|
||||
4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */,
|
||||
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */,
|
||||
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */,
|
||||
4B1B88BF202E3DB200B67DFF /* MultiConfigurable.hpp */,
|
||||
4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */,
|
||||
4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */,
|
||||
4B1B88C7202E469300B67DFF /* MultiJoystickMachine.hpp */,
|
||||
4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */,
|
||||
4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */,
|
||||
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
@@ -2895,6 +2908,7 @@
|
||||
children = (
|
||||
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */,
|
||||
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */,
|
||||
4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */,
|
||||
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */,
|
||||
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
|
||||
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */,
|
||||
@@ -2902,6 +2916,7 @@
|
||||
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */,
|
||||
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */,
|
||||
4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */,
|
||||
4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */,
|
||||
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */,
|
||||
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */,
|
||||
4BC3B74C1CD194CC00F86E85 /* Shaders */,
|
||||
@@ -2909,6 +2924,15 @@
|
||||
path = Internals;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */,
|
||||
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */,
|
||||
);
|
||||
path = "Joystick Manager";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BC3B74C1CD194CC00F86E85 /* Shaders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3124,8 +3148,9 @@
|
||||
children = (
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
@@ -3277,9 +3302,11 @@
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||
4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */,
|
||||
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */,
|
||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */,
|
||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
|
||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
|
||||
4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */,
|
||||
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */,
|
||||
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
|
||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
|
||||
@@ -3288,6 +3315,7 @@
|
||||
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
|
||||
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */,
|
||||
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -3307,6 +3335,7 @@
|
||||
4BB2998A1B587D8400A49093 /* lseix in Resources */,
|
||||
4BB2994E1B587D8400A49093 /* dexn in Resources */,
|
||||
4BB299971B587D8400A49093 /* nopa in Resources */,
|
||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */,
|
||||
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */,
|
||||
4BB299521B587D8400A49093 /* eoray in Resources */,
|
||||
4BB299411B587D8400A49093 /* cpyb in Resources */,
|
||||
@@ -3610,6 +3639,7 @@
|
||||
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
|
||||
4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */,
|
||||
4B894539201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */,
|
||||
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
|
||||
@@ -3626,7 +3656,6 @@
|
||||
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */,
|
||||
4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */,
|
||||
4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */,
|
||||
4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */,
|
||||
4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */,
|
||||
4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
|
||||
@@ -3688,7 +3717,7 @@
|
||||
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */,
|
||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
|
||||
4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */,
|
||||
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */,
|
||||
4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
|
||||
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
|
||||
@@ -3799,7 +3828,6 @@
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
|
||||
4B894532201967B4007DE474 /* 6502.cpp in Sources */,
|
||||
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */,
|
||||
4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */,
|
||||
4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */,
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
|
||||
@@ -3815,6 +3843,7 @@
|
||||
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
|
||||
4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
|
||||
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */,
|
||||
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */,
|
||||
4B89451C201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
4B302184208A550100773308 /* DiskII.cpp in Sources */,
|
||||
@@ -3834,11 +3863,11 @@
|
||||
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
|
||||
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
||||
4BA141BF2072E8AF00A31EC9 /* MachinePicker.swift in Sources */,
|
||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||
4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */,
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
||||
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */,
|
||||
4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
@@ -3855,7 +3884,7 @@
|
||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
||||
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */,
|
||||
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
||||
@@ -3900,6 +3929,7 @@
|
||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
|
||||
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
|
||||
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
|
||||
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */,
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
||||
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
|
||||
@@ -3951,7 +3981,6 @@
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
|
||||
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */,
|
||||
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */,
|
||||
4B121F951E05E66800BFDA12 /* PCMPatchedTrackTests.mm in Sources */,
|
||||
4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -3980,6 +4009,14 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
4B08A56720D72BEF0016CE5A /* Activity.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B08A56820D72BEF0016CE5A /* Base */,
|
||||
);
|
||||
name = Activity.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -3996,6 +4033,14 @@
|
||||
name = OricOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B55DD8220DF06680043F2E5 /* Base */,
|
||||
);
|
||||
name = MachinePicker.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -4020,14 +4065,6 @@
|
||||
name = QuickLoadCompositeOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BA141BB2072E8A400A31EC9 /* MachinePicker.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4BA141BC2072E8A400A31EC9 /* Base */,
|
||||
);
|
||||
name = MachinePicker.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -4036,6 +4073,14 @@
|
||||
name = MainMenu.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4BC5FC2F20CDDDEE00410AA0 /* Base */,
|
||||
);
|
||||
name = AppleIIOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -4044,6 +4089,14 @@
|
||||
name = QuickLoadOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4BEEE6BC20DC72EA003723BF /* Base */,
|
||||
);
|
||||
name = CompositeOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
|
||||
113
OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib
Normal file
113
OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="activityPanel" destination="ZW7-Bw-4RP" id="GRG-Q6-RQU"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Activity" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="300" y="76" width="200" height="131"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="131"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ySY-ir-hzb" userLabel="First indicator">
|
||||
<rect key="frame" x="20" y="95" width="17" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="ySY-ir-hzb" secondAttribute="height" multiplier="1:1" id="UX0-hT-7Td"/>
|
||||
</constraints>
|
||||
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="DhQ-Di-tRT"/>
|
||||
</levelIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tah-UQ-vdf">
|
||||
<rect key="frame" x="44" y="94" width="59" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 1" id="a5P-Ci-RzC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncQ-wN-C61" userLabel="Second indicator">
|
||||
<rect key="frame" x="20" y="70" width="17" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="ncQ-wN-C61" secondAttribute="height" multiplier="1:1" id="176-v3-mVW"/>
|
||||
</constraints>
|
||||
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="jlb-bk-FPd"/>
|
||||
</levelIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="14O-Lq-Npx">
|
||||
<rect key="frame" x="44" y="69" width="61" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 2" id="NE1-CO-pGI">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0rV-Th-Zwt" userLabel="Third indicator">
|
||||
<rect key="frame" x="20" y="45" width="17" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="0rV-Th-Zwt" secondAttribute="height" multiplier="1:1" id="Ai8-b3-Nn5"/>
|
||||
</constraints>
|
||||
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="CJy-Jn-eCL"/>
|
||||
</levelIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acy-tT-OFH">
|
||||
<rect key="frame" x="44" y="44" width="61" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 3" id="FSR-y6-7WE">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bvH-EJ-TYb" userLabel="Fourth indicator">
|
||||
<rect key="frame" x="20" y="20" width="17" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="bvH-EJ-TYb" secondAttribute="height" multiplier="1:1" id="cKc-q1-2Q4"/>
|
||||
</constraints>
|
||||
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="eoN-hl-30l"/>
|
||||
</levelIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R0g-Oa-VB5">
|
||||
<rect key="frame" x="44" y="19" width="62" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 4" id="aGr-cd-jC0">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="14O-Lq-Npx" firstAttribute="centerY" secondItem="ncQ-wN-C61" secondAttribute="centerY" id="0Ht-U2-sPg"/>
|
||||
<constraint firstItem="bvH-EJ-TYb" firstAttribute="top" secondItem="0rV-Th-Zwt" secondAttribute="bottom" constant="9" id="0xw-qA-6vP"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="14O-Lq-Npx" secondAttribute="trailing" constant="20" id="5eo-XI-a3W"/>
|
||||
<constraint firstItem="Tah-UQ-vdf" firstAttribute="centerY" secondItem="ySY-ir-hzb" secondAttribute="centerY" id="6Hn-ts-mTi"/>
|
||||
<constraint firstItem="R0g-Oa-VB5" firstAttribute="leading" secondItem="bvH-EJ-TYb" secondAttribute="trailing" constant="10" id="Dgy-JI-nA1"/>
|
||||
<constraint firstItem="R0g-Oa-VB5" firstAttribute="centerY" secondItem="bvH-EJ-TYb" secondAttribute="centerY" id="Gfq-mB-Y1z"/>
|
||||
<constraint firstItem="Acy-tT-OFH" firstAttribute="centerY" secondItem="0rV-Th-Zwt" secondAttribute="centerY" id="ImF-rK-oOr"/>
|
||||
<constraint firstItem="Acy-tT-OFH" firstAttribute="leading" secondItem="0rV-Th-Zwt" secondAttribute="trailing" constant="10" id="JSU-pZ-l9Q"/>
|
||||
<constraint firstItem="ySY-ir-hzb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="KMh-EO-rxE"/>
|
||||
<constraint firstItem="0rV-Th-Zwt" firstAttribute="top" secondItem="ncQ-wN-C61" secondAttribute="bottom" constant="9" id="Q2g-yM-nlJ"/>
|
||||
<constraint firstItem="ncQ-wN-C61" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="QUI-Hc-Bcl"/>
|
||||
<constraint firstItem="0rV-Th-Zwt" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="bKh-4L-mqj"/>
|
||||
<constraint firstItem="bvH-EJ-TYb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="cPA-Ls-fLj"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Tah-UQ-vdf" secondAttribute="trailing" constant="20" id="igX-7U-TeE"/>
|
||||
<constraint firstItem="14O-Lq-Npx" firstAttribute="leading" secondItem="ncQ-wN-C61" secondAttribute="trailing" constant="10" id="jjP-qH-Pqg"/>
|
||||
<constraint firstItem="Tah-UQ-vdf" firstAttribute="leading" secondItem="ySY-ir-hzb" secondAttribute="trailing" constant="10" id="lux-Nz-K7E"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Acy-tT-OFH" secondAttribute="trailing" constant="20" id="mEe-VT-dNr"/>
|
||||
<constraint firstItem="ncQ-wN-C61" firstAttribute="top" secondItem="ySY-ir-hzb" secondAttribute="bottom" constant="9" id="mSc-jj-amw"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="R0g-Oa-VB5" secondAttribute="trailing" constant="20" id="sR8-Ph-suC"/>
|
||||
<constraint firstItem="ySY-ir-hzb" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="wbj-48-DYq"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="84" y="115"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
49
OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib
Normal file
49
OSBindings/Mac/Clock Signal/Base.lproj/AppleIIOptions.xib
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Accelerate DOS 3.3" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
|
||||
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="175" y="30"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
@@ -14,8 +16,8 @@
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="121"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="121"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="aQh-Pm-DEo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="121"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
||||
56
OSBindings/Mac/Clock Signal/Base.lproj/CompositeOptions.xib
Normal file
56
OSBindings/Mac/Clock Signal/Base.lproj/CompositeOptions.xib
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="61"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n">
|
||||
<rect key="frame" x="18" y="17" width="165" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="RGB Monitor" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="L06-TO-EF0">
|
||||
<items>
|
||||
<menuItem title="RGB Monitor" state="on" id="tJM-kX-gaK"/>
|
||||
<menuItem title="S-Video" tag="2" id="Mtc-Ht-iY8"/>
|
||||
<menuItem title="Television" tag="1" id="fFm-fS-rWG"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="setDisplayType:" target="ZW7-Bw-4RP" id="PAH-CZ-zlk"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="rh8-km-57n" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="B6L-VS-2cN"/>
|
||||
<constraint firstItem="rh8-km-57n" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="VRo-6R-IKd"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rh8-km-57n" secondAttribute="bottom" constant="20" id="jHA-lf-e7V"/>
|
||||
<constraint firstAttribute="trailing" secondItem="rh8-km-57n" secondAttribute="trailing" constant="20" id="urO-Ac-aqK"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="displayTypeButton" destination="rh8-km-57n" id="FB2-Zg-VKq"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="175" y="33.5"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -18,8 +18,8 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="133" y="235" width="600" height="450"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<rect key="contentRect" x="80" y="250" width="600" height="450"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<value key="minSize" type="size" width="228" height="171"/>
|
||||
<view key="contentView" id="gIp-Ho-8D9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@@ -111,6 +111,17 @@
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
|
||||
<menuItem title="Save Screenshot" keyEquivalent="D" id="BVJ-oQ-hUp">
|
||||
<connections>
|
||||
<action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Insert..." keyEquivalent="O" id="qQa-kh-4nz">
|
||||
<connections>
|
||||
<action selector="insertMedia:" target="-1" id="9Hs-9J-dlY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="rXU-KX-GkZ"/>
|
||||
<menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
@@ -129,22 +140,6 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Undo" enabled="NO" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" enabled="NO" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||
<menuItem title="Cut" enabled="NO" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" enabled="NO" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||
@@ -155,192 +150,6 @@
|
||||
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste and Match Style" enabled="NO" keyEquivalent="V" id="WeT-3V-zwk">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" enabled="NO" id="pa3-QI-u2k">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" enabled="NO" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||
<menuItem title="Find" id="4EN-yA-p0u">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||
<items>
|
||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||
<connections>
|
||||
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||
<connections>
|
||||
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||
<items>
|
||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||
<items>
|
||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||
<items>
|
||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
@@ -348,10 +157,33 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||
<items>
|
||||
<menuItem title="Show Options" keyEquivalent="o" id="WCd-6R-baV">
|
||||
<menuItem title="Show Activity" keyEquivalent="a" id="WCd-6R-baV">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="showOptions:" target="-1" id="ge3-Qg-kb5"/>
|
||||
<action selector="showActivity:" target="-1" id="oeF-uJ-cOS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show Options" keyEquivalent="o" id="GtG-CV-Uro">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="showOptions:" target="-1" id="M6T-DE-Duo"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Input" id="5bL-VY-cxd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Input" id="6yv-Cf-E9r">
|
||||
<items>
|
||||
<menuItem title="Use Keyboard as Keyboard" state="on" tag="100" keyEquivalent="k" id="TfX-0B-j4U">
|
||||
<connections>
|
||||
<action selector="useKeyboardAsKeyboard:" target="-1" id="6fl-fS-Oe9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Keyboard as Joystick" tag="101" enabled="NO" keyEquivalent="j" id="5mn-ch-Xv6">
|
||||
<connections>
|
||||
<action selector="useKeyboardAsJoystick:" target="-1" id="Yz7-CL-f0y"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -16,8 +16,8 @@
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="83"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="83"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -16,8 +16,8 @@
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="83"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="83"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="83"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -16,8 +16,8 @@
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -14,9 +15,9 @@
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="83" y="102" width="261" height="100"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" topStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="150" width="261" height="100"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="7Pv-WL-2Rq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="261" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSAudioQueue.h"
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
#include "KeyCodes.h"
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
import Cocoa
|
||||
|
||||
class DocumentController: NSDocumentController {
|
||||
let joystickManager = CSJoystickManager()
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AudioToolbox
|
||||
import Cocoa
|
||||
|
||||
class MachineDocument:
|
||||
NSDocument,
|
||||
@@ -40,6 +40,11 @@ class MachineDocument:
|
||||
optionsPanel?.setIsVisible(true)
|
||||
}
|
||||
|
||||
@IBOutlet var activityPanel: NSPanel!
|
||||
@IBAction func showActivity(_ sender: AnyObject!) {
|
||||
activityPanel.setIsVisible(true)
|
||||
}
|
||||
|
||||
fileprivate var audioQueue: CSAudioQueue! = nil
|
||||
fileprivate var bestEffortUpdater: CSBestEffortUpdater?
|
||||
|
||||
@@ -51,7 +56,6 @@ class MachineDocument:
|
||||
super.windowControllerDidLoadNib(aController)
|
||||
aController.window?.contentAspectRatio = self.aspectRatio()
|
||||
setupMachineOutput()
|
||||
|
||||
}
|
||||
|
||||
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
|
||||
@@ -102,7 +106,7 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) {
|
||||
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
|
||||
setupAudioQueueClockRate()
|
||||
}
|
||||
|
||||
@@ -119,6 +123,9 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
override func close() {
|
||||
activityPanel?.setIsVisible(false)
|
||||
activityPanel = nil
|
||||
|
||||
optionsPanel?.setIsVisible(false)
|
||||
optionsPanel = nil
|
||||
|
||||
@@ -147,6 +154,7 @@ class MachineDocument:
|
||||
self.machine = machine
|
||||
self.optionsPanelNibName = analysis.optionsPanelNibName
|
||||
setupMachineOutput()
|
||||
setupActivityDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +212,7 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Runtime media insertion.
|
||||
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
if let mediaSet = mediaSet {
|
||||
@@ -211,6 +220,21 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction final func insertMedia(_ sender: AnyObject!) {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window."
|
||||
openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in
|
||||
if response == .OK {
|
||||
for url in openPanel.urls {
|
||||
let mediaSet = CSMediaSet(fileAt: url)
|
||||
if let mediaSet = mediaSet {
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSDocument overrides
|
||||
override func data(ofType typeName: String) throws -> Data {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
@@ -220,6 +244,13 @@ class MachineDocument:
|
||||
func windowDidResignKey(_ notification: Notification) {
|
||||
if let machine = self.machine {
|
||||
machine.clearAllKeys()
|
||||
machine.joystickManager = nil
|
||||
}
|
||||
}
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
if let machine = self.machine {
|
||||
machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,10 +281,165 @@ class MachineDocument:
|
||||
@IBAction func createMachine(_ sender: NSButton?) {
|
||||
self.configureAs(machinePicker!.selectedMachine())
|
||||
machinePicker = nil
|
||||
sender?.window?.close()
|
||||
self.windowControllers[0].window?.endSheet(self.machinePickerPanel!)
|
||||
}
|
||||
|
||||
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
|
||||
close()
|
||||
}
|
||||
|
||||
// MARK: Joystick-via-the-keyboard selection
|
||||
@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) {
|
||||
machine.inputMode = .keyboard
|
||||
}
|
||||
|
||||
@IBAction func useKeyboardAsJoystick(_ sender: NSMenuItem?) {
|
||||
machine.inputMode = .joystick
|
||||
}
|
||||
|
||||
override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
if let menuItem = item as? NSMenuItem {
|
||||
switch item.action {
|
||||
case #selector(self.useKeyboardAsKeyboard):
|
||||
if machine == nil || !machine.hasKeyboard {
|
||||
menuItem.state = .off
|
||||
return false
|
||||
}
|
||||
|
||||
menuItem.state = machine.inputMode == .keyboard ? .on : .off
|
||||
return true
|
||||
|
||||
case #selector(self.useKeyboardAsJoystick):
|
||||
if machine == nil || !machine.hasJoystick {
|
||||
menuItem.state = .off
|
||||
return false
|
||||
}
|
||||
|
||||
menuItem.state = machine.inputMode == .joystick ? .on : .off
|
||||
return true
|
||||
|
||||
case #selector(self.showActivity(_:)):
|
||||
return self.activityPanel != nil
|
||||
|
||||
case #selector(self.insertMedia(_:)):
|
||||
return self.machine != nil && self.machine.canInsertMedia
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
return super.validateUserInterfaceItem(item)
|
||||
}
|
||||
|
||||
// Screenshot capture.
|
||||
@IBAction func saveScreenshot(_ sender: AnyObject!) {
|
||||
// Grab a date formatter and form a file name.
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .short
|
||||
dateFormatter.timeStyle = .long
|
||||
|
||||
let filename = ("Clock Signal Screen Shot " + dateFormatter.string(from: Date()) + ".png").replacingOccurrences(of: "/", with: "-")
|
||||
.replacingOccurrences(of: ":", with: ".")
|
||||
let pictursURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
|
||||
let url = pictursURL.appendingPathComponent(filename)
|
||||
|
||||
// Obtain the machine's current display.
|
||||
var imageRepresentation: NSBitmapImageRep? = nil
|
||||
self.openGLView.perform {
|
||||
imageRepresentation = self.machine.imageRepresentation
|
||||
}
|
||||
|
||||
// Encode as a PNG and save.
|
||||
let pngData = imageRepresentation!.representation(using: .png, properties: [:])
|
||||
try! pngData?.write(to: url)
|
||||
}
|
||||
|
||||
// MARK: Activity display.
|
||||
class LED {
|
||||
let levelIndicator: NSLevelIndicator
|
||||
init(levelIndicator: NSLevelIndicator) {
|
||||
self.levelIndicator = levelIndicator
|
||||
}
|
||||
var isLit = false
|
||||
var isBlinking = false
|
||||
}
|
||||
fileprivate var leds: [String: LED] = [:]
|
||||
func setupActivityDisplay() {
|
||||
var leds = machine.leds
|
||||
if leds.count > 0 {
|
||||
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil)
|
||||
showActivity(nil)
|
||||
|
||||
// Inspect the activity panel for indicators.
|
||||
var activityIndicators: [NSLevelIndicator] = []
|
||||
var textFields: [NSTextField] = []
|
||||
if let contentView = self.activityPanel.contentView {
|
||||
for view in contentView.subviews {
|
||||
if let levelIndicator = view as? NSLevelIndicator {
|
||||
activityIndicators.append(levelIndicator)
|
||||
}
|
||||
|
||||
if let textField = view as? NSTextField {
|
||||
textFields.append(textField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are fewer level indicators than LEDs, trim that list.
|
||||
if activityIndicators.count < leds.count {
|
||||
leds.removeSubrange(activityIndicators.count ..< leds.count)
|
||||
}
|
||||
|
||||
// Remove unused views.
|
||||
for c in leds.count ..< activityIndicators.count {
|
||||
textFields[c].removeFromSuperview()
|
||||
activityIndicators[c].removeFromSuperview()
|
||||
}
|
||||
|
||||
// Apply labels and create leds entries.
|
||||
for c in 0 ..< leds.count {
|
||||
textFields[c].stringValue = leds[c]
|
||||
self.leds[leds[c]] = LED(levelIndicator: activityIndicators[c])
|
||||
}
|
||||
|
||||
// Add a constraints to minimise window height.
|
||||
let heightConstraint = NSLayoutConstraint(
|
||||
item: self.activityPanel.contentView!,
|
||||
attribute: .bottom,
|
||||
relatedBy: .equal,
|
||||
toItem: activityIndicators[leds.count-1],
|
||||
attribute: .bottom,
|
||||
multiplier: 1.0,
|
||||
constant: 20.0)
|
||||
self.activityPanel.contentView?.addConstraint(heightConstraint)
|
||||
}
|
||||
}
|
||||
|
||||
func machine(_ machine: CSMachine, ledShouldBlink ledName: String) {
|
||||
// If there is such an LED, switch it off for 0.03 of a second; if it's meant
|
||||
// to be off at the end of that, leave it off. Don't allow the blinks to
|
||||
// pile up — allow there to be only one in flight at a time.
|
||||
if let led = leds[ledName] {
|
||||
DispatchQueue.main.async {
|
||||
if !led.isBlinking {
|
||||
led.levelIndicator.floatValue = 0.0
|
||||
led.isBlinking = true
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) {
|
||||
led.levelIndicator.floatValue = led.isLit ? 1.0 : 0.0
|
||||
led.isBlinking = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func machine(_ machine: CSMachine, led ledName: String, didChangeToLit isLit: Bool) {
|
||||
// If there is such an LED, switch it appropriately.
|
||||
if let led = leds[ledName] {
|
||||
DispatchQueue.main.async {
|
||||
led.levelIndicator.floatValue = isLit ? 1.0 : 0.0
|
||||
led.isLit = isLit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,39 +13,43 @@
|
||||
<string>bin</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge</string>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rom</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>chip</string>
|
||||
<string>chip.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ROM Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -53,100 +57,110 @@
|
||||
<string>uef</string>
|
||||
<string>uef.gz</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC UEF Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>prg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Program</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>g64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -157,40 +171,44 @@
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dsk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -198,20 +216,22 @@
|
||||
<string>o</string>
|
||||
<string>80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX80 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -220,178 +240,196 @@
|
||||
<string>81</string>
|
||||
<string>p81</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX81 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>csw</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tzx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cdt</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amstrad CPC Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>hfe</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HxC Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cas</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dmk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tsx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>col</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge</string>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ColecoVision Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@@ -401,20 +439,22 @@
|
||||
<string>do</string>
|
||||
<string>po</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525</string>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Apple II Disk Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -436,7 +476,7 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<string>public.app-category.entertainment</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// CSJoystickManager.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/07/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/*!
|
||||
Models a single joystick button.
|
||||
Buttons have an index and are either currently pressed, or not.
|
||||
*/
|
||||
@interface CSJoystickButton: NSObject
|
||||
/// The button index. By convention the USB spec defines the first button as number 1.
|
||||
@property(nonatomic, readonly) NSInteger index;
|
||||
@property(nonatomic, readonly) bool isPressed;
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSJoystickAxisType) {
|
||||
CSJoystickAxisTypeX,
|
||||
CSJoystickAxisTypeY,
|
||||
CSJoystickAxisTypeZ,
|
||||
};
|
||||
|
||||
/*!
|
||||
Models a joystick axis.
|
||||
Axes have a nominated type and a continuous value between 0 and 1.
|
||||
*/
|
||||
@interface CSJoystickAxis: NSObject
|
||||
@property(nonatomic, readonly) CSJoystickAxisType type;
|
||||
/// The current position of this axis in the range [0, 1].
|
||||
@property(nonatomic, readonly) float position;
|
||||
@end
|
||||
|
||||
typedef NS_OPTIONS(NSInteger, CSJoystickHatDirection) {
|
||||
CSJoystickHatDirectionUp = 1 << 0,
|
||||
CSJoystickHatDirectionDown = 1 << 1,
|
||||
CSJoystickHatDirectionLeft = 1 << 2,
|
||||
CSJoystickHatDirectionRight = 1 << 3,
|
||||
};
|
||||
|
||||
/*!
|
||||
Models a joystick hat.
|
||||
A hat is a digital directional input, so e.g. this is how thumbpads are represented.
|
||||
*/
|
||||
@interface CSJoystickHat: NSObject
|
||||
@property(nonatomic, readonly) CSJoystickHatDirection direction;
|
||||
@end
|
||||
|
||||
/*!
|
||||
Models a joystick.
|
||||
|
||||
A joystick is a collection of buttons, axes and hats, each of which holds a current
|
||||
state. The holder must use @c update to cause this joystick to read a fresh copy
|
||||
of its state.
|
||||
*/
|
||||
@interface CSJoystick: NSObject
|
||||
@property(nonatomic, readonly) NSArray<CSJoystickButton *> *buttons;
|
||||
@property(nonatomic, readonly) NSArray<CSJoystickAxis *> *axes;
|
||||
@property(nonatomic, readonly) NSArray<CSJoystickHat *> *hats;
|
||||
|
||||
- (void)update;
|
||||
@end
|
||||
|
||||
/*!
|
||||
The joystick manager watches for joystick connections and disconnections and
|
||||
offers a list of joysticks currently attached.
|
||||
|
||||
Be warned: this means using Apple's IOKit directly to watch for Bluetooth and
|
||||
USB HID devices. So to use this code, make sure you have USB and Bluetooth
|
||||
enabled for the app's sandbox.
|
||||
*/
|
||||
@interface CSJoystickManager : NSObject
|
||||
@property(nonatomic, readonly) NSArray<CSJoystick *> *joysticks;
|
||||
|
||||
/// Updates all joysticks.
|
||||
- (void)update;
|
||||
@end
|
||||
333
OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
Normal file
333
OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
Normal file
@@ -0,0 +1,333 @@
|
||||
//
|
||||
// CSJoystickManager.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/07/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
@import IOKit;
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
|
||||
#pragma mark - CSJoystickButton
|
||||
|
||||
@implementation CSJoystickButton {
|
||||
IOHIDElementRef _element;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_index = index;
|
||||
_element = (IOHIDElementRef)CFRetain(element);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
CFRelease(_element);
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
|
||||
}
|
||||
|
||||
- (IOHIDElementRef)element {
|
||||
return _element;
|
||||
}
|
||||
|
||||
- (void)setIsPressed:(bool)isPressed {
|
||||
_isPressed = isPressed;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSJoystickAxis
|
||||
|
||||
@implementation CSJoystickAxis {
|
||||
IOHIDElementRef _element;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_element = (IOHIDElementRef)CFRetain(element);
|
||||
_type = type;
|
||||
_position = 0.5f;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
CFRelease(_element);
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
|
||||
}
|
||||
|
||||
- (IOHIDElementRef)element {
|
||||
return _element;
|
||||
}
|
||||
|
||||
- (void)setPosition:(float)position {
|
||||
_position = position;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSJoystickHat
|
||||
|
||||
@implementation CSJoystickHat {
|
||||
IOHIDElementRef _element;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_element = (IOHIDElementRef)CFRetain(element);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
CFRelease(_element);
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction];
|
||||
}
|
||||
|
||||
- (IOHIDElementRef)element {
|
||||
return _element;
|
||||
}
|
||||
|
||||
- (void)setDirection:(CSJoystickHatDirection)direction {
|
||||
_direction = direction;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSJoystick
|
||||
|
||||
@implementation CSJoystick {
|
||||
IOHIDDeviceRef _device;
|
||||
}
|
||||
|
||||
- (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons
|
||||
axes:(NSArray<CSJoystickAxis *> *)axes
|
||||
hats:(NSArray<CSJoystickHat *> *)hats
|
||||
device:(IOHIDDeviceRef)device {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
// Sort buttons by index.
|
||||
_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]];
|
||||
|
||||
// Sort axes by enum value.
|
||||
_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]];
|
||||
|
||||
// Hats have no guaranteed ordering.
|
||||
_hats = hats;
|
||||
|
||||
// Keep hold of the device.
|
||||
_device = (IOHIDDeviceRef)CFRetain(device);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
CFRelease(_device);
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
// Update buttons.
|
||||
for(CSJoystickButton *button in _buttons) {
|
||||
IOHIDValueRef value;
|
||||
if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) {
|
||||
// Some pressure-sensitive buttons return values greater than 1 for hard presses,
|
||||
// but this class rationalised everything to Boolean.
|
||||
button.isPressed = !!IOHIDValueGetIntegerValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Update hats.
|
||||
for(CSJoystickHat *hat in _hats) {
|
||||
IOHIDValueRef value;
|
||||
if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) {
|
||||
// Hats report a direction, which is either one of eight or one of four.
|
||||
CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(hat.element);
|
||||
const CFIndex range = 1 + IOHIDElementGetLogicalMax(hat.element) - IOHIDElementGetLogicalMin(hat.element);
|
||||
integerValue *= 8 / range;
|
||||
|
||||
// Map from the HID direction to the bit field.
|
||||
switch(integerValue) {
|
||||
default: hat.direction = 0; break;
|
||||
case 0: hat.direction = CSJoystickHatDirectionUp; break;
|
||||
case 1: hat.direction = CSJoystickHatDirectionUp | CSJoystickHatDirectionRight; break;
|
||||
case 2: hat.direction = CSJoystickHatDirectionRight; break;
|
||||
case 3: hat.direction = CSJoystickHatDirectionRight | CSJoystickHatDirectionDown; break;
|
||||
case 4: hat.direction = CSJoystickHatDirectionDown; break;
|
||||
case 5: hat.direction = CSJoystickHatDirectionDown | CSJoystickHatDirectionLeft; break;
|
||||
case 6: hat.direction = CSJoystickHatDirectionLeft; break;
|
||||
case 7: hat.direction = CSJoystickHatDirectionLeft | CSJoystickHatDirectionUp; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update axes.
|
||||
for(CSJoystickAxis *axis in _axes) {
|
||||
IOHIDValueRef value;
|
||||
if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) {
|
||||
const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element);
|
||||
const CFIndex range = 1 + IOHIDElementGetLogicalMax(axis.element) - IOHIDElementGetLogicalMin(axis.element);
|
||||
axis.position = (float)integerValue / (float)range;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IOHIDDeviceRef)device {
|
||||
return _device;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSJoystickManager
|
||||
|
||||
@interface CSJoystickManager ()
|
||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||
@end
|
||||
|
||||
static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
|
||||
[(__bridge CSJoystickManager *)context deviceMatched:device result:result sender:sender];
|
||||
}
|
||||
|
||||
static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
|
||||
[(__bridge CSJoystickManager *)context deviceRemoved:device result:result sender:sender];
|
||||
}
|
||||
|
||||
@implementation CSJoystickManager {
|
||||
IOHIDManagerRef _hidManager;
|
||||
NSMutableArray<CSJoystick *> *_joysticks;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_joysticks = [[NSMutableArray alloc] init];
|
||||
|
||||
_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||
if(!_hidManager) return nil;
|
||||
|
||||
NSArray<NSDictionary<NSString *, NSNumber *> *> *const multiple = @[
|
||||
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick) },
|
||||
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_GamePad) },
|
||||
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_MultiAxisController) },
|
||||
];
|
||||
|
||||
IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)multiple);
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self);
|
||||
IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self);
|
||||
IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
|
||||
|
||||
if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
|
||||
NSLog(@"Failed to open HID manager");
|
||||
// something
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
|
||||
IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone);
|
||||
CFRelease(_hidManager);
|
||||
}
|
||||
|
||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||
// Double check this joystick isn't already known.
|
||||
for(CSJoystick *joystick in _joysticks) {
|
||||
if(joystick.device == device) return;
|
||||
}
|
||||
|
||||
// Prepare to collate a list of buttons, axes and hats for the new device.
|
||||
NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init];
|
||||
|
||||
// Inspect all elements for those that are comprehensible to this code.
|
||||
const CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
|
||||
for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) {
|
||||
const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index);
|
||||
|
||||
// Check that this element is either on the generic desktop page or else is a button.
|
||||
const uint32_t usagePage = IOHIDElementGetUsagePage(element);
|
||||
if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue;
|
||||
|
||||
// Then inspect the type.
|
||||
switch(IOHIDElementGetType(element)) {
|
||||
default: break;
|
||||
|
||||
case kIOHIDElementTypeInput_Button: {
|
||||
// Add a button; pretty easy stuff. 'Usage' provides a button index.
|
||||
const uint32_t usage = IOHIDElementGetUsage(element);
|
||||
[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]];
|
||||
} break;
|
||||
|
||||
case kIOHIDElementTypeInput_Misc:
|
||||
case kIOHIDElementTypeInput_Axis: {
|
||||
CSJoystickAxisType axisType;
|
||||
switch(IOHIDElementGetUsage(element)) {
|
||||
default: continue;
|
||||
|
||||
// Three analogue axes are implemented here; there are another three sets
|
||||
// of these that could be parsed in the future if interesting.
|
||||
case kHIDUsage_GD_X: axisType = CSJoystickAxisTypeX; break;
|
||||
case kHIDUsage_GD_Y: axisType = CSJoystickAxisTypeY; break;
|
||||
case kHIDUsage_GD_Z: axisType = CSJoystickAxisTypeZ; break;
|
||||
|
||||
// A hatswitch is a multi-directional control all of its own.
|
||||
case kHIDUsage_GD_Hatswitch:
|
||||
[hats addObject:[[CSJoystickHat alloc] initWithElement:element]];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the axis; if it was a hat switch or unrecognised then the code doesn't
|
||||
// reach here.
|
||||
[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]];
|
||||
} break;
|
||||
}
|
||||
}
|
||||
CFRelease(elements);
|
||||
|
||||
// Add this joystick to the list.
|
||||
[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
|
||||
}
|
||||
|
||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||
// If this joystick was recorded, remove it.
|
||||
for(CSJoystick *joystick in [_joysticks copy]) {
|
||||
if(joystick.device == device) {
|
||||
[_joysticks removeObject:joystick];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
[self.joysticks makeObjectsPerformSelector:@selector(update)];
|
||||
}
|
||||
|
||||
- (NSArray<CSJoystick *> *)joysticks {
|
||||
return [_joysticks copy];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,10 +12,13 @@
|
||||
#import "CSFastLoading.h"
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSStaticAnalyser.h"
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
@class CSMachine;
|
||||
@protocol CSMachineDelegate
|
||||
- (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine;
|
||||
- (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine;
|
||||
- (void)machine:(nonnull CSMachine *)machine led:(nonnull NSString *)led didChangeToLit:(BOOL)isLit;
|
||||
- (void)machine:(nonnull CSMachine *)machine ledShouldBlink:(nonnull NSString *)led;
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
|
||||
@@ -24,47 +27,65 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
|
||||
CSMachineVideoSignalRGB
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
CSMachineKeyboardInputModeKeyboard,
|
||||
CSMachineKeyboardInputModeJoystick
|
||||
};
|
||||
|
||||
// Deliberately low; to ensure CSMachine has been declared as an @class already.
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
@interface CSMachine : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/*!
|
||||
Initialises an instance of CSMachine.
|
||||
|
||||
@param result The CSStaticAnalyser result that describes the machine needed.
|
||||
*/
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)runForInterval:(NSTimeInterval)interval;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
|
||||
|
||||
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
|
||||
- (void)setView:(nullable CSOpenGLView *)view aspectRatio:(float)aspectRatio;
|
||||
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
|
||||
|
||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed;
|
||||
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
|
||||
- (void)clearAllKeys;
|
||||
|
||||
@property (nonatomic, strong) CSAudioQueue *audioQueue;
|
||||
@property (nonatomic, readonly) CSOpenGLView *view;
|
||||
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
|
||||
@property (nonatomic, strong, nullable) CSAudioQueue *audioQueue;
|
||||
@property (nonatomic, readonly, nonnull) CSOpenGLView *view;
|
||||
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
|
||||
|
||||
@property (nonatomic, readonly) NSString *userDefaultsPrefix;
|
||||
@property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix;
|
||||
|
||||
- (void)paste:(NSString *)string;
|
||||
- (void)paste:(nonnull NSString *)string;
|
||||
@property (nonatomic, readonly, nonnull) NSBitmapImageRep *imageRepresentation;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) CSMachineVideoSignal videoSignal;
|
||||
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
|
||||
|
||||
@property (nonatomic, readonly) BOOL canInsertMedia;
|
||||
|
||||
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
|
||||
|
||||
// Input control.
|
||||
@property (nonatomic, readonly) BOOL hasKeyboard;
|
||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
||||
@property (nonatomic, nullable) CSJoystickManager *joystickManager;
|
||||
|
||||
// LED list.
|
||||
@property (nonatomic, readonly, nonnull) NSArray<NSString *> *leds;
|
||||
|
||||
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
|
||||
@property (nonatomic, readonly) CSAtari2600 *atari2600;
|
||||
@property (nonatomic, readonly) CSZX8081 *zx8081;
|
||||
@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600;
|
||||
@property (nonatomic, readonly, nullable) CSZX8081 *zx8081;
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
||||
#include "ConfigurationTarget.hpp"
|
||||
#include "MediaTarget.hpp"
|
||||
#include "JoystickMachine.hpp"
|
||||
#include "KeyboardMachine.hpp"
|
||||
#include "KeyCodes.h"
|
||||
#include "MachineForTarget.hpp"
|
||||
#include "StandardOptions.hpp"
|
||||
#include "Typer.hpp"
|
||||
#include "../../../../Activity/Observer.hpp"
|
||||
|
||||
#import "CSStaticAnalyser+TargetVector.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
@@ -28,6 +29,7 @@
|
||||
@interface CSMachine() <CSFastLoading>
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
||||
- (void)addLED:(NSString *)led;
|
||||
@end
|
||||
|
||||
struct LockProtectedDelegate {
|
||||
@@ -50,14 +52,34 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
}
|
||||
};
|
||||
|
||||
struct ActivityObserver: public Activity::Observer {
|
||||
void register_led(const std::string &name) override {
|
||||
[machine addLED:[NSString stringWithUTF8String:name.c_str()]];
|
||||
}
|
||||
|
||||
void set_led_status(const std::string &name, bool lit) override {
|
||||
[machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit];
|
||||
}
|
||||
|
||||
void announce_drive_event(const std::string &name, DriveEvent event) override {
|
||||
[machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]];
|
||||
}
|
||||
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
ActivityObserver _activityObserver;
|
||||
NSLock *_delegateMachineAccessLock;
|
||||
|
||||
CSStaticAnalyser *_analyser;
|
||||
std::unique_ptr<Machine::DynamicMachine> _machine;
|
||||
JoystickMachine::Machine *_joystickMachine;
|
||||
|
||||
CSJoystickManager *_joystickManager;
|
||||
std::bitset<65536> _depressedKeys;
|
||||
NSMutableArray<NSString *> *_leds;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
|
||||
@@ -69,10 +91,21 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error));
|
||||
if(!_machine) return nil;
|
||||
|
||||
_inputMode = _machine->keyboard_machine() ? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick;
|
||||
|
||||
_leds = [[NSMutableArray alloc] init];
|
||||
Activity::Source *const activity_source = _machine->activity_source();
|
||||
if(activity_source) {
|
||||
_activityObserver.machine = self;
|
||||
activity_source->set_activity_observer(&_activityObserver);
|
||||
}
|
||||
|
||||
_delegateMachineAccessLock = [[NSLock alloc] init];
|
||||
|
||||
_speakerDelegate.machine = self;
|
||||
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
|
||||
_joystickMachine = _machine->joystick_machine();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -133,6 +166,54 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
|
||||
- (void)runForInterval:(NSTimeInterval)interval {
|
||||
@synchronized(self) {
|
||||
if(_joystickMachine && _joystickManager) {
|
||||
[_joystickManager update];
|
||||
|
||||
// TODO: configurable mapping from physical joypad inputs to machine inputs.
|
||||
// Until then, apply a default mapping.
|
||||
|
||||
size_t c = 0;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks();
|
||||
for(CSJoystick *joystick in _joystickManager.joysticks) {
|
||||
size_t target = c % machine_joysticks.size();
|
||||
++++c;
|
||||
|
||||
// Post the first two analogue axes presented by the controller as horizontal and vertical inputs,
|
||||
// unless the user seems to be using a hat.
|
||||
// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0]
|
||||
if(!joystick.hats.count || !joystick.hats[0].direction) {
|
||||
if(joystick.axes.count > 0) {
|
||||
const float x_axis = joystick.axes[0].position;
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis);
|
||||
}
|
||||
if(joystick.axes.count > 1) {
|
||||
const float y_axis = joystick.axes[1].position;
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
|
||||
}
|
||||
} else {
|
||||
// Forward hats as directions; hats always override analogue inputs.
|
||||
for(CSJoystickHat *hat in joystick.hats) {
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
|
||||
}
|
||||
}
|
||||
|
||||
// Forward all fire buttons, mapping as a function of index.
|
||||
if(machine_joysticks[target]->get_number_of_fire_buttons()) {
|
||||
std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons());
|
||||
for(CSJoystickButton *button in joystick.buttons) {
|
||||
if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true;
|
||||
}
|
||||
for(size_t index = 0; index < button_states.size(); ++index) {
|
||||
machine_joysticks[target]->set_input(
|
||||
Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index),
|
||||
button_states[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_machine->crt_machine()->run_for(interval);
|
||||
}
|
||||
}
|
||||
@@ -162,16 +243,65 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
keyboardMachine->type_string([paste UTF8String]);
|
||||
}
|
||||
|
||||
- (NSBitmapImageRep *)imageRepresentation {
|
||||
// Get the current viewport to establish framebuffer size. Then determine how wide the
|
||||
// centre 4/3 of that would be.
|
||||
GLint dimensions[4];
|
||||
glGetIntegerv(GL_VIEWPORT, dimensions);
|
||||
GLint proportionalWidth = (dimensions[3] * 4) / 3;
|
||||
|
||||
// Grab the framebuffer contents.
|
||||
std::vector<uint8_t> temporaryData(static_cast<size_t>(proportionalWidth * dimensions[3] * 3));
|
||||
glReadPixels((dimensions[2] - proportionalWidth) >> 1, 0, proportionalWidth, dimensions[3], GL_RGB, GL_UNSIGNED_BYTE, temporaryData.data());
|
||||
|
||||
// Generate an NSBitmapImageRep and populate it with a vertical flip
|
||||
// of the original data.
|
||||
NSBitmapImageRep *const result =
|
||||
[[NSBitmapImageRep alloc]
|
||||
initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:proportionalWidth
|
||||
pixelsHigh:dimensions[3]
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:3
|
||||
hasAlpha:NO
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSDeviceRGBColorSpace
|
||||
bytesPerRow:3 * proportionalWidth
|
||||
bitsPerPixel:0];
|
||||
|
||||
const size_t line_size = static_cast<size_t>(proportionalWidth * 3);
|
||||
for(GLint y = 0; y < dimensions[3]; ++y) {
|
||||
memcpy(
|
||||
&result.bitmapData[static_cast<size_t>(y) * line_size],
|
||||
&temporaryData[static_cast<size_t>(dimensions[3] - y - 1) * line_size],
|
||||
line_size);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)applyMedia:(const Analyser::Static::Media &)media {
|
||||
@synchronized(self) {
|
||||
ConfigurationTarget::Machine *const configurationTarget = _machine->configuration_target();
|
||||
if(configurationTarget) configurationTarget->insert_media(media);
|
||||
MediaTarget::Machine *const mediaTarget = _machine->media_target();
|
||||
if(mediaTarget) mediaTarget->insert_media(media);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setJoystickManager:(CSJoystickManager *)joystickManager {
|
||||
@synchronized(self) {
|
||||
_joystickManager = joystickManager;
|
||||
if(_joystickMachine) {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks();
|
||||
for(const auto &joystick: machine_joysticks) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
||||
auto keyboard_machine = _machine->keyboard_machine();
|
||||
if(keyboard_machine) {
|
||||
if(self.inputMode == CSMachineKeyboardInputModeKeyboard && keyboard_machine) {
|
||||
// Don't pass anything on if this is not new information.
|
||||
if(_depressedKeys[key] == !!isPressed) return;
|
||||
_depressedKeys[key] = !!isPressed;
|
||||
@@ -248,23 +378,27 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
}
|
||||
|
||||
auto joystick_machine = _machine->joystick_machine();
|
||||
if(joystick_machine) {
|
||||
if(self.inputMode == CSMachineKeyboardInputModeJoystick && joystick_machine) {
|
||||
@synchronized(self) {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks = joystick_machine->get_joysticks();
|
||||
if(!joysticks.empty()) {
|
||||
// Convert to a C++ bool so that the following calls are resolved correctly even if overloaded.
|
||||
bool is_pressed = !!isPressed;
|
||||
switch(key) {
|
||||
case VK_LeftArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Left, isPressed); break;
|
||||
case VK_RightArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed); break;
|
||||
case VK_UpArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed); break;
|
||||
case VK_DownArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed); break;
|
||||
case VK_Space: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break;
|
||||
case VK_ANSI_A: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 0), isPressed); break;
|
||||
case VK_ANSI_S: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 1), isPressed); break;
|
||||
case VK_LeftArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Left, is_pressed); break;
|
||||
case VK_RightArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Right, is_pressed); break;
|
||||
case VK_UpArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Up, is_pressed); break;
|
||||
case VK_DownArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Down, is_pressed); break;
|
||||
case VK_Space: joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); break;
|
||||
case VK_ANSI_A: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 0), is_pressed); break;
|
||||
case VK_ANSI_S: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 1), is_pressed); break;
|
||||
case VK_ANSI_D: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed); break;
|
||||
case VK_ANSI_F: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed); break;
|
||||
default:
|
||||
if(characters) {
|
||||
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput([characters characterAtIndex:0]), isPressed);
|
||||
if(characters.length) {
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed);
|
||||
} else {
|
||||
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed);
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -382,6 +516,10 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
return [[NSString stringWithUTF8String:name.c_str()] lowercaseString];
|
||||
}
|
||||
|
||||
- (BOOL)canInsertMedia {
|
||||
return !!_machine->media_target();
|
||||
}
|
||||
|
||||
#pragma mark - Special machines
|
||||
|
||||
- (CSAtari2600 *)atari2600 {
|
||||
@@ -392,4 +530,24 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP
|
||||
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
|
||||
}
|
||||
|
||||
#pragma mark - Input device queries
|
||||
|
||||
- (BOOL)hasJoystick {
|
||||
return !!_machine->joystick_machine();
|
||||
}
|
||||
|
||||
- (BOOL)hasKeyboard {
|
||||
return !!_machine->keyboard_machine();
|
||||
}
|
||||
|
||||
#pragma mark - Activity observation
|
||||
|
||||
- (void)addLED:(NSString *)led {
|
||||
[_leds addObject:led];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)leds {
|
||||
return _leds;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
|
||||
CSMachineAppleIIModelAppleII,
|
||||
CSMachineAppleIIModelAppleIIPlus
|
||||
CSMachineAppleIIModelAppleIIPlus,
|
||||
CSMachineAppleIIModelAppleIIe,
|
||||
CSMachineAppleIIModelAppleEnhancedIIe
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) {
|
||||
|
||||
@@ -168,7 +168,12 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Analyser::Machine::AppleII;
|
||||
target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus;
|
||||
switch(model) {
|
||||
default: target->model = Target::Model::II; break;
|
||||
case CSMachineAppleIIModelAppleIIPlus: target->model = Target::Model::IIplus; break;
|
||||
case CSMachineAppleIIModelAppleIIe: target->model = Target::Model::IIe; break;
|
||||
case CSMachineAppleIIModelAppleEnhancedIIe: target->model = Target::Model::EnhancedIIe; break;
|
||||
}
|
||||
switch(diskController) {
|
||||
default:
|
||||
case CSMachineAppleIIDiskControllerNone: target->disk_controller = Target::DiskController::None; break;
|
||||
@@ -183,7 +188,8 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
|
||||
- (NSString *)optionsPanelNibName {
|
||||
switch(_targets.front()->machine) {
|
||||
case Analyser::Machine::AmstradCPC: return nil;
|
||||
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
|
||||
// case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
||||
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -64,11 +64,11 @@ Gw
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
|
||||
<view key="view" id="dHz-Yv-GNq">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
|
||||
<rect key="frame" x="15" y="71" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -76,7 +76,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
|
||||
<rect key="frame" x="15" y="40" width="96" height="17"/>
|
||||
<rect key="frame" x="15" y="41" width="96" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -84,7 +84,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
|
||||
<rect key="frame" x="65" y="66" width="91" height="26"/>
|
||||
<rect key="frame" x="65" y="67" width="115" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -92,12 +92,14 @@ Gw
|
||||
<items>
|
||||
<menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/>
|
||||
<menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/>
|
||||
<menuItem title="Apple IIe" tag="2" id="AMt-WU-a0H"/>
|
||||
<menuItem title="Enhanced IIe" tag="3" id="kUz-FG-lqW"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
|
||||
<rect key="frame" x="115" y="35" width="132" height="26"/>
|
||||
<rect key="frame" x="115" y="36" width="132" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -128,11 +130,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="33" width="554" height="99"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
<rect key="frame" x="65" y="72" width="94" height="26"/>
|
||||
<rect key="frame" x="65" y="67" width="94" height="26"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -146,7 +148,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
|
||||
<rect key="frame" x="15" y="77" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -130,6 +130,8 @@ class MachinePicker: NSObject {
|
||||
var model: CSMachineAppleIIModel = .appleII
|
||||
switch appleIIModelButton!.selectedTag() {
|
||||
case 1: model = .appleIIPlus
|
||||
case 2: model = .appleIIe
|
||||
case 3: model = .appleEnhancedIIe
|
||||
case 0: fallthrough
|
||||
default: model = .appleII
|
||||
}
|
||||
@@ -15,7 +15,7 @@ class MOS6502InterruptTests: XCTestCase {
|
||||
super.setUp()
|
||||
|
||||
// create a machine full of NOPs
|
||||
machine = CSTestMachine6502()
|
||||
machine = CSTestMachine6502(is65C02: false)
|
||||
for c in 0...65535 {
|
||||
machine.setValue(0xea, forAddress: UInt16(c))
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import XCTest
|
||||
class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler {
|
||||
|
||||
private var endTime: UInt32 = 0
|
||||
private let machine = CSTestMachine6502()
|
||||
private let machine = CSTestMachine6502(is65C02: false)
|
||||
|
||||
func testImplied() {
|
||||
let code: [UInt8] = [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user