From ac80d10cd886c7483f495e5fbbe62deb35d138c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Dec 2017 21:26:06 -0500 Subject: [PATCH] Separates the component parts of running an audio stream: task deferral, filtering and generation. Walking towards improving opportunities for composition. --- Components/6560/6560.cpp | 16 +- Components/6560/6560.hpp | 43 ++- Components/9918/9918.cpp | 4 +- Components/9918/9918.hpp | 2 +- Components/9918/Implementation/9918Base.hpp | 2 +- Components/AY38910/AY38910.cpp | 10 +- Components/AY38910/AY38910.hpp | 14 +- Concurrency/AsyncTaskQueue.cpp | 1 + Machines/AmstradCPC/AmstradCPC.cpp | 47 ++-- Machines/Atari2600/Atari2600.cpp | 13 +- Machines/Atari2600/Bus.hpp | 11 +- Machines/Atari2600/Cartridges/Cartridge.hpp | 8 +- Machines/Atari2600/TIA.hpp | 4 +- .../Atari2600/{Speaker.cpp => TIASound.cpp} | 19 +- .../Atari2600/{Speaker.hpp => TIASound.hpp} | 17 +- Machines/CRTMachine.hpp | 6 +- Machines/Commodore/Vic-20/Vic20.cpp | 9 +- Machines/Electron/Electron.cpp | 44 +-- .../{Speaker.cpp => SoundGenerator.cpp} | 17 +- Machines/Electron/SoundGenerator.hpp | 39 +++ Machines/Electron/Speaker.hpp | 35 --- Machines/Electron/Video.cpp | 4 +- Machines/Electron/Video.hpp | 4 +- Machines/MSX/MSX.cpp | 40 +-- Machines/Oric/Oric.cpp | 47 ++-- Machines/Oric/Video.cpp | 4 +- Machines/Oric/Video.hpp | 4 +- Machines/ZX8081/Video.cpp | 4 +- Machines/ZX8081/Video.hpp | 4 +- Machines/ZX8081/ZX8081.cpp | 4 +- .../Clock Signal.xcodeproj/project.pbxproj | 61 +++-- .../Mac/Clock Signal/Machine/CSMachine.mm | 14 +- OSBindings/SDL/main.cpp | 4 +- Outputs/Speaker.hpp | 257 ------------------ .../Implementation/FilteringSpeaker.cpp | 9 + .../Implementation/FilteringSpeaker.hpp | 238 ++++++++++++++++ Outputs/Speaker/Speaker.hpp | 43 +++ 37 files changed, 594 insertions(+), 508 deletions(-) rename Machines/Atari2600/{Speaker.cpp => TIASound.cpp} (86%) rename Machines/Atari2600/{Speaker.hpp => TIASound.hpp} (64%) rename Machines/Electron/{Speaker.cpp => SoundGenerator.cpp} (56%) create mode 100644 Machines/Electron/SoundGenerator.hpp delete mode 100644 Machines/Electron/Speaker.hpp delete mode 100644 Outputs/Speaker.hpp create mode 100644 Outputs/Speaker/Implementation/FilteringSpeaker.cpp create mode 100644 Outputs/Speaker/Implementation/FilteringSpeaker.hpp create mode 100644 Outputs/Speaker/Speaker.hpp diff --git a/Components/6560/6560.cpp b/Components/6560/6560.cpp index 909702b5f..20d86e063 100644 --- a/Components/6560/6560.cpp +++ b/Components/6560/6560.cpp @@ -12,14 +12,18 @@ using namespace MOS; -void Speaker::set_volume(uint8_t volume) { - enqueue([=]() { +AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + + +void AudioGenerator::set_volume(uint8_t volume) { + audio_queue_.defer([=]() { volume_ = volume; }); } -void Speaker::set_control(int channel, uint8_t value) { - enqueue([=]() { +void AudioGenerator::set_control(int channel, uint8_t value) { + audio_queue_.defer([=]() { control_registers_[channel] = value; }); } @@ -101,7 +105,7 @@ static uint8_t noise_pattern[] = { // testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e // means every second cycle, etc. -void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { +void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { for(unsigned int c = 0; c < number_of_samples; c++) { update(0, 2, shift); update(1, 1, shift); @@ -119,7 +123,7 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { } } -void Speaker::skip_samples(unsigned int number_of_samples) { +void AudioGenerator::skip_samples(std::size_t number_of_samples) { for(unsigned int c = 0; c < number_of_samples; c++) { update(0, 2, shift); update(1, 1, shift); diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index abcaac36b..faeddd124 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -9,22 +9,27 @@ #ifndef _560_hpp #define _560_hpp -#include "../../Outputs/CRT/CRT.hpp" -#include "../../Outputs/Speaker.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" +#include "../../Outputs/CRT/CRT.hpp" +#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp" namespace MOS { // audio state -class Speaker: public ::Outputs::Filter { +class AudioGenerator: public ::Outputs::Speaker::SampleSource { public: + AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue); + void set_volume(uint8_t volume); void set_control(int channel, uint8_t value); - void get_samples(unsigned int number_of_samples, int16_t *target); - void skip_samples(unsigned int number_of_samples); + void get_samples(std::size_t number_of_samples, int16_t *target); + void skip_samples(std::size_t number_of_samples); private: + Concurrency::DeferringAsyncTaskQueue &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}; uint8_t control_registers_[4] = {0, 0, 0, 0}; @@ -43,7 +48,9 @@ template class MOS6560 { public: MOS6560() : crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)), - speaker_(new Speaker) { + audio_generator_(audio_queue_), + speaker_(audio_generator_) + { crt_->set_composite_sampling_function( "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "{" @@ -59,11 +66,11 @@ template class MOS6560 { } void set_clock_rate(double clock_rate) { - speaker_->set_input_rate(static_cast(clock_rate / 4.0)); + speaker_.set_input_rate(static_cast(clock_rate / 4.0)); } - std::shared_ptr get_crt() { return crt_; } - std::shared_ptr get_speaker() { return speaker_; } + Outputs::CRT::CRT *get_crt() { return crt_.get(); } + Outputs::Speaker::Speaker *get_speaker() { return nullptr; } // speaker_; enum OutputMode { PAL, NTSC @@ -318,7 +325,10 @@ template class MOS6560 { /*! Causes the 6560 to flush as much pending CRT and speaker communications as possible. */ - inline void flush() { update_audio(); speaker_->flush(); } + inline void flush() { + update_audio(); + audio_queue_.perform(); + } /*! Writes to a 6560 register. @@ -356,13 +366,13 @@ template class MOS6560 { case 0xc: case 0xd: update_audio(); - speaker_->set_control(address - 0xa, value); + audio_generator_.set_control(address - 0xa, value); break; case 0xe: update_audio(); registers_.auxiliary_colour = colours_[value >> 4]; - speaker_->set_volume(value & 0xf); + audio_generator_.set_volume(value & 0xf); break; case 0xf: { @@ -398,12 +408,15 @@ template class MOS6560 { } private: - std::shared_ptr crt_; + std::unique_ptr crt_; + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + AudioGenerator audio_generator_; + Outputs::Speaker::LowpassSpeaker speaker_; - std::shared_ptr speaker_; Cycles cycles_since_speaker_update_; void update_audio() { - speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); + speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4)))); } // register state diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index d3d0af630..3c45f18fe 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -72,8 +72,8 @@ TMS9918::TMS9918(Personality p) { crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); } -std::shared_ptr TMS9918::get_crt() { - return crt_; +Outputs::CRT::CRT *TMS9918::get_crt() { + return crt_.get(); } void TMS9918Base::test_sprite(int sprite_number) { diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index 7613987ab..0fe743062 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -52,7 +52,7 @@ class TMS9918: public TMS9918Base { void set_tv_standard(TVStandard standard); /*! Provides the CRT this TMS is connected to. */ - std::shared_ptr get_crt(); + Outputs::CRT::CRT *get_crt(); /*! Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 8ca8dc834..830833b41 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -21,7 +21,7 @@ class TMS9918Base { protected: TMS9918Base(); - std::shared_ptr crt_; + std::unique_ptr crt_; uint8_t ram_[16384]; diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 9851e9ea4..e9c321205 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -10,7 +10,7 @@ using namespace GI::AY38910; -AY38910::AY38910() { +AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { // set up envelope lookup tables for(int c = 0; c < 16; c++) { for(int p = 0; p < 32; p++) { @@ -63,11 +63,7 @@ AY38910::AY38910() { volumes_[0] = 0; } -void AY38910::set_clock_rate(double clock_rate) { - set_input_rate(static_cast(clock_rate)); -} - -void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) { +void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { unsigned int c = 0; while((master_divider_&7) && c < number_of_samples) { target[c] = output_volume_; @@ -169,7 +165,7 @@ void AY38910::set_register_value(uint8_t value) { registers_[selected_register_] = value; if(selected_register_ < 14) { int selected_register = selected_register_; - enqueue([=] () { + task_queue_.defer([=] () { uint8_t masked_value = value; switch(selected_register) { case 0: case 2: case 4: diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 1dcd6c6f0..203527835 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -9,7 +9,8 @@ #ifndef AY_3_8910_hpp #define AY_3_8910_hpp -#include "../../Outputs/Speaker.hpp" +#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" namespace GI { namespace AY38910 { @@ -55,13 +56,10 @@ enum ControlLines { noise generator and a volume envelope generator, which also provides two bidirectional interface ports. */ -class AY38910: public ::Outputs::Filter { +class AY38910: public ::Outputs::Speaker::SampleSource { public: /// Creates a new AY38910. - AY38910(); - - /// Sets the clock rate at which this AY38910 will be run. - void set_clock_rate(double clock_rate); + AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue); /// Sets the value the AY would read from its data lines if it were not outputting. void set_data_input(uint8_t r); @@ -86,9 +84,11 @@ class AY38910: public ::Outputs::Filter { void set_port_handler(PortHandler *); // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption - void get_samples(unsigned int number_of_samples, int16_t *target); + void get_samples(std::size_t number_of_samples, int16_t *target); private: + Concurrency::DeferringAsyncTaskQueue &task_queue_; + int selected_register_ = 0; uint8_t registers_[16]; uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 571421d65..6b8fd90f8 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -88,6 +88,7 @@ void DeferringAsyncTaskQueue::defer(std::function function) { } void DeferringAsyncTaskQueue::perform() { + if(!deferred_tasks_) return; std::shared_ptr>> deferred_tasks = deferred_tasks_; deferred_tasks_.reset(); enqueue([deferred_tasks] { diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 6622ae584..e768e64f1 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -115,14 +115,8 @@ class InterruptTimer { class AYDeferrer { public: /// Constructs a new AY instance and sets its clock rate. - inline void setup_output() { - ay_.reset(new GI::AY38910::AY38910); - ay_->set_input_rate(1000000); - } - - /// Destructs the AY. - inline void close_output() { - ay_.reset(); + AYDeferrer() : ay_(audio_queue_), speaker_(ay_) { + speaker_.set_input_rate(1000000); } /// Adds @c half_cycles half cycles to the amount of time that has passed. @@ -132,26 +126,28 @@ class AYDeferrer { /// Enqueues an update-to-now into the AY's deferred queue. inline void update() { - ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4))); + speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4))); } /// Issues a request to the AY to perform all processing up to the current time. inline void flush() { - ay_->flush(); + audio_queue_.perform(); } /// @returns the speaker the AY is using for output. - std::shared_ptr get_speaker() { - return ay_; + Outputs::Speaker::Speaker *get_speaker() { + return &speaker_; } /// @returns the AY itself. - GI::AY38910::AY38910 *ay() { - return ay_.get(); + GI::AY38910::AY38910 &ay() { + return ay_; } private: - std::shared_ptr ay_; + Concurrency::DeferringAsyncTaskQueue audio_queue_; + GI::AY38910::AY38910 ay_; + Outputs::Speaker::LowpassSpeaker speaker_; HalfCycles cycles_since_update_; }; @@ -319,8 +315,8 @@ class CRTCBusHandler { } /// @returns the CRT. - std::shared_ptr get_crt() { - return crt_; + Outputs::CRT::CRT *get_crt() { + return crt_.get(); } /*! @@ -503,7 +499,7 @@ class CRTCBusHandler { bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false; int cycles_into_hsync_ = 0; - std::shared_ptr crt_; + std::unique_ptr crt_; uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; uint8_t *ram_ = nullptr; @@ -623,7 +619,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { case 0: // Port A is connected to the AY's data bus. ay_.update(); - ay_.ay()->set_data_input(value); + ay_.ay().set_data_input(value); break; case 1: // Port B is an input only. So output goes nowehere. @@ -639,7 +635,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { tape_player_.set_tape_output((value & 0x20) ? true : false); // Bits 6 and 7 set BDIR and BC1 for the AY. - ay_.ay()->set_control_lines( + ay_.ay().set_control_lines( (GI::AY38910::ControlLines)( ((value & 0x80) ? GI::AY38910::BDIR : 0) | ((value & 0x40) ? GI::AY38910::BC1 : 0) | @@ -652,7 +648,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { /// The i8255 will call this to obtain a new input for @c port. uint8_t get_value(int port) { switch(port) { - case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY + case 0: return ay_.ay().get_data_output(); // Port A is wired to the AY case 1: return (crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync. (tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input. @@ -703,6 +699,8 @@ class ConcreteMachine: tape_player_.set_sleep_observer(this); tape_player_is_sleeping_ = tape_player_.is_sleeping(); + + ay_.ay().set_port_handler(&key_state_); } /// The entry point for performing a partial Z80 machine cycle. @@ -845,23 +843,20 @@ class ConcreteMachine: /// A CRTMachine function; indicates that outputs should be created now. void setup_output(float aspect_ratio) override final { crtc_bus_handler_.setup_output(aspect_ratio); - ay_.setup_output(); - ay_.ay()->set_port_handler(&key_state_); } /// A CRTMachine function; indicates that outputs should be destroyed now. void close_output() override final { crtc_bus_handler_.close_output(); - ay_.close_output(); } /// @returns the CRT in use. - std::shared_ptr get_crt() override final { + Outputs::CRT::CRT *get_crt() override final { return crtc_bus_handler_.get_crt(); } /// @returns the speaker in use. - std::shared_ptr get_speaker() override final { + Outputs::Speaker::Speaker *get_speaker() override final { return ay_.get_speaker(); } diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 3ab41aad3..edfef0680 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -137,8 +137,7 @@ class ConcreteMachine: // to satisfy CRTMachine::Machine void setup_output(float aspect_ratio) override { bus_->tia_.reset(new TIA); - bus_->speaker_.reset(new Speaker); - bus_->speaker_->set_input_rate(static_cast(get_clock_rate() / static_cast(CPUTicksPerAudioTick))); + bus_->speaker_.set_input_rate(static_cast(get_clock_rate() / static_cast(CPUTicksPerAudioTick))); bus_->tia_->get_crt()->set_delegate(this); } @@ -146,12 +145,12 @@ class ConcreteMachine: bus_.reset(); } - std::shared_ptr get_crt() override { + Outputs::CRT::CRT *get_crt() override { return bus_->tia_->get_crt(); } - std::shared_ptr get_speaker() override { - return bus_->speaker_; + Outputs::Speaker::Speaker *get_speaker() override { + return &bus_->speaker_; } void run_for(const Cycles cycles) override { @@ -189,8 +188,8 @@ class ConcreteMachine: bus_->tia_->set_output_mode(TIA::OutputMode::PAL); } - bus_->speaker_->set_input_rate(static_cast(clock_rate / static_cast(CPUTicksPerAudioTick))); - bus_->speaker_->set_high_frequency_cut_off(static_cast(clock_rate / (static_cast(CPUTicksPerAudioTick) * 2.0))); + bus_->speaker_.set_input_rate(static_cast(clock_rate / static_cast(CPUTicksPerAudioTick))); + bus_->speaker_.set_high_frequency_cutoff(static_cast(clock_rate / (static_cast(CPUTicksPerAudioTick) * 2.0))); set_clock_rate(clock_rate); } } diff --git a/Machines/Atari2600/Bus.hpp b/Machines/Atari2600/Bus.hpp index fde08e3c0..1f0b147b8 100644 --- a/Machines/Atari2600/Bus.hpp +++ b/Machines/Atari2600/Bus.hpp @@ -11,8 +11,8 @@ #include "Atari2600.hpp" #include "PIA.hpp" -#include "Speaker.hpp" #include "TIA.hpp" +#include "TIASound.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" @@ -21,6 +21,8 @@ namespace Atari2600 { class Bus { public: Bus() : + tia_sound_(audio_queue_), + speaker_(tia_sound_), tia_input_value_{0xff, 0xff}, cycles_since_speaker_update_(0) {} @@ -30,7 +32,10 @@ class Bus { // the RIOT, TIA and speaker PIA mos6532_; std::shared_ptr tia_; - std::shared_ptr speaker_; + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + TIASound tia_sound_; + Outputs::Speaker::LowpassSpeaker speaker_; // joystick state uint8_t tia_input_value_[2]; @@ -39,7 +44,7 @@ class Bus { // speaker backlog accumlation counter Cycles cycles_since_speaker_update_; inline void update_audio() { - speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); + speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); } // video backlog accumulation counter diff --git a/Machines/Atari2600/Cartridges/Cartridge.hpp b/Machines/Atari2600/Cartridges/Cartridge.hpp index 7125e546e..1aa2fe0e5 100644 --- a/Machines/Atari2600/Cartridges/Cartridge.hpp +++ b/Machines/Atari2600/Cartridges/Cartridge.hpp @@ -148,11 +148,11 @@ template class Cartridge: case 0x2c: update_video(); tia_->clear_collision_flags(); break; case 0x15: - case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break; + case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break; case 0x17: - case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break; + case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break; case 0x19: - case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break; + case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break; } } } @@ -180,7 +180,7 @@ template class Cartridge: void flush() { update_audio(); update_video(); - speaker_->flush(); + audio_queue_.perform(); } protected: diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index 510d8350a..8edfcf2f5 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -73,11 +73,11 @@ class TIA { uint8_t get_collision_flags(int offset); void clear_collision_flags(); - virtual std::shared_ptr get_crt() { return crt_; } + Outputs::CRT::CRT *get_crt() { return crt_.get(); } private: TIA(bool create_crt); - std::shared_ptr crt_; + std::unique_ptr crt_; std::function line_end_function_; // the master counter; counts from 0 to 228 with all visible pixels being in the final 160 diff --git a/Machines/Atari2600/Speaker.cpp b/Machines/Atari2600/TIASound.cpp similarity index 86% rename from Machines/Atari2600/Speaker.cpp rename to Machines/Atari2600/TIASound.cpp index d573c363f..f0cbcf3c5 100644 --- a/Machines/Atari2600/Speaker.cpp +++ b/Machines/Atari2600/TIASound.cpp @@ -6,31 +6,32 @@ // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "Speaker.hpp" +#include "TIASound.hpp" using namespace Atari2600; -Atari2600::Speaker::Speaker() : +Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue), poly4_counter_{0x00f, 0x00f}, poly5_counter_{0x01f, 0x01f}, poly9_counter_{0x1ff, 0x1ff} {} -void Atari2600::Speaker::set_volume(int channel, uint8_t volume) { - enqueue([=]() { +void Atari2600::TIASound::set_volume(int channel, uint8_t volume) { + audio_queue_.defer([=]() { volume_[channel] = volume & 0xf; }); } -void Atari2600::Speaker::set_divider(int channel, uint8_t divider) { - enqueue([=]() { +void Atari2600::TIASound::set_divider(int channel, uint8_t divider) { + audio_queue_.defer([=]() { divider_[channel] = divider & 0x1f; divider_counter_[channel] = 0; }); } -void Atari2600::Speaker::set_control(int channel, uint8_t control) { - enqueue([=]() { +void Atari2600::TIASound::set_control(int channel, uint8_t control) { + audio_queue_.defer([=]() { control_[channel] = control & 0xf; }); } @@ -39,7 +40,7 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control) { #define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010) #define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100) -void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { +void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) { for(unsigned int c = 0; c < number_of_samples; c++) { target[c] = 0; for(int channel = 0; channel < 2; channel++) { diff --git a/Machines/Atari2600/Speaker.hpp b/Machines/Atari2600/TIASound.hpp similarity index 64% rename from Machines/Atari2600/Speaker.hpp rename to Machines/Atari2600/TIASound.hpp index 26886c8b2..78441489f 100644 --- a/Machines/Atari2600/Speaker.hpp +++ b/Machines/Atari2600/TIASound.hpp @@ -1,15 +1,16 @@ // -// Speaker.hpp +// TIASound.hpp // Clock Signal // // Created by Thomas Harte on 03/12/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef Atari2600_Speaker_hpp -#define Atari2600_Speaker_hpp +#ifndef Atari2600_TIASound_hpp +#define Atari2600_TIASound_hpp -#include "../../Outputs/Speaker.hpp" +#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" namespace Atari2600 { @@ -17,17 +18,19 @@ namespace Atari2600 { // will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38. const int CPUTicksPerAudioTick = 2; -class Speaker: public ::Outputs::Filter { +class TIASound: public Outputs::Speaker::SampleSource { public: - Speaker(); + TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue); void set_volume(int channel, uint8_t volume); void set_divider(int channel, uint8_t divider); void set_control(int channel, uint8_t control); - void get_samples(unsigned int number_of_samples, int16_t *target); + void get_samples(std::size_t number_of_samples, int16_t *target); private: + Concurrency::DeferringAsyncTaskQueue &audio_queue_; + uint8_t volume_[2]; uint8_t divider_[2]; uint8_t control_[2]; diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index 1b8f62016..06c796e36 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -10,7 +10,7 @@ #define CRTMachine_hpp #include "../Outputs/CRT/CRT.hpp" -#include "../Outputs/Speaker.hpp" +#include "../Outputs/Speaker/Speaker.hpp" #include "../ClockReceiver/ClockReceiver.hpp" #include "ROMMachine.hpp" @@ -36,10 +36,10 @@ class Machine: public ROMMachine::Machine { virtual void close_output() = 0; /// @returns The CRT this machine is drawing to. Should not be @c nullptr. - virtual std::shared_ptr get_crt() = 0; + virtual Outputs::CRT::CRT *get_crt() = 0; /// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute. - virtual std::shared_ptr get_speaker() = 0; + virtual Outputs::Speaker::Speaker *get_speaker() = 0; /// Runs the machine for @c cycles. virtual void run_for(const Cycles cycles) = 0; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 2416efed8..9b77c314e 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -631,7 +631,7 @@ class ConcreteMachine: void setup_output(float aspect_ratio) override final { mos6560_.reset(new Vic6560()); - mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20. +// mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20. // Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set. set_pal_6560(); } @@ -640,12 +640,13 @@ class ConcreteMachine: mos6560_ = nullptr; } - std::shared_ptr get_crt() override final { + Outputs::CRT::CRT *get_crt() override final { return mos6560_->get_crt(); } - std::shared_ptr get_speaker() override final { - return mos6560_->get_speaker(); + Outputs::Speaker::Speaker *get_speaker() override final { + return nullptr; +// return mos6560_->get_speaker(); } void mos6522_did_change_interrupt_status(void *mos6522) override final { diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 40ca3ce11..ab4524693 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -8,18 +8,19 @@ #include "Electron.hpp" -#include "../../Configurable/StandardOptions.hpp" -#include "../../Processors/6502/6502.hpp" -#include "../../Storage/Tape/Tape.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ForceInline.hpp" +#include "../../Configurable/StandardOptions.hpp" +#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp" +#include "../../Processors/6502/6502.hpp" +#include "../../Storage/Tape/Tape.hpp" #include "../Utility/Typer.hpp" #include "Interrupts.hpp" #include "Keyboard.hpp" #include "Plus3.hpp" -#include "Speaker.hpp" +#include "SoundGenerator.hpp" #include "Tape.hpp" #include "Video.hpp" @@ -37,13 +38,18 @@ class ConcreteMachine: public Tape::Delegate, public Utility::TypeRecipient { public: - ConcreteMachine() : m6502_(*this) { + ConcreteMachine() : + m6502_(*this), + sound_generator_(audio_queue_), + speaker_(sound_generator_) { memset(key_states_, 0, sizeof(key_states_)); for(int c = 0; c < 16; c++) memset(roms_[c], 0xff, 16384); tape_.set_delegate(this); set_clock_rate(2000000); + + speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); } void set_rom(ROMSlot slot, const std::vector &data, bool is_writeable) override final { @@ -180,7 +186,7 @@ class ConcreteMachine: bool new_speaker_is_enabled = (*value & 6) == 2; if(new_speaker_is_enabled != speaker_is_enabled_) { update_audio(); - speaker_->set_is_enabled(new_speaker_is_enabled); + sound_generator_.set_is_enabled(new_speaker_is_enabled); speaker_is_enabled_ = new_speaker_is_enabled; } @@ -241,7 +247,7 @@ class ConcreteMachine: case 0xfe06: if(!isReadOperation(operation)) { update_audio(); - speaker_->set_divider(*value); + sound_generator_.set_divider(*value); tape_.set_counter(*value); } break; @@ -372,29 +378,23 @@ class ConcreteMachine: forceinline void flush() { update_display(); update_audio(); - speaker_->flush(); + audio_queue_.perform(); } void setup_output(float aspect_ratio) override final { video_output_.reset(new VideoOutput(ram_)); - - // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; - // however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So - // run the speaker at a 2000000Hz input rate, at least for the time being. - speaker_.reset(new Speaker); - speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider); } void close_output() override final { video_output_.reset(); } - std::shared_ptr get_crt() override final { + Outputs::CRT::CRT *get_crt() override final { return video_output_->get_crt(); } - std::shared_ptr get_speaker() override final { - return speaker_; + Outputs::Speaker::Speaker *get_speaker() override final { + return &speaker_; } void run_for(const Cycles cycles) override final { @@ -469,9 +469,7 @@ class ConcreteMachine: } inline void update_audio() { - if(cycles_since_audio_update_ > 0) { - speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider))); - } + speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider))); } inline void signal_interrupt(Interrupt interrupt) { @@ -531,7 +529,11 @@ class ConcreteMachine: // Outputs std::unique_ptr video_output_; - std::shared_ptr speaker_; + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + SoundGenerator sound_generator_; + Outputs::Speaker::LowpassSpeaker speaker_; + bool speaker_is_enabled_ = false; }; diff --git a/Machines/Electron/Speaker.cpp b/Machines/Electron/SoundGenerator.cpp similarity index 56% rename from Machines/Electron/Speaker.cpp rename to Machines/Electron/SoundGenerator.cpp index 01162ee79..89971e801 100644 --- a/Machines/Electron/Speaker.cpp +++ b/Machines/Electron/SoundGenerator.cpp @@ -6,11 +6,14 @@ // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "Speaker.hpp" +#include "SoundGenerator.hpp" using namespace Electron; -void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { +SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + +void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { if(is_enabled_) { while(number_of_samples--) { *target = static_cast((counter_ / (divider_+1)) * 8192); @@ -22,18 +25,18 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { } } -void Speaker::skip_samples(unsigned int number_of_samples) { +void SoundGenerator::skip_samples(std::size_t number_of_samples) { counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2); } -void Speaker::set_divider(uint8_t divider) { - enqueue([=]() { +void SoundGenerator::set_divider(uint8_t divider) { + audio_queue_.defer([=]() { divider_ = divider * 32 / clock_rate_divider; }); } -void Speaker::set_is_enabled(bool is_enabled) { - enqueue([=]() { +void SoundGenerator::set_is_enabled(bool is_enabled) { + audio_queue_.defer([=]() { is_enabled_ = is_enabled; counter_ = 0; }); diff --git a/Machines/Electron/SoundGenerator.hpp b/Machines/Electron/SoundGenerator.hpp new file mode 100644 index 000000000..dd6530b33 --- /dev/null +++ b/Machines/Electron/SoundGenerator.hpp @@ -0,0 +1,39 @@ +// +// SoundGenerator.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Electron_SoundGenerator_hpp +#define Electron_SoundGenerator_hpp + +#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" + +namespace Electron { + +class SoundGenerator: public ::Outputs::Speaker::SampleSource { + public: + SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue); + + void set_divider(uint8_t divider); + + void set_is_enabled(bool is_enabled); + + void get_samples(std::size_t number_of_samples, int16_t *target); + void skip_samples(std::size_t number_of_samples); + + static const unsigned int clock_rate_divider = 8; + + private: + Concurrency::DeferringAsyncTaskQueue &audio_queue_; + unsigned int counter_ = 0; + unsigned int divider_ = 0; + bool is_enabled_ = false; +}; + +} + +#endif /* Speaker_hpp */ diff --git a/Machines/Electron/Speaker.hpp b/Machines/Electron/Speaker.hpp deleted file mode 100644 index 4208777ff..000000000 --- a/Machines/Electron/Speaker.hpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Speaker.hpp -// Clock Signal -// -// Created by Thomas Harte on 03/12/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#ifndef Electron_Speaker_hpp -#define Electron_Speaker_hpp - -#include "../../Outputs/Speaker.hpp" - -namespace Electron { - -class Speaker: public ::Outputs::Filter { - public: - void set_divider(uint8_t divider); - - void set_is_enabled(bool is_enabled); - - void get_samples(unsigned int number_of_samples, int16_t *target); - void skip_samples(unsigned int number_of_samples); - - static const unsigned int clock_rate_divider = 8; - - private: - unsigned int counter_ = 0; - unsigned int divider_ = 0; - bool is_enabled_ = false; -}; - -} - -#endif /* Speaker_hpp */ diff --git a/Machines/Electron/Video.cpp b/Machines/Electron/Video.cpp index 0527be8f9..fb3e3eefe 100644 --- a/Machines/Electron/Video.cpp +++ b/Machines/Electron/Video.cpp @@ -66,8 +66,8 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) { // MARK: - CRT getter -std::shared_ptr VideoOutput::get_crt() { - return crt_; +Outputs::CRT::CRT *VideoOutput::get_crt() { + return crt_.get(); } // MARK: - Display update methods diff --git a/Machines/Electron/Video.hpp b/Machines/Electron/Video.hpp index efb057132..3657b0503 100644 --- a/Machines/Electron/Video.hpp +++ b/Machines/Electron/Video.hpp @@ -31,7 +31,7 @@ class VideoOutput { VideoOutput(uint8_t *memory); /// @returns the CRT to which output is being painted. - std::shared_ptr get_crt(); + Outputs::CRT::CRT *get_crt(); /// Produces the next @c cycles of video output. void run_for(const Cycles cycles); @@ -111,7 +111,7 @@ class VideoOutput { uint8_t *initial_output_target_ = nullptr; unsigned int current_output_divider_ = 1; - std::shared_ptr crt_; + std::unique_ptr crt_; struct DrawAction { enum Type { diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index e85c53760..b68a4155e 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -44,30 +44,31 @@ class ConcreteMachine: ConcreteMachine(): z80_(*this), i8255_(i8255_port_handler_), + ay_(audio_queue_), + speaker_(ay_), i8255_port_handler_(*this) { set_clock_rate(3579545); std::memset(unpopulated_, 0xff, sizeof(unpopulated_)); clear_all_keys(); + + ay_.set_port_handler(&ay_port_handler_); + speaker_.set_input_rate(3579545.0f / 2.0f); } void setup_output(float aspect_ratio) override { vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); - ay_.reset(new GI::AY38910::AY38910()); - ay_->set_port_handler(&ay_port_handler_); - ay_->set_input_rate(3579545.0f / 2.0f); } void close_output() override { vdp_.reset(); - ay_.reset(); } - std::shared_ptr get_crt() override { + Outputs::CRT::CRT *get_crt() override { return vdp_->get_crt(); } - std::shared_ptr get_speaker() override { - return ay_; + Outputs::Speaker::Speaker *get_speaker() override { + return &speaker_; } void run_for(const Cycles cycles) override { @@ -130,10 +131,10 @@ class ConcreteMachine: break; case 0xa2: - ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2))); - ay_->set_control_lines(static_cast(GI::AY38910::BC2 | GI::AY38910::BC1)); - *cycle.value = ay_->get_data_output(); - ay_->set_control_lines(static_cast(0)); + speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + ay_.set_control_lines(static_cast(GI::AY38910::BC2 | GI::AY38910::BC1)); + *cycle.value = ay_.get_data_output(); + ay_.set_control_lines(static_cast(0)); break; case 0xa8: case 0xa9: @@ -158,10 +159,10 @@ class ConcreteMachine: break; case 0xa0: case 0xa1: - ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2))); - ay_->set_control_lines(static_cast(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); - ay_->set_data_input(*cycle.value); - ay_->set_control_lines(static_cast(0)); + speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + ay_.set_control_lines(static_cast(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(static_cast(0)); break; case 0xa8: case 0xa9: @@ -192,8 +193,8 @@ class ConcreteMachine: void flush() { vdp_->run_for(time_since_vdp_update_.flush()); - ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2))); - ay_->flush(); + speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + audio_queue_.perform(); } // Obtains the system ROMs. @@ -286,7 +287,10 @@ class ConcreteMachine: CPU::Z80::Processor z80_; std::unique_ptr vdp_; Intel::i8255::i8255 i8255_; - std::shared_ptr ay_; + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + GI::AY38910::AY38910 ay_; + Outputs::Speaker::LowpassSpeaker speaker_; i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 5d3db3ce5..135433cee 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -113,7 +113,8 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer { */ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { public: - VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {} + VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, GI::AY38910::AY38910 &ay8910, Outputs::Speaker::LowpassSpeaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) : + audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {} /*! Reponds to the 6522's control line output change signal; on an Oric A2 is connected to @@ -123,7 +124,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { if(line) { if(port) ay_bdir_ = value; else ay_bc1_ = value; update_ay(); - ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); + ay8910_.set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); } } @@ -137,7 +138,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { tape_player_.set_motor_control(value & 0x40); } else { update_ay(); - ay8910_->set_data_input(value); + ay8910_.set_data_input(value); } } @@ -146,10 +147,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { */ uint8_t get_port_input(MOS::MOS6522::Port port) { if(port) { - uint8_t column = ay8910_->get_port_output(false) ^ 0xff; + uint8_t column = ay8910_.get_port_output(false) ^ 0xff; return keyboard_.query_column(column) ? 0x08 : 0x00; } else { - return ay8910_->get_data_output(); + return ay8910_.get_data_output(); } } @@ -162,24 +163,21 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { /// Flushes any queued behaviour (which, specifically, means on the AY). void flush() { - ay8910_->run_for(cycles_since_ay_update_.flush()); - ay8910_->flush(); - } - - /// Sets the AY in use by the machine the VIA that uses this port handler sits within. - void set_ay(GI::AY38910::AY38910 *ay) { - ay8910_ = ay; + update_ay(); + audio_queue_.perform(); } private: void update_ay() { - ay8910_->run_for(cycles_since_ay_update_.flush()); + speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush()); } bool ay_bdir_ = false; bool ay_bc1_ = false; Cycles cycles_since_ay_update_; - GI::AY38910::AY38910 *ay8910_ = nullptr; + Concurrency::DeferringAsyncTaskQueue &audio_queue_; + GI::AY38910::AY38910 &ay8910_; + Outputs::Speaker::LowpassSpeaker &speaker_; TapePlayer &tape_player_; Keyboard &keyboard_; }; @@ -195,7 +193,9 @@ class ConcreteMachine: public: ConcreteMachine() : m6502_(*this), - via_port_handler_(tape_player_, keyboard_), + ay8910_(audio_queue_), + speaker_(ay8910_), + via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), via_(via_port_handler_), paged_rom_(rom_) { set_clock_rate(1000000); @@ -371,9 +371,7 @@ class ConcreteMachine: // to satisfy CRTMachine::Machine void setup_output(float aspect_ratio) override final { - ay8910_.reset(new GI::AY38910::AY38910()); - ay8910_->set_clock_rate(1000000); - via_port_handler_.set_ay(ay8910_.get()); + speaker_.set_input_rate(1000000.0f); video_output_.reset(new VideoOutput(ram_)); if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); @@ -382,16 +380,14 @@ class ConcreteMachine: void close_output() override final { video_output_.reset(); - ay8910_.reset(); - via_port_handler_.set_ay(nullptr); } - std::shared_ptr get_crt() override final { + Outputs::CRT::CRT *get_crt() override final { return video_output_->get_crt(); } - std::shared_ptr get_speaker() override final { - return ay8910_; + Outputs::Speaker::Speaker *get_speaker() override final { + return &speaker_; } void run_for(const Cycles cycles) override final { @@ -488,7 +484,10 @@ class ConcreteMachine: // Outputs std::unique_ptr video_output_; - std::shared_ptr ay8910_; + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + GI::AY38910::AY38910 ay8910_; + Outputs::Speaker::LowpassSpeaker speaker_; // Inputs Oric::KeyboardMapper keyboard_mapper_; diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index fcdfd60fb..9aff00933 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -67,8 +67,8 @@ void VideoOutput::set_colour_rom(const std::vector &rom) { } } -std::shared_ptr VideoOutput::get_crt() { - return crt_; +Outputs::CRT::CRT *VideoOutput::get_crt() { + return crt_.get(); } void VideoOutput::run_for(const Cycles cycles) { diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index cb95a579a..fe0ff2f38 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -17,14 +17,14 @@ namespace Oric { class VideoOutput { public: VideoOutput(uint8_t *memory); - std::shared_ptr get_crt(); + Outputs::CRT::CRT *get_crt(); void run_for(const Cycles cycles); void set_colour_rom(const std::vector &rom); void set_output_device(Outputs::CRT::OutputDevice output_device); private: uint8_t *ram_; - std::shared_ptr crt_; + std::unique_ptr crt_; // Counters and limits int counter_ = 0, frame_counter_ = 0; diff --git a/Machines/ZX8081/Video.cpp b/Machines/ZX8081/Video.cpp index f92e60479..82c6e59bd 100644 --- a/Machines/ZX8081/Video.cpp +++ b/Machines/ZX8081/Video.cpp @@ -100,6 +100,6 @@ void Video::output_byte(uint8_t byte) { } } -std::shared_ptr Video::get_crt() { - return crt_; +Outputs::CRT::CRT *Video::get_crt() { + return crt_.get(); } diff --git a/Machines/ZX8081/Video.hpp b/Machines/ZX8081/Video.hpp index d6ef09cf3..e8015d64e 100644 --- a/Machines/ZX8081/Video.hpp +++ b/Machines/ZX8081/Video.hpp @@ -29,7 +29,7 @@ class Video { /// Constructs an instance of the video feed; a CRT is also created. Video(); /// @returns The CRT this video feed is feeding. - std::shared_ptr get_crt(); + Outputs::CRT::CRT *get_crt(); /// Advances time by @c cycles. void run_for(const HalfCycles); @@ -46,7 +46,7 @@ class Video { uint8_t *line_data_ = nullptr; uint8_t *line_data_pointer_ = nullptr; unsigned int cycles_since_update_ = 0; - std::shared_ptr crt_; + std::unique_ptr crt_; void flush(bool next_sync); }; diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index 731d56871..680f50fa0 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -225,11 +225,11 @@ template class ConcreteMachine: video_.reset(); } - std::shared_ptr get_crt() override final { + Outputs::CRT::CRT *get_crt() override final { return video_->get_crt(); } - std::shared_ptr get_speaker() override final { + Outputs::Speaker::Speaker *get_speaker() override final { return nullptr; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c70f73d5e..47ca85554 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -79,7 +79,7 @@ 4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; }; 4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; }; 4B055AC51FAE9AEE0060FFFF /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; - 4B055AC61FAE9AEE0060FFFF /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; }; + 4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* TIASound.cpp */; }; 4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; 4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; }; 4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; }; @@ -88,7 +88,7 @@ 4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; 4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C61F8D91E50050900F /* Keyboard.cpp */; }; 4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; }; - 4B055ACF1FAE9B030060FFFF /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; }; + 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */; }; 4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; 4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; 4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */; }; @@ -251,6 +251,7 @@ 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; }; 4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F91DCFF807003085B1 /* Oric.cpp */; }; 4B8805FE1DD02552003085B1 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805FC1DD02552003085B1 /* Tape.cpp */; }; + 4B8EF6081FE5AF830076CCDD /* FilteringSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8EF6061FE5AF830076CCDD /* FilteringSpeaker.cpp */; }; 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; 4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */; }; @@ -577,8 +578,8 @@ 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; 4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; }; 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; - 4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; }; - 4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; }; + 4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */; }; + 4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* TIASound.cpp */; }; 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; @@ -672,7 +673,6 @@ 4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = ""; }; 4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = ""; }; 4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = ""; }; - 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; 4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OricOptionsPanel.swift; sourceTree = ""; }; @@ -873,6 +873,8 @@ 4B8805FD1DD02552003085B1 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Oric/Tape.hpp; sourceTree = ""; }; 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; + 4B8EF6061FE5AF830076CCDD /* FilteringSpeaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FilteringSpeaker.cpp; sourceTree = ""; }; + 4B8EF6071FE5AF830076CCDD /* FilteringSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FilteringSpeaker.hpp; sourceTree = ""; }; 4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ElectronOptions.xib"; sourceTree = SOURCE_ROOT; }; @@ -1232,6 +1234,7 @@ 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSOric.mm; sourceTree = ""; }; 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.cpp; sourceTree = ""; }; 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = ""; }; + 4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = ""; }; 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = ""; }; 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = ""; }; 4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 9918Base.hpp; path = 9918/Implementation/9918Base.hpp; sourceTree = ""; }; @@ -1254,10 +1257,10 @@ 4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = ""; }; 4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = ""; }; 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = ""; }; - 4BEA52611DF339D7007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = Electron/Speaker.cpp; sourceTree = ""; }; - 4BEA52621DF339D7007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = Electron/Speaker.hpp; sourceTree = ""; }; - 4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = ""; }; - 4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = ""; }; + 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SoundGenerator.cpp; path = Electron/SoundGenerator.cpp; sourceTree = ""; }; + 4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SoundGenerator.hpp; path = Electron/SoundGenerator.hpp; sourceTree = ""; }; + 4BEA52641DF3472B007E74F2 /* TIASound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIASound.cpp; sourceTree = ""; }; + 4BEA52651DF3472B007E74F2 /* TIASound.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIASound.hpp; sourceTree = ""; }; 4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = ""; }; 4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = ""; }; 4BEAC0821E7E0DF800EE56B2 /* ActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ActivisionStack.hpp; sourceTree = ""; }; @@ -1360,10 +1363,10 @@ 4B0CCC411C62D0B3001CAC5F /* CRT */ = { isa = PBXGroup; children = ( - 4BBF99071C8FBA6F0075DAFB /* Internals */, 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */, 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */, 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */, + 4BBF99071C8FBA6F0075DAFB /* Internals */, ); name = CRT; path = ../../Outputs/CRT; @@ -1533,13 +1536,13 @@ isa = PBXGroup; children = ( 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */, - 4BEA52641DF3472B007E74F2 /* Speaker.cpp */, + 4BEA52641DF3472B007E74F2 /* TIASound.cpp */, 4BE7C9161E3D397100A5496D /* TIA.cpp */, 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */, 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */, 4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */, 4BEA52671DF34909007E74F2 /* PIA.hpp */, - 4BEA52651DF3472B007E74F2 /* Speaker.hpp */, + 4BEA52651DF3472B007E74F2 /* TIASound.hpp */, 4BE7C9171E3D397100A5496D /* TIA.hpp */, 4BEAC0801E7E0DF800EE56B2 /* Cartridges */, ); @@ -1552,14 +1555,14 @@ 4B2E2D9B1C3A070400138695 /* Electron.cpp */, 4B54C0C61F8D91E50050900F /* Keyboard.cpp */, 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */, - 4BEA52611DF339D7007E74F2 /* Speaker.cpp */, + 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */, 4BEA525D1DF33323007E74F2 /* Tape.cpp */, 4B7913CA1DFCD80E00175A82 /* Video.cpp */, 4B2E2D9C1C3A070400138695 /* Electron.hpp */, 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */, 4B54C0C71F8D91E50050900F /* Keyboard.hpp */, 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */, - 4BEA52621DF339D7007E74F2 /* Speaker.hpp */, + 4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */, 4BEA525F1DF333D8007E74F2 /* Tape.hpp */, 4B7913CB1DFCD80E00175A82 /* Video.hpp */, ); @@ -1605,7 +1608,7 @@ isa = PBXGroup; children = ( 4B0CCC411C62D0B3001CAC5F /* CRT */, - 4B2409541C45AB05004DA684 /* Speaker.hpp */, + 4BD060A41FE49D3C006E14BE /* Speaker */, ); name = Outputs; sourceTree = ""; @@ -2016,6 +2019,15 @@ name = Data; sourceTree = ""; }; + 4B8EF6051FE5AF830076CCDD /* Implementation */ = { + isa = PBXGroup; + children = ( + 4B8EF6061FE5AF830076CCDD /* FilteringSpeaker.cpp */, + 4B8EF6071FE5AF830076CCDD /* FilteringSpeaker.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; 4BA799961D8B65730045123D /* Atari */ = { isa = PBXGroup; children = ( @@ -2603,6 +2615,16 @@ name = Oric; sourceTree = ""; }; + 4BD060A41FE49D3C006E14BE /* Speaker */ = { + isa = PBXGroup; + children = ( + 4BD060A51FE49D3C006E14BE /* Speaker.hpp */, + 4B8EF6051FE5AF830076CCDD /* Implementation */, + ); + name = Speaker; + path = ../../Outputs/Speaker; + sourceTree = ""; + }; 4BD14B121D7462810088EAD6 /* Acorn */ = { isa = PBXGroup; children = ( @@ -3250,7 +3272,7 @@ 4B055A801FAE85350060FFFF /* StaticAnalyser.cpp in Sources */, 4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, 4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, - 4B055ACF1FAE9B030060FFFF /* Speaker.cpp in Sources */, + 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, 4B055A881FAE85530060FFFF /* Disassembler6502.cpp in Sources */, 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, @@ -3287,7 +3309,7 @@ 4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */, 4B055A841FAE85450060FFFF /* Disk.cpp in Sources */, 4B055A831FAE85410060FFFF /* Tape.cpp in Sources */, - 4B055AC61FAE9AEE0060FFFF /* Speaker.cpp in Sources */, + 4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */, 4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */, 4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */, 4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */, @@ -3360,6 +3382,7 @@ 4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */, + 4B8EF6081FE5AF830076CCDD /* FilteringSpeaker.cpp in Sources */, 4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */, 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, @@ -3384,7 +3407,7 @@ 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, 4B1BA08A1FD4967800CB4ADA /* CSMSX.mm in Sources */, - 4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */, + 4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */, 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, 4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, 4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */, @@ -3440,7 +3463,7 @@ 4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */, 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */, 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */, - 4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */, + 4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 22f6468ed..9d8d25de5 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -22,7 +22,7 @@ #import "NSData+StdVector.h" @interface CSMachine() -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; +- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)machineDidChangeClockRate; - (void)machineDidChangeClockIsUnlimited; @end @@ -34,8 +34,8 @@ struct LockProtectedDelegate { __unsafe_unretained CSMachine *machine; }; -struct SpeakerDelegate: public Outputs::Speaker::Delegate, public LockProtectedDelegate { - void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector &buffer) { +struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate { + void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) { [machineAccessLock lock]; [machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()]; [machineAccessLock unlock]; @@ -95,7 +95,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg return self; } -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { +- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } @@ -128,7 +128,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg - (float)idealSamplingRateFromRange:(NSRange)range { @synchronized(self) { - std::shared_ptr speaker = _machine->crt_machine()->get_speaker(); + Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker(); if(speaker) { return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length)); } @@ -142,9 +142,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg } } -- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize { +- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize { @synchronized(self) { - std::shared_ptr speaker = _machine->crt_machine()->get_speaker(); + Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker(); if(speaker) { speaker->set_output_rate(sampleRate, (int)bufferSize); speaker->set_delegate(delegate); diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 040edacdb..0527972bb 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -42,10 +42,10 @@ struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegat }; // This is set to a relatively large number for now. -struct SpeakerDelegate: public Outputs::Speaker::Delegate { +struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { static const int buffer_size = 1024; - void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector &buffer) { + void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) { std::lock_guard lock_guard(audio_buffer_mutex_); if(audio_buffer_.size() > buffer_size) { audio_buffer_.erase(audio_buffer_.begin(), audio_buffer_.end() - buffer_size); diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp deleted file mode 100644 index ca3f00775..000000000 --- a/Outputs/Speaker.hpp +++ /dev/null @@ -1,257 +0,0 @@ -// -// Speaker.hpp -// Clock Signal -// -// Created by Thomas Harte on 12/01/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#ifndef Speaker_hpp -#define Speaker_hpp - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "../SignalProcessing/Stepper.hpp" -#include "../SignalProcessing/FIRFilter.hpp" -#include "../Concurrency/AsyncTaskQueue.hpp" -#include "../ClockReceiver/ClockReceiver.hpp" - -namespace Outputs { - -/*! - Provides the base class for an audio output source, with an input rate (the speed at which the source will - provide data), an output rate (the speed at which the destination will receive data), a delegate to receive - the output and some help for the output in picking an appropriate rate once the input rate is known. - - Intended to be a parent class, allowing descendants to pick the strategy by which input samples are mapped to - output samples. -*/ -class Speaker { - public: - class Delegate { - public: - virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector &buffer) = 0; - }; - - float get_ideal_clock_rate_in_range(float minimum, float maximum) { - // return twice the cut off, if applicable - if(high_frequency_cut_off_ > 0.0f && input_cycles_per_second_ >= high_frequency_cut_off_ * 3.0f && input_cycles_per_second_ <= high_frequency_cut_off_ * 3.0f) return high_frequency_cut_off_ * 3.0f; - - // return exactly the input rate if possible - if(input_cycles_per_second_ >= minimum && input_cycles_per_second_ <= maximum) return input_cycles_per_second_; - - // if the input rate is lower, return the minimum - if(input_cycles_per_second_ < minimum) return minimum; - - // otherwise, return the maximum - return maximum; - } - - void set_output_rate(float cycles_per_second, int buffer_size) { - output_cycles_per_second_ = cycles_per_second; - buffer_in_progress_.resize(static_cast(buffer_size)); - set_needs_updated_filter_coefficients(); - } - - void set_output_quality(int number_of_taps) { - requested_number_of_taps_ = static_cast(number_of_taps); - set_needs_updated_filter_coefficients(); - } - - void set_delegate(Delegate *delegate) { - delegate_ = delegate; - } - - void set_input_rate(float cycles_per_second) { - input_cycles_per_second_ = cycles_per_second; - set_needs_updated_filter_coefficients(); - } - - /*! - Sets the cut-off frequency for a low-pass filter attached to the output of this speaker; optional. - */ - void set_high_frequency_cut_off(float high_frequency) { - high_frequency_cut_off_ = high_frequency; - set_needs_updated_filter_coefficients(); - } - - Speaker() : _queue(new Concurrency::AsyncTaskQueue) {} - - /*! - Ensures any deferred processing occurs now. - */ - void flush() { - if(!queued_functions_) return; - std::shared_ptr>> queued_functions = queued_functions_; - queued_functions_.reset(); - _queue->enqueue([queued_functions] { - for(auto &function : *queued_functions) { - function(); - } - }); - } - - protected: - void enqueue(std::function function) { - if(!queued_functions_) queued_functions_.reset(new std::list>); - queued_functions_->push_back(function); - } - std::shared_ptr>> queued_functions_; - - std::vector buffer_in_progress_; - float high_frequency_cut_off_ = -1.0; - std::size_t buffer_in_progress_pointer_ = 0; - std::size_t number_of_taps_; - std::size_t requested_number_of_taps_ = 0; - bool coefficients_are_dirty_; - Delegate *delegate_ = nullptr; - - float input_cycles_per_second_ = 0.0f; - float output_cycles_per_second_ = 0.0f; - - void set_needs_updated_filter_coefficients() { - coefficients_are_dirty_ = true; - } - - void get_samples(unsigned int quantity, int16_t *target) {} - void skip_samples(unsigned int quantity) { - int16_t throwaway_samples[quantity]; - get_samples(quantity, throwaway_samples); - } - - std::unique_ptr _queue; -}; - -/*! - A concrete descendant of Speaker that uses a FIR filter to map from input data to output data when scaling - and a copy-through buffer when input and output rates are the same. - - Audio sources should use @c Filter as both a template and a parent, implementing at least - `get_samples(unsigned int quantity, int16_t *target)` and ideally also `skip_samples(unsigned int quantity)` - to provide source data. - - Call `run_for` to request that the next period of input data is collected. -*/ -template class Filter: public Speaker { - public: - ~Filter() { - _queue->flush(); - } - - void run_for(const Cycles cycles) { - enqueue([=]() { - unsigned int cycles_remaining = static_cast(cycles.as_int()); - if(coefficients_are_dirty_) update_filter_coefficients(); - - // if input and output rates exactly match, just accumulate results and pass on - if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) { - while(cycles_remaining) { - unsigned int cycles_to_read = static_cast(buffer_in_progress_.size() - static_cast(buffer_in_progress_pointer_)); - if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining; - - static_cast(this)->get_samples(cycles_to_read, &buffer_in_progress_[static_cast(buffer_in_progress_pointer_)]); - buffer_in_progress_pointer_ += cycles_to_read; - - // announce to delegate if full - if(buffer_in_progress_pointer_ == buffer_in_progress_.size()) { - buffer_in_progress_pointer_ = 0; - if(delegate_) { - delegate_->speaker_did_complete_samples(this, buffer_in_progress_); - } - } - - cycles_remaining -= cycles_to_read; - } - - return; - } - - // if the output rate is less than the input rate, use the filter - if(input_cycles_per_second_ > output_cycles_per_second_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0)) { - while(cycles_remaining) { - unsigned int cycles_to_read = static_cast(std::min(static_cast(cycles_remaining), number_of_taps_ - input_buffer_depth_)); - static_cast(this)->get_samples(cycles_to_read, &input_buffer_[static_cast(input_buffer_depth_)]); - cycles_remaining -= cycles_to_read; - input_buffer_depth_ += cycles_to_read; - - if(input_buffer_depth_ == number_of_taps_) { - buffer_in_progress_[static_cast(buffer_in_progress_pointer_)] = filter_->apply(input_buffer_.data()); - buffer_in_progress_pointer_++; - - // announce to delegate if full - if(buffer_in_progress_pointer_ == buffer_in_progress_.size()) { - buffer_in_progress_pointer_ = 0; - if(delegate_) { - delegate_->speaker_did_complete_samples(this, buffer_in_progress_); - } - } - - // If the next loop around is going to reuse some of the samples just collected, use a memmove to - // preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip - // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. - uint64_t steps = stepper_->step(); - if(steps < number_of_taps_) { - int16_t *input_buffer = input_buffer_.data(); - memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * (static_cast(number_of_taps_) - static_cast(steps))); - input_buffer_depth_ -= steps; - } else { - if(steps > number_of_taps_) - static_cast(this)->skip_samples(static_cast(steps) - static_cast(number_of_taps_)); - input_buffer_depth_ = 0; - } - } - } - - return; - } - - // TODO: input rate is less than output rate - }); - } - - private: - std::unique_ptr stepper_; - std::unique_ptr filter_; - - std::vector input_buffer_; - std::size_t input_buffer_depth_; - - void update_filter_coefficients() { - // make a guess at a good number of taps if this hasn't been provided explicitly - if(requested_number_of_taps_) { - number_of_taps_ = requested_number_of_taps_; - } else { - number_of_taps_ = static_cast(ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_)); - number_of_taps_ *= 2; - number_of_taps_ |= 1; - } - - coefficients_are_dirty_ = false; - buffer_in_progress_pointer_ = 0; - - stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_)); - - float high_pass_frequency; - if(high_frequency_cut_off_ > 0.0) { - high_pass_frequency = std::min(output_cycles_per_second_ / 2.0f, high_frequency_cut_off_); - } else { - high_pass_frequency = output_cycles_per_second_ / 2.0f; - } - filter_.reset(new SignalProcessing::FIRFilter(static_cast(number_of_taps_), static_cast(input_cycles_per_second_), 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation)); - - input_buffer_.resize(static_cast(number_of_taps_)); - input_buffer_depth_ = 0; - } -}; - -} - -#endif /* Speaker_hpp */ diff --git a/Outputs/Speaker/Implementation/FilteringSpeaker.cpp b/Outputs/Speaker/Implementation/FilteringSpeaker.cpp new file mode 100644 index 000000000..a9730b7dc --- /dev/null +++ b/Outputs/Speaker/Implementation/FilteringSpeaker.cpp @@ -0,0 +1,9 @@ +// +// FilteringSpeaker.cpp +// Clock Signal +// +// Created by Thomas Harte on 15/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "FilteringSpeaker.hpp" diff --git a/Outputs/Speaker/Implementation/FilteringSpeaker.hpp b/Outputs/Speaker/Implementation/FilteringSpeaker.hpp new file mode 100644 index 000000000..c97efd2c7 --- /dev/null +++ b/Outputs/Speaker/Implementation/FilteringSpeaker.hpp @@ -0,0 +1,238 @@ +// +// FilteringSpeaker.h +// Clock Signal +// +// Created by Thomas Harte on 15/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef FilteringSpeaker_h +#define FilteringSpeaker_h + +#include "../Speaker.hpp" +#include "../../../SignalProcessing/Stepper.hpp" +#include "../../../SignalProcessing/FIRFilter.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../Concurrency/AsyncTaskQueue.hpp" + +namespace Outputs { +namespace Speaker { + +/*! + A sample source is something that can provide a stream of audio. + This optional base class provides the interface expected to be exposed + by the template parameter to LowpassSpeaker. +*/ +class SampleSource { + public: + /*! + Should write the next @c number_of_samples to @c target. + */ + void get_samples(std::size_t number_of_samples, int16_t *target) {} + + /*! + Should skip the next @c number_of_samples. Subclasses of this SampleSource + need not implement this if it would no more efficient to do so than it is + merely to call get_samples and throw the result away, as per the default + implementation below. + */ + void skip_samples(const std::size_t number_of_samples) { + int16_t scratch_pad[number_of_samples]; + get_samples(number_of_samples, scratch_pad); + } +}; + +/*! + The low-pass speaker +*/ +template class LowpassSpeaker: public Speaker { + public: + LowpassSpeaker(T &sample_source) : sample_source_(sample_source) {} + + // Implemented as per Speaker. + float get_ideal_clock_rate_in_range(float minimum, float maximum) { + // return twice the cut off, if applicable + if( filter_parameters_.high_frequency_cutoff > 0.0f && + filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.0f && + filter_parameters_.input_cycles_per_second <= filter_parameters_.high_frequency_cutoff * 3.0f) + return filter_parameters_.high_frequency_cutoff * 3.0f; + + // return exactly the input rate if possible + if( filter_parameters_.input_cycles_per_second >= minimum && + filter_parameters_.input_cycles_per_second <= maximum) + return filter_parameters_.input_cycles_per_second; + + // if the input rate is lower, return the minimum + if(filter_parameters_.input_cycles_per_second < minimum) + return minimum; + + // otherwise, return the maximum + return maximum; + } + + // Implemented as per Speaker. + void set_output_rate(float cycles_per_second, int buffer_size) { + filter_parameters_.output_cycles_per_second = cycles_per_second; + filter_parameters_.parameters_are_dirty = true; + output_buffer_.resize(static_cast(buffer_size)); + } + + /*! + Sets the clock rate of the input audio. + */ + void set_input_rate(float cycles_per_second) { + filter_parameters_.input_cycles_per_second = cycles_per_second; + filter_parameters_.parameters_are_dirty = true; + } + + /*! + Allows a cut-off frequency to be specified for audio. Ordinarily this low-pass speaker + will determine a cut-off based on the output audio rate. A caller can manually select + an alternative cut-off. This allows machines with a low-pass filter on their audio output + path to be explicit about its effect, and get that simulation for free. + */ + void set_high_frequency_cutoff(float high_frequency) { + filter_parameters_.high_frequency_cutoff = high_frequency; + filter_parameters_.parameters_are_dirty = true; + } + + /*! + Advances by the number of cycles specified, obtaining data from the sample source supplied + at construction, filtering it and passing it on to the speaker's delegate if there is one. + */ + void run_for(const Cycles cycles) { + std::size_t cycles_remaining = static_cast(cycles.as_int()); + if(!cycles_remaining) return; + if(filter_parameters_.parameters_are_dirty) update_filter_coefficients(); + + // If input and output rates exactly match, and no additional cut-off has been specified, + // just accumulate results and pass on. + if( filter_parameters_.input_cycles_per_second == filter_parameters_.output_cycles_per_second && + filter_parameters_.high_frequency_cutoff < 0.0) { + while(cycles_remaining) { + std::size_t cycles_to_read = std::min(output_buffer_.size() - output_buffer_pointer_, cycles_remaining); + + sample_source_.get_samples(cycles_to_read, &output_buffer_[output_buffer_pointer_]); + output_buffer_pointer_ += cycles_to_read; + + // announce to delegate if full + if(output_buffer_pointer_ == output_buffer_.size()) { + output_buffer_pointer_ = 0; + if(delegate_) { + delegate_->speaker_did_complete_samples(this, output_buffer_); + } + } + + cycles_remaining -= cycles_to_read; + } + + return; + } + + // if the output rate is less than the input rate, or an additional cut-off has been specified, use the filter. + if( filter_parameters_.input_cycles_per_second > filter_parameters_.output_cycles_per_second || + (filter_parameters_.input_cycles_per_second == filter_parameters_.output_cycles_per_second && filter_parameters_.high_frequency_cutoff >= 0.0)) { + while(cycles_remaining) { + std::size_t cycles_to_read = std::min(cycles_remaining, input_buffer_.size() - input_buffer_depth_); + sample_source_.get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]); + cycles_remaining -= cycles_to_read; + input_buffer_depth_ += cycles_to_read; + + if(input_buffer_depth_ == input_buffer_.size()) { + output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data()); + output_buffer_pointer_++; + + // Announce to delegate if full. + if(output_buffer_pointer_ == output_buffer_.size()) { + output_buffer_pointer_ = 0; + if(delegate_) { + delegate_->speaker_did_complete_samples(this, output_buffer_); + } + } + + // If the next loop around is going to reuse some of the samples just collected, use a memmove to + // preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip + // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. + uint64_t steps = stepper_->step(); + if(steps < input_buffer_.size()) { + int16_t *input_buffer = input_buffer_.data(); + memmove( input_buffer, + &input_buffer[steps], + sizeof(int16_t) * (input_buffer_.size() - steps)); + input_buffer_depth_ -= steps; + } else { + if(steps > input_buffer_.size()) + sample_source_.skip_samples(steps - input_buffer_.size()); + input_buffer_depth_ = 0; + } + } + } + + return; + } + + // TODO: input rate is less than output rate + } + + /*! + Provides a convenience shortcut for deferring a call to run_for. + */ + void run_for(Concurrency::DeferringAsyncTaskQueue &queue, const Cycles cycles) { + queue.defer([this, cycles] { + run_for(cycles); + }); + } + + private: + T &sample_source_; + + std::size_t output_buffer_pointer_ = 0; + std::size_t input_buffer_depth_ = 0; + std::vector input_buffer_; + std::vector output_buffer_; + + std::unique_ptr stepper_; + std::unique_ptr filter_; + + struct FilterParameters { + float input_cycles_per_second = 0.0f; + float output_cycles_per_second = 0.0f; + float high_frequency_cutoff = -1.0; + + bool parameters_are_dirty = true; + } filter_parameters_; + + void update_filter_coefficients() { + // Make a guess at a good number of taps. + std::size_t number_of_taps = static_cast( + ceilf((filter_parameters_.input_cycles_per_second + filter_parameters_.output_cycles_per_second) / filter_parameters_.output_cycles_per_second) + ); + number_of_taps = (number_of_taps * 2) | 1; + + filter_parameters_.parameters_are_dirty = false; + output_buffer_pointer_ = 0; + + stepper_.reset(new SignalProcessing::Stepper( + static_cast(filter_parameters_.input_cycles_per_second), + static_cast(filter_parameters_.output_cycles_per_second))); + + float high_pass_frequency = filter_parameters_.output_cycles_per_second / 2.0f; + if(filter_parameters_.high_frequency_cutoff > 0.0) { + high_pass_frequency = std::min(filter_parameters_.output_cycles_per_second / 2.0f, high_pass_frequency); + } + filter_.reset(new SignalProcessing::FIRFilter( + static_cast(number_of_taps), + filter_parameters_.input_cycles_per_second, + 0.0, + high_pass_frequency, + SignalProcessing::FIRFilter::DefaultAttenuation)); + + input_buffer_.resize(static_cast(number_of_taps)); + input_buffer_depth_ = 0; + } +}; + +} +} + +#endif /* FilteringSpeaker_h */ diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp new file mode 100644 index 000000000..b5a334b25 --- /dev/null +++ b/Outputs/Speaker/Speaker.hpp @@ -0,0 +1,43 @@ +// +// Speaker.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Speaker_hpp +#define Speaker_hpp + +#include +#include + +namespace Outputs { +namespace Speaker { + +/*! + Provides a communication point for sound; machines that have a speaker provide an + audio output. +*/ +class Speaker { + public: + virtual ~Speaker() {} + + virtual float get_ideal_clock_rate_in_range(float minimum, float maximum) = 0; + virtual void set_output_rate(float cycles_per_second, int buffer_size) = 0; + + struct Delegate { + virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector &buffer) = 0; + }; + void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } + + protected: + Delegate *delegate_ = nullptr; +}; + +} +} + +#endif /* Speaker_hpp */