mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-25 08:25:09 +00:00
Compare commits
431 Commits
2020-03-07
...
2020-07-27
Author | SHA1 | Date | |
---|---|---|---|
|
b1e062945e | ||
|
3db4a8c312 | ||
|
db8e1b0edf | ||
|
71c3f58c99 | ||
|
7c05b1788e | ||
|
a2db6ddea5 | ||
|
6ad1e3e17e | ||
|
e097a841d2 | ||
|
fa95a17af5 | ||
|
b961665985 | ||
|
8af35bc6bb | ||
|
9b75287a52 | ||
|
84d5316aa7 | ||
|
e8cd5a0511 | ||
|
5ebbab6f35 | ||
|
84dd194afd | ||
|
e1ad1a4cb6 | ||
|
9de43dac95 | ||
|
47f121ee4c | ||
|
d8b699c869 | ||
|
a7855e8c98 | ||
|
8dcb48254a | ||
|
f6b7467d75 | ||
|
9d1d162cc8 | ||
|
4ee29b3266 | ||
|
cbb0594e6b | ||
|
8aeebdbc99 | ||
|
c7ef258494 | ||
|
4fec7c82ab | ||
|
9a952c889f | ||
|
8da7806ee9 | ||
|
aed61f6251 | ||
|
d065d6d98f | ||
|
ab20a23f2b | ||
|
e1c57b6fbe | ||
|
371c26251c | ||
|
645d198bee | ||
|
ab1a999df4 | ||
|
42f2bf05bf | ||
|
5e55d3d7c7 | ||
|
1288369865 | ||
|
d86b0d4213 | ||
|
d91cf598be | ||
|
9be56aa4a2 | ||
|
86737454a0 | ||
|
f0c0caf800 | ||
|
223a960a06 | ||
|
f72570386c | ||
|
56e5491e5c | ||
|
be1c3e9136 | ||
|
2d223305eb | ||
|
48c2dcf50e | ||
|
fa26c82273 | ||
|
0763ae38dd | ||
|
2230ac6c38 | ||
|
abe1e7f244 | ||
|
24b03f733f | ||
|
5a729f92c1 | ||
|
366793498a | ||
|
cdda3f74ab | ||
|
51f4242e9b | ||
|
d97ebae200 | ||
|
daa195a7fb | ||
|
2d5e9bf1bb | ||
|
402f2ddbd9 | ||
|
8bf5ed52ea | ||
|
b850183a1e | ||
|
f7e13356c4 | ||
|
55cc3089f9 | ||
|
a096a09c72 | ||
|
365e1f2e85 | ||
|
b9e117cdcf | ||
|
fc3a03a856 | ||
|
f6e5a2fb04 | ||
|
404c35feb5 | ||
|
b5962c58bb | ||
|
74da762ae1 | ||
|
d87c840b76 | ||
|
afb835398f | ||
|
c982c78285 | ||
|
b4cdf8d987 | ||
|
6925a04088 | ||
|
a0e534b309 | ||
|
74d1ca4fa8 | ||
|
3c896050fb | ||
|
387500f01a | ||
|
21c41ed4cb | ||
|
293ab25634 | ||
|
3ddc1a1722 | ||
|
478d081095 | ||
|
9d4b49bbb5 | ||
|
4417f81014 | ||
|
b96f7711e3 | ||
|
1875a03757 | ||
|
13336b8ad5 | ||
|
b17cceaeaf | ||
|
782a62585e | ||
|
c5d8d9127b | ||
|
336dffefe0 | ||
|
e297d4cced | ||
|
b052ca5ca2 | ||
|
68d4d7d10a | ||
|
a03211c410 | ||
|
c953ab09db | ||
|
68645742f7 | ||
|
1fbb733f7f | ||
|
6e4b8d58a5 | ||
|
7af8646470 | ||
|
945a9da94f | ||
|
2477752fa4 | ||
|
240d3c482b | ||
|
91229a1dbd | ||
|
4f9b3259d5 | ||
|
3cb1072c29 | ||
|
12ee8e4db4 | ||
|
95e98323c5 | ||
|
7431d56166 | ||
|
222c16c5b8 | ||
|
4e83e80962 | ||
|
4fdbe578cc | ||
|
c5cad865d7 | ||
|
ae5fe9225f | ||
|
327b9051c8 | ||
|
8151c24cf5 | ||
|
ee659095c2 | ||
|
8c35fe1062 | ||
|
9ca6a1031c | ||
|
59458f6444 | ||
|
e8939aada4 | ||
|
17bb3dce26 | ||
|
495024d6fe | ||
|
902b33d25d | ||
|
ac732e2e7b | ||
|
d08ffd6c8b | ||
|
530ff7471d | ||
|
79833deeaf | ||
|
405e9e7c68 | ||
|
5f13ee7c19 | ||
|
d9f02aecdf | ||
|
bcb23d9a15 | ||
|
d027450502 | ||
|
63ad1290f4 | ||
|
7c7cb61d2f | ||
|
68b165e244 | ||
|
fe1b6812f1 | ||
|
378ff39e5e | ||
|
e47cb91223 | ||
|
d62fb16a58 | ||
|
235efcb2d4 | ||
|
a6ada129e8 | ||
|
a681576d6c | ||
|
fdc234ed3b | ||
|
e2ceb77501 | ||
|
11c28357a1 | ||
|
f215405beb | ||
|
ba2a0600dc | ||
|
ab53165b34 | ||
|
a30723c3d4 | ||
|
d64b4fbc26 | ||
|
73131735fa | ||
|
5e0bea9d1c | ||
|
48afc54af6 | ||
|
d066dd2b44 | ||
|
0bf7de9d43 | ||
|
267006782f | ||
|
a9de745e51 | ||
|
ca1f3c600d | ||
|
743353a0ed | ||
|
f7c10ef9e9 | ||
|
ecb44711d1 | ||
|
603b747ac5 | ||
|
0f2f776e6a | ||
|
1308f119a6 | ||
|
51d684820f | ||
|
023d76a3e7 | ||
|
4d34d9ae2b | ||
|
c83c827484 | ||
|
b8b880a91d | ||
|
bb2f21a22e | ||
|
b3587d4cde | ||
|
39ffe45f3c | ||
|
d36e592afb | ||
|
74fb697fa6 | ||
|
512a52e88d | ||
|
41fc6c20a0 | ||
|
28881cb391 | ||
|
a16b710d22 | ||
|
a3d4c7599b | ||
|
6f16928215 | ||
|
ff3c2fdc59 | ||
|
57edfe8751 | ||
|
dcc0ee3679 | ||
|
f7a16762b4 | ||
|
375835a950 | ||
|
4481386a3d | ||
|
8b76d4007e | ||
|
4f30118b37 | ||
|
c5b746543b | ||
|
11d936331d | ||
|
4f619de675 | ||
|
80f2836cb8 | ||
|
3709aa7555 | ||
|
7c9d9ee048 | ||
|
e4335577ca | ||
|
66c2eb0414 | ||
|
f82e4ee923 | ||
|
b62ee33318 | ||
|
8596a9826f | ||
|
3f2fb1fa58 | ||
|
5f39938a19 | ||
|
d964ebd4c1 | ||
|
9458963311 | ||
|
44690b1066 | ||
|
c41028cdc7 | ||
|
64c62c16fb | ||
|
afef4f05fe | ||
|
fc0f290c85 | ||
|
81d70ee325 | ||
|
6dc7a4471d | ||
|
fcb8bd00b6 | ||
|
05c3f2a30d | ||
|
25996ce180 | ||
|
3729bddb2a | ||
|
4136428db3 | ||
|
31c6faf3c8 | ||
|
5c1ae40a9c | ||
|
4c6d0f7fa0 | ||
|
40b60fe5d4 | ||
|
eed357abb4 | ||
|
8f541602c1 | ||
|
668f4b77f3 | ||
|
303965fbb8 | ||
|
792aed242d | ||
|
dc5654b941 | ||
|
e51e2425cc | ||
|
95c6b9b55d | ||
|
ea25ead19d | ||
|
24100ec3b0 | ||
|
32437fbf8b | ||
|
5219a86a41 | ||
|
e12dc5d894 | ||
|
75315406bb | ||
|
ea42fe638a | ||
|
744211cec0 | ||
|
1a4321d7d0 | ||
|
b943441901 | ||
|
0505b82384 | ||
|
c9fb5721cd | ||
|
386a7ca442 | ||
|
e929d5d819 | ||
|
94614ae4c3 | ||
|
1223c99e0f | ||
|
1ff5ea0a6e | ||
|
9d2691d1d2 | ||
|
e4ef2c68bb | ||
|
7fffafdfd4 | ||
|
5896288edd | ||
|
c4135fad2b | ||
|
1f34214fb3 | ||
|
f899af0eef | ||
|
9f0c8bcae7 | ||
|
2bc36a6cde | ||
|
ee10fe3d2c | ||
|
a424e867f9 | ||
|
f52b40396a | ||
|
cd2ab70a58 | ||
|
a5d1941d28 | ||
|
65a3783dd2 | ||
|
b9b5c2a3bc | ||
|
12c618642e | ||
|
6ebc93c995 | ||
|
6d4e29c851 | ||
|
b3979e2fda | ||
|
983c32bf75 | ||
|
9e3614066a | ||
|
c7ad6b1b50 | ||
|
676dcf7fbb | ||
|
50d725330c | ||
|
2886dd1dae | ||
|
40424ac38b | ||
|
a4d3865394 | ||
|
0ac99e8d42 | ||
|
bdce1c464a | ||
|
475d75c16a | ||
|
32fd1897d0 | ||
|
39e6a28730 | ||
|
3852e119aa | ||
|
f19fd7c166 | ||
|
100fddcee1 | ||
|
99fa86a67e | ||
|
6568c29c54 | ||
|
c54bbc5a04 | ||
|
92d0c466c2 | ||
|
020c760976 | ||
|
cdfd7de221 | ||
|
3da2e91acf | ||
|
3948304172 | ||
|
4a295cd95e | ||
|
6f7c8b35c5 | ||
|
e58ba27c00 | ||
|
0aceddd088 | ||
|
30ff399218 | ||
|
a7e63b61eb | ||
|
b13b0d9311 | ||
|
d8380dc3e2 | ||
|
d805e9a8f0 | ||
|
aa45142728 | ||
|
09d1aed3a5 | ||
|
a1f80b5142 | ||
|
cb1970ebab | ||
|
d3fbdba77c | ||
|
632d797c9d | ||
|
559a2d81c1 | ||
|
7a5f23c0a5 | ||
|
84b115f15f | ||
|
a0d14f4030 | ||
|
dd6769bfbc | ||
|
027af5acca | ||
|
db4b71fc9a | ||
|
d9e41d42b5 | ||
|
0ed7d257e1 | ||
|
335a68396f | ||
|
84cdf6130f | ||
|
b0abc4f7bb | ||
|
ab81d1093d | ||
|
e4d4e4e002 | ||
|
cc357a6afa | ||
|
dfc1c7d358 | ||
|
7ed8e33622 | ||
|
474822e83d | ||
|
fe3942c5b3 | ||
|
f417fa82a4 | ||
|
c4b114133a | ||
|
2f4b0c2b9a | ||
|
a491650c8b | ||
|
6805acd74f | ||
|
95c68c76e1 | ||
|
60aa383c95 | ||
|
edc553fa1d | ||
|
4f2ebad8e0 | ||
|
1810ef60be | ||
|
f720a6201b | ||
|
cfb75b58ca | ||
|
4fbe983527 | ||
|
272383cac7 | ||
|
39380c63cb | ||
|
ea26f4f7bf | ||
|
5fd2be3c8e | ||
|
2320b5c1fe | ||
|
e5cbdfc67c | ||
|
894d196b64 | ||
|
af037649c3 | ||
|
cfca3e2507 | ||
|
7a12a0149a | ||
|
fcdc1bfbd0 | ||
|
d1d14ba9a0 | ||
|
0e502f6d5c | ||
|
d3bac57d6a | ||
|
bd1b4b8a9f | ||
|
38d81c394f | ||
|
72103a4adb | ||
|
e6bae261c4 | ||
|
5edb0c0ee7 | ||
|
442ce403f9 | ||
|
7398cb44e2 | ||
|
15d54dfb4c | ||
|
9087bb9b08 | ||
|
0c689e85a5 | ||
|
75f2b0487e | ||
|
5a1bae8a9c | ||
|
129bc485bf | ||
|
69277bbb27 | ||
|
b8b335f67d | ||
|
eef7868199 | ||
|
23aa7ea85f | ||
|
c1b69fd091 | ||
|
7ab7efdbc1 | ||
|
b8ebdc012f | ||
|
9995d776de | ||
|
c6f35c9aac | ||
|
615ea2f573 | ||
|
311458f41f | ||
|
b2a381d401 | ||
|
ffc1b0ff29 | ||
|
ead2823322 | ||
|
a7e1920597 | ||
|
ec6664f590 | ||
|
8c6ca89da2 | ||
|
b6e81242e7 | ||
|
f9ca443667 | ||
|
394ee61c78 | ||
|
1d40aa687e | ||
|
8e3bf0dbca | ||
|
2031a33edf | ||
|
fc3d3c76f8 | ||
|
880bed04f5 | ||
|
f9c8470b20 | ||
|
36acc2dddd | ||
|
a59963b6a0 | ||
|
cab4bead72 | ||
|
1a2872c815 | ||
|
f27e0a141d | ||
|
52f644c4f1 | ||
|
06c08a0574 | ||
|
724e2e6d27 | ||
|
fd052189ca | ||
|
044a2b67e1 | ||
|
7e8b86e9bb | ||
|
ce80825abb | ||
|
a99bb3ba6d | ||
|
3428e9887d | ||
|
5a8fcac4dc | ||
|
6a9b14f7d1 | ||
|
a74d8bd6e8 | ||
|
3c70f056ed | ||
|
a546880a65 | ||
|
238145f27f | ||
|
0502e6be67 | ||
|
6a8c6f5a06 | ||
|
5248475e73 | ||
|
d6c6b9bdb8 | ||
|
7bf04d5338 | ||
|
9668ec789a | ||
|
ead32fb6b2 | ||
|
45a391d69e | ||
|
15bc18b64f | ||
|
fb0343cafb | ||
|
7d9bedf7de | ||
|
6c99048211 | ||
|
2638a901d9 | ||
|
71083fd0f7 |
18
.github/workflows/ccpp.yml
vendored
18
.github/workflows/ccpp.yml
vendored
@@ -1,22 +1,16 @@
|
||||
name: SDL/Ubuntu
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
|
||||
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
|
||||
- name: Make
|
||||
run: cd OSBindings/SDL; scons
|
||||
working-directory: OSBindings/SDL
|
||||
run: scons -j$(nproc --all)
|
||||
|
@@ -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) {}
|
||||
virtual void register_led([[maybe_unused]] 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) {}
|
||||
virtual void register_drive([[maybe_unused]] 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) {}
|
||||
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
|
||||
|
||||
enum class DriveEvent {
|
||||
StepNormal,
|
||||
@@ -39,10 +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) {}
|
||||
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] 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) {}
|
||||
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -11,20 +11,20 @@
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
float ConfidenceCounter::get_confidence() {
|
||||
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
|
||||
return float(hits_) / float(hits_ + misses_);
|
||||
}
|
||||
|
||||
void ConfidenceCounter::add_hit() {
|
||||
hits_++;
|
||||
++hits_;
|
||||
}
|
||||
|
||||
void ConfidenceCounter::add_miss() {
|
||||
misses_++;
|
||||
++misses_;
|
||||
}
|
||||
|
||||
void ConfidenceCounter::add_equivocal() {
|
||||
if(hits_ > misses_) {
|
||||
hits_++;
|
||||
misses_++;
|
||||
++hits_;
|
||||
++misses_;
|
||||
}
|
||||
}
|
||||
|
@@ -35,8 +35,8 @@ class ConfidenceSummary: public ConfidenceSource {
|
||||
float get_confidence() final;
|
||||
|
||||
private:
|
||||
std::vector<ConfidenceSource *> sources_;
|
||||
std::vector<float> weights_;
|
||||
const std::vector<ConfidenceSource *> sources_;
|
||||
const std::vector<float> weights_;
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
|
@@ -1,94 +0,0 @@
|
||||
//
|
||||
// MultiCRTMachine.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiCRTMachine.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
|
||||
speaker_ = MultiSpeaker::create(machines);
|
||||
}
|
||||
|
||||
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
|
||||
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
|
||||
// to a separate queue and this queue will block until all are done.
|
||||
volatile std::size_t outstanding_machines;
|
||||
std::condition_variable condition;
|
||||
std::mutex mutex;
|
||||
{
|
||||
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
outstanding_machines = machines_.size();
|
||||
|
||||
for(std::size_t index = 0; index < machines_.size(); ++index) {
|
||||
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
|
||||
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
|
||||
if(crt_machine) function(crt_machine);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
outstanding_machines--;
|
||||
condition.notify_all();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
|
||||
}
|
||||
|
||||
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
|
||||
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
|
||||
for(const auto &machine: machines_) {
|
||||
CRTMachine::Machine *const crt_machine = machine->crt_machine();
|
||||
if(crt_machine) function(crt_machine);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
scan_target_ = scan_target;
|
||||
|
||||
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
|
||||
if(crt_machine) crt_machine->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus MultiCRTMachine::get_scan_status() const {
|
||||
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
|
||||
if(crt_machine) crt_machine->get_scan_status();
|
||||
|
||||
return Outputs::Display::ScanStatus();
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
void MultiCRTMachine::run_for(Time::Seconds duration) {
|
||||
perform_parallel([duration](::CRTMachine::Machine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
if(delegate_) delegate_->multi_crt_did_run_machines();
|
||||
}
|
||||
|
||||
void MultiCRTMachine::did_change_machine_order() {
|
||||
if(scan_target_) scan_target_->will_change_owner();
|
||||
|
||||
perform_serial([](::CRTMachine::Machine *machine) {
|
||||
machine->set_scan_target(nullptr);
|
||||
});
|
||||
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
|
||||
if(crt_machine) crt_machine->set_scan_target(scan_target_);
|
||||
|
||||
if(speaker_) {
|
||||
speaker_->set_new_front_machine(machines_.front().get());
|
||||
}
|
||||
}
|
@@ -12,6 +12,97 @@
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
namespace {
|
||||
|
||||
class MultiStruct: public Reflection::Struct {
|
||||
public:
|
||||
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
|
||||
for(auto device: devices) {
|
||||
options_.emplace_back(device->get_options());
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
auto options = options_.begin();
|
||||
for(auto device: devices_) {
|
||||
device->set_options(*options);
|
||||
++options;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> all_keys() const final {
|
||||
std::set<std::string> keys;
|
||||
for(auto &options: options_) {
|
||||
const auto new_keys = options->all_keys();
|
||||
keys.insert(new_keys.begin(), new_keys.end());
|
||||
}
|
||||
return std::vector<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
std::vector<std::string> values_for(const std::string &name) const final {
|
||||
std::set<std::string> values;
|
||||
for(auto &options: options_) {
|
||||
const auto new_values = options->values_for(name);
|
||||
values.insert(new_values.begin(), new_values.end());
|
||||
}
|
||||
return std::vector<std::string>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
const std::type_info *type_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t count_of(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto info = options->type_of(name);
|
||||
if(info) return options->count_of(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *get(const std::string &name) const final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *get(const std::string &name) final {
|
||||
for(auto &options: options_) {
|
||||
auto value = options->get(name);
|
||||
if(value) return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set(const std::string &name, const void *value, size_t offset) final {
|
||||
const auto safe_type = type_of(name);
|
||||
if(!safe_type) return;
|
||||
|
||||
// Set this property only where the child's type is the same as that
|
||||
// which was returned from here for type_of.
|
||||
for(auto &options: options_) {
|
||||
const auto type = options->type_of(name);
|
||||
if(!type) continue;
|
||||
|
||||
if(*type == *safe_type) {
|
||||
options->set(name, value, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Configurable::Device *> &devices_;
|
||||
std::vector<std::unique_ptr<Reflection::Struct>> options_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
for(const auto &machine: machines) {
|
||||
Configurable::Device *device = machine->configurable_device();
|
||||
@@ -19,46 +110,11 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> options;
|
||||
|
||||
// Produce the list of unique options.
|
||||
for(const auto &device : devices_) {
|
||||
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
|
||||
for(auto &option : device_options) {
|
||||
if(std::find(options.begin(), options.end(), option) == options.end()) {
|
||||
options.push_back(std::move(option));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &str) {
|
||||
const auto options = dynamic_cast<MultiStruct *>(str.get());
|
||||
options->apply();
|
||||
}
|
||||
|
||||
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
|
||||
for(const auto &device : devices_) {
|
||||
device->set_selections(selection_by_option);
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
|
||||
Configurable::SelectionSet set;
|
||||
for(const auto &device : devices_) {
|
||||
Configurable::SelectionSet device_set = device->get_accurate_selections();
|
||||
for(auto &selection : device_set) {
|
||||
set.insert(std::move(selection));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
|
||||
Configurable::SelectionSet set;
|
||||
for(const auto &device : devices_) {
|
||||
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
|
||||
for(auto &selection : device_set) {
|
||||
set.insert(std::move(selection));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
|
||||
return std::make_unique<MultiStruct>(devices_);
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#define MultiConfigurable_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Configurable/Configurable.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -28,10 +29,8 @@ class MultiConfigurable: public Configurable::Device {
|
||||
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
// Below is the standard Configurable::Device interface; see there for documentation.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() final;
|
||||
void set_selections(const Configurable::SelectionSet &selection_by_option) final;
|
||||
Configurable::SelectionSet get_accurate_selections() final;
|
||||
Configurable::SelectionSet get_user_friendly_selections() final;
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
|
||||
std::unique_ptr<Reflection::Struct> get_options() final;
|
||||
|
||||
private:
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
|
@@ -16,7 +16,7 @@ namespace {
|
||||
|
||||
class MultiJoystick: public Inputs::Joystick {
|
||||
public:
|
||||
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
|
||||
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
|
||||
for(const auto &machine: machines) {
|
||||
const auto &joysticks = machine->get_joysticks();
|
||||
if(joysticks.size() >= index) {
|
||||
@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Input> &get_inputs() final {
|
||||
const std::vector<Input> &get_inputs() final {
|
||||
if(inputs.empty()) {
|
||||
for(const auto &joystick: joysticks_) {
|
||||
std::vector<Input> joystick_inputs = joystick->get_inputs();
|
||||
@@ -67,9 +67,9 @@ class MultiJoystick: public Inputs::Joystick {
|
||||
|
||||
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
std::size_t total_joysticks = 0;
|
||||
std::vector<JoystickMachine::Machine *> joystick_machines;
|
||||
std::vector<MachineTypes::JoystickMachine *> joystick_machines;
|
||||
for(const auto &machine: machines) {
|
||||
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
|
||||
auto joystick_machine = machine->joystick_machine();
|
||||
if(joystick_machine) {
|
||||
joystick_machines.push_back(joystick_machine);
|
||||
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
|
||||
|
@@ -23,7 +23,7 @@ namespace Dynamic {
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiJoystickMachine: public JoystickMachine::Machine {
|
||||
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
public:
|
||||
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ using namespace Analyser::Dynamic;
|
||||
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
|
||||
keyboard_(machines_) {
|
||||
for(const auto &machine: machines) {
|
||||
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
|
||||
auto keyboard_machine = machine->keyboard_machine();
|
||||
if(keyboard_machine) machines_.push_back(keyboard_machine);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::can_type(char c) {
|
||||
bool MultiKeyboardMachine::can_type(char c) const {
|
||||
bool can_type = true;
|
||||
for(const auto &machine: machines_) {
|
||||
can_type &= machine->can_type(c);
|
||||
@@ -48,7 +48,7 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
|
||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
|
||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
|
||||
: machines_(machines) {
|
||||
for(const auto &machine: machines_) {
|
||||
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
|
||||
@@ -70,10 +70,10 @@ void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
|
||||
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() const {
|
||||
return observed_keys_;
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
|
||||
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() const {
|
||||
return is_exclusive_;
|
||||
}
|
||||
|
@@ -24,21 +24,21 @@ namespace Dynamic {
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
class MultiKeyboardMachine: public KeyboardMachine::Machine {
|
||||
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
private:
|
||||
std::vector<::KeyboardMachine::Machine *> machines_;
|
||||
std::vector<MachineTypes::KeyboardMachine *> machines_;
|
||||
|
||||
class MultiKeyboard: public Inputs::Keyboard {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() final;
|
||||
bool is_exclusive() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
|
||||
private:
|
||||
const std::vector<::KeyboardMachine::Machine *> &machines_;
|
||||
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
|
||||
std::set<Key> observed_keys_;
|
||||
bool is_exclusive_ = false;
|
||||
};
|
||||
@@ -51,7 +51,7 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
|
||||
void clear_all_keys() final;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final;
|
||||
void type_string(const std::string &) final;
|
||||
bool can_type(char c) final;
|
||||
bool can_type(char c) const final;
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
|
@@ -12,7 +12,7 @@ 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();
|
||||
auto media_target = machine->media_target();
|
||||
if(media_target) targets_.push_back(media_target);
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ namespace Dynamic {
|
||||
Makes a static internal copy of the list of machines; makes no guarantees about the
|
||||
order of delivered messages.
|
||||
*/
|
||||
struct MultiMediaTarget: public MediaTarget::Machine {
|
||||
struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
public:
|
||||
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
|
||||
|
||||
@@ -32,7 +32,7 @@ struct MultiMediaTarget: public MediaTarget::Machine {
|
||||
bool insert_media(const Analyser::Static::Media &media) final;
|
||||
|
||||
private:
|
||||
std::vector<MediaTarget::Machine *> targets_;
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
|
105
Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp
Normal file
105
Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// MultiProducer.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MultiProducer.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
// MARK: - MultiInterface
|
||||
|
||||
template <typename MachineType>
|
||||
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) {
|
||||
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
|
||||
// to a separate queue and this queue will block until all are done.
|
||||
volatile std::size_t outstanding_machines;
|
||||
std::condition_variable condition;
|
||||
std::mutex mutex;
|
||||
{
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
std::lock_guard lock(mutex);
|
||||
outstanding_machines = machines_.size();
|
||||
|
||||
for(std::size_t index = 0; index < machines_.size(); ++index) {
|
||||
const auto machine = ::Machine::get<MachineType>(*machines_[index].get());
|
||||
queues_[index].enqueue([&mutex, &condition, machine, function, &outstanding_machines]() {
|
||||
if(machine) function(machine);
|
||||
|
||||
std::lock_guard lock(mutex);
|
||||
outstanding_machines--;
|
||||
condition.notify_all();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock lock(mutex);
|
||||
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
|
||||
}
|
||||
|
||||
template <typename MachineType>
|
||||
void MultiInterface<MachineType>::perform_serial(const std::function<void(MachineType *)> &function) {
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
for(const auto &machine: machines_) {
|
||||
const auto typed_machine = ::Machine::get<MachineType>(*machine.get());
|
||||
if(typed_machine) function(typed_machine);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MultiScanProducer
|
||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
scan_target_ = scan_target;
|
||||
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
const auto machine = machines_.front()->scan_producer();
|
||||
if(machine) machine->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus MultiScanProducer::get_scan_status() const {
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
const auto machine = machines_.front()->scan_producer();
|
||||
if(machine) return machine->get_scan_status();
|
||||
return Outputs::Display::ScanStatus();
|
||||
}
|
||||
|
||||
void MultiScanProducer::did_change_machine_order() {
|
||||
if(scan_target_) scan_target_->will_change_owner();
|
||||
|
||||
perform_serial([](MachineTypes::ScanProducer *machine) {
|
||||
machine->set_scan_target(nullptr);
|
||||
});
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
const auto machine = machines_.front()->scan_producer();
|
||||
if(machine) machine->set_scan_target(scan_target_);
|
||||
}
|
||||
|
||||
// MARK: - MultiAudioProducer
|
||||
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
|
||||
speaker_ = MultiSpeaker::create(machines);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *MultiAudioProducer::get_speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
void MultiAudioProducer::did_change_machine_order() {
|
||||
if(speaker_) {
|
||||
speaker_->set_new_front_machine(machines_.front().get());
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MultiTimedMachine
|
||||
|
||||
void MultiTimedMachine::run_for(Time::Seconds duration) {
|
||||
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
|
||||
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
|
||||
});
|
||||
|
||||
if(delegate_) delegate_->did_run_machines(this);
|
||||
}
|
@@ -1,16 +1,16 @@
|
||||
//
|
||||
// MultiCRTMachine.hpp
|
||||
// MultiProducer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/01/2018.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MultiCRTMachine_hpp
|
||||
#define MultiCRTMachine_hpp
|
||||
#ifndef MultiProducer_hpp
|
||||
#define MultiProducer_hpp
|
||||
|
||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../../Machines/CRTMachine.hpp"
|
||||
#include "../../../../Machines/MachineTypes.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "MultiSpeaker.hpp"
|
||||
@@ -22,6 +22,91 @@
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
|
||||
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(MachineType *)> &);
|
||||
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(MachineType *)> &);
|
||||
|
||||
protected:
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void did_run_machines(MultiTimedMachine *) = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void run_for(Time::Seconds duration) final;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles) final {}
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
|
||||
public:
|
||||
using MultiInterface::MultiInterface;
|
||||
|
||||
/*!
|
||||
Informs the MultiScanProducer that the order of machines has changed; it
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
|
||||
private:
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
};
|
||||
|
||||
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
|
||||
public:
|
||||
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
|
||||
|
||||
/*!
|
||||
Informs the MultiAudio that the order of machines has changed; it
|
||||
uses this as an opportunity to switch speaker delegates as appropriate.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
|
||||
private:
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the CRT machine interface to multiple machines.
|
||||
|
||||
@@ -29,61 +114,9 @@ namespace Dynamic {
|
||||
acquiring a supplied mutex. The owner should also call did_change_machine_order()
|
||||
if the order of machines changes.
|
||||
*/
|
||||
class MultiCRTMachine: public CRTMachine::Machine {
|
||||
public:
|
||||
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
|
||||
|
||||
/*!
|
||||
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
|
||||
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
|
||||
are necessary to bridge the gap between one machine and the next.
|
||||
*/
|
||||
void did_change_machine_order();
|
||||
|
||||
/*!
|
||||
Provides a mechanism by which a delegate can be informed each time a call to run_for has
|
||||
been received.
|
||||
*/
|
||||
struct Delegate {
|
||||
virtual void multi_crt_did_run_machines() = 0;
|
||||
};
|
||||
/// Sets @c delegate as the receiver of delegate messages.
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
// Below is the standard CRTMachine::Machine interface; see there for documentation.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
|
||||
Outputs::Display::ScanStatus get_scan_status() const final;
|
||||
Outputs::Speaker::Speaker *get_speaker() final;
|
||||
void run_for(Time::Seconds duration) final;
|
||||
|
||||
private:
|
||||
void run_for(const Cycles cycles) final {}
|
||||
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
MultiSpeaker *speaker_ = nullptr;
|
||||
Delegate *delegate_ = nullptr;
|
||||
Outputs::Display::ScanTarget *scan_target_ = nullptr;
|
||||
|
||||
/*!
|
||||
Performs a parallel for operation across all machines, performing the supplied
|
||||
function on each and returning only once all applications have completed.
|
||||
|
||||
No guarantees are extended as to which thread operations will occur on.
|
||||
*/
|
||||
void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
|
||||
|
||||
/*!
|
||||
Performs a serial for operation across all machines, performing the supplied
|
||||
function on each on the calling thread.
|
||||
*/
|
||||
void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* MultiCRTMachine_hpp */
|
||||
#endif /* MultiProducer_hpp */
|
@@ -13,7 +13,7 @@ using namespace Analyser::Dynamic;
|
||||
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
|
||||
std::vector<Outputs::Speaker::Speaker *> speakers;
|
||||
for(const auto &machine: machines) {
|
||||
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
|
||||
Outputs::Speaker::Speaker *speaker = machine->audio_producer()->get_speaker();
|
||||
if(speaker) speakers.push_back(speaker);
|
||||
}
|
||||
if(speakers.empty()) return nullptr;
|
||||
@@ -34,7 +34,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
|
||||
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
|
||||
}
|
||||
|
||||
return ideal / static_cast<float>(speakers_.size());
|
||||
return ideal / float(speakers_.size());
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
|
||||
@@ -54,6 +54,12 @@ bool MultiSpeaker::get_is_stereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_volume(float volume) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
@@ -61,7 +67,7 @@ void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
|
||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
if(!delegate_) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
}
|
||||
did_complete_samples(this, buffer, stereo_output_);
|
||||
@@ -70,7 +76,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
|
||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
if(!delegate_) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
if(speaker != front_speaker_) return;
|
||||
}
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
@@ -78,8 +84,8 @@ void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
|
||||
|
||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->crt_machine()->get_speaker();
|
||||
std::lock_guard lock_guard(front_speaker_mutex_);
|
||||
front_speaker_ = machine->audio_producer()->get_speaker();
|
||||
}
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_change_input_clock(this);
|
||||
|
@@ -42,6 +42,7 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
|
@@ -16,74 +16,55 @@ using namespace Analyser::Dynamic;
|
||||
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
|
||||
machines_(std::move(machines)),
|
||||
configurable_(machines_),
|
||||
crt_machine_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines),
|
||||
timed_machine_(machines_, machines_mutex_),
|
||||
scan_producer_(machines_, machines_mutex_),
|
||||
audio_producer_(machines_, machines_mutex_),
|
||||
joystick_machine_(machines_),
|
||||
keyboard_machine_(machines_),
|
||||
media_target_(machines_) {
|
||||
crt_machine_.set_delegate(this);
|
||||
timed_machine_.set_delegate(this);
|
||||
}
|
||||
|
||||
Activity::Source *MultiMachine::activity_source() {
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
MediaTarget::Machine *MultiMachine::media_target() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->media_target();
|
||||
} else {
|
||||
return &media_target_;
|
||||
#define Provider(type, name, member) \
|
||||
type *MultiMachine::name() { \
|
||||
if(has_picked_) { \
|
||||
return machines_.front()->name(); \
|
||||
} else { \
|
||||
return &member; \
|
||||
} \
|
||||
}
|
||||
}
|
||||
|
||||
CRTMachine::Machine *MultiMachine::crt_machine() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->crt_machine();
|
||||
} else {
|
||||
return &crt_machine_;
|
||||
}
|
||||
}
|
||||
Provider(Configurable::Device, configurable_device, configurable_)
|
||||
Provider(MachineTypes::TimedMachine, timed_machine, timed_machine_)
|
||||
Provider(MachineTypes::ScanProducer, scan_producer, scan_producer_)
|
||||
Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
|
||||
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
|
||||
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
|
||||
Provider(MachineTypes::MediaTarget, media_target, media_target_)
|
||||
|
||||
JoystickMachine::Machine *MultiMachine::joystick_machine() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->joystick_machine();
|
||||
} else {
|
||||
return &joystick_machine_;
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->keyboard_machine();
|
||||
} else {
|
||||
return &keyboard_machine_;
|
||||
}
|
||||
}
|
||||
|
||||
MouseMachine::Machine *MultiMachine::mouse_machine() {
|
||||
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
|
||||
// TODO.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Configurable::Device *MultiMachine::configurable_device() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->configurable_device();
|
||||
} else {
|
||||
return &configurable_;
|
||||
}
|
||||
}
|
||||
#undef Provider
|
||||
|
||||
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
|
||||
return
|
||||
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
|
||||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
|
||||
(machines.front()->timed_machine()->get_confidence() > 0.9f) ||
|
||||
(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
|
||||
}
|
||||
|
||||
void MultiMachine::multi_crt_did_run_machines() {
|
||||
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
|
||||
void MultiMachine::did_run_machines(MultiTimedMachine *) {
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
#ifndef NDEBUG
|
||||
for(const auto &machine: machines_) {
|
||||
CRTMachine::Machine *crt = machine->crt_machine();
|
||||
LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
|
||||
auto timed_machine = machine->timed_machine();
|
||||
LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
|
||||
}
|
||||
LOGNBR(std::endl);
|
||||
#endif
|
||||
@@ -91,13 +72,14 @@ void MultiMachine::multi_crt_did_run_machines() {
|
||||
DynamicMachine *front = machines_.front().get();
|
||||
std::stable_sort(machines_.begin(), machines_.end(),
|
||||
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
|
||||
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
|
||||
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
|
||||
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
|
||||
auto lhs_timed = lhs->timed_machine();
|
||||
auto rhs_timed = rhs->timed_machine();
|
||||
return lhs_timed->get_confidence() > rhs_timed->get_confidence();
|
||||
});
|
||||
|
||||
if(machines_.front().get() != front) {
|
||||
crt_machine_.did_change_machine_order();
|
||||
scan_producer_.did_change_machine_order();
|
||||
audio_producer_.did_change_machine_order();
|
||||
}
|
||||
|
||||
if(would_collapse(machines_)) {
|
||||
|
@@ -11,8 +11,9 @@
|
||||
|
||||
#include "../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include "Implementation/MultiProducer.hpp"
|
||||
#include "Implementation/MultiConfigurable.hpp"
|
||||
#include "Implementation/MultiCRTMachine.hpp"
|
||||
#include "Implementation/MultiProducer.hpp"
|
||||
#include "Implementation/MultiJoystickMachine.hpp"
|
||||
#include "Implementation/MultiKeyboardMachine.hpp"
|
||||
#include "Implementation/MultiMediaTarget.hpp"
|
||||
@@ -38,7 +39,7 @@ namespace Dynamic {
|
||||
If confidence for any machine becomes disproportionately low compared to
|
||||
the others in the set, that machine stops running.
|
||||
*/
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
|
||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
|
||||
public:
|
||||
/*!
|
||||
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
|
||||
@@ -52,21 +53,25 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
|
||||
|
||||
Activity::Source *activity_source() final;
|
||||
Configurable::Device *configurable_device() final;
|
||||
CRTMachine::Machine *crt_machine() final;
|
||||
JoystickMachine::Machine *joystick_machine() final;
|
||||
MouseMachine::Machine *mouse_machine() final;
|
||||
KeyboardMachine::Machine *keyboard_machine() final;
|
||||
MediaTarget::Machine *media_target() final;
|
||||
MachineTypes::TimedMachine *timed_machine() final;
|
||||
MachineTypes::ScanProducer *scan_producer() final;
|
||||
MachineTypes::AudioProducer *audio_producer() final;
|
||||
MachineTypes::JoystickMachine *joystick_machine() final;
|
||||
MachineTypes::KeyboardMachine *keyboard_machine() final;
|
||||
MachineTypes::MouseMachine *mouse_machine() final;
|
||||
MachineTypes::MediaTarget *media_target() final;
|
||||
void *raw_pointer() final;
|
||||
|
||||
private:
|
||||
void multi_crt_did_run_machines() final;
|
||||
void did_run_machines(MultiTimedMachine *) final;
|
||||
|
||||
std::vector<std::unique_ptr<DynamicMachine>> machines_;
|
||||
std::recursive_mutex machines_mutex_;
|
||||
|
||||
MultiConfigurable configurable_;
|
||||
MultiCRTMachine crt_machine_;
|
||||
MultiTimedMachine timed_machine_;
|
||||
MultiScanProducer scan_producer_;
|
||||
MultiAudioProducer audio_producer_;
|
||||
MultiJoystickMachine joystick_machine_;
|
||||
MultiKeyboardMachine keyboard_machine_;
|
||||
MultiMediaTarget media_target_;
|
||||
|
@@ -21,8 +21,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||
|
||||
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
|
||||
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
|
||||
const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
|
||||
const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
|
||||
|
||||
if(!names || !details) return nullptr;
|
||||
if(names->samples.empty() || details->samples.empty()) return nullptr;
|
||||
@@ -48,18 +48,18 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
char name[10];
|
||||
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
|
||||
new_file.name = name;
|
||||
new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
|
||||
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.is_protected = names->samples[0][file_offset + 7] & 0x80;
|
||||
|
||||
long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||
new_file.data.reserve(static_cast<std::size_t>(data_length));
|
||||
new_file.data.reserve(size_t(data_length));
|
||||
|
||||
if(start_sector < 2) continue;
|
||||
while(data_length > 0) {
|
||||
uint8_t sector = static_cast<uint8_t>(start_sector % 10);
|
||||
uint8_t track = static_cast<uint8_t>(start_sector / 10);
|
||||
uint8_t sector = uint8_t(start_sector % 10);
|
||||
uint8_t track = uint8_t(start_sector / 10);
|
||||
start_sector++;
|
||||
|
||||
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
|
||||
@@ -84,7 +84,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
|
||||
if(!sector) return nullptr;
|
||||
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
|
||||
|
||||
// is a copyright string present?
|
||||
uint8_t copyright_offset = segment.data[7];
|
||||
const uint8_t copyright_offset = segment.data[7];
|
||||
if(
|
||||
segment.data[copyright_offset] != 0x00 ||
|
||||
segment.data[copyright_offset+1] != 0x28 ||
|
||||
@@ -57,9 +57,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::Electron;
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
target->has_dfs = false;
|
||||
target->has_adfs = false;
|
||||
@@ -84,8 +83,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
|
||||
// so that's also justification to *RUN
|
||||
std::size_t pointer = 0;
|
||||
uint8_t *data = &files.front().data[0];
|
||||
std::size_t data_size = files.front().data.size();
|
||||
uint8_t *const data = &files.front().data[0];
|
||||
const std::size_t data_size = files.front().data.size();
|
||||
while(1) {
|
||||
if(pointer >= data_size-1 || data[pointer] != 13) {
|
||||
is_basic = false;
|
||||
|
@@ -41,24 +41,24 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
char name[11];
|
||||
std::size_t name_ptr = 0;
|
||||
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
|
||||
name[name_ptr] = (char)parser.get_next_byte(tape);
|
||||
name[name_ptr] = char(parser.get_next_byte(tape));
|
||||
if(!name[name_ptr]) break;
|
||||
name_ptr++;
|
||||
++name_ptr;
|
||||
}
|
||||
name[sizeof(name)-1] = '\0';
|
||||
new_chunk->name = name;
|
||||
|
||||
// addresses
|
||||
new_chunk->load_address = (uint32_t)parser.get_next_word(tape);
|
||||
new_chunk->execution_address = (uint32_t)parser.get_next_word(tape);
|
||||
new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
|
||||
new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
|
||||
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
|
||||
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
|
||||
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
|
||||
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
|
||||
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
|
||||
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
|
||||
|
||||
uint16_t calculated_header_crc = parser.get_crc();
|
||||
uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
|
||||
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
|
||||
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
|
||||
|
||||
if(!new_chunk->header_crc_matched) return nullptr;
|
||||
@@ -66,13 +66,13 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
parser.reset_crc();
|
||||
new_chunk->data.reserve(new_chunk->block_length);
|
||||
for(int c = 0; c < new_chunk->block_length; c++) {
|
||||
new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
|
||||
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
|
||||
}
|
||||
|
||||
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
|
||||
uint16_t calculated_data_crc = parser.get_crc();
|
||||
uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
|
||||
stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
|
||||
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
|
||||
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
|
||||
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
|
||||
} else {
|
||||
new_chunk->data_crc_matched = true;
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#ifndef Analyser_Static_Acorn_Target_h
|
||||
#define Analyser_Static_Acorn_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -16,11 +17,18 @@ namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_adfs = false;
|
||||
bool has_dfs = false;
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_adfs);
|
||||
DeclareField(has_dfs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -179,10 +179,9 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
|
||||
return false;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::AmstradCPC;
|
||||
target->confidence = 0.5;
|
||||
|
||||
target->model = Target::Model::CPC6128;
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Analyser_Static_AmstradCPC_Target_h
|
||||
#define Analyser_Static_AmstradCPC_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -16,15 +18,17 @@ namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
CPC464,
|
||||
CPC664,
|
||||
CPC6128
|
||||
};
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
|
||||
Model model = Model::CPC464;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AmstradCPC) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -9,9 +9,8 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::AppleII;
|
||||
target->media = media;
|
||||
|
||||
if(!target->media.disks.empty())
|
||||
|
@@ -9,27 +9,38 @@
|
||||
#ifndef Target_h
|
||||
#define Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
II,
|
||||
IIplus,
|
||||
IIe,
|
||||
EnhancedIIe
|
||||
};
|
||||
enum class DiskController {
|
||||
);
|
||||
ReflectableEnum(DiskController,
|
||||
None,
|
||||
SixteenSector,
|
||||
ThirteenSector
|
||||
};
|
||||
);
|
||||
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -16,24 +16,22 @@ using namespace Analyser::Static::Atari2600;
|
||||
using Target = Analyser::Static::Atari2600::Target;
|
||||
|
||||
static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
||||
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
|
||||
uint16_t entry_address, break_address;
|
||||
// If this is a 2kb cartridge then it's definitely either unpaged or a CommaVid.
|
||||
const uint16_t entry_address = uint16_t(segment.data[0x7fc] | (segment.data[0x7fd] << 8)) & 0x1fff;
|
||||
const uint16_t break_address = uint16_t(segment.data[0x7fe] | (segment.data[0x7ff] << 8)) & 0x1fff;
|
||||
|
||||
entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
|
||||
break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
|
||||
|
||||
// a CommaVid start address needs to be outside of its RAM
|
||||
// A CommaVid start address needs to be outside of its RAM.
|
||||
if(entry_address < 0x1800 || break_address < 0x1800) return;
|
||||
|
||||
std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
|
||||
address &= 0x1fff;
|
||||
return static_cast<std::size_t>(address - 0x1800);
|
||||
return size_t(address - 0x1800);
|
||||
};
|
||||
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
|
||||
|
||||
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
||||
// large amounts of memory
|
||||
// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
||||
// large amounts of memory.
|
||||
bool has_wide_area_store = false;
|
||||
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
||||
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
|
||||
@@ -45,17 +43,17 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
|
||||
}
|
||||
}
|
||||
|
||||
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
|
||||
// Conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
|
||||
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
|
||||
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
|
||||
// attempts to modify itself but it probably doesn't
|
||||
// attempts to modify itself but it probably doesn't.
|
||||
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
||||
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
||||
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
|
||||
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
|
||||
if(
|
||||
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
||||
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
||||
@@ -65,7 +63,7 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
|
||||
return;
|
||||
}
|
||||
|
||||
// make an assumption that this is the Atari paging model
|
||||
// Make an assumption that this is the Atari paging model.
|
||||
target.paging_model = Target::PagingModel::Atari8k;
|
||||
|
||||
std::set<uint16_t> internal_accesses;
|
||||
@@ -90,8 +88,8 @@ static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartrid
|
||||
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
// make an assumption that this is the Atari paging model
|
||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
// Make an assumption that this is the Atari paging model.
|
||||
target.paging_model = Target::PagingModel::Atari16k;
|
||||
|
||||
std::set<uint16_t> internal_accesses;
|
||||
@@ -110,8 +108,8 @@ static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartri
|
||||
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
|
||||
}
|
||||
|
||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
// make an assumption that this is a Tigervision if there is a write to 3F
|
||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||
// Make an assumption that this is a Tigervision if there is a write to 3F.
|
||||
target.paging_model =
|
||||
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
||||
Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
|
||||
@@ -123,17 +121,15 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t entry_address, break_address;
|
||||
|
||||
entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
||||
break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
||||
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
||||
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
||||
|
||||
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
||||
if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
|
||||
return static_cast<std::size_t>(address & 0xfff);
|
||||
if(!(address & 0x1000)) return size_t(-1);
|
||||
return size_t(address & 0xfff);
|
||||
};
|
||||
|
||||
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
||||
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
||||
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
||||
|
||||
switch(segment.data.size()) {
|
||||
@@ -159,7 +155,7 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
break;
|
||||
}
|
||||
|
||||
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
|
||||
// Check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
|
||||
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
|
||||
// next 128 bytes. So check for that.
|
||||
if( target.paging_model != Target::PagingModel::CBSRamPlus &&
|
||||
@@ -174,17 +170,16 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
|
||||
target.uses_superchip = has_superchip;
|
||||
}
|
||||
|
||||
// check for a Tigervision or Tigervision-esque scheme
|
||||
// Check for a Tigervision or Tigervision-esque scheme
|
||||
if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
|
||||
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
|
||||
if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
|
||||
}
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// TODO: sanity checking; is this image really for an Atari 2600?
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::Atari2600;
|
||||
target->confidence = 0.5;
|
||||
target->media.cartridges = media.cartridges;
|
||||
target->paging_model = Target::PagingModel::None;
|
||||
|
@@ -34,6 +34,8 @@ struct Target: public ::Analyser::Static::Target {
|
||||
// TODO: shouldn't these be properties of the cartridge?
|
||||
PagingModel paging_model = PagingModel::None;
|
||||
bool uses_superchip = false;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Atari2600) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -9,16 +9,15 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::AtariST;
|
||||
using Target = Analyser::Static::AtariST::Target;
|
||||
auto *const target = new Target();
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
|
@@ -9,11 +9,15 @@
|
||||
#ifndef Analyser_Static_AtariST_Target_h
|
||||
#define Analyser_Static_AtariST_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
|
||||
// the two bytes that will be first must be 0xaa and 0x55, either way around
|
||||
auto *start = &segment.data[0];
|
||||
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
|
||||
if((data_size & size_t(~8191)) > 32768) {
|
||||
start = &segment.data[segment.data.size() - 16384];
|
||||
}
|
||||
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
|
||||
@@ -52,10 +52,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return coleco_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
TargetList targets;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::ColecoVision;
|
||||
auto target = std::make_unique<Target>(Machine::ColecoVision);
|
||||
target->confidence = 1.0f - 1.0f / 32768.0f;
|
||||
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
|
||||
if(!target->media.empty())
|
||||
|
@@ -38,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
|
||||
int difference = static_cast<int>(track) - static_cast<int>(track_);
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
|
||||
if(difference) {
|
||||
@@ -71,7 +71,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
@@ -112,15 +112,15 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> get_sector(uint8_t sector) {
|
||||
uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
|
||||
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
const std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1) {
|
||||
std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
const std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
@@ -138,12 +138,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
}
|
||||
|
||||
// get sector details, skip if this looks malformed
|
||||
uint8_t checksum = static_cast<uint8_t>(get_next_byte());
|
||||
sector->sector = static_cast<uint8_t>(get_next_byte());
|
||||
sector->track = static_cast<uint8_t>(get_next_byte());
|
||||
uint8_t checksum = uint8_t(get_next_byte());
|
||||
sector->sector = uint8_t(get_next_byte());
|
||||
sector->track = uint8_t(get_next_byte());
|
||||
uint8_t disk_id[2];
|
||||
disk_id[0] = static_cast<uint8_t>(get_next_byte());
|
||||
disk_id[1] = static_cast<uint8_t>(get_next_byte());
|
||||
disk_id[0] = uint8_t(get_next_byte());
|
||||
disk_id[1] = uint8_t(get_next_byte());
|
||||
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
|
||||
|
||||
// look for the following data
|
||||
@@ -154,12 +154,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
|
||||
checksum = 0;
|
||||
for(std::size_t c = 0; c < 256; c++) {
|
||||
sector->data[c] = static_cast<uint8_t>(get_next_byte());
|
||||
sector->data[c] = uint8_t(get_next_byte());
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) {
|
||||
uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
|
||||
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
@@ -192,7 +192,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
}
|
||||
|
||||
// parse directory
|
||||
std::size_t header_pointer = static_cast<std::size_t>(-32);
|
||||
std::size_t header_pointer = size_t(-32);
|
||||
while(header_pointer+32+31 < directory.size()) {
|
||||
header_pointer += 32;
|
||||
|
||||
@@ -216,7 +216,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
}
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
|
||||
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
||||
bool is_first_sector = true;
|
||||
@@ -227,7 +227,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8);
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
|
||||
else
|
||||
|
@@ -23,7 +23,7 @@ bool Analyser::Static::Commodore::File::is_basic() {
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
while(1) {
|
||||
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
|
||||
if(size_t(line_address - starting_address) >= data.size() + 2) break;
|
||||
|
||||
uint16_t next_line_address = data[line_address - starting_address];
|
||||
next_line_address |= data[line_address - starting_address + 1] << 8;
|
||||
@@ -33,13 +33,13 @@ bool Analyser::Static::Commodore::File::is_basic() {
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break;
|
||||
if(size_t(line_address - starting_address) >= data.size() + 5) break;
|
||||
uint16_t next_line_number = data[line_address - starting_address + 2];
|
||||
next_line_number |= data[line_address - starting_address + 3] << 8;
|
||||
|
||||
if(next_line_number <= line_number) break;
|
||||
|
||||
line_number = static_cast<uint16_t>(next_line_number);
|
||||
line_number = uint16_t(next_line_number);
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
|
@@ -42,7 +42,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return vic20_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
|
||||
auto target = std::make_unique<Target>();
|
||||
@@ -94,6 +94,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
memory_model = Target::MemoryModel::Unexpanded;
|
||||
break;
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Analyser_Static_Commodore_Target_h
|
||||
#define Analyser_Static_Commodore_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -16,20 +18,20 @@ namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class MemoryModel {
|
||||
Unexpanded,
|
||||
EightKB,
|
||||
ThirtyTwoKB
|
||||
};
|
||||
|
||||
enum class Region {
|
||||
ReflectableEnum(Region,
|
||||
American,
|
||||
Danish,
|
||||
Japanese,
|
||||
European,
|
||||
Swedish
|
||||
};
|
||||
);
|
||||
|
||||
/// Maps from a named memory model to a bank enabled/disabled set.
|
||||
void set_memory_model(MemoryModel memory_model) {
|
||||
@@ -54,6 +56,19 @@ struct Target: public ::Analyser::Static::Target {
|
||||
Region region = Region::European;
|
||||
bool has_c1540 = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Vic20) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(enabled_ram.bank0);
|
||||
DeclareField(enabled_ram.bank1);
|
||||
DeclareField(enabled_ram.bank2);
|
||||
DeclareField(enabled_ram.bank3);
|
||||
DeclareField(enabled_ram.bank5);
|
||||
DeclareField(region);
|
||||
DeclareField(has_c1540);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -26,12 +26,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
|
||||
Instruction instruction;
|
||||
instruction.address = address;
|
||||
address++;
|
||||
++address;
|
||||
|
||||
// get operation
|
||||
uint8_t operation = memory[local_address];
|
||||
// Get operation.
|
||||
const uint8_t operation = memory[local_address];
|
||||
|
||||
// decode addressing mode
|
||||
// Decode addressing mode.
|
||||
switch(operation&0x1f) {
|
||||
case 0x00:
|
||||
if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
|
||||
@@ -74,7 +74,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
break;
|
||||
}
|
||||
|
||||
// decode operation
|
||||
// Decode operation.
|
||||
#define RM_INSTRUCTION(base, op) \
|
||||
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
|
||||
instruction.operation = op; \
|
||||
@@ -222,14 +222,14 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
#undef M_INSTRUCTION
|
||||
#undef IM_INSTRUCTION
|
||||
|
||||
// get operand
|
||||
// Get operand.
|
||||
switch(instruction.addressing_mode) {
|
||||
// zero-byte operands
|
||||
// Zero-byte operands.
|
||||
case Instruction::Implied:
|
||||
instruction.operand = 0;
|
||||
break;
|
||||
|
||||
// one-byte operands
|
||||
// One-byte operands.
|
||||
case Instruction::Immediate:
|
||||
case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
|
||||
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
|
||||
@@ -242,7 +242,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
break;
|
||||
|
||||
// two-byte operands
|
||||
// Two-byte operands.
|
||||
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
|
||||
case Instruction::Indirect: {
|
||||
std::size_t low_operand_address = address_mapper(address);
|
||||
@@ -250,18 +250,18 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
|
||||
address += 2;
|
||||
|
||||
instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
|
||||
instruction.operand = memory[low_operand_address] | uint16_t(memory[high_operand_address] << 8);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// store the instruction away
|
||||
// Store the instruction.
|
||||
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
|
||||
|
||||
// TODO: something wider-ranging than this
|
||||
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
|
||||
std::size_t mapped_address = address_mapper(instruction.operand);
|
||||
bool is_external = mapped_address >= memory.size();
|
||||
const size_t mapped_address = address_mapper(instruction.operand);
|
||||
const bool is_external = mapped_address >= memory.size();
|
||||
|
||||
switch(instruction.operation) {
|
||||
default: break;
|
||||
@@ -290,7 +290,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
}
|
||||
|
||||
// decide on overall flow control
|
||||
// Decide on overall flow control.
|
||||
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
|
||||
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
|
||||
if(instruction.operation == Instruction::JSR) {
|
||||
@@ -302,7 +302,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
return;
|
||||
}
|
||||
if(instruction.addressing_mode == Instruction::Relative) {
|
||||
uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
|
||||
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
|
||||
disassembly.remaining_entry_points.push_back(destination);
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +0,0 @@
|
||||
//
|
||||
// AddressMapper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/12/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AddressMapper.hpp"
|
@@ -21,7 +21,7 @@ namespace Disassembler {
|
||||
*/
|
||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
|
||||
return [start_address](T argument) {
|
||||
return static_cast<std::size_t>(argument - start_address);
|
||||
return size_t(argument - start_address);
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,7 @@ class Accessor {
|
||||
uint16_t word() {
|
||||
uint8_t low = byte();
|
||||
uint8_t high = byte();
|
||||
return static_cast<uint16_t>(low | (high << 8));
|
||||
return uint16_t(low | (high << 8));
|
||||
}
|
||||
|
||||
bool overrun() {
|
||||
@@ -562,7 +562,7 @@ struct Z80Disassembler {
|
||||
int access_type =
|
||||
((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
|
||||
((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
|
||||
uint16_t address = static_cast<uint16_t>(instruction.operand);
|
||||
uint16_t address = uint16_t(instruction.operand);
|
||||
bool is_internal = address_mapper(address) < memory.size();
|
||||
switch(access_type) {
|
||||
default: break;
|
||||
@@ -594,7 +594,7 @@ struct Z80Disassembler {
|
||||
instruction.operation == Instruction::Operation::JR ||
|
||||
instruction.operation == Instruction::Operation::CALL ||
|
||||
instruction.operation == Instruction::Operation::RST) {
|
||||
disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
|
||||
disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand));
|
||||
}
|
||||
|
||||
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
|
||||
|
@@ -20,8 +20,7 @@ namespace {
|
||||
|
||||
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::AppleII;
|
||||
auto *const target = new Target;
|
||||
|
||||
if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
|
||||
target->disk_controller = Target::DiskController::ThirteenSector;
|
||||
@@ -32,10 +31,9 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector
|
||||
return target;
|
||||
}
|
||||
|
||||
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
|
||||
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) {
|
||||
using Target = Analyser::Static::Oric::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::Oric;
|
||||
auto *const target = new Target;
|
||||
target->rom = Target::ROM::Pravetz;
|
||||
target->disk_interface = Target::DiskInterface::Pravetz;
|
||||
target->loading_command = "CALL 800\n";
|
||||
@@ -44,13 +42,13 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// Grab track 0, sector 0: the boot sector.
|
||||
auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
const auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
|
||||
|
||||
const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
|
||||
@@ -77,7 +75,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
|
||||
// If the boot sector looks like it's intended for the Oric, create an Oric.
|
||||
// Otherwise go with the Apple II.
|
||||
|
||||
auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
|
||||
|
||||
bool did_read_shift_register = false;
|
||||
bool is_oric = false;
|
||||
|
@@ -27,7 +27,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
|
||||
if(segment.data.size() & 0x1fff) {
|
||||
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()) & ~0x1fff;
|
||||
std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
|
||||
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
|
||||
output_segments.emplace_back(start_address, truncated_data);
|
||||
} else {
|
||||
@@ -35,7 +35,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
}
|
||||
|
||||
auto target = std::make_unique<Analyser::Static::MSX::Target>();
|
||||
target->machine = Analyser::Machine::MSX;
|
||||
target->confidence = confidence;
|
||||
|
||||
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||
@@ -97,7 +96,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// Reject cartridge if the ROM header wasn't found.
|
||||
if(!found_start) continue;
|
||||
|
||||
uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
|
||||
uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
@@ -147,7 +146,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// ) &&
|
||||
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
|
||||
// ) {
|
||||
// const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
|
||||
// const uint16_t address = uint16_t(next_iterator->second.operand);
|
||||
// switch(iterator->second.operand) {
|
||||
// case 0x6000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
@@ -208,13 +207,13 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
if( instruction_pair.second.operation == Instruction::Operation::LD &&
|
||||
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
|
||||
instruction_pair.second.source == Instruction::Location::A) {
|
||||
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
|
||||
address_counts[uint16_t(instruction_pair.second.operand)]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits.
|
||||
float total_hits =
|
||||
static_cast<float>(
|
||||
float(
|
||||
address_counts[0x6000] + address_counts[0x6800] +
|
||||
address_counts[0x7000] + address_counts[0x7800] +
|
||||
address_counts[0x77ff] + address_counts[0x8000] +
|
||||
@@ -226,42 +225,42 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||
static_cast<float>( address_counts[0x6000] +
|
||||
address_counts[0x6800] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x7800]) / total_hits));
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x6800] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x7800]) / total_hits));
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||
static_cast<float>( address_counts[0x6000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x77ff]) / total_hits));
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x77ff]) / total_hits));
|
||||
if(!is_ascii) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::Konami,
|
||||
static_cast<float>( address_counts[0x6000] +
|
||||
address_counts[0x8000] +
|
||||
address_counts[0xa000]) / total_hits));
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x8000] +
|
||||
address_counts[0xa000]) / total_hits));
|
||||
}
|
||||
if(!is_ascii) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
||||
static_cast<float>( address_counts[0x5000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x9000] +
|
||||
address_counts[0xb000]) / total_hits));
|
||||
float( address_counts[0x5000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x9000] +
|
||||
address_counts[0xb000]) / total_hits));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
|
||||
// Append targets for any cartridges that look correct.
|
||||
@@ -295,7 +294,6 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
|
||||
target->has_disk_drive = !media.disks.empty();
|
||||
|
||||
if(!target->media.empty()) {
|
||||
target->machine = Machine::MSX;
|
||||
target->confidence = 0.5;
|
||||
destination.push_back(std::move(target));
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
for(std::size_t c = 0; c < sizeof(header); ++c) {
|
||||
int next_byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(next_byte == -1) break;
|
||||
header[c] = static_cast<uint8_t>(next_byte);
|
||||
header[c] = uint8_t(next_byte);
|
||||
}
|
||||
|
||||
bool bytes_are_same = true;
|
||||
@@ -67,7 +67,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
// Read file name.
|
||||
char name[7];
|
||||
for(std::size_t c = 1; c < 6; ++c)
|
||||
name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
|
||||
name[c] = char(Parser::get_byte(*file_speed, tape_player));
|
||||
name[6] = '\0';
|
||||
file.name = name;
|
||||
|
||||
@@ -82,7 +82,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) break;
|
||||
contains_end_of_file |= (byte == 0x1a);
|
||||
file.data.push_back(static_cast<uint8_t>(byte));
|
||||
file.data.push_back(uint8_t(byte));
|
||||
}
|
||||
if(c != -1) break;
|
||||
if(contains_end_of_file) {
|
||||
@@ -105,13 +105,13 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
for(c = 0; c < sizeof(locations); ++c) {
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) break;
|
||||
locations[c] = static_cast<uint8_t>(byte);
|
||||
locations[c] = uint8_t(byte);
|
||||
}
|
||||
if(c != sizeof(locations)) continue;
|
||||
|
||||
file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
|
||||
end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
|
||||
file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
|
||||
file.starting_address = uint16_t(locations[0] | (locations[1] << 8));
|
||||
end_address = uint16_t(locations[2] | (locations[3] << 8));
|
||||
file.entry_address = uint16_t(locations[4] | (locations[5] << 8));
|
||||
|
||||
if(end_address < file.starting_address) continue;
|
||||
|
||||
@@ -119,7 +119,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
while(length--) {
|
||||
int byte = Parser::get_byte(*file_speed, tape_player);
|
||||
if(byte == -1) continue;
|
||||
file.data.push_back(static_cast<uint8_t>(byte));
|
||||
file.data.push_back(uint8_t(byte));
|
||||
}
|
||||
|
||||
files.push_back(std::move(file));
|
||||
@@ -135,10 +135,10 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
|
||||
|
||||
if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
|
||||
file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
|
||||
file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
|
||||
file.data.push_back(uint8_t(next_address_buffer[0]));
|
||||
file.data.push_back(uint8_t(next_address_buffer[1]));
|
||||
|
||||
uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
|
||||
uint16_t next_address = uint16_t(next_address_buffer[0] | (next_address_buffer[1] << 8));
|
||||
if(!next_address) {
|
||||
files.push_back(std::move(file));
|
||||
break;
|
||||
@@ -155,7 +155,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
|
||||
found_error = true;
|
||||
break;
|
||||
}
|
||||
file.data.push_back(static_cast<uint8_t>(byte));
|
||||
file.data.push_back(uint8_t(byte));
|
||||
}
|
||||
if(found_error) break;
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Analyser_Static_MSX_Target_h
|
||||
#define Analyser_Static_MSX_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -16,15 +18,24 @@ namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_disk_drive = false;
|
||||
std::string loading_command;
|
||||
|
||||
enum class Region {
|
||||
ReflectableEnum(Region,
|
||||
Japan,
|
||||
USA,
|
||||
Europe
|
||||
} region = Region::USA;
|
||||
);
|
||||
Region region = Region::USA;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
|
||||
|
||||
@@ -17,8 +17,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::Macintosh;
|
||||
auto *const target = new Target;
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
|
@@ -9,19 +9,25 @@
|
||||
#ifndef Analyser_Static_Macintosh_Target_h
|
||||
#define Analyser_Static_Macintosh_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
Mac128k,
|
||||
Mac512k,
|
||||
Mac512ke,
|
||||
MacPlus
|
||||
};
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
|
||||
Model model = Model::MacPlus;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Macintosh) {
|
||||
// Boilerplate for declaring fields and potential values.
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -145,9 +145,8 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::Oric;
|
||||
target->confidence = 0.5;
|
||||
|
||||
int basic10_votes = 0;
|
||||
|
@@ -49,10 +49,10 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
|
||||
}
|
||||
|
||||
// read end and start addresses
|
||||
new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
|
||||
new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
|
||||
new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
|
||||
new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
|
||||
new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
|
||||
|
||||
// skip an empty byte
|
||||
parser.get_next_byte(tape, is_fast);
|
||||
@@ -61,7 +61,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
|
||||
char file_name[17];
|
||||
int name_pos = 0;
|
||||
while(name_pos < 16) {
|
||||
file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
|
||||
file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
|
||||
if(!file_name[name_pos]) break;
|
||||
name_pos++;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
|
||||
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
|
||||
new_file.data.reserve(body_length);
|
||||
for(std::size_t c = 0; c < body_length; c++) {
|
||||
new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
|
||||
new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
|
||||
}
|
||||
|
||||
// only one validation check: was there enough tape?
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Analyser_Static_Oric_Target_h
|
||||
#define Analyser_Static_Oric_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -16,25 +18,34 @@ namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class ROM {
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ROM,
|
||||
BASIC10,
|
||||
BASIC11,
|
||||
Pravetz
|
||||
};
|
||||
);
|
||||
|
||||
enum class DiskInterface {
|
||||
ReflectableEnum(DiskInterface,
|
||||
None,
|
||||
Microdisc,
|
||||
Pravetz,
|
||||
Jasmin,
|
||||
BD500,
|
||||
None
|
||||
};
|
||||
BD500
|
||||
);
|
||||
|
||||
ROM rom = ROM::BASIC11;
|
||||
DiskInterface disk_interface = DiskInterface::None;
|
||||
std::string loading_command;
|
||||
bool should_start_jasmin = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::Oric) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(rom);
|
||||
DeclareField(disk_interface);
|
||||
AnnounceEnum(ROM);
|
||||
AnnounceEnum(DiskInterface);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -13,15 +13,13 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
if(media.cartridges.empty())
|
||||
return {};
|
||||
|
||||
TargetList targets;
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
target->machine = Machine::MasterSystem;
|
||||
|
||||
// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
|
||||
if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
|
||||
target->model = Target::Model::SG1000;
|
||||
|
@@ -9,23 +9,27 @@
|
||||
#ifndef Analyser_Static_Sega_Target_h
|
||||
#define Analyser_Static_Sega_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class Model {
|
||||
SG1000,
|
||||
MasterSystem,
|
||||
MasterSystem2,
|
||||
};
|
||||
|
||||
enum class Region {
|
||||
ReflectableEnum(Region,
|
||||
Japan,
|
||||
USA,
|
||||
Europe,
|
||||
Brazil
|
||||
};
|
||||
);
|
||||
|
||||
enum class PagingScheme {
|
||||
Sega,
|
||||
@@ -35,6 +39,13 @@ struct Target: public ::Analyser::Static::Target {
|
||||
Model model = Model::MasterSystem;
|
||||
Region region = Region::Japan;
|
||||
PagingScheme paging_scheme = PagingScheme::Sega;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::MasterSystem) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
|
||||
|
@@ -35,6 +35,16 @@ struct Media {
|
||||
bool empty() const {
|
||||
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
|
||||
}
|
||||
|
||||
Media &operator +=(const Media &rhs) {
|
||||
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
|
||||
append(disks);
|
||||
append(tapes);
|
||||
append(cartridges);
|
||||
append(mass_storage_devices);
|
||||
#undef append
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -42,11 +52,12 @@ struct Media {
|
||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||
*/
|
||||
struct Target {
|
||||
Target(Machine machine) : machine(machine) {}
|
||||
virtual ~Target() {}
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence;
|
||||
float confidence = 0.0f;
|
||||
};
|
||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
|
||||
|
||||
|
@@ -28,13 +28,13 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
|
||||
return files;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
|
||||
TargetList destination;
|
||||
if(!media.tapes.empty()) {
|
||||
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
|
||||
media.tapes.front()->reset();
|
||||
if(!files.empty()) {
|
||||
Target *target = new Target;
|
||||
Target *const target = new Target;
|
||||
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
|
||||
target->machine = Machine::ZX8081;
|
||||
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef Analyser_Static_ZX8081_Target_h
|
||||
#define Analyser_Static_ZX8081_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
@@ -16,17 +18,26 @@ namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class MemoryModel {
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemoryModel,
|
||||
Unexpanded,
|
||||
SixteenKB,
|
||||
SixtyFourKB
|
||||
};
|
||||
);
|
||||
|
||||
MemoryModel memory_model = MemoryModel::Unexpanded;
|
||||
bool is_ZX81 = false;
|
||||
bool ZX80_uses_ZX81_ROM = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZX8081) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_model);
|
||||
DeclareField(is_ZX81);
|
||||
DeclareField(ZX80_uses_ZX81_ROM);
|
||||
AnnounceEnum(MemoryModel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -176,7 +176,6 @@ class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
@@ -198,7 +197,6 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
|
@@ -67,7 +67,7 @@ class Source {
|
||||
}
|
||||
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() = 0;
|
||||
virtual Preference preferred_clocking() const = 0;
|
||||
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
|
@@ -49,7 +49,7 @@ template <typename TimeUnit> class DeferredQueue {
|
||||
@returns The amount of time until the next enqueued action will occur,
|
||||
or TimeUnit(-1) if the queue is empty.
|
||||
*/
|
||||
TimeUnit time_until_next_action() {
|
||||
TimeUnit time_until_next_action() const {
|
||||
if(pending_actions_.empty()) return TimeUnit(-1);
|
||||
return pending_actions_.front().delay;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ template <typename TimeUnit> class DeferredQueue {
|
||||
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
|
||||
public:
|
||||
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
|
||||
DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
|
||||
|
||||
/*!
|
||||
Runs for @c length units of time.
|
||||
|
155
ClockReceiver/VSyncPredictor.hpp
Normal file
155
ClockReceiver/VSyncPredictor.hpp
Normal file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// VSyncPredictor.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/06/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef VSyncPredictor_hpp
|
||||
#define VSyncPredictor_hpp
|
||||
|
||||
#include "TimeTypes.hpp"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Time {
|
||||
|
||||
/*!
|
||||
For platforms that provide no avenue into vsync tracking other than block-until-sync,
|
||||
this class tracks: (i) how long frame draw takes; (ii) the apparent frame period; and
|
||||
(iii) optionally, timer jitter; in order to suggest when you should next start drawing.
|
||||
*/
|
||||
class VSyncPredictor {
|
||||
public:
|
||||
/*!
|
||||
Announces to the predictor that the work of producing an output frame has begun.
|
||||
*/
|
||||
void begin_redraw() {
|
||||
redraw_begin_time_ = nanos_now();
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces to the predictor that the work of producing an output frame has ended;
|
||||
the predictor will use the amount of time between each begin/end pair to modify
|
||||
its expectations as to how long it takes to draw a frame.
|
||||
*/
|
||||
void end_redraw() {
|
||||
redraw_period_.post(nanos_now() - redraw_begin_time_);
|
||||
}
|
||||
|
||||
/*!
|
||||
Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
|
||||
machine calls retrace is now. The predictor uses these notifications to estimate output
|
||||
frame rate.
|
||||
*/
|
||||
void announce_vsync() {
|
||||
const auto now = nanos_now();
|
||||
|
||||
if(last_vsync_) {
|
||||
last_vsync_ += frame_duration_;
|
||||
vsync_jitter_.post(last_vsync_ - now);
|
||||
last_vsync_ = (last_vsync_ + now) >> 1;
|
||||
} else {
|
||||
last_vsync_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the frame rate for the target display.
|
||||
*/
|
||||
void set_frame_rate(float rate) {
|
||||
frame_duration_ = Nanos(1'000'000'000.0f / rate);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The time this class currently believes a whole frame occupies.
|
||||
*/
|
||||
Time::Nanos frame_duration() {
|
||||
return frame_duration_;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds a record of how much jitter was experienced in scheduling; these values will be
|
||||
factored into the @c suggested_draw_time if supplied.
|
||||
|
||||
A positive number means the timer occurred late. A negative number means it occurred early.
|
||||
*/
|
||||
void add_timer_jitter(Time::Nanos jitter) {
|
||||
timer_jitter_.post(jitter);
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces to the vsync predictor that output is now paused. This ends frame period
|
||||
calculations until the next announce_vsync() restarts frame-length counting.
|
||||
*/
|
||||
void pause() {
|
||||
last_vsync_ = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@return The time at which redrawing should begin, given the predicted frame period, how
|
||||
long it appears to take to draw a frame and how much jitter there is in scheduling
|
||||
(if those figures are being supplied).
|
||||
*/
|
||||
Nanos suggested_draw_time() {
|
||||
const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean();
|
||||
const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance();
|
||||
|
||||
// Permit three standard deviations from the mean, to cover 99.9% of cases.
|
||||
const auto period = mean + Nanos(3.0f * sqrt(float(variance)));
|
||||
|
||||
return last_vsync_ + frame_duration_ - period;
|
||||
}
|
||||
|
||||
private:
|
||||
class VarianceCollector {
|
||||
public:
|
||||
VarianceCollector(Time::Nanos default_value) {
|
||||
sum_ = default_value * 128;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
history_[c] = default_value;
|
||||
}
|
||||
}
|
||||
|
||||
void post(Time::Nanos value) {
|
||||
sum_ -= history_[write_pointer_];
|
||||
sum_ += value;
|
||||
history_[write_pointer_] = value;
|
||||
write_pointer_ = (write_pointer_ + 1) & 127;
|
||||
}
|
||||
|
||||
Time::Nanos mean() {
|
||||
return sum_ / 128;
|
||||
}
|
||||
|
||||
Time::Nanos variance() {
|
||||
// I haven't yet come up with a better solution that calculating this
|
||||
// in whole every time, given the way that the mean mutates.
|
||||
Time::Nanos variance = 0;
|
||||
for(int c = 0; c < 128; ++c) {
|
||||
const auto difference = ((history_[c] * 128) - sum_) / 128;
|
||||
variance += (difference * difference);
|
||||
}
|
||||
return variance / 128;
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Nanos sum_;
|
||||
Time::Nanos history_[128];
|
||||
size_t write_pointer_ = 0;
|
||||
};
|
||||
|
||||
Nanos redraw_begin_time_ = 0;
|
||||
Nanos last_vsync_ = 0;
|
||||
Nanos frame_duration_ = 1'000'000'000 / 60;
|
||||
|
||||
VarianceCollector vsync_jitter_{0};
|
||||
VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess.
|
||||
VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner.
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* VSyncPredictor_hpp */
|
@@ -498,7 +498,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
|
||||
if(get_crc_generator().get_value()) {
|
||||
LOG("CRC error; terminating");
|
||||
update_status([this] (Status &status) {
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
@@ -816,19 +816,19 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
|
||||
if(status_.busy != old_status.busy) update_clocking_observer();
|
||||
}
|
||||
|
||||
void WD1770::set_head_load_request(bool head_load) {}
|
||||
void WD1770::set_motor_on(bool motor_on) {}
|
||||
void WD1770::set_head_load_request(bool) {}
|
||||
void WD1770::set_motor_on(bool) {}
|
||||
|
||||
void WD1770::set_head_loaded(bool head_loaded) {
|
||||
head_is_loaded_ = head_loaded;
|
||||
if(head_loaded) posit_event(int(Event1770::HeadLoad));
|
||||
}
|
||||
|
||||
bool WD1770::get_head_loaded() {
|
||||
bool WD1770::get_head_loaded() const {
|
||||
return head_is_loaded_;
|
||||
}
|
||||
|
||||
ClockingHint::Preference WD1770::preferred_clocking() {
|
||||
ClockingHint::Preference WD1770::preferred_clocking() const {
|
||||
if(status_.busy) return ClockingHint::Preference::RealTime;
|
||||
return Storage::Disk::MFMController::preferred_clocking();
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
WD1770(Personality p);
|
||||
virtual ~WD1770() {}
|
||||
|
||||
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
|
||||
using Storage::Disk::MFMController::set_is_double_density;
|
||||
@@ -62,18 +63,18 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
};
|
||||
|
||||
/// @returns The current value of the IRQ line output.
|
||||
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
|
||||
inline bool get_interrupt_request_line() const { return status_.interrupt_request; }
|
||||
|
||||
/// @returns The current value of the DRQ line output.
|
||||
inline bool get_data_request_line() { return status_.data_request; }
|
||||
inline bool get_data_request_line() const { return status_.data_request; }
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
|
||||
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
protected:
|
||||
virtual void set_head_load_request(bool head_load);
|
||||
@@ -81,12 +82,12 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
void set_head_loaded(bool head_loaded);
|
||||
|
||||
/// @returns The last value posted to @c set_head_loaded.
|
||||
bool get_head_loaded();
|
||||
bool get_head_loaded() const;
|
||||
|
||||
private:
|
||||
Personality personality_;
|
||||
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
|
||||
inline bool has_head_load_line() { return (personality_ == P1793 ); }
|
||||
const Personality personality_;
|
||||
bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); }
|
||||
bool has_head_load_line() const { return (personality_ == P1793 ); }
|
||||
|
||||
struct Status {
|
||||
bool write_protect = false;
|
||||
|
@@ -18,9 +18,14 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
|
||||
clock_rate_(clock_rate) {
|
||||
device_id_ = bus_.add_device();
|
||||
bus_.add_observer(this);
|
||||
|
||||
// TODO: use clock rate and expected phase. This implementation currently
|
||||
// provides only CPU-driven polling behaviour.
|
||||
(void)clock_rate_;
|
||||
(void)expected_phase_;
|
||||
}
|
||||
|
||||
void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
|
||||
void NCR5380::write(int address, uint8_t value, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
@@ -128,7 +133,7 @@ void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t NCR5380::read(int address, bool dma_acknowledge) {
|
||||
uint8_t NCR5380::read(int address, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
@@ -258,6 +263,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
|
||||
case ExecutionState::WaitingForBusy:
|
||||
if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
|
||||
state_ = ExecutionState::WatchingBusy;
|
||||
[[fallthrough]];
|
||||
|
||||
case ExecutionState::WatchingBusy:
|
||||
if(!(new_state & SCSI::Line::Busy)) {
|
||||
|
@@ -37,22 +37,22 @@ enum Line {
|
||||
class PortHandler {
|
||||
public:
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input(Port port) { return 0xff; }
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
|
||||
|
||||
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
|
||||
|
||||
/// Sets the current logical output level for line @c line on port @c port.
|
||||
void set_control_line_output(Port port, Line line, bool value) {}
|
||||
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status(bool status) {}
|
||||
void set_interrupt_status([[maybe_unused]] bool status) {}
|
||||
|
||||
/// Provides a measure of time elapsed between other calls.
|
||||
void run_for(HalfCycles duration) {}
|
||||
void run_for([[maybe_unused]] HalfCycles duration) {}
|
||||
|
||||
/// Receives passed-on flush() calls from the 6522.
|
||||
void flush() {}
|
||||
void flush() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -112,7 +112,7 @@ template <class T> class MOS6522: public MOS6522Storage {
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
bool get_interrupt_line() const;
|
||||
|
||||
/// Updates the port handler to the current time and then requests that it flush.
|
||||
void flush();
|
||||
|
@@ -69,7 +69,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
// Timer 1
|
||||
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
|
||||
case 0x5: case 0x7:
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
|
||||
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
|
||||
if(address == 0x05) {
|
||||
registers_.next_timer[0] = registers_.timer_latch[0];
|
||||
@@ -82,7 +82,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
|
||||
case 0x8: registers_.timer_latch[1] = value; break;
|
||||
case 0x9:
|
||||
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
|
||||
registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8);
|
||||
timer_is_running_[1] = true;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
@@ -281,11 +281,11 @@ template <typename T> void MOS6522<T>::do_phase2() {
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.timer[0] = uint16_t(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.timer[1] = uint16_t(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
|
||||
@@ -383,9 +383,9 @@ template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
template <typename T> bool MOS6522<T>::get_interrupt_line() {
|
||||
template <typename T> bool MOS6522<T>::get_interrupt_line() const {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
return interrupt_status;
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
|
||||
|
@@ -14,6 +14,6 @@ void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
|
||||
void IRQDelegatePortHandler::set_interrupt_status(bool) {
|
||||
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
if(address & 0x10) {
|
||||
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
|
||||
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
|
||||
timer_.value = (unsigned(value) << timer_.activeShift) ;
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
|
||||
|
||||
// Timer and interrupt control
|
||||
case 0x04: case 0x06: {
|
||||
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
|
||||
uint8_t value = uint8_t(timer_.value >> timer_.activeShift);
|
||||
timer_.interrupt_enabled = !!(address&0x08);
|
||||
interrupt_status_ &= ~InterruptFlag::Timer;
|
||||
evaluate_interrupts();
|
||||
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
|
||||
inline void run_for(const Cycles cycles) {
|
||||
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral());
|
||||
unsigned int number_of_cycles = unsigned(cycles.as_integral());
|
||||
|
||||
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
|
||||
if(timer_.value >= number_of_cycles) {
|
||||
@@ -122,7 +122,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
|
||||
MOS6532() {
|
||||
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
|
||||
timer_.value = unsigned((rand() & 0xff) << 10);
|
||||
}
|
||||
|
||||
inline void set_port_did_change(int port) {
|
||||
@@ -142,7 +142,7 @@ template <class T> class MOS6532 {
|
||||
}
|
||||
}
|
||||
|
||||
inline bool get_inerrupt_line() {
|
||||
inline bool get_inerrupt_line() const {
|
||||
return interrupt_line_;
|
||||
}
|
||||
|
||||
@@ -173,9 +173,11 @@ template <class T> class MOS6532 {
|
||||
bool interrupt_line_ = false;
|
||||
|
||||
// expected to be overridden
|
||||
uint8_t get_port_input(int port) { return 0xff; }
|
||||
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
|
||||
void set_irq_line(bool new_value) {}
|
||||
void set_port_output([[maybe_unused]] int port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t output_mask) {}
|
||||
uint8_t get_port_input([[maybe_unused]] int port) {
|
||||
return 0xff;
|
||||
}
|
||||
void set_irq_line(bool) {}
|
||||
|
||||
inline void evaluate_interrupts() {
|
||||
interrupt_line_ =
|
||||
|
@@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = {
|
||||
|
||||
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
|
||||
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
|
||||
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
|
||||
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
|
||||
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
|
||||
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
|
||||
@@ -114,7 +114,7 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
|
||||
|
||||
// this sums the output of all three sounds channels plus a DC offset for volume;
|
||||
// TODO: what's the real ratio of this stuff?
|
||||
target[c] = static_cast<int16_t>(
|
||||
target[c] = int16_t(
|
||||
(shift_registers_[0]&1) +
|
||||
(shift_registers_[1]&1) +
|
||||
(shift_registers_[2]&1) +
|
||||
@@ -133,7 +133,7 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
}
|
||||
|
||||
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
|
||||
range_multiplier_ = static_cast<int16_t>(range / 64);
|
||||
range_multiplier_ = int16_t(range / 64);
|
||||
}
|
||||
|
||||
#undef shift
|
||||
|
@@ -43,7 +43,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
};
|
||||
|
||||
struct BusHandler {
|
||||
void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
|
||||
void perform_read([[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *pixel_data, [[maybe_unused]] uint8_t *colour_data) {
|
||||
*pixel_data = 0xff;
|
||||
*colour_data = 0xff;
|
||||
}
|
||||
@@ -81,13 +81,14 @@ template <class BusHandler> class MOS6560 {
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate) {
|
||||
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||
speaker_.set_input_rate(float(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||
Outputs::Display::DisplayType get_display_type() const { return crt_.get_display_type(); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
|
||||
|
||||
void set_high_frequency_cutoff(float cutoff) {
|
||||
speaker_.set_high_frequency_cutoff(cutoff);
|
||||
@@ -234,7 +235,7 @@ template <class BusHandler> class MOS6560 {
|
||||
if(column_counter_&1) {
|
||||
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
|
||||
} else {
|
||||
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_);
|
||||
video_matrix_address_counter_++;
|
||||
if(
|
||||
(current_character_row_ == 15) ||
|
||||
@@ -370,7 +371,7 @@ template <class BusHandler> class MOS6560 {
|
||||
|
||||
case 0x2:
|
||||
registers_.number_of_columns = value & 0x7f;
|
||||
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
@@ -379,8 +380,8 @@ template <class BusHandler> class MOS6560 {
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10);
|
||||
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
|
||||
break;
|
||||
|
||||
case 0xa:
|
||||
@@ -419,11 +420,11 @@ template <class BusHandler> class MOS6560 {
|
||||
/*
|
||||
Reads from a 6560 register.
|
||||
*/
|
||||
uint8_t read(int address) {
|
||||
uint8_t read(int address) const {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
default: return registers_.direct_values[address];
|
||||
case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
|
||||
case 0x04: return (raster_value() >> 1) & 0xff;
|
||||
}
|
||||
}
|
||||
@@ -461,11 +462,11 @@ template <class BusHandler> class MOS6560 {
|
||||
|
||||
// counters that cover an entire field
|
||||
int horizontal_counter_ = 0, vertical_counter_ = 0;
|
||||
const int lines_this_field() {
|
||||
int lines_this_field() const {
|
||||
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
|
||||
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
|
||||
}
|
||||
const int raster_value() {
|
||||
int raster_value() const {
|
||||
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
|
||||
const int line = vertical_counter_ + bonus_line;
|
||||
const int final_line = lines_this_field();
|
||||
@@ -480,7 +481,7 @@ template <class BusHandler> class MOS6560 {
|
||||
}
|
||||
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
|
||||
}
|
||||
bool is_odd_frame() {
|
||||
bool is_odd_frame() const {
|
||||
return is_odd_frame_ || !registers_.interlaced;
|
||||
}
|
||||
|
||||
|
@@ -167,8 +167,8 @@ template <class T> class CRTC6845 {
|
||||
private:
|
||||
inline void perform_bus_cycle_phase1() {
|
||||
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
|
||||
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
|
||||
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
|
||||
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | unsigned(character_is_visible_);
|
||||
bus_state_.display_enable = (int(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
|
||||
bus_handler_.perform_bus_cycle_phase1(bus_state_);
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ template <class T> class CRTC6845 {
|
||||
inline void do_end_of_frame() {
|
||||
line_counter_ = 0;
|
||||
line_is_visible_ = true;
|
||||
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
|
||||
line_address_ = uint16_t((registers_[12] << 8) | registers_[13]);
|
||||
bus_state_.refresh_address = line_address_;
|
||||
}
|
||||
|
||||
|
@@ -120,7 +120,7 @@ void ACIA::consider_transmission() {
|
||||
}
|
||||
}
|
||||
|
||||
ClockingHint::Preference ACIA::preferred_clocking() {
|
||||
ClockingHint::Preference ACIA::preferred_clocking() const {
|
||||
// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
|
||||
// is on the receiving end.
|
||||
if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
|
||||
@@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
|
||||
return value ^ (parity_ == Parity::Even);
|
||||
}
|
||||
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
|
||||
// Shift this bit into the 11-bit input register; this is big enough to hold
|
||||
// the largest transmission symbol.
|
||||
++bits_received_;
|
||||
|
@@ -86,7 +86,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
Serial::Line request_to_send;
|
||||
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
struct InterruptDelegate {
|
||||
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
using namespace Motorola::MFP68901;
|
||||
|
||||
ClockingHint::Preference MFP68901::preferred_clocking() {
|
||||
ClockingHint::Preference MFP68901::preferred_clocking() const {
|
||||
// Rule applied: if any timer is actively running and permitted to produce an
|
||||
// interrupt, request real-time running.
|
||||
return
|
||||
|
@@ -76,7 +76,7 @@ class MFP68901: public ClockingHint::Source {
|
||||
void set_interrupt_delegate(InterruptDelegate *delegate);
|
||||
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
private:
|
||||
// MARK: - Timers
|
||||
|
@@ -9,13 +9,15 @@
|
||||
#ifndef i8255_hpp
|
||||
#define i8255_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8255 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
void set_value(int port, uint8_t value) {}
|
||||
uint8_t get_value(int port) { return 0xff; }
|
||||
void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {}
|
||||
uint8_t get_value([[maybe_unused]] int port) { return 0xff; }
|
||||
};
|
||||
|
||||
// TODO: Modes 1 and 2.
|
||||
|
@@ -79,10 +79,14 @@ namespace {
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||
Storage::Disk::MFMController(clock_rate),
|
||||
bus_handler_(bus_handler) {
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
posit_event(int(Event8272::CommandByte));
|
||||
|
||||
// TODO: implement DMA, etc. I have a vague intention to implement the IBM PC
|
||||
// one day, that should help to force that stuff.
|
||||
(void)bus_handler_;
|
||||
}
|
||||
|
||||
ClockingHint::Preference i8272::preferred_clocking() {
|
||||
ClockingHint::Preference i8272::preferred_clocking() const {
|
||||
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;
|
||||
@@ -97,7 +101,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
if(delay_time_ > 0) {
|
||||
if(cycles.as_integral() >= delay_time_) {
|
||||
delay_time_ = 0;
|
||||
posit_event(static_cast<int>(Event8272::Timer));
|
||||
posit_event(int(Event8272::Timer));
|
||||
} else {
|
||||
delay_time_ -= cycles.as_integral();
|
||||
}
|
||||
@@ -114,7 +118,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;
|
||||
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position));
|
||||
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << 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;
|
||||
@@ -156,7 +160,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
|
||||
// check for busy plus ready disabled
|
||||
if(is_executing_ && !get_drive().get_is_ready()) {
|
||||
posit_event(static_cast<int>(Event8272::NoLongerReady));
|
||||
posit_event(int(Event8272::NoLongerReady));
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
@@ -177,7 +181,7 @@ void i8272::write(int address, uint8_t value) {
|
||||
} else {
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
posit_event(int(Event8272::CommandByte));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +190,7 @@ uint8_t i8272::read(int address) {
|
||||
if(result_stack_.empty()) return 0xff;
|
||||
uint8_t result = result_stack_.back();
|
||||
result_stack_.pop_back();
|
||||
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
|
||||
if(result_stack_.empty()) posit_event(int(Event8272::ResultEmpty));
|
||||
|
||||
return result;
|
||||
} else {
|
||||
@@ -198,16 +202,16 @@ uint8_t i8272::read(int address) {
|
||||
#define END_SECTION() }
|
||||
|
||||
#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_clocking_observer(); case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = 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)
|
||||
|
||||
#define FIND_HEADER() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
|
||||
if(event_type == int(Event::IndexHole)) { index_hole_limit_--; } \
|
||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||
\
|
||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||
@@ -215,8 +219,8 @@ uint8_t i8272::read(int address) {
|
||||
|
||||
#define FIND_DATA() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
|
||||
if(event_type == static_cast<int>(Event::Token)) { \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
|
||||
if(event_type == int(Event::Token)) { \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||
}
|
||||
|
||||
@@ -264,8 +268,8 @@ uint8_t i8272::read(int address) {
|
||||
}
|
||||
|
||||
void i8272::posit_event(int event_type) {
|
||||
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
|
||||
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
|
||||
if(event_type == int(Event::IndexHole)) index_hole_count_++;
|
||||
if(event_type == int(Event8272::NoLongerReady)) {
|
||||
SetNotReady();
|
||||
goto abort;
|
||||
}
|
||||
@@ -425,12 +429,12 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs the read data or read deleted data command.
|
||||
read_data:
|
||||
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]) << "]");
|
||||
<< int(command_[2]) << " "
|
||||
<< int(command_[3]) << " "
|
||||
<< int(command_[4]) << " "
|
||||
<< int(command_[5]) << " ... "
|
||||
<< int(command_[6]) << " "
|
||||
<< int(command_[8]) << "]");
|
||||
read_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
@@ -439,7 +443,7 @@ void i8272::posit_event(int event_type) {
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
ClearControlMark();
|
||||
if(event_type == static_cast<int>(Event::Token)) {
|
||||
if(event_type == int(Event::Token)) {
|
||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||
// Something other than a data mark came next, impliedly an ID or index mark.
|
||||
SetMissingAddressMark();
|
||||
@@ -470,24 +474,24 @@ void i8272::posit_event(int event_type) {
|
||||
//
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
if(event_type == static_cast<int>(Event::Token)) {
|
||||
WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
|
||||
if(event_type == int(Event::Token)) {
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
|
||||
WAIT_FOR_EVENT(int(Event8272::ResultEmpty) | int(Event::Token) | int(Event::IndexHole));
|
||||
}
|
||||
switch(event_type) {
|
||||
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
|
||||
case int(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
||||
break;
|
||||
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
|
||||
case int(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
|
||||
SetOverrun();
|
||||
goto abort;
|
||||
break;
|
||||
case static_cast<int>(Event::IndexHole):
|
||||
case int(Event::IndexHole):
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
break;
|
||||
@@ -515,12 +519,12 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
write_data:
|
||||
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]) << "]");
|
||||
<< int(command_[2]) << " "
|
||||
<< int(command_[3]) << " "
|
||||
<< int(command_[4]) << " "
|
||||
<< int(command_[5]) << " ... "
|
||||
<< int(command_[6]) << " "
|
||||
<< int(command_[8]) << "]");
|
||||
|
||||
if(get_drive().get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
@@ -571,7 +575,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.
|
||||
LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]");
|
||||
LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << 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.
|
||||
@@ -594,10 +598,10 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs read track.
|
||||
read_track:
|
||||
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]) << "]");
|
||||
<< int(command_[2]) << " "
|
||||
<< int(command_[3]) << " "
|
||||
<< int(command_[4]) << " "
|
||||
<< int(command_[5]) << "]");
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
@@ -627,7 +631,7 @@ void i8272::posit_event(int event_type) {
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
|
||||
WAIT_FOR_EVENT(int(Event8272::ResultEmpty));
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
||||
|
||||
@@ -664,13 +668,13 @@ void i8272::posit_event(int event_type) {
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
format_track_write_header:
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||
WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
|
||||
switch(event_type) {
|
||||
case static_cast<int>(Event::IndexHole):
|
||||
case int(Event::IndexHole):
|
||||
SetOverrun();
|
||||
goto abort;
|
||||
break;
|
||||
case static_cast<int>(Event::DataWritten):
|
||||
case int(Event::DataWritten):
|
||||
header_[distance_into_section_] = input_;
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
@@ -683,10 +687,10 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
|
||||
LOG(PADHEX(2) << "W:"
|
||||
<< static_cast<int>(header_[0]) << " "
|
||||
<< static_cast<int>(header_[1]) << " "
|
||||
<< static_cast<int>(header_[2]) << " "
|
||||
<< static_cast<int>(header_[3]) << ", "
|
||||
<< int(header_[0]) << " "
|
||||
<< int(header_[1]) << " "
|
||||
<< int(header_[2]) << " "
|
||||
<< int(header_[3]) << ", "
|
||||
<< get_crc_generator().get_value());
|
||||
write_crc();
|
||||
|
||||
@@ -706,8 +710,8 @@ void i8272::posit_event(int event_type) {
|
||||
// Otherwise, pad out to the index hole.
|
||||
format_track_pad:
|
||||
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
||||
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
|
||||
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
|
||||
WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
|
||||
if(event_type != int(Event::IndexHole)) goto format_track_pad;
|
||||
|
||||
end_writing();
|
||||
|
||||
@@ -758,7 +762,7 @@ 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];
|
||||
LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
|
||||
LOG(PADHEX(2) << "Seek to " << int(command_[2]));
|
||||
} else {
|
||||
drives_[drive].target_head_position = -1;
|
||||
drives_[drive].head_position = 0;
|
||||
@@ -789,7 +793,7 @@ void i8272::posit_event(int event_type) {
|
||||
// If a drive was found, return its results. Otherwise return a single 0x80.
|
||||
if(found_drive != -1) {
|
||||
drives_[found_drive].phase = Drive::NotSeeking;
|
||||
status_[0] = static_cast<uint8_t>(found_drive);
|
||||
status_[0] = uint8_t(found_drive);
|
||||
main_status_ &= ~(1 << found_drive);
|
||||
SetSeekEnd();
|
||||
|
||||
@@ -819,7 +823,7 @@ void i8272::posit_event(int event_type) {
|
||||
int drive = command_[1] & 3;
|
||||
select_drive(drive);
|
||||
result_stack_= {
|
||||
static_cast<uint8_t>(
|
||||
uint8_t(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||
@@ -853,9 +857,9 @@ 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:
|
||||
LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
|
||||
LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; ");
|
||||
for(std::size_t c = 0; c < result_stack_.size(); c++) {
|
||||
LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
|
||||
LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c]));
|
||||
}
|
||||
LOGNBR(std::endl);
|
||||
|
||||
@@ -880,13 +884,13 @@ bool i8272::seek_is_satisfied(int drive) {
|
||||
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
|
||||
}
|
||||
|
||||
void i8272::set_dma_acknowledge(bool dack) {
|
||||
void i8272::set_dma_acknowledge(bool) {
|
||||
}
|
||||
|
||||
void i8272::set_terminal_count(bool tc) {
|
||||
void i8272::set_terminal_count(bool) {
|
||||
}
|
||||
|
||||
void i8272::set_data_input(uint8_t value) {
|
||||
void i8272::set_data_input(uint8_t) {
|
||||
}
|
||||
|
||||
uint8_t i8272::get_data_output() {
|
||||
|
@@ -20,8 +20,9 @@ namespace i8272 {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
virtual void set_dma_data_request(bool drq) {}
|
||||
virtual void set_interrupt(bool irq) {}
|
||||
virtual ~BusHandler() {}
|
||||
virtual void set_dma_data_request([[maybe_unused]] bool drq) {}
|
||||
virtual void set_interrupt([[maybe_unused]] bool irq) {}
|
||||
};
|
||||
|
||||
class i8272 : public Storage::Disk::MFMController {
|
||||
@@ -39,13 +40,13 @@ class i8272 : public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
|
||||
private:
|
||||
// The bus handler, for interrupt and DMA-driven usage.
|
||||
// The bus handler, for interrupt and DMA-driven usage. [TODO]
|
||||
BusHandler &bus_handler_;
|
||||
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
||||
|
||||
@@ -68,7 +69,7 @@ class i8272 : public Storage::Disk::MFMController {
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type) final;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int interesting_event_mask_ = int(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "z8530.hpp"
|
||||
|
||||
#define LOG_PREFIX "[SCC] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Zilog::SCC;
|
||||
@@ -16,7 +17,7 @@ void z8530::reset() {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
bool z8530::get_interrupt_line() {
|
||||
bool z8530::get_interrupt_line() const {
|
||||
return
|
||||
(master_interrupt_control_ & 0x8) &&
|
||||
(
|
||||
@@ -25,22 +26,30 @@ bool z8530::get_interrupt_line() {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Per the standard defined in the header file, this implementation follows
|
||||
an addressing convention of:
|
||||
|
||||
A0 = A/B (i.e. channel select)
|
||||
A1 = C/D (i.e. control or data)
|
||||
*/
|
||||
|
||||
std::uint8_t z8530::read(int address) {
|
||||
if(address & 2) {
|
||||
// Read data register for channel
|
||||
return 0x00;
|
||||
// Read data register for channel.
|
||||
return channels_[address & 1].read(true, pointer_);
|
||||
} else {
|
||||
// Read control register for channel.
|
||||
uint8_t result = 0;
|
||||
|
||||
switch(pointer_) {
|
||||
default:
|
||||
result = channels_[address & 1].read(address & 2, pointer_);
|
||||
result = channels_[address & 1].read(false, pointer_);
|
||||
break;
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
LOG("[SCC] Unimplemented: register 2 status bits");
|
||||
LOG("Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
@@ -63,7 +72,11 @@ std::uint8_t z8530::read(int address) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Cf. the two-step control register selection process in ::write. Since this
|
||||
// definitely wasn't a *write* to register 0, it follows that the next selected
|
||||
// control register will be 0.
|
||||
pointer_ = 0;
|
||||
|
||||
update_delegate();
|
||||
return result;
|
||||
}
|
||||
@@ -73,24 +86,31 @@ std::uint8_t z8530::read(int address) {
|
||||
|
||||
void z8530::write(int address, std::uint8_t value) {
|
||||
if(address & 2) {
|
||||
// Write data register for channel.
|
||||
// Write data register for channel. This is completely independent
|
||||
// of whatever is going on over in the control realm.
|
||||
channels_[address & 1].write(true, pointer_, value);
|
||||
} else {
|
||||
// Write control register for channel.
|
||||
// Write control register for channel; there's a two-step sequence
|
||||
// here for the programmer. Initially the selected register
|
||||
// (i.e. `pointer_`) is zero. That register includes a field to
|
||||
// set the next selected register. After any other register has
|
||||
// been written to, register 0 is selected again.
|
||||
|
||||
// Most registers are per channel, but a couple are shared; sever
|
||||
// them here.
|
||||
// Most registers are per channel, but a couple are shared;
|
||||
// sever them here, send the rest to the appropriate chnanel.
|
||||
switch(pointer_) {
|
||||
default:
|
||||
channels_[address & 1].write(address & 2, pointer_, value);
|
||||
channels_[address & 1].write(false, pointer_, value);
|
||||
break;
|
||||
|
||||
case 2: // Interrupt vector register; shared between both channels.
|
||||
case 2: // Interrupt vector register; used only by Channel B.
|
||||
// So there's only one of these.
|
||||
interrupt_vector_ = value;
|
||||
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
LOG("Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; also shared between both channels.
|
||||
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
|
||||
case 9: // Master interrupt and reset register; there is also only one of these.
|
||||
LOG("Master interrupt and reset register: " << PADHEX(2) << int(value));
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
@@ -105,7 +125,8 @@ void z8530::write(int address, std::uint8_t value) {
|
||||
pointer_ = value & 7;
|
||||
|
||||
// If the command part of the byte is a 'point high', also set the
|
||||
// top bit of the pointer.
|
||||
// top bit of the pointer. Channels themselves therefore need not
|
||||
// (/should not) respond to the point high command.
|
||||
if(((value >> 3)&7) == 1) {
|
||||
pointer_ |= 8;
|
||||
}
|
||||
@@ -126,16 +147,79 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
LOG("Control read from register " << int(pointer));
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
return 0x00;
|
||||
|
||||
case 0:
|
||||
case 0x0: // Read Register 0; see p.37 (PDF p.45).
|
||||
// b0: Rx character available.
|
||||
// b1: zero count.
|
||||
// b2: Tx buffer empty.
|
||||
// b3: DCD.
|
||||
// b4: sync/hunt.
|
||||
// b5: CTS.
|
||||
// b6: Tx underrun/EOM.
|
||||
// b7: break/abort.
|
||||
return dcd_ ? 0x8 : 0x0;
|
||||
|
||||
case 0xf:
|
||||
case 0x1: // Read Register 1; see p.37 (PDF p.45).
|
||||
// b0: all sent.
|
||||
// b1: residue code 0.
|
||||
// b2: residue code 1.
|
||||
// b3: residue code 2.
|
||||
// b4: parity error.
|
||||
// b5: Rx overrun error.
|
||||
// b6: CRC/framing error.
|
||||
// b7: end of frame (SDLC).
|
||||
return 0x01;
|
||||
|
||||
case 0x2: // Read Register 2; see p.37 (PDF p.45).
|
||||
// Interrupt vector — modified by status information in B channel.
|
||||
return 0x00;
|
||||
|
||||
case 0x3: // Read Register 3; see p.37 (PDF p.45).
|
||||
// B channel: all bits are 0.
|
||||
// A channel:
|
||||
// b0: Channel B ext/status IP.
|
||||
// b1: Channel B Tx IP.
|
||||
// b2: Channel B Rx IP.
|
||||
// b3: Channel A ext/status IP.
|
||||
// b4: Channel A Tx IP.
|
||||
// b5: Channel A Rx IP.
|
||||
// b6, b7: 0.
|
||||
return 0x00;
|
||||
|
||||
case 0xa: // Read Register 10; see p.37 (PDF p.45).
|
||||
// b0: 0
|
||||
// b1: On loop.
|
||||
// b2: 0
|
||||
// b3: 0
|
||||
// b4: Loop sending.
|
||||
// b5: 0
|
||||
// b6: Two clocks missing.
|
||||
// b7: One clock missing.
|
||||
return 0x00;
|
||||
|
||||
case 0xc: // Read Register 12; see p.37 (PDF p.45).
|
||||
// Lower byte of time constant.
|
||||
return 0x00;
|
||||
|
||||
case 0xd: // Read Register 13; see p.38 (PDF p.46).
|
||||
// Upper byte of time constant.
|
||||
return 0x00;
|
||||
|
||||
case 0xf: // Read Register 15; see p.38 (PDF p.46).
|
||||
// External interrupt status:
|
||||
// b0: 0
|
||||
// b1: Zero count.
|
||||
// b2: 0
|
||||
// b3: DCD.
|
||||
// b4: Sync/hunt.
|
||||
// b5: CTS.
|
||||
// b6: Tx underrun/EOM.
|
||||
// b7: Break/abort.
|
||||
return external_interrupt_status_;
|
||||
}
|
||||
}
|
||||
@@ -148,9 +232,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
LOG("Control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
LOG("Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
@@ -158,13 +243,13 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
LOG("[SCC] TODO: reset Rx CRC checker.");
|
||||
LOG("TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset Tx CRC checker.");
|
||||
LOG("TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
|
||||
LOG("TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -172,32 +257,84 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
// LOG("[SCC] reset ext/status interrupts.");
|
||||
// LOG("reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: send abort (SDLC).");
|
||||
LOG("TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
LOG("[SCC] TODO: enable interrupt on next Rx character.");
|
||||
LOG("TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
LOG("[SCC] TODO: reset Tx interrupt pending.");
|
||||
LOG("TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
LOG("[SCC] TODO: reset error.");
|
||||
LOG("TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
LOG("[SCC] TODO: reset highest IUS.");
|
||||
LOG("TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
|
||||
interrupt_mask_ = value;
|
||||
|
||||
/*
|
||||
b7 = 0 => Wait/Request output is inactive; 1 => output is informative.
|
||||
b6 = Wait/request output is for...
|
||||
0 => wait: floating when inactive, low if CPU is attempting to transfer data the SCC isn't yet ready for.
|
||||
1 => request: high if inactive, low if SCC is ready to transfer data.
|
||||
b5 = 1 => wait/request is relative to read buffer; 0 => relative to write buffer.
|
||||
|
||||
b4/b3:
|
||||
00 = disable receive interrupt
|
||||
01 = interrupt on first character or special condition
|
||||
10 = interrupt on all characters and special conditions
|
||||
11 = interrupt only upon special conditions.
|
||||
|
||||
b2 = 1 => parity error is a special condition; 0 => it isn't.
|
||||
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
|
||||
b0 = 1 => external interrupt is enabled; 0 => it isn't.
|
||||
*/
|
||||
LOG("Interrupt mask: " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 0x2: // Write register 2 - interrupt vector.
|
||||
break;
|
||||
|
||||
case 0x3: { // Write register 3 — Receive Parameters and Control.
|
||||
// Get bit count.
|
||||
int receive_bit_count = 8;
|
||||
switch(value >> 6) {
|
||||
default: receive_bit_count = 5; break;
|
||||
case 1: receive_bit_count = 7; break;
|
||||
case 2: receive_bit_count = 6; break;
|
||||
case 3: receive_bit_count = 8; break;
|
||||
}
|
||||
LOG("Receive bit count: " << receive_bit_count);
|
||||
|
||||
(void)receive_bit_count;
|
||||
|
||||
/*
|
||||
b7,b6:
|
||||
00 = 5 receive bits per character
|
||||
01 = 7 bits
|
||||
10 = 6 bits
|
||||
11 = 8 bits
|
||||
|
||||
b5 = 1 => DCD and CTS outputs are set automatically; 0 => they're inputs to read register 0.
|
||||
(DCD is ignored in local loopback; CTS is ignored in both auto echo and local loopback).
|
||||
b4: enter hunt mode (if set to 1, presumably?)
|
||||
b3 = 1 => enable receiver CRC generation; 0 => don't.
|
||||
b2: address search mode (SDLC)
|
||||
b1: sync character load inhibit.
|
||||
b0: Rx enable.
|
||||
*/
|
||||
} break;
|
||||
|
||||
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
|
||||
// Bits 0 and 1 select parity mode.
|
||||
if(!(value&1)) {
|
||||
@@ -236,6 +373,23 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x5:
|
||||
// b7: DTR
|
||||
// b6/b5:
|
||||
// 00 = Tx 5 bits (or less) per character
|
||||
// 01 = Tx 7 bits per character
|
||||
// 10 = Tx 6 bits per character
|
||||
// 11 = Tx 8 bits per character
|
||||
// b4: send break.
|
||||
// b3: Tx enable.
|
||||
// b2: SDLC (if 0) / CRC-16 (if 1)
|
||||
// b1: RTS
|
||||
// b0: Tx CRC enable.
|
||||
break;
|
||||
|
||||
case 0x6:
|
||||
break;
|
||||
|
||||
case 0xf: // Write register 15 — External/Status Interrupt Control.
|
||||
external_interrupt_mask_ = value;
|
||||
break;
|
||||
@@ -253,12 +407,17 @@ void z8530::Channel::set_dcd(bool level) {
|
||||
}
|
||||
}
|
||||
|
||||
bool z8530::Channel::get_interrupt_line() {
|
||||
bool z8530::Channel::get_interrupt_line() const {
|
||||
return
|
||||
(interrupt_mask_ & 1) && external_status_interrupt_;
|
||||
// TODO: other potential causes of an interrupt.
|
||||
}
|
||||
|
||||
/*!
|
||||
Evaluates the new level of the interrupt line and notifies the delegate if
|
||||
both: (i) there is one; and (ii) the interrupt line has changed since last
|
||||
the delegate was notified.
|
||||
*/
|
||||
void z8530::update_delegate() {
|
||||
const bool interrupt_line = get_interrupt_line();
|
||||
if(interrupt_line != previous_interrupt_line_) {
|
||||
|
@@ -30,16 +30,33 @@ class z8530 {
|
||||
A/B = A0
|
||||
C/D = A1
|
||||
*/
|
||||
|
||||
/// Performs a read from the SCC; see above for conventions as to 'address'.
|
||||
std::uint8_t read(int address);
|
||||
/// Performs a write to the SCC; see above for conventions as to 'address'.
|
||||
void write(int address, std::uint8_t value);
|
||||
/// Resets the SCC.
|
||||
void reset();
|
||||
bool get_interrupt_line();
|
||||
|
||||
/// @returns The current value of the status output: @c true for active; @c false for inactive.
|
||||
bool get_interrupt_line() const;
|
||||
|
||||
struct Delegate {
|
||||
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
|
||||
/*!
|
||||
Communicates that @c scc now has the interrupt line status @c new_status.
|
||||
*/
|
||||
virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Sets the delegate for this SCC. If this is a new delegate it is sent
|
||||
an immediate did_change_interrupt_status message, to get it
|
||||
up to speed.
|
||||
*/
|
||||
void set_delegate(Delegate *delegate) {
|
||||
if(delegate_ == delegate) return;
|
||||
delegate_ = delegate;
|
||||
delegate_->did_change_interrupt_status(this, get_interrupt_line());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -53,7 +70,7 @@ class z8530 {
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
void set_dcd(bool level);
|
||||
bool get_interrupt_line();
|
||||
bool get_interrupt_line() const;
|
||||
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
|
@@ -33,7 +33,7 @@ struct ReverseTable {
|
||||
|
||||
ReverseTable() {
|
||||
for(int c = 0; c < 256; ++c) {
|
||||
map[c] = static_cast<uint8_t>(
|
||||
map[c] = uint8_t(
|
||||
((c & 0x80) >> 7) |
|
||||
((c & 0x40) >> 5) |
|
||||
((c & 0x20) >> 3) |
|
||||
@@ -129,6 +129,10 @@ void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType TMS9918::get_display_type() const {
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
|
||||
void Base::LineBuffer::reset_sprite_collection() {
|
||||
sprites_stopped = false;
|
||||
active_sprite_slot = 0;
|
||||
@@ -140,7 +144,7 @@ void Base::LineBuffer::reset_sprite_collection() {
|
||||
|
||||
void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
|
||||
if(!(status_ & StatusSpriteOverflow)) {
|
||||
status_ = static_cast<uint8_t>((status_ & ~0x1f) | (sprite_number & 0x1f));
|
||||
status_ = uint8_t((status_ & ~0x1f) | (sprite_number & 0x1f));
|
||||
}
|
||||
if(buffer.sprites_stopped)
|
||||
return;
|
||||
@@ -527,7 +531,7 @@ void TMS9918::write(int address, uint8_t value) {
|
||||
|
||||
// The RAM pointer is always set on a second write, regardless of
|
||||
// whether the caller is intending to enqueue a VDP operation.
|
||||
ram_pointer_ = (ram_pointer_ & 0x00ff) | static_cast<uint16_t>(value << 8);
|
||||
ram_pointer_ = (ram_pointer_ & 0x00ff) | uint16_t(value << 8);
|
||||
|
||||
write_phase_ = false;
|
||||
if(value & 0x80) {
|
||||
@@ -661,7 +665,7 @@ uint8_t TMS9918::get_current_line() {
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<uint8_t>(source_row);
|
||||
return uint8_t(source_row);
|
||||
}
|
||||
|
||||
uint8_t TMS9918::get_latched_horizontal_counter() {
|
||||
|
@@ -50,6 +50,9 @@ class TMS9918: public Base {
|
||||
/*! Sets the type of display the CRT will request. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/*! Gets the type of display the CRT will request. */
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/*!
|
||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
|
||||
|
@@ -40,7 +40,7 @@ enum class TVStandard {
|
||||
|
||||
class Base {
|
||||
public:
|
||||
static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
|
||||
static uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
|
||||
uint32_t result = 0;
|
||||
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
|
||||
result_ptr[0] = r;
|
||||
@@ -51,7 +51,7 @@ class Base {
|
||||
}
|
||||
|
||||
protected:
|
||||
const static int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
|
||||
static constexpr int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
|
||||
|
||||
// The default TMS palette.
|
||||
const uint32_t palette[16] = {
|
||||
@@ -352,9 +352,9 @@ class Base {
|
||||
if(master_system_.cram_is_selected) {
|
||||
// Adjust the palette.
|
||||
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
|
||||
uint8_t(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
|
||||
uint8_t(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
|
||||
uint8_t(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
|
||||
);
|
||||
|
||||
// Schedule a CRAM dot; this is scheduled for wherever it should appear
|
||||
@@ -421,7 +421,8 @@ class Base {
|
||||
*/
|
||||
|
||||
#define slot(n) \
|
||||
if(use_end && end == n) return;\
|
||||
if(use_end && end == n) return; \
|
||||
[[fallthrough]]; \
|
||||
case n
|
||||
|
||||
#define external_slot(n) \
|
||||
@@ -449,7 +450,7 @@ class Base {
|
||||
|
||||
|
||||
/***********************************************
|
||||
TMS9918 Fetching Code
|
||||
TMS9918 Fetching Code
|
||||
************************************************/
|
||||
|
||||
template<bool use_end> void fetch_tms_refresh(int start, int end) {
|
||||
@@ -518,7 +519,7 @@ class Base {
|
||||
fetch_columns_4(location+12, column+4);
|
||||
|
||||
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
|
||||
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
|
||||
const size_t row_base = pattern_name_address_ & (0x3c00 | size_t(write_pointer_.row >> 3) * 40);
|
||||
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
|
||||
|
||||
switch(start) {
|
||||
@@ -693,7 +694,7 @@ class Base {
|
||||
|
||||
|
||||
/***********************************************
|
||||
Master System Fetching Code
|
||||
Master System Fetching Code
|
||||
************************************************/
|
||||
|
||||
template<bool use_end> void fetch_sms(int start, int end) {
|
||||
@@ -731,7 +732,7 @@ class Base {
|
||||
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
|
||||
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
|
||||
line_buffer.names[column].flags = ram_[address+1]; \
|
||||
line_buffer.names[column].offset = static_cast<size_t>( \
|
||||
line_buffer.names[column].offset = size_t( \
|
||||
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
|
||||
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
|
||||
}
|
||||
@@ -785,7 +786,7 @@ class Base {
|
||||
};
|
||||
const RowInfo scrolled_row_info = {
|
||||
(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
|
||||
{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
|
||||
{size_t((scrolled_row & 7) << 2), 28 ^ size_t((scrolled_row & 7) << 2)}
|
||||
};
|
||||
RowInfo row_info;
|
||||
if(master_system_.vertical_scroll_lock) {
|
||||
|
@@ -234,7 +234,7 @@ template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() {
|
||||
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() const {
|
||||
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
|
||||
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ class PortHandler {
|
||||
|
||||
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
|
||||
*/
|
||||
virtual uint8_t get_port_input(bool port_b) {
|
||||
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class PortHandler {
|
||||
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
|
||||
@param value the value now being output.
|
||||
*/
|
||||
virtual void set_port_output(bool port_b, uint8_t value) {}
|
||||
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -107,7 +107,7 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
bool is_zero_level();
|
||||
bool is_zero_level() const;
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
static constexpr bool get_is_stereo() { return is_stereo; }
|
||||
|
||||
|
@@ -23,7 +23,7 @@ void Toggle::set_sample_volume_range(std::int16_t range) {
|
||||
volume_ = range;
|
||||
}
|
||||
|
||||
void Toggle::skip_samples(const std::size_t number_of_samples) {}
|
||||
void Toggle::skip_samples(std::size_t) {}
|
||||
|
||||
void Toggle::set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
@@ -33,6 +33,6 @@ void Toggle::set_output(bool enabled) {
|
||||
});
|
||||
}
|
||||
|
||||
bool Toggle::get_output() {
|
||||
bool Toggle::get_output() const {
|
||||
return is_enabled_;
|
||||
}
|
||||
|
@@ -24,10 +24,9 @@ class Toggle: public Outputs::Speaker::SampleSource {
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
void skip_samples(const std::size_t number_of_samples);
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
void set_output(bool enabled);
|
||||
bool get_output();
|
||||
bool get_output() const;
|
||||
|
||||
private:
|
||||
// Accessed on the calling thread.
|
||||
|
@@ -85,13 +85,13 @@ void DiskII::run_for(const Cycles cycles) {
|
||||
--flux_duration_;
|
||||
if(!flux_duration_) inputs_ |= input_flux;
|
||||
}
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
state_ = state_machine_[size_t(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
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 0x9: shift_register_ = uint8_t(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = 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);
|
||||
@@ -105,7 +105,7 @@ void DiskII::run_for(const Cycles cycles) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
|
||||
// Currently writing?
|
||||
@@ -191,7 +191,6 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
|
||||
((source_address&0x02) ? 0x02 : 0x00);
|
||||
uint8_t source_value = state_machine[source_address];
|
||||
|
||||
// Remap into Beneath Apple Pro-DOS value form.
|
||||
source_value =
|
||||
((source_value & 0x80) ? 0x10 : 0x0) |
|
||||
((source_value & 0x40) ? 0x20 : 0x0) |
|
||||
@@ -219,13 +218,13 @@ void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
}
|
||||
}
|
||||
|
||||
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
void DiskII::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::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();
|
||||
}
|
||||
|
||||
ClockingHint::Preference DiskII::preferred_clocking() {
|
||||
ClockingHint::Preference DiskII::preferred_clocking() const {
|
||||
return clocking_preference_;
|
||||
}
|
||||
|
||||
|
@@ -76,7 +76,7 @@ class DiskII :
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
|
||||
// As per Sleeper.
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
@@ -307,8 +307,8 @@ void IWM::run_for(const Cycles cycles) {
|
||||
} else {
|
||||
shift_register_ = sense();
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
/* Deliberate fallthrough. */
|
||||
default:
|
||||
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
|
||||
break;
|
||||
|
@@ -24,7 +24,7 @@ namespace Apple {
|
||||
Defines the drive interface used by the IWM, derived from the external pinout as
|
||||
per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
|
||||
|
||||
These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
|
||||
These are subclasses of Storage::Disk::Drive, so accept any disk the emulator supports,
|
||||
and provide the usual read/write interface for on-disk data.
|
||||
*/
|
||||
struct IWMDrive: public Storage::Disk::Drive {
|
||||
@@ -82,8 +82,9 @@ class IWM:
|
||||
|
||||
uint8_t data_register_ = 0;
|
||||
uint8_t mode_ = 0;
|
||||
bool read_write_ready_ = true;
|
||||
bool write_overran_ = false;
|
||||
// These related to functionality not-yet implemented.
|
||||
// bool read_write_ready_ = true;
|
||||
// bool write_overran_ = false;
|
||||
|
||||
int state_ = 0;
|
||||
|
||||
|
@@ -15,7 +15,7 @@ using namespace Konami;
|
||||
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
|
||||
task_queue_(task_queue) {}
|
||||
|
||||
bool SCC::is_zero_level() {
|
||||
bool SCC::is_zero_level() const {
|
||||
return !(channel_enable_ & 0x1f);
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ void SCC::write(uint16_t address, uint8_t value) {
|
||||
|
||||
void SCC::evaluate_output_volume() {
|
||||
transient_output_level_ =
|
||||
static_cast<int16_t>(
|
||||
int16_t(
|
||||
((
|
||||
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
|
||||
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
|
||||
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
|
||||
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
|
||||
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
|
||||
(channel_enable_ & 0x01) ? int8_t(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
|
||||
(channel_enable_ & 0x02) ? int8_t(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
|
||||
(channel_enable_ & 0x04) ? int8_t(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
|
||||
(channel_enable_ & 0x08) ? int8_t(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
|
||||
(channel_enable_ & 0x10) ? int8_t(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
|
||||
) * master_volume_) / (255*15*5)
|
||||
// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5.
|
||||
);
|
||||
|
@@ -27,7 +27,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
|
||||
/// As per ::SampleSource; provides a broadphase test for silence.
|
||||
bool is_zero_level();
|
||||
bool is_zero_level() const;
|
||||
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
@@ -61,7 +61,6 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
} waves_[4];
|
||||
|
||||
std::uint8_t channel_enable_ = 0;
|
||||
std::uint8_t test_register_ = 0;
|
||||
|
||||
void evaluate_output_volume();
|
||||
|
||||
|
264
Components/OPx/Implementation/EnvelopeGenerator.hpp
Normal file
264
Components/OPx/Implementation/EnvelopeGenerator.hpp
Normal file
@@ -0,0 +1,264 @@
|
||||
//
|
||||
// EnvelopeGenerator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/05/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EnvelopeGenerator_h
|
||||
#define EnvelopeGenerator_h
|
||||
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
/*!
|
||||
Models an OPL-style envelope generator.
|
||||
|
||||
Damping is optional; if damping is enabled then if there is a transition to key-on while
|
||||
attenuation is less than maximum then attenuation will be quickly transitioned to maximum
|
||||
before the attack phase can begin.
|
||||
|
||||
in real hardware damping is used by the envelope generators associated with
|
||||
carriers, with phases being reset upon the transition from damping to attack.
|
||||
|
||||
This code considers application of tremolo to be a function of the envelope generator;
|
||||
this is largely for logical conformity with the phase generator that necessarily has to
|
||||
apply vibrato.
|
||||
|
||||
TODO: use envelope_precision.
|
||||
*/
|
||||
template <int envelope_precision, int period_precision> class EnvelopeGenerator {
|
||||
public:
|
||||
/*!
|
||||
Advances the envelope generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
|
||||
*/
|
||||
void update(const LowFrequencyOscillator &oscillator) {
|
||||
// Apply tremolo, which is fairly easy.
|
||||
tremolo_ = tremolo_enable_ * oscillator.tremolo << 4;
|
||||
|
||||
// Something something something...
|
||||
const int key_scaling_rate = key_scale_rate_ >> key_scale_rate_shift_;
|
||||
switch(phase_) {
|
||||
case Phase::Damp:
|
||||
update_decay(oscillator, 12 << 2);
|
||||
if(attenuation_ == 511) {
|
||||
(*will_attack_)();
|
||||
phase_ = Phase::Attack;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Attack:
|
||||
update_attack(oscillator, attack_rate_ + key_scaling_rate);
|
||||
|
||||
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
|
||||
if(attenuation_ <= 0) {
|
||||
attenuation_ = 0;
|
||||
phase_ = Phase::Decay;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Decay:
|
||||
update_decay(oscillator, decay_rate_ + key_scaling_rate);
|
||||
if(attenuation_ >= sustain_level_) {
|
||||
attenuation_ = sustain_level_;
|
||||
phase_ = use_sustain_level_ ? Phase::Sustain : Phase::Release;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Sustain:
|
||||
// Nothing to do.
|
||||
break;
|
||||
|
||||
case Phase::Release:
|
||||
update_decay(oscillator, release_rate_ + key_scaling_rate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The current attenuation from this envelope generator. This is independent of the envelope precision.
|
||||
*/
|
||||
int attenuation() const {
|
||||
// TODO: if this envelope is fully released, should tremolo still be able to vocalise it?
|
||||
return (attenuation_ << 3) + tremolo_;
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables damping on this envelope generator. If damping is enabled then this envelope generator will
|
||||
use the damping phase when necessary (i.e. when transitioning to key on if attenuation is not already at maximum)
|
||||
and in any case will call @c will_attack before transitioning from any other state to attack.
|
||||
|
||||
@param will_attack Supply a will_attack callback to enable damping mode; supply nullopt to disable damping mode.
|
||||
*/
|
||||
void set_should_damp(const std::optional<std::function<void(void)>> &will_attack) {
|
||||
will_attack_ = will_attack;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current state of the key-on input.
|
||||
*/
|
||||
void set_key_on(bool key_on) {
|
||||
// Do nothing if this is not a leading or trailing edge.
|
||||
if(key_on == key_on_) return;
|
||||
key_on_ = key_on;
|
||||
|
||||
// Always transition to release upon a key off.
|
||||
if(!key_on_) {
|
||||
phase_ = Phase::Release;
|
||||
return;
|
||||
}
|
||||
|
||||
// On key on: if this is an envelope generator with damping, and damping is required,
|
||||
// schedule that. If damping is not required, announce a pending attack now and
|
||||
// transition to attack.
|
||||
if(will_attack_) {
|
||||
if(attenuation_ != 511) {
|
||||
phase_ = Phase::Damp;
|
||||
return;
|
||||
}
|
||||
|
||||
(*will_attack_)();
|
||||
}
|
||||
phase_ = Phase::Attack;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the attack rate, which should be in the range 0–15.
|
||||
*/
|
||||
void set_attack_rate(int rate) {
|
||||
attack_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the decay rate, which should be in the range 0–15.
|
||||
*/
|
||||
void set_decay_rate(int rate) {
|
||||
decay_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the release rate, which should be in the range 0–15.
|
||||
*/
|
||||
void set_release_rate(int rate) {
|
||||
release_rate_ = rate << 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the sustain level, which should be in the range 0–15.
|
||||
*/
|
||||
void set_sustain_level(int level) {
|
||||
sustain_level_ = level << 3;
|
||||
// TODO: verify the shift level here. Especially re: precision.
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables use of the sustain level. If this is disabled, the envelope proceeds
|
||||
directly from decay to release.
|
||||
*/
|
||||
void set_use_sustain_level(bool use) {
|
||||
use_sustain_level_ = use;
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables key-rate scaling.
|
||||
*/
|
||||
void set_key_scaling_rate_enabled(bool enabled) {
|
||||
key_scale_rate_shift_ = int(enabled) * 2;
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables application of the low-frequency oscillator's tremolo.
|
||||
*/
|
||||
void set_tremolo_enabled(bool enabled) {
|
||||
tremolo_enable_ = int(enabled);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current period associated with the channel that owns this envelope generator;
|
||||
this is used to select a key scaling rate if key-rate scaling is enabled.
|
||||
*/
|
||||
void set_period(int period, int octave) {
|
||||
key_scale_rate_ = (octave << 1) | (period >> (period_precision - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
Attack, Decay, Sustain, Release, Damp
|
||||
} phase_ = Phase::Release;
|
||||
int attenuation_ = 511, tremolo_ = 0;
|
||||
|
||||
bool key_on_ = false;
|
||||
std::optional<std::function<void(void)>> will_attack_;
|
||||
|
||||
int key_scale_rate_ = 0;
|
||||
int key_scale_rate_shift_ = 0;
|
||||
|
||||
int tremolo_enable_ = 0;
|
||||
|
||||
int attack_rate_ = 0;
|
||||
int decay_rate_ = 0;
|
||||
int release_rate_ = 0;
|
||||
int sustain_level_ = 0;
|
||||
bool use_sustain_level_ = false;
|
||||
|
||||
static constexpr int dithering_patterns[4][8] = {
|
||||
{0, 1, 0, 1, 0, 1, 0, 1},
|
||||
{0, 1, 0, 1, 1, 1, 0, 1},
|
||||
{0, 1, 1, 1, 0, 1, 1, 1},
|
||||
{0, 1, 1, 1, 1, 1, 1, 1},
|
||||
};
|
||||
|
||||
void update_attack(const LowFrequencyOscillator &oscillator, int rate) {
|
||||
// Special case: no attack.
|
||||
if(rate < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: instant attack.
|
||||
if(rate >= 60) {
|
||||
attenuation_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Work out the number of cycles between each adjustment tick, and stop now
|
||||
// if not at the next adjustment boundary.
|
||||
const int shift_size = 13 - (std::min(rate, 52) >> 2);
|
||||
if(oscillator.counter & ((1 << shift_size) - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply dithered adjustment.
|
||||
const int rate_shift = (rate > 55);
|
||||
const int step = dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7];
|
||||
attenuation_ -= ((attenuation_ >> (3 - rate_shift)) + 1) * step;
|
||||
}
|
||||
|
||||
void update_decay(const LowFrequencyOscillator &oscillator, int rate) {
|
||||
// Special case: no decay.
|
||||
if(rate < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Work out the number of cycles between each adjustment tick, and stop now
|
||||
// if not at the next adjustment boundary.
|
||||
const int shift_size = 13 - (std::min(rate, 52) >> 2);
|
||||
if(oscillator.counter & ((1 << shift_size) - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply dithered adjustment and clamp.
|
||||
const int rate_shift = 1 + (rate > 59) + (rate > 55);
|
||||
attenuation_ += dithering_patterns[rate & 3][(oscillator.counter >> shift_size) & 7] * (4 << rate_shift);
|
||||
attenuation_ = std::min(attenuation_, 511);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EnvelopeGenerator_h */
|
58
Components/OPx/Implementation/KeyLevelScaler.hpp
Normal file
58
Components/OPx/Implementation/KeyLevelScaler.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// KeyLevelScaler.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/05/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef KeyLevelScaler_h
|
||||
#define KeyLevelScaler_h
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
template <int frequency_precision> class KeyLevelScaler {
|
||||
public:
|
||||
|
||||
/*!
|
||||
Sets the current period associated with the channel that owns this envelope generator;
|
||||
this is used to select a key scaling rate if key-rate scaling is enabled.
|
||||
*/
|
||||
void set_period(int period, int octave) {
|
||||
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
|
||||
constexpr int masks[2] = {~0, 0};
|
||||
|
||||
// A two's complement assumption is embedded below; the use of masks relies
|
||||
// on the sign bit to clamp to zero.
|
||||
level_ = key_level_scales[period >> (frequency_precision - 4)];
|
||||
level_ -= 16 * (octave ^ 7);
|
||||
level_ &= masks[(level_ >> ((sizeof(int) * 8) - 1)) & 1];
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables key-rate scaling.
|
||||
*/
|
||||
void set_key_scaling_level(int level) {
|
||||
// '7' is just a number large enough to render all possible scaling coefficients as 0.
|
||||
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
|
||||
shift_ = key_level_scale_shifts[level];
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The current attenuation level due to key-level scaling.
|
||||
*/
|
||||
int attenuation() const {
|
||||
return level_ >> shift_;
|
||||
}
|
||||
|
||||
private:
|
||||
int level_ = 0;
|
||||
int shift_ = 0;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* KeyLevelScaler_h */
|
68
Components/OPx/Implementation/LowFrequencyOscillator.hpp
Normal file
68
Components/OPx/Implementation/LowFrequencyOscillator.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// LowFrequencyOscillator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/04/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LowFrequencyOscillator_hpp
|
||||
#define LowFrequencyOscillator_hpp
|
||||
|
||||
#include "../../../Numeric/LFSR.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
/*!
|
||||
Models the output of the OPL low-frequency oscillator, which provides a couple of optional fixed-frequency
|
||||
modifications to an operator: tremolo and vibrato. Also exposes a global time counter, which oscillators use
|
||||
as part of their ADSR envelope.
|
||||
*/
|
||||
class LowFrequencyOscillator {
|
||||
public:
|
||||
/// Current attenuation due to tremolo / amplitude modulation, between 0 and 26.
|
||||
int tremolo = 0;
|
||||
|
||||
/// A number between 0 and 7 indicating the current vibrato offset; this should be combined by operators
|
||||
/// with their frequency number to get the actual vibrato.
|
||||
int vibrato = 0;
|
||||
|
||||
/// A counter of the number of operator update cycles (i.e. input clock / 72) since an arbitrary time.
|
||||
int counter = 0;
|
||||
|
||||
/// Describes the current output of the LFSR; will be either 0 or 1.
|
||||
int lfsr = 0;
|
||||
|
||||
/// Updates the oscillator outputs. Should be called at the (input clock/72) rate.
|
||||
void update() {
|
||||
++counter;
|
||||
|
||||
// This produces output of:
|
||||
//
|
||||
// four instances of 0, four instances of 1... _three_ instances of 26,
|
||||
// four instances of 25, four instances of 24... _three_ instances of 0.
|
||||
//
|
||||
// ... advancing once every 64th update.
|
||||
const int tremolo_index = (counter >> 6) % 210;
|
||||
const int tremolo_levels[2] = {tremolo_index >> 2, 52 - ((tremolo_index+1) >> 2)};
|
||||
tremolo = tremolo_levels[tremolo_index / 107];
|
||||
|
||||
// Vibrato is relatively simple: it's just three bits from the counter.
|
||||
vibrato = (counter >> 10) & 7;
|
||||
}
|
||||
|
||||
/// Updartes the LFSR output. Should be called at the input clock rate.
|
||||
void update_lfsr() {
|
||||
lfsr = noise_source_.next();
|
||||
}
|
||||
|
||||
private:
|
||||
// This is the correct LSFR per forums.submarine.org.uk.
|
||||
Numeric::LFSR<int, 0x800302> noise_source_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LowFrequencyOscillator_hpp */
|
40
Components/OPx/Implementation/OPLBase.hpp
Normal file
40
Components/OPx/Implementation/OPLBase.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// OPLBase.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef OPLBase_h
|
||||
#define OPLBase_h
|
||||
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
void write(uint16_t address, uint8_t value) {
|
||||
if(address & 1) {
|
||||
static_cast<Child *>(this)->write_register(selected_register_, value);
|
||||
} else {
|
||||
selected_register_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
private:
|
||||
uint8_t selected_register_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OPLBase_h */
|
125
Components/OPx/Implementation/PhaseGenerator.hpp
Normal file
125
Components/OPx/Implementation/PhaseGenerator.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// PhaseGenerator.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/04/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PhaseGenerator_h
|
||||
#define PhaseGenerator_h
|
||||
|
||||
#include <cassert>
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
#include "Tables.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
/*!
|
||||
Models an OPL-style phase generator of templated precision; having been told its period ('f-num'), octave ('block') and
|
||||
multiple, and whether to apply vibrato, this will then appropriately update and return phase.
|
||||
*/
|
||||
template <int precision> class PhaseGenerator {
|
||||
public:
|
||||
/*!
|
||||
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
|
||||
*/
|
||||
void update(const LowFrequencyOscillator &oscillator) {
|
||||
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
|
||||
constexpr int vibrato_signs[2] = {1, -1};
|
||||
|
||||
// Get just the top three bits of the period_.
|
||||
const int top_freq = period_ >> (precision - 3);
|
||||
|
||||
// Cacluaute applicable vibrato as a function of (i) the top three bits of the
|
||||
// oscillator period; (ii) the current low-frequency oscillator vibrato output; and
|
||||
// (iii) whether vibrato is enabled.
|
||||
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_;
|
||||
|
||||
// Apply phase update with vibrato from the low-frequency oscillator.
|
||||
phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@returns Current phase; real hardware provides only the low ten bits of this result.
|
||||
*/
|
||||
int phase() const {
|
||||
// My table if multipliers is multiplied by two, so shift by one more
|
||||
// than the stated precision.
|
||||
return phase_ >> precision_shift;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns Current phase, scaled up by (1 << precision).
|
||||
*/
|
||||
int scaled_phase() const {
|
||||
return phase_ >> 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Applies feedback based on two historic samples of a total output level,
|
||||
plus the degree of feedback to apply
|
||||
*/
|
||||
void apply_feedback(LogSign first, LogSign second, int level) {
|
||||
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
|
||||
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the multiple for this phase generator, in the same terms as an OPL programmer,
|
||||
i.e. a 4-bit number that is used as a lookup into the internal multiples table.
|
||||
*/
|
||||
void set_multiple(int multiple) {
|
||||
// This encodes the MUL -> multiple table given on page 12,
|
||||
// multiplied by two.
|
||||
constexpr int multipliers[] = {
|
||||
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
|
||||
};
|
||||
assert(multiple < 16);
|
||||
multiple_ = multipliers[multiple];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the period of this generator, along with its current octave.
|
||||
|
||||
Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave.
|
||||
*/
|
||||
void set_period(int period, int octave) {
|
||||
period_ = period;
|
||||
octave_ = octave;
|
||||
|
||||
assert(octave_ < 8);
|
||||
assert(period_ < (1 << precision));
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables vibrato.
|
||||
*/
|
||||
void set_vibrato_enabled(bool enabled) {
|
||||
enable_vibrato_ = int(enabled);
|
||||
}
|
||||
|
||||
/*!
|
||||
Resets the current phase.
|
||||
*/
|
||||
void reset() {
|
||||
phase_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int precision_shift = 1 + precision;
|
||||
|
||||
int phase_ = 0;
|
||||
|
||||
int multiple_ = 0;
|
||||
int period_ = 0;
|
||||
int octave_ = 0;
|
||||
int enable_vibrato_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PhaseGenerator_h */
|
227
Components/OPx/Implementation/Tables.hpp
Normal file
227
Components/OPx/Implementation/Tables.hpp
Normal file
@@ -0,0 +1,227 @@
|
||||
//
|
||||
// Tables.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/04/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Tables_hpp
|
||||
#define Tables_hpp
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
/*
|
||||
These are the OPL's built-in log-sin and exponentiation tables, as recovered by
|
||||
Matthew Gambrell and Olli Niemitalo in 'OPLx decapsulated'. Despite the formulas
|
||||
being well known, I've elected not to generate these at runtime because even if I
|
||||
did, I'd just end up with the proper values laid out in full in a unit test, and
|
||||
they're very compact.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Represents both the logarithm of a value and its sign.
|
||||
|
||||
It's actually the negative logarithm, in base two, in fixed point.
|
||||
*/
|
||||
struct LogSign {
|
||||
int log;
|
||||
int sign;
|
||||
|
||||
void reset() {
|
||||
log = 0;
|
||||
sign = 1;
|
||||
}
|
||||
|
||||
LogSign &operator +=(int attenuation) {
|
||||
log += attenuation;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LogSign &operator +=(LogSign log_sign) {
|
||||
log += log_sign.log;
|
||||
sign *= log_sign.sign;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int level(int fractional = 0) const;
|
||||
};
|
||||
|
||||
/*!
|
||||
@returns Negative log sin of x, assuming a 1024-unit circle.
|
||||
*/
|
||||
constexpr LogSign negative_log_sin(int x) {
|
||||
/// Defines the first quadrant of 1024-unit negative log to the base two of sine (that conveniently misses sin(0)).
|
||||
///
|
||||
/// Expected branchless usage for a full 1024 unit output:
|
||||
///
|
||||
/// constexpr int multiplier[] = { 1, -1 };
|
||||
/// constexpr int mask[] = { 0, 255 };
|
||||
///
|
||||
/// value = exp( log_sin[angle & 255] ^ mask[(angle >> 8) & 1]) * multitplier[(angle >> 9) & 1]
|
||||
///
|
||||
/// ... where exp(x) = 2 ^ -x / 256
|
||||
constexpr int16_t log_sin[] = {
|
||||
2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137,
|
||||
1091, 1050, 1013, 979, 949, 920, 894, 869,
|
||||
846, 825, 804, 785, 767, 749, 732, 717,
|
||||
701, 687, 672, 659, 646, 633, 621, 609,
|
||||
598, 587, 576, 566, 556, 546, 536, 527,
|
||||
518, 509, 501, 492, 484, 476, 468, 461,
|
||||
453, 446, 439, 432, 425, 418, 411, 405,
|
||||
399, 392, 386, 380, 375, 369, 363, 358,
|
||||
352, 347, 341, 336, 331, 326, 321, 316,
|
||||
311, 307, 302, 297, 293, 289, 284, 280,
|
||||
276, 271, 267, 263, 259, 255, 251, 248,
|
||||
244, 240, 236, 233, 229, 226, 222, 219,
|
||||
215, 212, 209, 205, 202, 199, 196, 193,
|
||||
190, 187, 184, 181, 178, 175, 172, 169,
|
||||
167, 164, 161, 159, 156, 153, 151, 148,
|
||||
146, 143, 141, 138, 136, 134, 131, 129,
|
||||
127, 125, 122, 120, 118, 116, 114, 112,
|
||||
110, 108, 106, 104, 102, 100, 98, 96,
|
||||
94, 92, 91, 89, 87, 85, 83, 82,
|
||||
80, 78, 77, 75, 74, 72, 70, 69,
|
||||
67, 66, 64, 63, 62, 60, 59, 57,
|
||||
56, 55, 53, 52, 51, 49, 48, 47,
|
||||
46, 45, 43, 42, 41, 40, 39, 38,
|
||||
37, 36, 35, 34, 33, 32, 31, 30,
|
||||
29, 28, 27, 26, 25, 24, 23, 23,
|
||||
22, 21, 20, 20, 19, 18, 17, 17,
|
||||
16, 15, 15, 14, 13, 13, 12, 12,
|
||||
11, 10, 10, 9, 9, 8, 8, 7,
|
||||
7, 7, 6, 6, 5, 5, 5, 4,
|
||||
4, 4, 3, 3, 3, 2, 2, 2,
|
||||
2, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
constexpr int16_t sign[] = { 1, -1 };
|
||||
constexpr int16_t mask[] = { 0, 255 };
|
||||
|
||||
return {
|
||||
.log = log_sin[(x & 255) ^ mask[(x >> 8) & 1]],
|
||||
.sign = sign[(x >> 9) & 1]
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
Computes the linear value represented by the log-sign @c ls, shifted left by @c fractional prior
|
||||
to loss of precision.
|
||||
*/
|
||||
constexpr int power_two(LogSign ls, int fractional = 0) {
|
||||
/// A derivative of the exponent table in a real OPL2; mapped_exp[x] = (source[c ^ 0xff] << 1) | 0x800.
|
||||
///
|
||||
/// The ahead-of-time transformation represents fixed work the OPL2 does when reading its table
|
||||
/// independent on the input.
|
||||
///
|
||||
/// The original table is a 0.10 fixed-point representation of 2^x - 1 with bit 10 implicitly set, where x is
|
||||
/// in 0.8 fixed point.
|
||||
///
|
||||
/// Since the log_sin table represents sine in a negative base-2 logarithm, values from it would need
|
||||
/// to be negatived before being put into the original table. That's haned with the ^ 0xff. The | 0x800 is to
|
||||
/// set the implicit bit 10 (subject to the shift).
|
||||
///
|
||||
/// The shift by 1 is to allow the chip's exploitation of the recursive symmetry of the exponential table to
|
||||
/// be achieved more easily. Specifically, to convert a logarithmic attenuation to a linear one, just perform:
|
||||
///
|
||||
/// result = mapped_exp[x & 0xff] >> (x >> 8)
|
||||
constexpr int16_t mapped_exp[] = {
|
||||
4084, 4074, 4062, 4052, 4040, 4030, 4020, 4008,
|
||||
3998, 3986, 3976, 3966, 3954, 3944, 3932, 3922,
|
||||
3912, 3902, 3890, 3880, 3870, 3860, 3848, 3838,
|
||||
3828, 3818, 3808, 3796, 3786, 3776, 3766, 3756,
|
||||
3746, 3736, 3726, 3716, 3706, 3696, 3686, 3676,
|
||||
3666, 3656, 3646, 3636, 3626, 3616, 3606, 3596,
|
||||
3588, 3578, 3568, 3558, 3548, 3538, 3530, 3520,
|
||||
3510, 3500, 3492, 3482, 3472, 3464, 3454, 3444,
|
||||
3434, 3426, 3416, 3408, 3398, 3388, 3380, 3370,
|
||||
3362, 3352, 3344, 3334, 3326, 3316, 3308, 3298,
|
||||
3290, 3280, 3272, 3262, 3254, 3246, 3236, 3228,
|
||||
3218, 3210, 3202, 3192, 3184, 3176, 3168, 3158,
|
||||
3150, 3142, 3132, 3124, 3116, 3108, 3100, 3090,
|
||||
3082, 3074, 3066, 3058, 3050, 3040, 3032, 3024,
|
||||
3016, 3008, 3000, 2992, 2984, 2976, 2968, 2960,
|
||||
2952, 2944, 2936, 2928, 2920, 2912, 2904, 2896,
|
||||
2888, 2880, 2872, 2866, 2858, 2850, 2842, 2834,
|
||||
2826, 2818, 2812, 2804, 2796, 2788, 2782, 2774,
|
||||
2766, 2758, 2752, 2744, 2736, 2728, 2722, 2714,
|
||||
2706, 2700, 2692, 2684, 2678, 2670, 2664, 2656,
|
||||
2648, 2642, 2634, 2628, 2620, 2614, 2606, 2600,
|
||||
2592, 2584, 2578, 2572, 2564, 2558, 2550, 2544,
|
||||
2536, 2530, 2522, 2516, 2510, 2502, 2496, 2488,
|
||||
2482, 2476, 2468, 2462, 2456, 2448, 2442, 2436,
|
||||
2428, 2422, 2416, 2410, 2402, 2396, 2390, 2384,
|
||||
2376, 2370, 2364, 2358, 2352, 2344, 2338, 2332,
|
||||
2326, 2320, 2314, 2308, 2300, 2294, 2288, 2282,
|
||||
2276, 2270, 2264, 2258, 2252, 2246, 2240, 2234,
|
||||
2228, 2222, 2216, 2210, 2204, 2198, 2192, 2186,
|
||||
2180, 2174, 2168, 2162, 2156, 2150, 2144, 2138,
|
||||
2132, 2128, 2122, 2116, 2110, 2104, 2098, 2092,
|
||||
2088, 2082, 2076, 2070, 2064, 2060, 2054, 2048,
|
||||
};
|
||||
|
||||
return ((mapped_exp[ls.log & 0xff] << fractional) >> (ls.log >> 8)) * ls.sign;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Credit for the fixed register lists goes to Nuke.YKT; I found them at:
|
||||
https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instrument_rom
|
||||
|
||||
The arrays below begin with channel 1, then each line is a single channel defined
|
||||
in exactly the same terms as the OPL's user-defined channel.
|
||||
|
||||
*/
|
||||
|
||||
constexpr uint8_t opll_patch_set[] = {
|
||||
0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17,
|
||||
0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13,
|
||||
0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x11, 0x23,
|
||||
0x31, 0x61, 0x0e, 0x07, 0xa8, 0x64, 0x70, 0x27,
|
||||
0x32, 0x21, 0x1e, 0x06, 0xe0, 0x76, 0x00, 0x28,
|
||||
0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18,
|
||||
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x10, 0x07,
|
||||
0x23, 0x21, 0x2d, 0x14, 0xa2, 0x72, 0x00, 0x07,
|
||||
0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17,
|
||||
0x41, 0x61, 0x0b, 0x18, 0x85, 0xf7, 0x71, 0x07,
|
||||
0x13, 0x01, 0x83, 0x11, 0xfa, 0xe4, 0x10, 0x04,
|
||||
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
|
||||
0x61, 0x50, 0x0c, 0x05, 0xc2, 0xf5, 0x20, 0x42,
|
||||
0x01, 0x01, 0x55, 0x03, 0xc9, 0x95, 0x03, 0x02,
|
||||
0x61, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0x40, 0x13,
|
||||
};
|
||||
|
||||
constexpr uint8_t vrc7_patch_set[] = {
|
||||
0x03, 0x21, 0x05, 0x06, 0xe8, 0x81, 0x42, 0x27,
|
||||
0x13, 0x41, 0x14, 0x0d, 0xd8, 0xf6, 0x23, 0x12,
|
||||
0x11, 0x11, 0x08, 0x08, 0xfa, 0xb2, 0x20, 0x12,
|
||||
0x31, 0x61, 0x0c, 0x07, 0xa8, 0x64, 0x61, 0x27,
|
||||
0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28,
|
||||
0x02, 0x01, 0x06, 0x00, 0xa3, 0xe2, 0xf4, 0xf4,
|
||||
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07,
|
||||
0x23, 0x21, 0x22, 0x17, 0xa2, 0x72, 0x01, 0x17,
|
||||
0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01,
|
||||
0xb5, 0x01, 0x0f, 0x0f, 0xa8, 0xa5, 0x51, 0x02,
|
||||
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
|
||||
0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16,
|
||||
0x01, 0x02, 0xd3, 0x05, 0xc9, 0x95, 0x03, 0x02,
|
||||
0x61, 0x63, 0x0c, 0x00, 0x94, 0xc0, 0x33, 0xf6,
|
||||
0x21, 0x72, 0x0d, 0x00, 0xc1, 0xd5, 0x56, 0x06,
|
||||
};
|
||||
|
||||
constexpr uint8_t percussion_patch_set[] = {
|
||||
0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d,
|
||||
0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x48,
|
||||
0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55,
|
||||
};
|
||||
|
||||
|
||||
inline int LogSign::level(int fractional) const {
|
||||
return power_two(*this, fractional);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tables_hpp */
|
92
Components/OPx/Implementation/WaveformGenerator.hpp
Normal file
92
Components/OPx/Implementation/WaveformGenerator.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// WaveformGenerator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef WaveformGenerator_h
|
||||
#define WaveformGenerator_h
|
||||
|
||||
#include "Tables.hpp"
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
enum class Waveform {
|
||||
Sine, HalfSine, AbsSine, PulseSine
|
||||
};
|
||||
|
||||
template <int phase_precision> class WaveformGenerator {
|
||||
public:
|
||||
/*!
|
||||
@returns The output of waveform @c form at [integral] phase @c phase.
|
||||
*/
|
||||
static constexpr LogSign wave(Waveform form, int phase) {
|
||||
constexpr int waveforms[4][4] = {
|
||||
{1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant.
|
||||
{511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half.
|
||||
{511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave.
|
||||
{255, 0, 255, 0}, // PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0.
|
||||
};
|
||||
return negative_log_sin(phase & waveforms[int(form)][(phase >> 8) & 3]);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The output of waveform @c form at [scaled] phase @c scaled_phase given the modulation input @c modulation.
|
||||
*/
|
||||
static constexpr LogSign wave(Waveform form, int scaled_phase, LogSign modulation) {
|
||||
const int scaled_phase_offset = modulation.level(phase_precision);
|
||||
const int phase = (scaled_phase + scaled_phase_offset) >> phase_precision;
|
||||
return wave(form, phase);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns Snare output, calculated from the current LFSR state as captured in @c oscillator and an operator's phase.
|
||||
*/
|
||||
static constexpr LogSign snare(const LowFrequencyOscillator &oscillator, int phase) {
|
||||
// If noise is 0, output is positive.
|
||||
// If noise is 1, output is negative.
|
||||
// If (noise ^ sign) is 0, output is 0. Otherwise it is max.
|
||||
const int sign = phase & 0x200;
|
||||
const int level = ((phase >> 9) & 1) ^ oscillator.lfsr;
|
||||
return negative_log_sin(sign + (level << 8));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns Cymbal output, calculated from an operator's phase and a modulator's phase.
|
||||
*/
|
||||
static constexpr LogSign cymbal(int carrier_phase, int modulator_phase) {
|
||||
return negative_log_sin(256 + (phase_combination(carrier_phase, modulator_phase) << 9));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns High-hat output, calculated from the current LFSR state as captured in @c oscillator, an operator's phase and a modulator's phase.
|
||||
*/
|
||||
static constexpr LogSign high_hat(const LowFrequencyOscillator &oscillator, int carrier_phase, int modulator_phase) {
|
||||
constexpr int angles[] = {0x234, 0xd0, 0x2d0, 0x34};
|
||||
return negative_log_sin(angles[
|
||||
phase_combination(carrier_phase, modulator_phase) |
|
||||
(oscillator.lfsr << 1)
|
||||
]);
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@returns The phase bit used for cymbal and high-hat generation, which is a function of two operators' phases.
|
||||
*/
|
||||
static constexpr int phase_combination(int carrier_phase, int modulator_phase) {
|
||||
return (
|
||||
((carrier_phase >> 5) ^ (carrier_phase >> 3)) &
|
||||
((modulator_phase >> 7) ^ (modulator_phase >> 2)) &
|
||||
((carrier_phase >> 5) ^ (modulator_phase >> 3))
|
||||
) & 1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* WaveformGenerator_h */
|
443
Components/OPx/OPLL.cpp
Normal file
443
Components/OPx/OPLL.cpp
Normal file
@@ -0,0 +1,443 @@
|
||||
//
|
||||
// OPLL.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "OPLL.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace Yamaha::OPL;
|
||||
|
||||
OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7):
|
||||
OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
|
||||
// Due to the way that sound mixing works on the OPLL, the audio divider may not
|
||||
// be larger than 4.
|
||||
assert(audio_divider <= 4);
|
||||
|
||||
// Setup the rhythm envelope generators.
|
||||
|
||||
// Treat the bass exactly as if it were a melodic channel.
|
||||
rhythm_envelope_generators_[BassCarrier].set_should_damp([this] {
|
||||
// Propagate attack mode to the modulator, and reset both phases.
|
||||
rhythm_envelope_generators_[BassModulator].set_key_on(true);
|
||||
phase_generators_[6 + 0].reset();
|
||||
phase_generators_[6 + 9].reset();
|
||||
});
|
||||
|
||||
// Set the other drums to damp, but only the TomTom to affect phase.
|
||||
rhythm_envelope_generators_[TomTom].set_should_damp([this] {
|
||||
phase_generators_[8 + 9].reset();
|
||||
});
|
||||
rhythm_envelope_generators_[Snare].set_should_damp({});
|
||||
rhythm_envelope_generators_[Cymbal].set_should_damp({});
|
||||
rhythm_envelope_generators_[HighHat].set_should_damp({});
|
||||
|
||||
// Crib the proper rhythm envelope generator settings by installing
|
||||
// the rhythm instruments and copying them over.
|
||||
rhythm_mode_enabled_ = true;
|
||||
install_instrument(6);
|
||||
install_instrument(7);
|
||||
install_instrument(8);
|
||||
|
||||
rhythm_envelope_generators_[BassCarrier] = envelope_generators_[6];
|
||||
rhythm_envelope_generators_[BassModulator] = envelope_generators_[6 + 9];
|
||||
rhythm_envelope_generators_[HighHat] = envelope_generators_[7 + 9];
|
||||
rhythm_envelope_generators_[Cymbal] = envelope_generators_[8];
|
||||
rhythm_envelope_generators_[TomTom] = envelope_generators_[8 + 9];
|
||||
rhythm_envelope_generators_[Snare] = envelope_generators_[7];
|
||||
|
||||
// Return to ordinary default mode.
|
||||
rhythm_mode_enabled_ = false;
|
||||
|
||||
// Set up damping for the melodic channels.
|
||||
for(int c = 0; c < 9; ++c) {
|
||||
envelope_generators_[c].set_should_damp([this, c] {
|
||||
// Propagate attack mode to the modulator, and reset both phases.
|
||||
envelope_generators_[c + 9].set_key_on(true);
|
||||
phase_generators_[c + 0].reset();
|
||||
phase_generators_[c + 9].reset();
|
||||
});
|
||||
}
|
||||
|
||||
// Set default instrument.
|
||||
for(int c = 0; c < 9; ++c) {
|
||||
install_instrument(c);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Machine-facing programmatic input.
|
||||
|
||||
void OPLL::write_register(uint8_t address, uint8_t value) {
|
||||
// The OPLL doesn't have timers or other non-audio functions, so all writes
|
||||
// go to the audio queue.
|
||||
task_queue_.defer([this, address, value] {
|
||||
// The first 8 locations are used to define the custom instrument, and have
|
||||
// exactly the same format as the patch set arrays at the head of this file.
|
||||
if(address < 8) {
|
||||
custom_instrument_[address] = value;
|
||||
|
||||
// Update all channels that refer to instrument 0.
|
||||
for(int c = 0; c < 9; ++c) {
|
||||
if(!channels_[c].instrument) {
|
||||
install_instrument(c);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Register 0xe enables or disables rhythm mode and contains the
|
||||
// percussion key-on bits.
|
||||
if(address == 0xe) {
|
||||
const bool old_rhythm_mode = rhythm_mode_enabled_;
|
||||
rhythm_mode_enabled_ = value & 0x20;
|
||||
if(old_rhythm_mode != rhythm_mode_enabled_) {
|
||||
// Change the instlled instruments for channels 6, 7 and 8
|
||||
// if this was a transition into or out of rhythm mode.
|
||||
install_instrument(6);
|
||||
install_instrument(7);
|
||||
install_instrument(8);
|
||||
}
|
||||
rhythm_envelope_generators_[HighHat].set_key_on(value & 0x01);
|
||||
rhythm_envelope_generators_[Cymbal].set_key_on(value & 0x02);
|
||||
rhythm_envelope_generators_[TomTom].set_key_on(value & 0x04);
|
||||
rhythm_envelope_generators_[Snare].set_key_on(value & 0x08);
|
||||
if(value & 0x10) {
|
||||
rhythm_envelope_generators_[BassCarrier].set_key_on(true);
|
||||
} else {
|
||||
rhythm_envelope_generators_[BassCarrier].set_key_on(false);
|
||||
rhythm_envelope_generators_[BassModulator].set_key_on(false);
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// That leaves only per-channel selections, for which the addressing
|
||||
// is completely orthogonal; check that a valid channel is being requested.
|
||||
const auto index = address & 0xf;
|
||||
if(index > 8) return;
|
||||
|
||||
switch(address & 0xf0) {
|
||||
default: break;
|
||||
|
||||
// Address 1x sets the low 8 bits of the period for channel x.
|
||||
case 0x10:
|
||||
channels_[index].period = (channels_[index].period & ~0xff) | value;
|
||||
set_channel_period(index);
|
||||
return;
|
||||
|
||||
// Address 2x Sets the octave and a single bit of the frequency, as well
|
||||
// as setting key on and sustain mode.
|
||||
case 0x20:
|
||||
channels_[index].period = (channels_[index].period & 0xff) | ((value & 1) << 8);
|
||||
channels_[index].octave = (value >> 1) & 7;
|
||||
set_channel_period(index);
|
||||
|
||||
// In this implementation the first 9 envelope generators are for
|
||||
// channel carriers, and their will_attack callback is used to trigger
|
||||
// key-on for modulators. But key-off needs to be set to both envelope
|
||||
// generators now.
|
||||
if(value & 0x10) {
|
||||
envelope_generators_[index].set_key_on(true);
|
||||
} else {
|
||||
envelope_generators_[index + 0].set_key_on(false);
|
||||
envelope_generators_[index + 9].set_key_on(false);
|
||||
}
|
||||
|
||||
// Set sustain bit to both the relevant operators.
|
||||
channels_[index].use_sustain = value & 0x20;
|
||||
set_use_sustain(index);
|
||||
return;
|
||||
|
||||
// Address 3x selects the instrument and attenuation for a channel;
|
||||
// in rhythm mode some of the nibbles that ordinarily identify instruments
|
||||
// instead nominate additional attenuations. This code reads those back
|
||||
// from the stored instrument values.
|
||||
case 0x30:
|
||||
channels_[index].attenuation = value & 0xf;
|
||||
|
||||
// Install an instrument only if it's new.
|
||||
if(channels_[index].instrument != value >> 4) {
|
||||
channels_[index].instrument = value >> 4;
|
||||
if(index < 6 || !rhythm_mode_enabled_) {
|
||||
install_instrument(index);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OPLL::set_channel_period(int channel) {
|
||||
phase_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
|
||||
phase_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
|
||||
|
||||
envelope_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
|
||||
envelope_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
|
||||
|
||||
key_level_scalers_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
|
||||
key_level_scalers_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
|
||||
}
|
||||
|
||||
const uint8_t *OPLL::instrument_definition(int instrument, int channel) {
|
||||
// Divert to the appropriate rhythm instrument if in rhythm mode.
|
||||
if(channel >= 6 && rhythm_mode_enabled_) {
|
||||
return &percussion_patch_set[(channel - 6) * 8];
|
||||
}
|
||||
|
||||
// Instrument 0 is the custom instrument.
|
||||
if(!instrument) return custom_instrument_;
|
||||
|
||||
// Instruments other than 0 are taken from the fixed set.
|
||||
const int index = (instrument - 1) * 8;
|
||||
return is_vrc7_ ? &vrc7_patch_set[index] : &opll_patch_set[index];
|
||||
}
|
||||
|
||||
void OPLL::install_instrument(int channel) {
|
||||
auto &carrier_envelope = envelope_generators_[channel + 0];
|
||||
auto &carrier_phase = phase_generators_[channel + 0];
|
||||
auto &carrier_scaler = key_level_scalers_[channel + 0];
|
||||
|
||||
auto &modulator_envelope = envelope_generators_[channel + 9];
|
||||
auto &modulator_phase = phase_generators_[channel + 9];
|
||||
auto &modulator_scaler = key_level_scalers_[channel + 9];
|
||||
|
||||
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
|
||||
|
||||
// Bytes 0 (modulator) and 1 (carrier):
|
||||
//
|
||||
// b0-b3: multiplier;
|
||||
// b4: key-scale rate enable;
|
||||
// b5: sustain-level enable;
|
||||
// b6: vibrato enable;
|
||||
// b7: tremolo enable.
|
||||
modulator_phase.set_multiple(instrument[0] & 0xf);
|
||||
channels_[channel].modulator_key_rate_scale_multiplier = (instrument[0] >> 4) & 1;
|
||||
modulator_phase.set_vibrato_enabled(instrument[0] & 0x40);
|
||||
modulator_envelope.set_tremolo_enabled(instrument[0] & 0x80);
|
||||
|
||||
carrier_phase.set_multiple(instrument[1] & 0xf);
|
||||
channels_[channel].carrier_key_rate_scale_multiplier = (instrument[1] >> 4) & 1;
|
||||
carrier_phase.set_vibrato_enabled(instrument[1] & 0x40);
|
||||
carrier_envelope.set_tremolo_enabled(instrument[1] & 0x80);
|
||||
|
||||
// Pass off bit 5.
|
||||
set_use_sustain(channel);
|
||||
|
||||
// Byte 2:
|
||||
//
|
||||
// b0–b5: modulator attenuation;
|
||||
// b6–b7: modulator key-scale level.
|
||||
modulator_scaler.set_key_scaling_level(instrument[3] >> 6);
|
||||
channels_[channel].modulator_attenuation = instrument[2] & 0x3f;
|
||||
|
||||
// Byte 3:
|
||||
//
|
||||
// b0–b2: modulator feedback level;
|
||||
// b3: modulator waveform selection;
|
||||
// b4: carrier waveform selection;
|
||||
// b5: [unused]
|
||||
// b6–b7: carrier key-scale level.
|
||||
channels_[channel].modulator_feedback = instrument[3] & 7;
|
||||
channels_[channel].modulator_waveform = Waveform((instrument[3] >> 3) & 1);
|
||||
channels_[channel].carrier_waveform = Waveform((instrument[3] >> 4) & 1);
|
||||
carrier_scaler.set_key_scaling_level(instrument[3] >> 6);
|
||||
|
||||
// Bytes 4 (modulator) and 5 (carrier):
|
||||
//
|
||||
// b0–b3: decay rate;
|
||||
// b4–b7: attack rate.
|
||||
modulator_envelope.set_decay_rate(instrument[4] & 0xf);
|
||||
modulator_envelope.set_attack_rate(instrument[4] >> 4);
|
||||
carrier_envelope.set_decay_rate(instrument[5] & 0xf);
|
||||
carrier_envelope.set_attack_rate(instrument[5] >> 4);
|
||||
|
||||
// Bytes 6 (modulator) and 7 (carrier):
|
||||
//
|
||||
// b0–b3: release rate;
|
||||
// b4–b7: sustain level.
|
||||
modulator_envelope.set_release_rate(instrument[6] & 0xf);
|
||||
modulator_envelope.set_sustain_level(instrument[6] >> 4);
|
||||
carrier_envelope.set_release_rate(instrument[7] & 0xf);
|
||||
carrier_envelope.set_sustain_level(instrument[7] >> 4);
|
||||
}
|
||||
|
||||
void OPLL::set_use_sustain(int channel) {
|
||||
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
|
||||
envelope_generators_[channel + 0].set_use_sustain_level((instrument[1] & 0x20) || channels_[channel].use_sustain);
|
||||
envelope_generators_[channel + 9].set_use_sustain_level((instrument[0] & 0x20) || channels_[channel].use_sustain);
|
||||
}
|
||||
|
||||
// MARK: - Output generation.
|
||||
|
||||
void OPLL::set_sample_volume_range(std::int16_t range) {
|
||||
total_volume_ = range;
|
||||
}
|
||||
|
||||
void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
// Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency;
|
||||
// unlike the OPL2 the OPLL time-divides the output for 'mixing'.
|
||||
|
||||
const int update_period = 72 / audio_divider_;
|
||||
const int channel_output_period = 4 / audio_divider_;
|
||||
|
||||
// TODO: the conditional below is terrible. Fix.
|
||||
while(number_of_samples--) {
|
||||
if(!audio_offset_) update_all_channels();
|
||||
|
||||
*target = output_levels_[audio_offset_ / channel_output_period];
|
||||
++target;
|
||||
audio_offset_ = (audio_offset_ + 1) % update_period;
|
||||
}
|
||||
}
|
||||
|
||||
void OPLL::update_all_channels() {
|
||||
oscillator_.update();
|
||||
|
||||
// Update all phase generators. That's guaranteed.
|
||||
for(int c = 0; c < 18; ++c) {
|
||||
phase_generators_[c].update(oscillator_);
|
||||
}
|
||||
|
||||
// Update the ADSR envelopes that are guaranteed to be melodic.
|
||||
for(int c = 0; c < 6; ++c) {
|
||||
envelope_generators_[c + 0].update(oscillator_);
|
||||
envelope_generators_[c + 9].update(oscillator_);
|
||||
}
|
||||
|
||||
#define VOLUME(x) int16_t(((x) * total_volume_) >> 12)
|
||||
|
||||
if(rhythm_mode_enabled_) {
|
||||
// Advance the rhythm envelope generators.
|
||||
for(int c = 0; c < 6; ++c) {
|
||||
rhythm_envelope_generators_[c].update(oscillator_);
|
||||
}
|
||||
|
||||
// Fill in the melodic channels.
|
||||
output_levels_[3] = VOLUME(melodic_output(0));
|
||||
output_levels_[4] = VOLUME(melodic_output(1));
|
||||
output_levels_[5] = VOLUME(melodic_output(2));
|
||||
|
||||
output_levels_[9] = VOLUME(melodic_output(3));
|
||||
output_levels_[10] = VOLUME(melodic_output(4));
|
||||
output_levels_[11] = VOLUME(melodic_output(5));
|
||||
|
||||
// Bass drum, which is a regular FM effect.
|
||||
output_levels_[2] = output_levels_[15] = VOLUME(bass_drum());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Tom tom, which is a single operator.
|
||||
output_levels_[1] = output_levels_[14] = VOLUME(tom_tom());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Snare.
|
||||
output_levels_[6] = output_levels_[16] = VOLUME(snare_drum());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Cymbal.
|
||||
output_levels_[7] = output_levels_[17] = VOLUME(cymbal());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// High-hat.
|
||||
output_levels_[0] = output_levels_[13] = VOLUME(high_hat());
|
||||
oscillator_.update_lfsr();
|
||||
|
||||
// Unutilised slots.
|
||||
output_levels_[8] = output_levels_[12] = 0;
|
||||
oscillator_.update_lfsr();
|
||||
} else {
|
||||
for(int c = 6; c < 9; ++c) {
|
||||
envelope_generators_[c + 0].update(oscillator_);
|
||||
envelope_generators_[c + 9].update(oscillator_);
|
||||
}
|
||||
|
||||
// All melodic. Fairly easy.
|
||||
output_levels_[0] = output_levels_[1] = output_levels_[2] =
|
||||
output_levels_[6] = output_levels_[7] = output_levels_[8] =
|
||||
output_levels_[12] = output_levels_[13] = output_levels_[14] = 0;
|
||||
|
||||
output_levels_[3] = VOLUME(melodic_output(0));
|
||||
output_levels_[4] = VOLUME(melodic_output(1));
|
||||
output_levels_[5] = VOLUME(melodic_output(2));
|
||||
|
||||
output_levels_[9] = VOLUME(melodic_output(3));
|
||||
output_levels_[10] = VOLUME(melodic_output(4));
|
||||
output_levels_[11] = VOLUME(melodic_output(5));
|
||||
|
||||
output_levels_[15] = VOLUME(melodic_output(6));
|
||||
output_levels_[16] = VOLUME(melodic_output(7));
|
||||
output_levels_[17] = VOLUME(melodic_output(8));
|
||||
}
|
||||
|
||||
#undef VOLUME
|
||||
|
||||
// TODO: batch updates of the LFSR.
|
||||
}
|
||||
|
||||
// TODO: verify attenuation scales pervasively below.
|
||||
|
||||
#define ATTENUATION(x) ((x) << 7)
|
||||
|
||||
int OPLL::melodic_output(int channel) {
|
||||
// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value.
|
||||
auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output);
|
||||
carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
|
||||
|
||||
// Get the modulator's new value.
|
||||
auto modulation = WaveformGenerator<period_precision>::wave(channels_[channel].modulator_waveform, phase_generators_[channel + 9].phase());
|
||||
modulation += envelope_generators_[channel + 9].attenuation() + (channels_[channel].modulator_attenuation << 5) + key_level_scalers_[channel + 9].attenuation();
|
||||
|
||||
// Apply feedback, if any.
|
||||
phase_generators_[channel + 9].apply_feedback(channels_[channel].modulator_output, modulation, channels_[channel].modulator_feedback);
|
||||
channels_[channel].modulator_output = modulation;
|
||||
|
||||
return carrier.level();
|
||||
}
|
||||
|
||||
int OPLL::bass_drum() {
|
||||
// Use modulator 6 and carrier 6, attenuated as per the bass-specific envelope generators and the attenuation level for channel 6.
|
||||
auto modulation = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6 + 9].phase());
|
||||
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
|
||||
|
||||
auto carrier = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6].scaled_phase(), modulation);
|
||||
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + ATTENUATION(channels_[6].attenuation);
|
||||
return carrier.level();
|
||||
}
|
||||
|
||||
int OPLL::tom_tom() {
|
||||
// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation.
|
||||
auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
|
||||
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
|
||||
tom_tom += ATTENUATION(channels_[8].instrument);
|
||||
return tom_tom.level();
|
||||
}
|
||||
|
||||
int OPLL::snare_drum() {
|
||||
// Use modulator 7 and the carrier attenuation level for channel 7.
|
||||
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
|
||||
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
|
||||
snare += ATTENUATION(channels_[7].attenuation);
|
||||
return snare.level();
|
||||
}
|
||||
|
||||
int OPLL::cymbal() {
|
||||
// Use modulator 7, carrier 8 and the attenuation level for channel 8.
|
||||
LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
|
||||
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
|
||||
cymbal += ATTENUATION(channels_[8].attenuation);
|
||||
return cymbal.level();
|
||||
}
|
||||
|
||||
int OPLL::high_hat() {
|
||||
// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation.
|
||||
LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
|
||||
high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation();
|
||||
high_hat += ATTENUATION(channels_[7].instrument);
|
||||
return high_hat.level();
|
||||
}
|
||||
|
||||
#undef ATTENUATION
|
131
Components/OPx/OPLL.hpp
Normal file
131
Components/OPx/OPLL.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// OPLL.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2020.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef OPLL_hpp
|
||||
#define OPLL_hpp
|
||||
|
||||
#include "Implementation/OPLBase.hpp"
|
||||
#include "Implementation/EnvelopeGenerator.hpp"
|
||||
#include "Implementation/KeyLevelScaler.hpp"
|
||||
#include "Implementation/PhaseGenerator.hpp"
|
||||
#include "Implementation/LowFrequencyOscillator.hpp"
|
||||
#include "Implementation/WaveformGenerator.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
|
||||
class OPLL: public OPLBase<OPLL> {
|
||||
public:
|
||||
/// Creates a new OPLL or VRC7.
|
||||
OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
|
||||
// rhythm mode, but it's correct for melodic output.
|
||||
double get_average_output_peak() const { return 0.5; }
|
||||
|
||||
/// Reads from the OPL.
|
||||
uint8_t read(uint16_t address);
|
||||
|
||||
private:
|
||||
friend OPLBase<OPLL>;
|
||||
void write_register(uint8_t address, uint8_t value);
|
||||
|
||||
int audio_divider_ = 0;
|
||||
int audio_offset_ = 0;
|
||||
std::atomic<int> total_volume_;
|
||||
|
||||
int16_t output_levels_[18];
|
||||
void update_all_channels();
|
||||
|
||||
int melodic_output(int channel);
|
||||
int bass_drum();
|
||||
int tom_tom();
|
||||
int snare_drum();
|
||||
int cymbal();
|
||||
int high_hat();
|
||||
|
||||
static constexpr int period_precision = 9;
|
||||
static constexpr int envelope_precision = 7;
|
||||
|
||||
// Standard melodic phase and envelope generators;
|
||||
//
|
||||
// These are assigned as:
|
||||
//
|
||||
// [x], 0 <= x < 9 = carrier for channel x;
|
||||
// [x+9] = modulator for channel x.
|
||||
//
|
||||
PhaseGenerator<period_precision> phase_generators_[18];
|
||||
EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
|
||||
KeyLevelScaler<period_precision> key_level_scalers_[18];
|
||||
|
||||
// Dedicated rhythm envelope generators and attenuations.
|
||||
EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
|
||||
enum RhythmIndices {
|
||||
HighHat = 0,
|
||||
Cymbal = 1,
|
||||
TomTom = 2,
|
||||
Snare = 3,
|
||||
BassCarrier = 4,
|
||||
BassModulator = 5
|
||||
};
|
||||
|
||||
// Channel specifications.
|
||||
struct Channel {
|
||||
int octave = 0;
|
||||
int period = 0;
|
||||
int instrument = 0;
|
||||
|
||||
int attenuation = 0;
|
||||
int modulator_attenuation = 0;
|
||||
|
||||
Waveform carrier_waveform = Waveform::Sine;
|
||||
Waveform modulator_waveform = Waveform::Sine;
|
||||
|
||||
int carrier_key_rate_scale_multiplier = 0;
|
||||
int modulator_key_rate_scale_multiplier = 0;
|
||||
|
||||
LogSign modulator_output;
|
||||
int modulator_feedback = 0;
|
||||
|
||||
bool use_sustain = false;
|
||||
} channels_[9];
|
||||
|
||||
// The low-frequency oscillator.
|
||||
LowFrequencyOscillator oscillator_;
|
||||
bool rhythm_mode_enabled_ = false;
|
||||
bool is_vrc7_ = false;
|
||||
|
||||
// Contains the current configuration of the custom instrument.
|
||||
uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Helpers to push per-channel information.
|
||||
|
||||
/// Pushes the current octave and period to channel @c channel.
|
||||
void set_channel_period(int channel);
|
||||
|
||||
/// Installs the appropriate instrument on channel @c channel.
|
||||
void install_instrument(int channel);
|
||||
|
||||
/// Sets whether the sustain level is used for channel @c channel based on its current instrument
|
||||
/// and the user's selection.
|
||||
void set_use_sustain(int channel);
|
||||
|
||||
/// @returns The 8-byte definition of instrument @c instrument.
|
||||
const uint8_t *instrument_definition(int instrument, int channel);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OPLL_hpp */
|
@@ -39,9 +39,9 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &
|
||||
void SN76489::set_sample_volume_range(std::int16_t range) {
|
||||
// Build a volume table.
|
||||
double multiplier = pow(10.0, -0.1);
|
||||
double volume = static_cast<float>(range) / 4.0f; // As there are four channels.
|
||||
double volume = float(range) / 4.0f; // As there are four channels.
|
||||
for(int c = 0; c < 16; ++c) {
|
||||
volumes_[c] = (int)round(volume);
|
||||
volumes_[c] = int(round(volume));
|
||||
volume *= multiplier;
|
||||
}
|
||||
volumes_[15] = 0;
|
||||
@@ -65,7 +65,7 @@ void SN76489::write(uint8_t value) {
|
||||
if(value & 0x80) {
|
||||
channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
|
||||
} else {
|
||||
channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
|
||||
channels_[channel].divider = uint16_t((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
|
||||
}
|
||||
} else {
|
||||
// writes to the noise register always reset the shifter
|
||||
@@ -77,7 +77,7 @@ void SN76489::write(uint8_t value) {
|
||||
noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
|
||||
}
|
||||
|
||||
channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
|
||||
channels_[3].divider = uint16_t(0x10 << (value & 3));
|
||||
// Special case: if these bits are both set, the noise channel should track channel 2,
|
||||
// which is marked with a divider of 0xffff.
|
||||
if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
|
||||
@@ -86,12 +86,12 @@ void SN76489::write(uint8_t value) {
|
||||
});
|
||||
}
|
||||
|
||||
bool SN76489::is_zero_level() {
|
||||
bool SN76489::is_zero_level() const {
|
||||
return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
|
||||
}
|
||||
|
||||
void SN76489::evaluate_output_volume() {
|
||||
output_volume_ = static_cast<int16_t>(
|
||||
output_volume_ = int16_t(
|
||||
channels_[0].level * volumes_[channels_[0].volume] +
|
||||
channels_[1].level * volumes_[channels_[1].volume] +
|
||||
channels_[2].level * volumes_[channels_[2].volume] +
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user