From d49c07687c2f5b590623eb34a242e45c4182324d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Feb 2024 10:55:52 -0500 Subject: [PATCH] Unify [get_/skip_]samples, adding a third option for in-place mixing. --- Components/6560/6560.cpp | 25 +++--- Components/6560/6560.hpp | 5 +- Components/AY38910/AY38910.cpp | 14 ++- Components/AY38910/AY38910.hpp | 5 +- Components/AudioToggle/AudioToggle.cpp | 6 -- Components/AudioToggle/AudioToggle.hpp | 6 +- Components/KonamiSCC/KonamiSCC.cpp | 14 +-- Components/KonamiSCC/KonamiSCC.hpp | 5 +- Components/OPx/OPLL.cpp | 9 +- Components/OPx/OPLL.hpp | 3 +- Components/SN76489/SN76489.cpp | 10 ++- Components/SN76489/SN76489.hpp | 3 +- Machines/Apple/AppleIIgs/Sound.cpp | 53 +++++++----- Machines/Apple/AppleIIgs/Sound.hpp | 7 +- Machines/Apple/Macintosh/Audio.cpp | 8 +- Machines/Apple/Macintosh/Audio.hpp | 3 +- Machines/Atari/2600/TIASound.cpp | 11 ++- Machines/Atari/2600/TIASound.hpp | 3 +- Machines/Electron/SoundGenerator.cpp | 19 ++-- Machines/Electron/SoundGenerator.hpp | 4 +- Machines/Enterprise/Dave.cpp | 8 +- Machines/Enterprise/Dave.hpp | 3 +- .../xcschemes/Clock Signal.xcscheme | 2 +- .../Speaker/Implementation/BufferSource.hpp | 86 ++++++++++--------- .../Speaker/Implementation/CompoundSource.hpp | 44 +++------- .../Speaker/Implementation/LowpassSpeaker.hpp | 7 +- Outputs/Speaker/Speaker.hpp | 3 + 27 files changed, 204 insertions(+), 162 deletions(-) diff --git a/Components/6560/6560.cpp b/Components/6560/6560.cpp index 3ca2b145f..51d053994 100644 --- a/Components/6560/6560.cpp +++ b/Components/6560/6560.cpp @@ -19,6 +19,7 @@ AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue &audio_queue) void AudioGenerator::set_volume(uint8_t volume) { audio_queue_.enqueue([this, volume]() { volume_ = int16_t(volume) * range_multiplier_; + dc_offset_ = volume_ >> 4; }); } @@ -105,7 +106,8 @@ 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 AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { +template +void AudioGenerator::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { for(unsigned int c = 0; c < number_of_samples; ++c) { update(0, 2, shift); update(1, 1, shift); @@ -114,23 +116,22 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) // this sums the output of all three sounds channels plus a DC offset for volume; // TODO: what's the real ratio of this stuff? - target[c] = int16_t( + const int16_t sample = (shift_registers_[0]&1) + (shift_registers_[1]&1) + (shift_registers_[2]&1) + - ((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1) - ) * volume_ + (volume_ >> 4); - } -} + ((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1); -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); - update(2, 0, shift); - update(3, 1, increment); + Outputs::Speaker::apply( + target[c], + Outputs::Speaker::MonoSample( + sample * volume_ + dc_offset_ + )); } } +template void AudioGenerator::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void AudioGenerator::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void AudioGenerator::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); void AudioGenerator::set_sample_volume_range(std::int16_t range) { range_multiplier_ = int16_t(range / 64); diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index a2535674e..e87580fad 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -25,8 +25,8 @@ class AudioGenerator: public Outputs::Speaker::BufferSource + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); void set_sample_volume_range(std::int16_t range); private: @@ -36,6 +36,7 @@ class AudioGenerator: public Outputs::Speaker::BufferSource void AY38910::set_output_mixing(float a_lef } template -void AY38910::get_samples( +template +void AY38910::apply_samples( std::size_t number_of_samples, typename Outputs::Speaker::SampleT::type *target ) { @@ -117,7 +118,7 @@ void AY38910::get_samples( std::size_t c = 0; while((master_divider_&3) && c < number_of_samples) { - target[c] = output_volume_; + Outputs::Speaker::apply(target[c], output_volume_); master_divider_++; c++; } @@ -159,7 +160,7 @@ void AY38910::get_samples( evaluate_output_volume(); for(int ic = 0; ic < 4 && c < number_of_samples; ic++) { - target[c] = output_volume_; + Outputs::Speaker::apply(target[c], output_volume_); c++; master_divider_++; } @@ -168,6 +169,13 @@ void AY38910::get_samples( master_divider_ &= 3; } +template void AY38910::apply_samples(std::size_t, typename Outputs::Speaker::SampleT::type *); +template void AY38910::apply_samples(std::size_t, typename Outputs::Speaker::SampleT::type *); +template void AY38910::apply_samples(std::size_t, typename Outputs::Speaker::SampleT::type *); +template void AY38910::apply_samples(std::size_t, typename Outputs::Speaker::SampleT::type *); +template void AY38910::apply_samples(std::size_t, typename Outputs::Speaker::SampleT::type *); +template void AY38910::apply_samples(std::size_t, typename Outputs::Speaker::SampleT::type *); + template void AY38910::evaluate_output_volume() { int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_]; diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 2f30f61d9..722abcba2 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -105,8 +105,9 @@ template class AY38910: public ::Outputs::Speaker::BufferSource::type *target); + // Buffer generation. + template + void apply_samples(std::size_t number_of_samples, typename Outputs::Speaker::SampleT::type *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); diff --git a/Components/AudioToggle/AudioToggle.cpp b/Components/AudioToggle/AudioToggle.cpp index ee8e2b417..77c329972 100644 --- a/Components/AudioToggle/AudioToggle.cpp +++ b/Components/AudioToggle/AudioToggle.cpp @@ -15,17 +15,11 @@ using namespace Audio; Audio::Toggle::Toggle(Concurrency::AsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {} -void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) { - std::fill(target, target + number_of_samples, level_); -} - void Toggle::set_sample_volume_range(std::int16_t range) { volume_ = range; level_ = level_active_ ? volume_ : 0; } -void Toggle::skip_samples(std::size_t) {} - void Toggle::set_output(bool enabled) { if(is_enabled_ == enabled) return; is_enabled_ = enabled; diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp index c8e962742..ea304e06a 100644 --- a/Components/AudioToggle/AudioToggle.hpp +++ b/Components/AudioToggle/AudioToggle.hpp @@ -20,9 +20,11 @@ class Toggle: public Outputs::Speaker::BufferSource { public: Toggle(Concurrency::AsyncTaskQueue &audio_queue); - void get_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { + Outputs::Speaker::fill(target, target + number_of_samples, level_); + } void set_sample_volume_range(std::int16_t range); - void skip_samples(const std::size_t number_of_samples); void set_output(bool enabled); bool get_output() const; diff --git a/Components/KonamiSCC/KonamiSCC.cpp b/Components/KonamiSCC/KonamiSCC.cpp index 81a2af502..c4c1abc8d 100644 --- a/Components/KonamiSCC/KonamiSCC.cpp +++ b/Components/KonamiSCC/KonamiSCC.cpp @@ -19,15 +19,16 @@ bool SCC::is_zero_level() const { return !(channel_enable_ & 0x1f); } -void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { +template +void SCC::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { if(is_zero_level()) { - std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); + Outputs::Speaker::fill(target, target + number_of_samples, Outputs::Speaker::MonoSample()); return; } std::size_t c = 0; while((master_divider_&7) && c < number_of_samples) { - target[c] = transient_output_level_; + Outputs::Speaker::apply(target[c], transient_output_level_); master_divider_++; c++; } @@ -44,12 +45,15 @@ void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { evaluate_output_volume(); for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { - target[c] = transient_output_level_; + Outputs::Speaker::apply(target[c], transient_output_level_); c++; master_divider_++; } } } +template void SCC::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void SCC::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void SCC::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); void SCC::write(uint16_t address, uint8_t value) { address &= 0xff; @@ -111,5 +115,3 @@ uint8_t SCC::read(uint16_t address) { } return 0xff; } - - diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index 7da684a73..5a379c3d0 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -29,7 +29,8 @@ class SCC: public ::Outputs::Speaker::BufferSource { bool is_zero_level() const; /// As per ::SampleSource; provides audio output. - void get_samples(std::size_t number_of_samples, std::int16_t *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); void set_sample_volume_range(std::int16_t range); /// Writes to the SCC. @@ -44,7 +45,7 @@ class SCC: public ::Outputs::Speaker::BufferSource { // State from here on down is accessed ony from the audio thread. int master_divider_ = 0; std::int16_t master_volume_ = 0; - int16_t transient_output_level_ = 0; + Outputs::Speaker::MonoSample transient_output_level_ = 0; struct Channel { int period = 0; diff --git a/Components/OPx/OPLL.cpp b/Components/OPx/OPLL.cpp index f5d8feddd..b1bb81935 100644 --- a/Components/OPx/OPLL.cpp +++ b/Components/OPx/OPLL.cpp @@ -278,7 +278,8 @@ void OPLL::set_sample_volume_range(std::int16_t range) { total_volume_ = range; } -void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) { +template +void OPLL::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { // Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency; // unlike the OPL2 the OPLL time-divides the output for 'mixing'. @@ -289,12 +290,16 @@ void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) { while(number_of_samples--) { if(!audio_offset_) update_all_channels(); - *target = output_levels_[audio_offset_ / channel_output_period]; + Outputs::Speaker::apply(*target, output_levels_[audio_offset_ / channel_output_period]); ++target; audio_offset_ = (audio_offset_ + 1) % update_period; } } +template void OPLL::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void OPLL::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void OPLL::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); + void OPLL::update_all_channels() { oscillator_.update(); diff --git a/Components/OPx/OPLL.hpp b/Components/OPx/OPLL.hpp index 8d35e8e1b..da08a3f97 100644 --- a/Components/OPx/OPLL.hpp +++ b/Components/OPx/OPLL.hpp @@ -25,7 +25,8 @@ class OPLL: public OPLBase { OPLL(Concurrency::AsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false); /// As per ::SampleSource; provides audio output. - void get_samples(std::size_t number_of_samples, std::int16_t *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); void set_sample_volume_range(std::int16_t range); // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index 8705b24c7..007df3516 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -99,10 +99,11 @@ void SN76489::evaluate_output_volume() { ); } -void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { +template +void SN76489::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { std::size_t c = 0; while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) { - target[c] = output_volume_; + Outputs::Speaker::apply(target[c], output_volume_); master_divider_++; c++; } @@ -151,7 +152,7 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { evaluate_output_volume(); for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) { - target[c] = output_volume_; + Outputs::Speaker::apply(target[c], output_volume_); c++; master_divider_++; } @@ -159,3 +160,6 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { master_divider_ &= (master_divider_period_ - 1); } +template void SN76489::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void SN76489::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void SN76489::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index ec35fb06b..5e79e3e83 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -28,7 +28,8 @@ class SN76489: public Outputs::Speaker::BufferSource { void write(uint8_t value); // As per SampleSource. - void get_samples(std::size_t number_of_samples, std::int16_t *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); diff --git a/Machines/Apple/AppleIIgs/Sound.cpp b/Machines/Apple/AppleIIgs/Sound.cpp index 0a5c78869..d52d6f3e3 100644 --- a/Machines/Apple/AppleIIgs/Sound.cpp +++ b/Machines/Apple/AppleIIgs/Sound.cpp @@ -159,29 +159,34 @@ void GLU::run_for(Cycles cycles) { pending_store_write_time_ += cycles.as(); } -void GLU::get_samples(std::size_t number_of_samples, std::int16_t *target) { +template +void GLU::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { // Update remote state, generating audio. - generate_audio(number_of_samples, target); + generate_audio(number_of_samples, target); } +template void GLU::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void GLU::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void GLU::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); -void GLU::skip_samples(const std::size_t number_of_samples) { - // Update remote state, without generating audio. - skip_audio(remote_, number_of_samples); - // Apply any pending stores. - std::atomic_thread_fence(std::memory_order::memory_order_acquire); - const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples); - while(true) { - auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire); - if(!next_store.enabled) break; - if(next_store.time >= final_time) break; - remote_.ram_[next_store.address] = next_store.value; - next_store.enabled = false; - pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed); - - pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1); - } -} +//void GLU::skip_samples(const std::size_t number_of_samples) { +// // Update remote state, without generating audio. +// skip_audio(remote_, number_of_samples); +// +// // Apply any pending stores. +// std::atomic_thread_fence(std::memory_order::memory_order_acquire); +// const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples); +// while(true) { +// auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire); +// if(!next_store.enabled) break; +// if(next_store.time >= final_time) break; +// remote_.ram_[next_store.address] = next_store.value; +// next_store.enabled = false; +// pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed); +// +// pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1); +// } +//} void GLU::set_sample_volume_range(std::int16_t range) { output_range_ = range; @@ -256,7 +261,8 @@ void GLU::skip_audio(EnsoniqState &state, size_t number_of_samples) { } } -void GLU::generate_audio(size_t number_of_samples, std::int16_t *target) { +template +void GLU::generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target) { auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire); uint8_t next_amplitude = 255; for(size_t sample = 0; sample < number_of_samples; sample++) { @@ -325,7 +331,12 @@ void GLU::generate_audio(size_t number_of_samples, std::int16_t *target) { // Maximum total output was 32 channels times a 16-bit range. Map that down. // TODO: dynamic total volume? - target[sample] = (output * output_range_) >> 20; + Outputs::Speaker::apply( + target[sample], + Outputs::Speaker::MonoSample( + (output * output_range_) >> 20 + ) + ); // Apply any RAM writes that interleave here. ++pending_store_read_time_; diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index c65e9ceb9..ea25afba7 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -34,9 +34,9 @@ class GLU: public Outputs::Speaker::BufferSource { // TODO: isn't th bool get_interrupt_line(); // SampleSource. - void get_samples(std::size_t number_of_samples, std::int16_t *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); void set_sample_volume_range(std::int16_t range); - void skip_samples(const std::size_t number_of_samples); private: Concurrency::AsyncTaskQueue &audio_queue_; @@ -94,7 +94,8 @@ class GLU: public Outputs::Speaker::BufferSource { // TODO: isn't th // Functions to update an EnsoniqState; these don't belong to the state itself // because they also access the pending stores (inter alia). - void generate_audio(size_t number_of_samples, std::int16_t *target); + template + void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target); void skip_audio(EnsoniqState &state, size_t number_of_samples); // Audio-thread state. diff --git a/Machines/Apple/Macintosh/Audio.cpp b/Machines/Apple/Macintosh/Audio.cpp index caa312248..2eab2a710 100644 --- a/Machines/Apple/Macintosh/Audio.cpp +++ b/Machines/Apple/Macintosh/Audio.cpp @@ -71,7 +71,8 @@ void Audio::set_volume_multiplier() { volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_); } -void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { +template +void Audio::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { // TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation; // in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so // that's something to return to. @@ -82,7 +83,7 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { // Determine the output level, and output that many samples. const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer].load(std::memory_order::memory_order_relaxed)) - 128); - std::fill(target, target + cycles_left_in_sample, output_level); + Outputs::Speaker::fill(target, target + cycles_left_in_sample, output_level); target += cycles_left_in_sample; // Advance the sample pointer. @@ -94,3 +95,6 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { number_of_samples -= cycles_left_in_sample; } } +template void Audio::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void Audio::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void Audio::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index 09781c5fa..1adb4b06a 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -50,7 +50,8 @@ class Audio: public ::Outputs::Speaker::BufferSource { void set_enabled(bool on); // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. - void get_samples(std::size_t number_of_samples, int16_t *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); diff --git a/Machines/Atari/2600/TIASound.cpp b/Machines/Atari/2600/TIASound.cpp index d109c3187..22ab1aec7 100644 --- a/Machines/Atari/2600/TIASound.cpp +++ b/Machines/Atari/2600/TIASound.cpp @@ -40,9 +40,10 @@ void Atari2600::TIASound::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::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) { +template +void Atari2600::TIASound::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { for(unsigned int c = 0; c < number_of_samples; c++) { - target[c] = 0; + Outputs::Speaker::MonoSample output = 0; for(int channel = 0; channel < 2; channel++) { divider_counter_[channel] ++; int divider_value = divider_counter_[channel] / (38 / CPUTicksPerAudioTick); @@ -119,10 +120,14 @@ void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *ta break; } - target[c] += (volume_[channel] * per_channel_volume_ * level) >> 4; + output += (volume_[channel] * per_channel_volume_ * level) >> 4; } + Outputs::Speaker::apply(target[c], output); } } +template void Atari2600::TIASound::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void Atari2600::TIASound::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void Atari2600::TIASound::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); void Atari2600::TIASound::set_sample_volume_range(std::int16_t range) { per_channel_volume_ = range / 2; diff --git a/Machines/Atari/2600/TIASound.hpp b/Machines/Atari/2600/TIASound.hpp index c1cace014..a71a16e9e 100644 --- a/Machines/Atari/2600/TIASound.hpp +++ b/Machines/Atari/2600/TIASound.hpp @@ -26,7 +26,8 @@ class TIASound: public Outputs::Speaker::BufferSource { void set_control(int channel, uint8_t control); // To satisfy ::SampleSource. - void get_samples(std::size_t number_of_samples, int16_t *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); void set_sample_volume_range(std::int16_t range); private: diff --git a/Machines/Electron/SoundGenerator.cpp b/Machines/Electron/SoundGenerator.cpp index f0a58f5c5..14aa4fa22 100644 --- a/Machines/Electron/SoundGenerator.cpp +++ b/Machines/Electron/SoundGenerator.cpp @@ -19,21 +19,26 @@ void SoundGenerator::set_sample_volume_range(std::int16_t range) { volume_ = unsigned(range / 2); } -void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) { +template +void SoundGenerator::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) { + if constexpr (action == Outputs::Speaker::Action::Ignore) { + counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2); + return; + } + if(is_enabled_) { while(number_of_samples--) { - *target = int16_t((counter_ / (divider_+1)) * volume_); + Outputs::Speaker::apply(*target, Outputs::Speaker::MonoSample((counter_ / (divider_+1)) * volume_)); target++; counter_ = (counter_ + 1) % ((divider_+1) * 2); } } else { - std::memset(target, 0, sizeof(int16_t) * number_of_samples); + Outputs::Speaker::fill(target, target + number_of_samples, Outputs::Speaker::MonoSample(0)); } } - -void SoundGenerator::skip_samples(std::size_t number_of_samples) { - counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2); -} +template void SoundGenerator::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void SoundGenerator::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); +template void SoundGenerator::apply_samples(std::size_t, Outputs::Speaker::MonoSample *); void SoundGenerator::set_divider(uint8_t divider) { audio_queue_.enqueue([this, divider]() { diff --git a/Machines/Electron/SoundGenerator.hpp b/Machines/Electron/SoundGenerator.hpp index 3222c68da..e787695ab 100644 --- a/Machines/Electron/SoundGenerator.hpp +++ b/Machines/Electron/SoundGenerator.hpp @@ -24,8 +24,8 @@ class SoundGenerator: public ::Outputs::Speaker::BufferSource + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); void set_sample_volume_range(std::int16_t range); private: diff --git a/Machines/Enterprise/Dave.cpp b/Machines/Enterprise/Dave.cpp index ba079087b..5da74b673 100644 --- a/Machines/Enterprise/Dave.cpp +++ b/Machines/Enterprise/Dave.cpp @@ -99,7 +99,8 @@ void Audio::update_channel(int c) { channels_[c].output |= output; } -void Audio::get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target) { +template +void Audio::apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target) { Outputs::Speaker::StereoSample output_level; size_t c = 0; @@ -130,7 +131,7 @@ void Audio::get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoS while(global_divider_ && c < number_of_samples) { --global_divider_; - target[c] = output_level; + Outputs::Speaker::apply(target[c], output_level); ++c; } global_divider_ = global_divider_reload_; @@ -203,6 +204,9 @@ void Audio::get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoS } } } +template void Audio::apply_samples(std::size_t, Outputs::Speaker::StereoSample *); +template void Audio::apply_samples(std::size_t, Outputs::Speaker::StereoSample *); +template void Audio::apply_samples(std::size_t, Outputs::Speaker::StereoSample *); // MARK: - Interrupt source diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index 8e5b70622..7951ae1a6 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -37,7 +37,8 @@ class Audio: public Outputs::Speaker::BufferSource { // MARK: - SampleSource. void set_sample_volume_range(int16_t range); - void get_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target); + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target); private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 19b54b90e..474fa352e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -62,7 +62,7 @@ #include #include #include @@ -16,6 +17,38 @@ namespace Outputs::Speaker { +enum class Action { + /// New values should be _stored_ to the sample buffer. + Store, + /// New values should be _added_ to the sample buffer. + Mix, + /// New values shouldn't be stored; the source can skip generation of them if desired. + Ignore, +}; + +template void apply(SampleT &lhs, SampleT rhs) { + switch(action) { + case Action::Mix: lhs += rhs; break; + case Action::Store: lhs = rhs; break; + case Action::Ignore: break; + } +} + +template void fill(IteratorT begin, IteratorT end, SampleT value) { + switch(action) { + case Action::Mix: + while(begin != end) { + apply(*begin, value); + ++begin; + } + break; + case Action::Store: + std::fill(begin, end, value); + break; + case Action::Ignore: break; + } +} + /*! A sample source is something that can provide a stream of audio. This optional base class provides the interface expected to be exposed @@ -30,20 +63,11 @@ class BufferSource { static constexpr bool is_stereo = stereo; /*! - Should write the next @c number_of_samples to @c target. + 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) */ - void get_samples([[maybe_unused]] std::size_t number_of_samples, [[maybe_unused]] typename SampleT::type *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(std::size_t number_of_samples) { - typename SampleT::type scratch_pad[number_of_samples]; - get_samples(number_of_samples, scratch_pad); - } + template + void apply_samples([[maybe_unused]] std::size_t number_of_samples, [[maybe_unused]] typename SampleT::type *target) {} /*! @returns @c true if it is trivially true that a call to get_samples would just @@ -75,13 +99,15 @@ class BufferSource { template struct SampleSource: public BufferSource { public: - void get_samples(std::size_t number_of_samples, typename SampleT::type *target) { + template + void apply_samples(std::size_t number_of_samples, typename SampleT::type *target) { const auto &source = *static_cast(this); if constexpr (divider == 1) { while(number_of_samples--) { - *target = source.level(); + apply(*target, source.level()); ++target; + source.advance(); } } else { std::size_t c = 0; @@ -89,7 +115,7 @@ struct SampleSource: public BufferSource { // Fill in the tail of any partially-captured level. auto level = source.level(); while(c < number_of_samples && master_divider_ != divider) { - target[c] = level; + apply(target[c], level); ++c; ++master_divider_; } @@ -98,7 +124,7 @@ struct SampleSource: public BufferSource { // Provide all full levels. int whole_steps = (number_of_samples - c) / divider; while(whole_steps--) { - std::fill(&target[c], &target[c + divider], source.level()); + fill(&target[c], &target[c + divider], source.level()); c += divider; source.advance(); } @@ -106,31 +132,7 @@ struct SampleSource: public BufferSource { // Provide the head of a further partial capture. level = source.level(); master_divider_ = number_of_samples - c; - std::fill(&target[c], &target[number_of_samples], source.level()); - } - } - - void skip_samples(std::size_t number_of_samples) { - const auto &source = *static_cast(this); - - if constexpr (&SourceT::advance == &SampleSource::advance) { - return; - } - - if constexpr (divider == 1) { - while(--number_of_samples) { - source.advance(); - } - } else { - if(number_of_samples >= divider - master_divider_) { - source.advance(); - number_of_samples -= (divider - master_divider_); - } - while(number_of_samples > divider) { - advance(); - number_of_samples -= divider; - } - master_divider_ = number_of_samples; + fill(&target[c], &target[number_of_samples], source.level()); } } diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index cfdf3d962..f3fdff68c 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -49,14 +49,13 @@ template class CompoundSource: private: template class CompoundSourceHolder { public: - template - void get_samples(std::size_t number_of_samples, typename SampleT::type *target) { + template + void apply_samples(std::size_t number_of_samples, typename SampleT::type *target) { // Default-construct all samples, to fill with silence. - std::fill(target, target + number_of_samples, typename SampleT::type()); + Outputs::Speaker::fill(target, target + number_of_samples, typename SampleT::type()); } void set_scaled_volume_range(int16_t, double *, double) {} - void skip_samples(const std::size_t) {} static constexpr std::size_t size() { return 0; @@ -75,8 +74,8 @@ template class CompoundSource: static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder::is_stereo; - template - void get_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT::type *target) { + template + void apply_samples(std::size_t number_of_samples, typename ::Outputs::Speaker::SampleT::type *target) { // If this is the step at which a mono-to-stereo adaptation happens, apply it. if constexpr (output_stereo && !S::is_stereo) { @@ -87,40 +86,26 @@ template class CompoundSource: } // Populate the conversion buffer with this source and all below. - get_samples(number_of_samples, conversion_source_.data()); + apply_samples(number_of_samples, conversion_source_.data()); // Map up and return. for(std::size_t c = 0; c < number_of_samples; c++) { - target[c].left = target[c].right = conversion_source_[c]; + Outputs::Speaker::apply(target[c].left, StereoSample(conversion_source_[c])); } return; } // Get the rest of the output. - next_source_.template get_samples(number_of_samples, target); + next_source_.template apply_samples(number_of_samples, target); if(source_.is_zero_level()) { // This component is currently outputting silence; therefore don't add anything to the output // audio — just pass the call onward. - source_.skip_samples(number_of_samples); return; } - // Get this component's output. - typename SampleT::type local_samples[number_of_samples]; - source_.get_samples(number_of_samples, local_samples); - - // Merge it in. - while(number_of_samples--) { - target[number_of_samples] += local_samples[number_of_samples]; - } - - // TODO: accelerate above? - } - - void skip_samples(const std::size_t number_of_samples) { - source_.skip_samples(number_of_samples); - next_source_.skip_samples(number_of_samples); + // Add in this component's output. + source_.template apply_samples(number_of_samples, target); } void set_scaled_volume_range(int16_t range, double *volumes, double scale) { @@ -157,12 +142,9 @@ template class CompoundSource: } } - void get_samples(std::size_t number_of_samples, Sample *target) { - source_holder_.template get_samples<::Outputs::Speaker::is_stereo()>(number_of_samples, target); - } - - void skip_samples(const std::size_t number_of_samples) { - source_holder_.skip_samples(number_of_samples); + template + void apply_samples(std::size_t number_of_samples, Sample *target) { + source_holder_.template apply_samples()>(number_of_samples, target); } /*! diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index 5634de15d..4b3d5d16e 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -8,6 +8,7 @@ #pragma once +#include "BufferSource.hpp" #include "../Speaker.hpp" #include "../../../SignalProcessing/FIRFilter.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" @@ -396,7 +397,7 @@ template class PullLowpass: public LowpassBase(count, nullptr); } int get_scale() { @@ -406,9 +407,9 @@ template class PullLowpass: public LowpassBase(target); - sample_source_.get_samples(length, stereo_target); + sample_source_.template apply_samples(length, stereo_target); } else { - sample_source_.get_samples(length, target); + sample_source_.template apply_samples(length, target); } } }; diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp index 860395536..edeee7811 100644 --- a/Outputs/Speaker/Speaker.hpp +++ b/Outputs/Speaker/Speaker.hpp @@ -27,6 +27,9 @@ struct StereoSample { left = rhs.left; right = rhs.right; } + StereoSample(MonoSample value) { + left = right = value; + } StereoSample &operator +=(const StereoSample &rhs) { left += rhs.left;