mirror of
https://github.com/TomHarte/CLK.git
synced 2025-11-23 21:17:42 +00:00
Compare commits
8 Commits
master
...
QueueDeleg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4be5ee5b35 | ||
|
|
92e6dc64d4 | ||
|
|
f422cda553 | ||
|
|
2c44d3a7d3 | ||
|
|
051ce98ecb | ||
|
|
33ae24c961 | ||
|
|
4247d0ef40 | ||
|
|
ffababdb45 |
@@ -12,7 +12,7 @@
|
||||
|
||||
using namespace MOS::MOS6560;
|
||||
|
||||
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
AudioGenerator::AudioGenerator(Outputs::Speaker::TaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void AudioGenerator::set_volume(const uint8_t volume) {
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace MOS::MOS6560 {
|
||||
// audio state
|
||||
class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, false> {
|
||||
public:
|
||||
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
AudioGenerator(Outputs::Speaker::TaskQueue &audio_queue);
|
||||
|
||||
void set_volume(uint8_t);
|
||||
void set_control(int channel, uint8_t value);
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
void set_sample_volume_range(std::int16_t);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Outputs::Speaker::TaskQueue &audio_queue_;
|
||||
|
||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||
@@ -64,8 +64,7 @@ public:
|
||||
MOS6560(BusHandler &bus_handler) :
|
||||
bus_handler_(bus_handler),
|
||||
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
|
||||
audio_generator_(audio_queue_),
|
||||
speaker_(audio_generator_)
|
||||
audio_(Cycles(4))
|
||||
{
|
||||
// default to s-video output
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
|
||||
@@ -75,11 +74,11 @@ public:
|
||||
}
|
||||
|
||||
~MOS6560() {
|
||||
audio_queue_.lock_flush();
|
||||
audio_.stop();
|
||||
}
|
||||
|
||||
void set_clock_rate(const double clock_rate) {
|
||||
speaker_.set_input_rate(float(clock_rate / 4.0));
|
||||
audio_.speaker().set_input_rate(float(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
|
||||
@@ -95,11 +94,11 @@ public:
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
return &audio_.speaker();
|
||||
}
|
||||
|
||||
void set_high_frequency_cutoff(const float cutoff) {
|
||||
speaker_.set_high_frequency_cutoff(cutoff);
|
||||
audio_.speaker().set_high_frequency_cutoff(cutoff);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -180,7 +179,7 @@ public:
|
||||
*/
|
||||
inline void run_for(const Cycles cycles) {
|
||||
// keep track of the amount of time since the speaker was updated; lazy updates are applied
|
||||
cycles_since_speaker_update_ += cycles;
|
||||
audio_ += cycles;
|
||||
|
||||
auto number_of_cycles = cycles.as_integral();
|
||||
while(number_of_cycles--) {
|
||||
@@ -377,8 +376,7 @@ public:
|
||||
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
||||
*/
|
||||
inline void flush() {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -420,14 +418,12 @@ public:
|
||||
case 0xb:
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
update_audio();
|
||||
audio_generator_.set_control(address - 0xa, value);
|
||||
audio_->set_control(address - 0xa, value);
|
||||
break;
|
||||
|
||||
case 0xe:
|
||||
update_audio();
|
||||
registers_.auxiliary_colour = colours_[value >> 4];
|
||||
audio_generator_.set_volume(value & 0xf);
|
||||
audio_->set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf: {
|
||||
@@ -467,14 +463,7 @@ private:
|
||||
BusHandler &bus_handler_;
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
|
||||
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
}
|
||||
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, AudioGenerator> audio_;
|
||||
|
||||
// register state
|
||||
struct {
|
||||
|
||||
@@ -40,9 +40,12 @@ private:
|
||||
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.
|
||||
template <> struct TaskQueueStorage<void> {
|
||||
TaskQueueStorage() {}
|
||||
protected:
|
||||
void update() {}
|
||||
};
|
||||
|
||||
protected:
|
||||
void update() {}
|
||||
struct EnqueueDelegate {
|
||||
virtual std::function<void(void)> prepare_enqueue() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -67,10 +70,15 @@ template <> struct TaskQueueStorage<void> {
|
||||
template <
|
||||
bool perform_automatically,
|
||||
bool start_immediately = true,
|
||||
bool use_enqueue_delegate = false,
|
||||
typename Performer = void
|
||||
>
|
||||
class AsyncTaskQueue: public TaskQueueStorage<Performer> {
|
||||
public:
|
||||
void set_enqueue_delegate(EnqueueDelegate *const delegate) {
|
||||
enqueue_delegate_ = delegate;
|
||||
}
|
||||
|
||||
template <typename... Args> AsyncTaskQueue(Args&&... args) :
|
||||
TaskQueueStorage<Performer>(std::forward<Args>(args)...) {
|
||||
if constexpr (start_immediately) {
|
||||
@@ -90,6 +98,9 @@ public:
|
||||
/// to 'now'.
|
||||
void enqueue(const std::function<void(void)> &post_action) {
|
||||
const std::lock_guard guard(condition_mutex_);
|
||||
if constexpr (use_enqueue_delegate) {
|
||||
actions_.push_back(enqueue_delegate_->prepare_enqueue());
|
||||
}
|
||||
actions_.push_back(post_action);
|
||||
|
||||
if constexpr (perform_automatically) {
|
||||
@@ -206,6 +217,8 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
EnqueueDelegate *enqueue_delegate_ = nullptr;
|
||||
|
||||
// The list of actions waiting be performed. These will be elided,
|
||||
// increasing their latency, if the emulation thread falls behind.
|
||||
using ActionVector = std::vector<std::function<void(void)>>;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
#include "ClockReceiver/ForceInline.hpp"
|
||||
#include "Configurable/StandardOptions.hpp"
|
||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "Processors/6502/6502.hpp"
|
||||
|
||||
#include "Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
@@ -27,6 +26,8 @@
|
||||
|
||||
#include "ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
#include "Interrupts.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Plus3.hpp"
|
||||
@@ -58,8 +59,11 @@ public:
|
||||
hard_drive_(scsi_bus_, 0),
|
||||
scsi_device_(scsi_bus_.add_device()),
|
||||
video_(ram_),
|
||||
sound_generator_(audio_queue_),
|
||||
speaker_(sound_generator_) {
|
||||
audio_(
|
||||
2000000.0 / SoundGenerator::clock_rate_divider,
|
||||
SoundGenerator::clock_rate_divider,
|
||||
6000.0f
|
||||
) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
@@ -67,9 +71,6 @@ public:
|
||||
tape_.set_delegate(this);
|
||||
set_clock_rate(2000000);
|
||||
|
||||
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||
speaker_.set_high_frequency_cutoff(6000);
|
||||
|
||||
::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100);
|
||||
if(target.has_pres_adfs) {
|
||||
request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2);
|
||||
@@ -143,7 +144,7 @@ public:
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.lock_flush();
|
||||
audio_.stop();
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) final {
|
||||
@@ -234,8 +235,7 @@ public:
|
||||
const auto [cycles, video_interrupts] = run_for_access(address);
|
||||
signal_interrupt(video_interrupts);
|
||||
|
||||
cycles_since_audio_update_ += cycles;
|
||||
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
|
||||
audio_ += cycles;
|
||||
tape_.run_for(cycles);
|
||||
|
||||
if(typer_) typer_->run_for(cycles);
|
||||
@@ -278,8 +278,7 @@ public:
|
||||
// update speaker mode
|
||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
||||
update_audio();
|
||||
sound_generator_.set_is_enabled(new_speaker_is_enabled);
|
||||
audio_->set_is_enabled(new_speaker_is_enabled);
|
||||
speaker_is_enabled_ = new_speaker_is_enabled;
|
||||
}
|
||||
|
||||
@@ -340,8 +339,7 @@ public:
|
||||
break;
|
||||
case 0xfe06:
|
||||
if(!is_read(operation)) {
|
||||
update_audio();
|
||||
sound_generator_.set_divider(*value);
|
||||
audio_->set_divider(*value);
|
||||
tape_.set_counter(*value);
|
||||
}
|
||||
break;
|
||||
@@ -510,8 +508,7 @@ public:
|
||||
|
||||
void flush_output(int outputs) final {
|
||||
if(outputs & Output::Audio) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +529,7 @@ public:
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
return &audio_.speaker();
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
@@ -682,10 +679,6 @@ private:
|
||||
}
|
||||
|
||||
// MARK: - Work deferral updates.
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider)));
|
||||
}
|
||||
|
||||
inline void signal_interrupt(uint8_t interrupt) {
|
||||
if(!interrupt) {
|
||||
return;
|
||||
@@ -732,9 +725,6 @@ private:
|
||||
uint8_t key_states_[14];
|
||||
Electron::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
// Counters related to simultaneous subsystems
|
||||
Cycles cycles_since_audio_update_ = 0;
|
||||
|
||||
// Tape
|
||||
Tape tape_;
|
||||
bool use_fast_tape_hack_ = false;
|
||||
@@ -770,10 +760,7 @@ private:
|
||||
|
||||
// Outputs
|
||||
VideoOutput video_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
SoundGenerator sound_generator_;
|
||||
Outputs::Speaker::PullLowpass<SoundGenerator> speaker_;
|
||||
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, SoundGenerator> audio_;
|
||||
|
||||
bool speaker_is_enabled_ = false;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
SoundGenerator::SoundGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
SoundGenerator::SoundGenerator(Outputs::Speaker::TaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void SoundGenerator::set_sample_volume_range(std::int16_t range) {
|
||||
@@ -40,13 +40,13 @@ template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Mix>(std::
|
||||
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
|
||||
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
|
||||
|
||||
void SoundGenerator::set_divider(uint8_t divider) {
|
||||
void SoundGenerator::set_divider(const uint8_t divider) {
|
||||
audio_queue_.enqueue([this, divider]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void SoundGenerator::set_is_enabled(bool is_enabled) {
|
||||
void SoundGenerator::set_is_enabled(const bool is_enabled) {
|
||||
audio_queue_.enqueue([this, is_enabled]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "Outputs/Speaker/SpeakerQueue.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class SoundGenerator: public ::Outputs::Speaker::BufferSource<SoundGenerator, false> {
|
||||
public:
|
||||
SoundGenerator(Concurrency::AsyncTaskQueue<false> &);
|
||||
SoundGenerator(Outputs::Speaker::TaskQueue &);
|
||||
|
||||
void set_divider(uint8_t);
|
||||
void set_is_enabled(bool);
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Outputs::Speaker::TaskQueue &audio_queue_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick)));
|
||||
bus_->audio_.speaker().set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick)));
|
||||
bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_);
|
||||
bus_->tia_.set_scan_target(scan_target);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public:
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &bus_->speaker_;
|
||||
return &bus_->audio_.speaker();
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
@@ -206,11 +206,11 @@ private:
|
||||
// a confidence counter
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||
|
||||
void set_is_ntsc(bool is_ntsc) {
|
||||
void set_is_ntsc(const bool is_ntsc) {
|
||||
bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL);
|
||||
const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate;
|
||||
bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
|
||||
bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
|
||||
bus_->audio_.speaker().set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
|
||||
bus_->audio_.speaker().set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,12 +21,10 @@ namespace Atari2600 {
|
||||
|
||||
class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_sound_(audio_queue_),
|
||||
speaker_(tia_sound_) {}
|
||||
Bus() : audio_(Cycles(CPUTicksPerAudioTick * 3)) {}
|
||||
|
||||
virtual ~Bus() {
|
||||
audio_queue_.lock_flush();
|
||||
audio_.stop();
|
||||
}
|
||||
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
@@ -34,31 +32,22 @@ public:
|
||||
virtual void set_reset_line(bool state) = 0;
|
||||
virtual void flush() = 0;
|
||||
|
||||
// the RIOT, TIA and speaker
|
||||
// The RIOT, TIA and speaker.
|
||||
PIA mos6532_;
|
||||
TIA tia_;
|
||||
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, TIASound> audio_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
TIASound tia_sound_;
|
||||
Outputs::Speaker::PullLowpass<TIASound> speaker_;
|
||||
|
||||
// joystick state
|
||||
// Joystick state.
|
||||
uint8_t tia_input_value_[2] = {0xff, 0xff};
|
||||
|
||||
protected:
|
||||
// speaker backlog accumlation counter
|
||||
Cycles cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
}
|
||||
|
||||
// video backlog accumulation counter
|
||||
// Video backlog accumulation counter.
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
|
||||
}
|
||||
|
||||
// RIOT backlog accumulation counter
|
||||
// RIOT backlog accumulation counter.
|
||||
Cycles cycles_since_6532_update_;
|
||||
inline void update_6532() {
|
||||
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
|
||||
|
||||
@@ -70,10 +70,11 @@ public:
|
||||
// leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take
|
||||
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
|
||||
// skips to the end of the line.
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready)
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready) {
|
||||
cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
|
||||
}
|
||||
|
||||
cycles_since_speaker_update_ += Cycles(cycles_run_for);
|
||||
audio_ += Cycles(cycles_run_for);
|
||||
cycles_since_video_update_ += Cycles(cycles_run_for);
|
||||
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
|
||||
bus_extender_.advance_cycles(cycles_run_for / 3);
|
||||
@@ -171,11 +172,11 @@ public:
|
||||
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
|
||||
|
||||
case 0x15:
|
||||
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x16: audio_->set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x17:
|
||||
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x18: audio_->set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x19:
|
||||
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
|
||||
case 0x1a: audio_->set_volume(decodedAddress - 0x19, *value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,9 +202,8 @@ public:
|
||||
}
|
||||
|
||||
void flush() override {
|
||||
update_audio();
|
||||
update_video();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace Atari2600;
|
||||
|
||||
Atari2600::TIASound::TIASound(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
Atari2600::TIASound::TIASound(Outputs::Speaker::TaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue)
|
||||
{}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "Outputs/Speaker/SpeakerQueue.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
@@ -19,7 +19,7 @@ constexpr int CPUTicksPerAudioTick = 2;
|
||||
|
||||
class TIASound: public Outputs::Speaker::BufferSource<TIASound, false> {
|
||||
public:
|
||||
TIASound(Concurrency::AsyncTaskQueue<false> &);
|
||||
TIASound(Outputs::Speaker::TaskQueue &);
|
||||
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
void set_sample_volume_range(std::int16_t);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Outputs::Speaker::TaskQueue &audio_queue_;
|
||||
|
||||
uint8_t volume_[2];
|
||||
uint8_t divider_[2];
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "Outputs/Speaker/SpeakerQueue.hpp"
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
class Audio: public Outputs::Speaker::BufferSource<Audio, false> {
|
||||
public:
|
||||
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
Audio(Outputs::Speaker::TaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
template <Outputs::Speaker::Action action>
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
|
||||
private:
|
||||
// Calling-thread state.
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Outputs::Speaker::TaskQueue &audio_queue_;
|
||||
|
||||
// Audio-thread state.
|
||||
int16_t external_volume_ = 0;
|
||||
|
||||
@@ -186,13 +186,11 @@ public:
|
||||
interrupts_(*this),
|
||||
timers_(interrupts_),
|
||||
video_(video_map_, interrupts_),
|
||||
audio_(audio_queue_),
|
||||
speaker_(audio_)
|
||||
audio_(clock_rate(false), Cycles(1))
|
||||
{
|
||||
const auto clock = clock_rate(false);
|
||||
media_divider_ = Cycles(clock);
|
||||
set_clock_rate(clock);
|
||||
speaker_.set_input_rate(float(clock));
|
||||
|
||||
const auto kernel = ROM::Name::Plus4KernelPALv5;
|
||||
const auto basic = ROM::Name::Plus4BASIC;
|
||||
@@ -236,7 +234,7 @@ public:
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.lock_flush();
|
||||
audio_.stop();
|
||||
}
|
||||
|
||||
// HACK. NOCOMMIT.
|
||||
@@ -258,8 +256,7 @@ public:
|
||||
c1541_->run_for(c1541_cycles_.divide(media_divider_));
|
||||
}
|
||||
|
||||
|
||||
time_since_audio_update_ += length;
|
||||
audio_ += length;
|
||||
}
|
||||
|
||||
if(operation == CPU::MOS6502Mk2::BusOperation::Ready) {
|
||||
@@ -532,8 +529,7 @@ public:
|
||||
case 0xff06: video_.write<0xff06>(value); break;
|
||||
case 0xff07:
|
||||
video_.write<0xff07>(value);
|
||||
update_audio();
|
||||
audio_.set_divider(value);
|
||||
audio_->set_divider(value);
|
||||
break;
|
||||
case 0xff08:
|
||||
// Observation here: the kernel posts a 0 to this
|
||||
@@ -560,23 +556,19 @@ public:
|
||||
case 0xff0d: video_.write<0xff0d>(value); break;
|
||||
case 0xff0e:
|
||||
ff0e_ = value;
|
||||
update_audio();
|
||||
audio_.set_frequency_low<0>(value);
|
||||
audio_->set_frequency_low<0>(value);
|
||||
break;
|
||||
case 0xff0f:
|
||||
ff0f_ = value;
|
||||
update_audio();
|
||||
audio_.set_frequency_low<1>(value);
|
||||
audio_->set_frequency_low<1>(value);
|
||||
break;
|
||||
case 0xff10:
|
||||
ff10_ = value;
|
||||
update_audio();
|
||||
audio_.set_frequency_high<1>(value);
|
||||
audio_->set_frequency_high<1>(value);
|
||||
break;
|
||||
case 0xff11:
|
||||
ff11_ = value;
|
||||
update_audio();
|
||||
audio_.set_control(value);
|
||||
audio_->set_control(value);
|
||||
break;
|
||||
case 0xff12:
|
||||
ff12_ = value & 0x3f;
|
||||
@@ -588,8 +580,7 @@ public:
|
||||
page_video_ram();
|
||||
}
|
||||
|
||||
update_audio();
|
||||
audio_.set_frequency_high<0>(value);
|
||||
audio_->set_frequency_high<0>(value);
|
||||
break;
|
||||
case 0xff13:
|
||||
ff13_ = value & 0xfe;
|
||||
@@ -633,7 +624,7 @@ private:
|
||||
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &speaker_;
|
||||
return &audio_.speaker();
|
||||
}
|
||||
|
||||
void set_activity_observer(Activity::Observer *const observer) final {
|
||||
@@ -687,16 +678,12 @@ private:
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
m6502_.run_for(cycles);
|
||||
|
||||
// I don't know why.
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
|
||||
void flush_output(int outputs) override {
|
||||
if(outputs & Output::Audio) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,14 +710,7 @@ private:
|
||||
Cycles timers_subcycles_;
|
||||
Timers timers_;
|
||||
Video video_;
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
Audio audio_;
|
||||
Cycles time_since_audio_update_;
|
||||
Outputs::Speaker::PullLowpass<Audio> speaker_;
|
||||
void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.flush<Cycles>());
|
||||
}
|
||||
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, Audio> audio_;
|
||||
|
||||
// MARK: - MappedKeyboardMachine.
|
||||
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace Enterprise::Dave;
|
||||
|
||||
// MARK: - Audio generator
|
||||
|
||||
Audio::Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
Audio::Audio(Outputs::Speaker::TaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Audio::write(uint16_t address, const uint8_t value) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "Outputs/Speaker/SpeakerQueue.hpp"
|
||||
#include "Numeric/LFSR.hpp"
|
||||
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
|
||||
|
||||
@@ -28,7 +28,7 @@ enum class Interrupt: uint8_t {
|
||||
*/
|
||||
class Audio: public Outputs::Speaker::BufferSource<Audio, true> {
|
||||
public:
|
||||
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
Audio(Outputs::Speaker::TaskQueue &);
|
||||
|
||||
/// Modifies an register in the audio range; only the low 4 bits are
|
||||
/// used for register decoding so it's assumed that the caller has
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target);
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
Outputs::Speaker::TaskQueue &audio_queue_;
|
||||
|
||||
// Global divider (i.e. 8MHz/12Mhz switch).
|
||||
uint8_t global_divider_;
|
||||
|
||||
@@ -103,12 +103,10 @@ public:
|
||||
min_ram_slot_(min_ram_slot(target)),
|
||||
z80_(*this),
|
||||
nick_(ram_.end() - 65536),
|
||||
dave_audio_(audio_queue_),
|
||||
speaker_(dave_audio_) {
|
||||
audio_(float(clock_rate) / float(DaveDivider), DaveDivider) {
|
||||
|
||||
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
|
||||
set_clock_rate(clock_rate);
|
||||
speaker_.set_input_rate(float(clock_rate) / float(dave_divider));
|
||||
|
||||
ROM::Request request;
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
@@ -257,7 +255,7 @@ public:
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.lock_flush();
|
||||
audio_.stop();
|
||||
}
|
||||
|
||||
// MARK: - Z80::BusHandler.
|
||||
@@ -344,7 +342,7 @@ public:
|
||||
}
|
||||
|
||||
const HalfCycles full_length = cycle.length + penalty;
|
||||
time_since_audio_update_ += full_length;
|
||||
audio_ += full_length;
|
||||
advance_nick(full_length);
|
||||
if(dave_timer_ += full_length) {
|
||||
set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun());
|
||||
@@ -475,8 +473,7 @@ public:
|
||||
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
|
||||
case 0xa8: case 0xa9: case 0xaa: case 0xab:
|
||||
case 0xac: case 0xad: case 0xae: case 0xaf:
|
||||
update_audio();
|
||||
dave_audio_.write(address, *cycle.value);
|
||||
audio_->write(address, *cycle.value);
|
||||
dave_timer_->write(address, *cycle.value);
|
||||
break;
|
||||
|
||||
@@ -563,8 +560,7 @@ public:
|
||||
nick_.flush();
|
||||
}
|
||||
if(outputs & Output::Audio) {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
audio_.perform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +646,7 @@ private:
|
||||
// MARK: - AudioProducer
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
return &audio_.speaker();
|
||||
}
|
||||
|
||||
// MARK: - TimedMachine
|
||||
@@ -726,20 +722,13 @@ private:
|
||||
bool previous_nick_interrupt_line_ = false;
|
||||
// Cf. timing guesses above.
|
||||
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
Dave::Audio dave_audio_;
|
||||
Outputs::Speaker::PullLowpass<Dave::Audio> speaker_;
|
||||
HalfCycles time_since_audio_update_;
|
||||
|
||||
Outputs::Speaker::PullLowpassSpeakerQueue<HalfCycles, Dave::Audio> audio_;
|
||||
HalfCycles dave_delay_ = HalfCycles(2);
|
||||
|
||||
// The divider supplied to the JustInTimeActor and the manual divider used in
|
||||
// update_audio() should match.
|
||||
static constexpr int dave_divider = 8;
|
||||
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, dave_divider> dave_timer_;
|
||||
inline void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider)));
|
||||
}
|
||||
// the spekaer queue should match.
|
||||
static constexpr int DaveDivider = 8;
|
||||
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, DaveDivider> dave_timer_;
|
||||
|
||||
// MARK: - EXDos card.
|
||||
EXDos exdos_;
|
||||
|
||||
@@ -2523,6 +2523,7 @@
|
||||
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
|
||||
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
|
||||
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
|
||||
4BEF9CA92EC8294E00DDD0F6 /* SpeakerQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SpeakerQueue.hpp; sourceTree = "<group>"; };
|
||||
4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemorySlotHandler.cpp; sourceTree = "<group>"; };
|
||||
4BF0BC6F2973318E00CCA2B5 /* RP5C01.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RP5C01.cpp; sourceTree = "<group>"; };
|
||||
4BF0BC702973318E00CCA2B5 /* RP5C01.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RP5C01.hpp; sourceTree = "<group>"; };
|
||||
@@ -5312,6 +5313,7 @@
|
||||
4BD060A41FE49D3C006E14BE /* Speaker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BEF9CA92EC8294E00DDD0F6 /* SpeakerQueue.hpp */,
|
||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */,
|
||||
4B8EF6051FE5AF830076CCDD /* Implementation */,
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
namespace {
|
||||
|
||||
struct MachineUpdater {
|
||||
void perform(Time::Nanos duration) {
|
||||
void perform(const Time::Nanos duration) {
|
||||
// Top out at 1/20th of a second; this is a safeguard against a negative
|
||||
// feedback loop if emulation starts running slowly.
|
||||
const auto seconds = std::min(Time::seconds(duration), 0.05);
|
||||
@@ -51,7 +51,7 @@ struct MachineUpdater {
|
||||
MachineTypes::TimedMachine *timed_machine = nullptr;
|
||||
};
|
||||
|
||||
using Updater = Concurrency::AsyncTaskQueue<true, false, MachineUpdater>;
|
||||
using Updater = Concurrency::AsyncTaskQueue<true, false, false, MachineUpdater>;
|
||||
|
||||
}
|
||||
|
||||
|
||||
83
OSBindings/Mac/Clock SignalTests/SIDTests.mm
Normal file
83
OSBindings/Mac/Clock SignalTests/SIDTests.mm
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// SIDTests.mm
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 11/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Components/SID/SID.hpp"
|
||||
|
||||
@interface SIDTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SIDTests
|
||||
|
||||
- (void)testOscillator {
|
||||
MOS::SID::Voice prior;
|
||||
MOS::SID::Voice voice;
|
||||
const uint32_t pulse_width = 0x02'3;
|
||||
|
||||
voice.oscillator.pitch = 0x00'1000'00;
|
||||
voice.oscillator.pulse_width = pulse_width << 20;
|
||||
voice.oscillator.reset_phase();
|
||||
|
||||
int c = 0;
|
||||
|
||||
// Run for first half of a cycle.
|
||||
while(!voice.oscillator.did_raise_b23()) {
|
||||
// Force envelope.
|
||||
voice.adsr.envelope = 255;
|
||||
|
||||
// Test sawtooth.
|
||||
voice.set_control(0x20);
|
||||
XCTAssertEqual(voice.output(prior), c);
|
||||
|
||||
// Test triangle.
|
||||
voice.set_control(0x10);
|
||||
XCTAssertEqual(voice.output(prior), c << 1);
|
||||
|
||||
// Test pulse.
|
||||
voice.set_control(0x40);
|
||||
XCTAssertEqual(voice.output(prior), (c < pulse_width) ? 0 : 4095);
|
||||
|
||||
// Advance.
|
||||
voice.update();
|
||||
++c;
|
||||
}
|
||||
|
||||
// B23 should go up halfway through the 12-bit range.
|
||||
XCTAssertEqual(c, 2048);
|
||||
|
||||
// Run for second half of a cycle.
|
||||
while(c < 4096) {
|
||||
// Force envelope.
|
||||
voice.adsr.envelope = 255;
|
||||
|
||||
// Test sawtooth.
|
||||
voice.set_control(0x20);
|
||||
XCTAssertEqual(voice.output(prior), c);
|
||||
|
||||
// Test triangle.
|
||||
voice.set_control(0x10);
|
||||
XCTAssertEqual(voice.output(prior), 4095 - ((c << 1) & 4095));
|
||||
|
||||
// Test pulse.
|
||||
voice.set_control(0x40);
|
||||
XCTAssertEqual(voice.output(prior), (c <= pulse_width) ? 0 : 4095);
|
||||
|
||||
// Advance.
|
||||
voice.update();
|
||||
++c;
|
||||
|
||||
XCTAssert(!voice.oscillator.did_raise_b23());
|
||||
}
|
||||
|
||||
// Check that B23 doesn't false rise again.
|
||||
voice.update();
|
||||
XCTAssert(!voice.oscillator.did_raise_b23());
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/Speaker/Speaker.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -26,7 +27,7 @@ enum class Action {
|
||||
Ignore,
|
||||
};
|
||||
|
||||
template <Action action, typename SampleT> void apply(SampleT &lhs, SampleT rhs) {
|
||||
template <Action action, typename SampleT> void apply(SampleT &lhs, const SampleT rhs) {
|
||||
switch(action) {
|
||||
case Action::Mix: lhs += rhs; break;
|
||||
case Action::Store: lhs = rhs; break;
|
||||
@@ -34,7 +35,8 @@ template <Action action, typename SampleT> void apply(SampleT &lhs, SampleT rhs)
|
||||
}
|
||||
}
|
||||
|
||||
template <Action action, typename IteratorT, typename SampleT> void fill(IteratorT begin, IteratorT end, SampleT value) {
|
||||
template <Action action, typename IteratorT, typename SampleT>
|
||||
void fill(IteratorT begin, const IteratorT end, const SampleT value) {
|
||||
switch(action) {
|
||||
case Action::Mix:
|
||||
while(begin != end) {
|
||||
@@ -56,45 +58,45 @@ template <Action action, typename IteratorT, typename SampleT> void fill(Iterato
|
||||
*/
|
||||
template <typename SourceT, bool stereo>
|
||||
class BufferSource {
|
||||
public:
|
||||
/*!
|
||||
Indicates whether this component will write stereo samples.
|
||||
*/
|
||||
static constexpr bool is_stereo = stereo;
|
||||
public:
|
||||
/*!
|
||||
Indicates whether this component will write stereo samples.
|
||||
*/
|
||||
static constexpr bool is_stereo = stereo;
|
||||
|
||||
/*!
|
||||
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
|
||||
helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available).
|
||||
/*!
|
||||
Should 'apply' the next @c number_of_samples to @c target ; application means applying @c action which can be achieved either via the
|
||||
helper functions above — @c apply and @c fill — or by semantic inspection (primarily, if an obvious quick route for @c Action::Ignore is available).
|
||||
|
||||
No default implementation is provided.
|
||||
*/
|
||||
template <Action action>
|
||||
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *target);
|
||||
No default implementation is provided.
|
||||
*/
|
||||
template <Action action>
|
||||
void apply_samples(std::size_t number_of_samples, typename SampleT<stereo>::type *);
|
||||
|
||||
/*!
|
||||
@returns @c true if it is trivially true that a call to get_samples would just
|
||||
fill the target with zeroes; @c false if a call might return all zeroes or
|
||||
might not.
|
||||
*/
|
||||
// bool is_zero_level() const { return false; }
|
||||
/*!
|
||||
@returns @c true if it is trivially true that a call to get_samples would just
|
||||
fill the target with zeroes; @c false if a call might return all zeroes or
|
||||
might not.
|
||||
*/
|
||||
// bool is_zero_level() const { return false; }
|
||||
|
||||
/*!
|
||||
Sets the proper output range for this sample source; it should write values
|
||||
between 0 and volume.
|
||||
*/
|
||||
// void set_sample_volume_range(std::int16_t volume);
|
||||
/*!
|
||||
Sets the proper output range for this sample source; it should write values
|
||||
between 0 and volume.
|
||||
*/
|
||||
// void set_sample_volume_range(std::int16_t volume);
|
||||
|
||||
/*!
|
||||
Permits a sample source to declare that, averaged over time, it will use only
|
||||
a certain proportion of the allocated volume range. This commonly happens
|
||||
in sample sources that use a time-multiplexed sound output — for example, if
|
||||
one were to output only every other sample then it would return 0.5.
|
||||
/*!
|
||||
Permits a sample source to declare that, averaged over time, it will use only
|
||||
a certain proportion of the allocated volume range. This commonly happens
|
||||
in sample sources that use a time-multiplexed sound output — for example, if
|
||||
one were to output only every other sample then it would return 0.5.
|
||||
|
||||
This is permitted to vary over time but there is no contract as to when it will be
|
||||
used by a speaker. If it varies, it should do so very infrequently and only to
|
||||
represent changes in hardware configuration.
|
||||
*/
|
||||
double average_output_peak() const { return 1.0; }
|
||||
This is permitted to vary over time but there is no contract as to when it will be
|
||||
used by a speaker. If it varies, it should do so very infrequently and only to
|
||||
represent changes in hardware configuration.
|
||||
*/
|
||||
double average_output_peak() const { return 1.0; }
|
||||
};
|
||||
|
||||
///
|
||||
@@ -140,7 +142,7 @@ public:
|
||||
|
||||
// TODO: use a concept here, when C++20 filters through.
|
||||
//
|
||||
// Until then: sample sources should implement this.
|
||||
// Until then: sample sources can implement this rather than apply_samples.
|
||||
// typename SampleT<stereo>::type level() const;
|
||||
// void advance();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "BufferSource.hpp"
|
||||
#include "Outputs/Speaker/Speaker.hpp"
|
||||
#include "Outputs/Speaker/SpeakerQueue.hpp"
|
||||
#include "SignalProcessing/FIRFilter.hpp"
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
@@ -375,10 +376,13 @@ public:
|
||||
if(cycles == Cycles(0)) {
|
||||
return;
|
||||
}
|
||||
queue.enqueue(update_for(cycles));
|
||||
}
|
||||
|
||||
queue.enqueue([this, cycles] {
|
||||
std::function<void(void)> update_for(const Cycles cycles) {
|
||||
return [this, cycles] {
|
||||
run_for(cycles);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -414,4 +418,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename CyclesT, typename GeneratorT>
|
||||
using PullLowpassSpeakerQueue = SpeakerQueue<CyclesT, Outputs::Speaker::PullLowpass<GeneratorT>, GeneratorT>;
|
||||
|
||||
}
|
||||
|
||||
68
Outputs/Speaker/SpeakerQueue.hpp
Normal file
68
Outputs/Speaker/SpeakerQueue.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// SpeakerQueue.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Outputs::Speaker {
|
||||
using TaskQueue = Concurrency::AsyncTaskQueue<false, true, true>;
|
||||
|
||||
template <typename CyclesT, typename SpeakerT, typename GeneratorT>
|
||||
struct SpeakerQueue: private Concurrency::EnqueueDelegate {
|
||||
constexpr SpeakerQueue(const CyclesT divider) noexcept :
|
||||
generator_(queue_), speaker_(generator_), divider_(divider)
|
||||
{
|
||||
queue_.set_enqueue_delegate(this);
|
||||
}
|
||||
|
||||
constexpr SpeakerQueue(const float input_rate, const CyclesT divider, const float high_cutoff = -1.0f) noexcept :
|
||||
SpeakerQueue(divider)
|
||||
{
|
||||
speaker_.set_input_rate(input_rate);
|
||||
if(high_cutoff >= 0.0) {
|
||||
speaker_.set_high_frequency_cutoff(high_cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
void operator += (const CyclesT &duration) {
|
||||
time_since_update_ += duration;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
queue_.stop();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
// TODO: is there a way to avoid the empty lambda?
|
||||
queue_.enqueue([]() {});
|
||||
queue_.perform();
|
||||
}
|
||||
|
||||
SpeakerT &speaker() {
|
||||
return speaker_;
|
||||
}
|
||||
|
||||
GeneratorT *operator ->() {
|
||||
return &generator_;
|
||||
}
|
||||
|
||||
private:
|
||||
TaskQueue queue_;
|
||||
GeneratorT generator_;
|
||||
SpeakerT speaker_;
|
||||
CyclesT divider_;
|
||||
CyclesT time_since_update_;
|
||||
|
||||
std::function<void(void)> prepare_enqueue() final {
|
||||
return speaker_.update_for(time_since_update_.template divide<Cycles>(divider_));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user