From ce0d53b277d99957de718c16fb923ad36516e251 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Feb 2024 21:29:00 -0500 Subject: [PATCH 01/20] Clean up SampleSource's getters. --- Components/6560/6560.hpp | 2 +- Components/AY38910/AY38910.hpp | 4 +- Components/KonamiSCC/KonamiSCC.hpp | 2 +- Components/OPx/OPLL.hpp | 2 +- Components/SN76489/SN76489.hpp | 2 +- Machines/Apple/Macintosh/Audio.hpp | 2 +- Machines/Atari/2600/TIASound.hpp | 2 +- Machines/Electron/SoundGenerator.hpp | 2 +- Machines/Enterprise/Dave.hpp | 2 +- .../Speaker/Implementation/CompoundSource.hpp | 125 +++++++++--------- .../Speaker/Implementation/LowpassSpeaker.hpp | 8 +- .../Speaker/Implementation/SampleSource.hpp | 4 +- 12 files changed, 77 insertions(+), 80 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index f8c570924..5a55317eb 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -28,7 +28,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, int16_t *target); void skip_samples(std::size_t number_of_samples); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index eee96c093..90c5c9b1f 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -66,7 +66,7 @@ enum class Personality { This AY has an attached mono or stereo mixer. */ -template class AY38910: public ::Outputs::Speaker::SampleSource { +template class AY38910: public ::Outputs::Speaker::SampleSource { public: /// Creates a new AY38910. AY38910(Personality, Concurrency::AsyncTaskQueue &); @@ -109,7 +109,7 @@ template class AY38910: public ::Outputs::Speaker::SampleSource void get_samples(std::size_t number_of_samples, int16_t *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return is_stereo; } + static constexpr bool is_stereo = stereo; private: Concurrency::AsyncTaskQueue &task_queue_; diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index 9b16e8a1d..a0cb3ca30 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -31,7 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource { /// As per ::SampleSource; provides audio output. void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; /// Writes to the SCC. void write(uint16_t address, uint8_t value); diff --git a/Components/OPx/OPLL.hpp b/Components/OPx/OPLL.hpp index 36bb0b51d..284c9ade9 100644 --- a/Components/OPx/OPLL.hpp +++ b/Components/OPx/OPLL.hpp @@ -30,7 +30,7 @@ class OPLL: public OPLBase { // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in // rhythm mode, but it's correct for melodic output. - double get_average_output_peak() const { return 0.5; } + double average_output_peak() const { return 0.5; } /// Reads from the OPL. uint8_t read(uint16_t address); diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index d419692f8..d720ee256 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -31,7 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, std::int16_t *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: int master_divider_ = 0; diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index e132015e0..3eabbc832 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -53,7 +53,7 @@ class Audio: public ::Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, int16_t *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); - constexpr static bool get_is_stereo() { return false; } + constexpr static bool is_stereo = false; private: Concurrency::AsyncTaskQueue &task_queue_; diff --git a/Machines/Atari/2600/TIASound.hpp b/Machines/Atari/2600/TIASound.hpp index 67976be58..097b70f52 100644 --- a/Machines/Atari/2600/TIASound.hpp +++ b/Machines/Atari/2600/TIASound.hpp @@ -28,7 +28,7 @@ class TIASound: public Outputs::Speaker::SampleSource { // To satisfy ::SampleSource. void get_samples(std::size_t number_of_samples, int16_t *target); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Machines/Electron/SoundGenerator.hpp b/Machines/Electron/SoundGenerator.hpp index 32cd04074..2429bb37b 100644 --- a/Machines/Electron/SoundGenerator.hpp +++ b/Machines/Electron/SoundGenerator.hpp @@ -27,7 +27,7 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, int16_t *target); void skip_samples(std::size_t number_of_samples); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index 21c08cfe8..7b32f8db1 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -37,7 +37,7 @@ class Audio: public Outputs::Speaker::SampleSource { // MARK: - SampleSource. void set_sample_volume_range(int16_t range); - static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound. + static constexpr bool is_stereo = true; void get_samples(std::size_t number_of_samples, int16_t *target); private: diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index 75f798e55..52a0a3e71 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -22,62 +22,7 @@ namespace Outputs::Speaker { */ template class CompoundSource: public Outputs::Speaker::SampleSource { - public: - CompoundSource(T &... sources) : source_holder_(sources...) { - // Default: give all sources equal volume. - const auto volume = 1.0 / double(source_holder_.size()); - for(std::size_t c = 0; c < source_holder_.size(); ++c) { - volumes_.push_back(volume); - } - } - - void get_samples(std::size_t number_of_samples, std::int16_t *target) { - source_holder_.template get_samples(number_of_samples, target); - } - - void skip_samples(const std::size_t number_of_samples) { - source_holder_.skip_samples(number_of_samples); - } - - /*! - Sets the total output volume of this CompoundSource. - */ - void set_sample_volume_range(int16_t range) { - volume_range_ = range; - push_volumes(); - } - - /*! - Sets the relative volumes of the various sources underlying this - compound. The caller should ensure that the number of items supplied - matches the number of sources and that the values in it sum to 1.0. - */ - void set_relative_volumes(const std::vector &volumes) { - assert(volumes.size() == source_holder_.size()); - volumes_ = volumes; - push_volumes(); - average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data()); - } - - /*! - @returns true if any of the sources owned by this CompoundSource is stereo. - */ - static constexpr bool get_is_stereo() { return CompoundSourceHolder::get_is_stereo(); } - - /*! - @returns the average output peak given the sources owned by this CompoundSource and the - current relative volumes. - */ - double get_average_output_peak() const { - return average_output_peak_; - } - private: - void push_volumes() { - const double scale = source_holder_.total_scale(volumes_.data()); - source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale); - } - template class CompoundSourceHolder: public Outputs::Speaker::SampleSource { public: template void get_samples(std::size_t number_of_samples, std::int16_t *target) { @@ -90,9 +35,7 @@ template class CompoundSource: return 0; } - static constexpr bool get_is_stereo() { - return false; - } + static constexpr bool is_stereo = false; double total_scale(double *) const { return 0.0; @@ -121,7 +64,7 @@ template class CompoundSource: // Merge it in; furthermore if total output is stereo but this source isn't, // map it to stereo. - if constexpr (output_stereo == S::get_is_stereo()) { + if constexpr (output_stereo == S::is_stereo) { while(buffer_size--) { target[buffer_size] += local_samples[buffer_size]; } @@ -144,7 +87,7 @@ template class CompoundSource: } void set_scaled_volume_range(int16_t range, double *volumes, double scale) { - const auto scaled_range = volumes[0] / double(source_.get_average_output_peak()) * double(range) / scale; + const auto scaled_range = volumes[0] / double(source_.average_output_peak()) * double(range) / scale; source_.set_sample_volume_range(int16_t(scaled_range)); next_source_.set_scaled_volume_range(range, &volumes[1], scale); } @@ -153,12 +96,10 @@ template class CompoundSource: return 1 + CompoundSourceHolder::size(); } - static constexpr bool get_is_stereo() { - return S::get_is_stereo() || CompoundSourceHolder::get_is_stereo(); - } + static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder::is_stereo; double total_scale(double *volumes) const { - return (volumes[0] / source_.get_average_output_peak()) + next_source_.total_scale(&volumes[1]); + return (volumes[0] / source_.average_output_peak()) + next_source_.total_scale(&volumes[1]); } private: @@ -166,6 +107,62 @@ template class CompoundSource: CompoundSourceHolder next_source_; }; + public: + CompoundSource(T &... sources) : source_holder_(sources...) { + // Default: give all sources equal volume. + const auto volume = 1.0 / double(source_holder_.size()); + for(std::size_t c = 0; c < source_holder_.size(); ++c) { + volumes_.push_back(volume); + } + } + + void get_samples(std::size_t number_of_samples, std::int16_t *target) { + source_holder_.template get_samples(number_of_samples, target); + } + + void skip_samples(const std::size_t number_of_samples) { + source_holder_.skip_samples(number_of_samples); + } + + /*! + Sets the total output volume of this CompoundSource. + */ + void set_sample_volume_range(int16_t range) { + volume_range_ = range; + push_volumes(); + } + + /*! + Sets the relative volumes of the various sources underlying this + compound. The caller should ensure that the number of items supplied + matches the number of sources and that the values in it sum to 1.0. + */ + void set_relative_volumes(const std::vector &volumes) { + assert(volumes.size() == source_holder_.size()); + volumes_ = volumes; + push_volumes(); + average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data()); + } + + /*! + @c true if any of the sources owned by this CompoundSource is stereo. + */ + static constexpr bool is_stereo = CompoundSourceHolder::is_stereo; + + /*! + @returns the average output peak given the sources owned by this CompoundSource and the + current relative volumes. + */ + double average_output_peak() const { + return average_output_peak_; + } + + private: + void push_volumes() { + const double scale = source_holder_.total_scale(volumes_.data()); + source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale); + } + CompoundSourceHolder source_holder_; std::vector volumes_; int16_t volume_range_ = 0; diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index 294e19589..3b64dfcc6 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -348,7 +348,7 @@ template class PushLowpass: public LowpassBase class PullLowpass: public LowpassBase, SampleSource::get_is_stereo()> { +template class PullLowpass: public LowpassBase, SampleSource::is_stereo> { public: PullLowpass(SampleSource &sample_source) : sample_source_(sample_source) { // Propagate an initial volume level. @@ -362,7 +362,7 @@ template class PullLowpass: public LowpassBase class PullLowpass: public LowpassBase, SampleSource::get_is_stereo()>; + using BaseT = LowpassBase, SampleSource::is_stereo>; friend BaseT; using BaseT::process; @@ -400,7 +400,7 @@ template class PullLowpass: public LowpassBase Date: Thu, 1 Feb 2024 21:32:16 -0500 Subject: [PATCH 02/20] Use `std::fill`; update volume with slider. --- Components/AudioToggle/AudioToggle.cpp | 8 +++++--- Components/AudioToggle/AudioToggle.hpp | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Components/AudioToggle/AudioToggle.cpp b/Components/AudioToggle/AudioToggle.cpp index 7188badd9..ee8e2b417 100644 --- a/Components/AudioToggle/AudioToggle.cpp +++ b/Components/AudioToggle/AudioToggle.cpp @@ -8,19 +8,20 @@ #include "AudioToggle.hpp" +#include + 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) { - for(std::size_t sample = 0; sample < number_of_samples; ++sample) { - target[sample] = level_; - } + 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) {} @@ -29,6 +30,7 @@ void Toggle::set_output(bool enabled) { if(is_enabled_ == enabled) return; is_enabled_ = enabled; audio_queue_.enqueue([this, enabled] { + level_active_ = enabled; level_ = enabled ? volume_ : 0; }); } diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp index 8209b4653..defd7a976 100644 --- a/Components/AudioToggle/AudioToggle.hpp +++ b/Components/AudioToggle/AudioToggle.hpp @@ -34,6 +34,7 @@ class Toggle: public Outputs::Speaker::SampleSource { // Accessed on the audio thread. int16_t level_ = 0, volume_ = 0; + bool level_active_ = false; }; } From c6c9be0b08be981f4be7fdc1d31f95c8f223d1b9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Feb 2024 21:47:44 -0500 Subject: [PATCH 03/20] Adopt CRTP for SampleSource. --- Components/6560/6560.hpp | 2 +- Components/AY38910/AY38910.hpp | 2 +- Components/AudioToggle/AudioToggle.hpp | 3 ++- Components/KonamiSCC/KonamiSCC.hpp | 2 +- Components/OPx/Implementation/OPLBase.hpp | 2 +- Components/OPx/OPLL.hpp | 1 + Components/SN76489/SN76489.hpp | 2 +- Machines/Apple/AppleIIgs/Sound.hpp | 3 ++- Machines/Apple/Macintosh/Audio.hpp | 2 +- Machines/Atari/2600/TIASound.hpp | 2 +- Machines/Electron/SoundGenerator.hpp | 2 +- Machines/Enterprise/Dave.hpp | 2 +- Outputs/Speaker/Implementation/CompoundSource.hpp | 4 ++-- Outputs/Speaker/Implementation/SampleSource.hpp | 8 +++++++- 14 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 5a55317eb..5bafeba65 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -17,7 +17,7 @@ namespace MOS::MOS6560 { // audio state -class AudioGenerator: public ::Outputs::Speaker::SampleSource { +class AudioGenerator: public ::Outputs::Speaker::SampleSource { public: AudioGenerator(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 90c5c9b1f..48dcf6277 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -66,7 +66,7 @@ enum class Personality { This AY has an attached mono or stereo mixer. */ -template class AY38910: public ::Outputs::Speaker::SampleSource { +template class AY38910: public ::Outputs::Speaker::SampleSource> { public: /// Creates a new AY38910. AY38910(Personality, Concurrency::AsyncTaskQueue &); diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp index defd7a976..84cebb762 100644 --- a/Components/AudioToggle/AudioToggle.hpp +++ b/Components/AudioToggle/AudioToggle.hpp @@ -16,13 +16,14 @@ namespace Audio { /*! Provides a sample source that can programmatically be set to one of two values. */ -class Toggle: public Outputs::Speaker::SampleSource { +class Toggle: public Outputs::Speaker::SampleSource { public: Toggle(Concurrency::AsyncTaskQueue &audio_queue); void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); void skip_samples(const std::size_t number_of_samples); + static constexpr bool is_stereo = false; void set_output(bool enabled); bool get_output() const; diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index a0cb3ca30..a8c767f79 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -20,7 +20,7 @@ namespace Konami { and five channels of output. The original SCC uses the same wave for channels four and five, the SCC+ supports different waves for the two channels. */ -class SCC: public ::Outputs::Speaker::SampleSource { +class SCC: public ::Outputs::Speaker::SampleSource { public: /// Creates a new SCC. SCC(Concurrency::AsyncTaskQueue &task_queue); diff --git a/Components/OPx/Implementation/OPLBase.hpp b/Components/OPx/Implementation/OPLBase.hpp index a3067395c..98bee264f 100644 --- a/Components/OPx/Implementation/OPLBase.hpp +++ b/Components/OPx/Implementation/OPLBase.hpp @@ -13,7 +13,7 @@ namespace Yamaha::OPL { -template class OPLBase: public ::Outputs::Speaker::SampleSource { +template class OPLBase: public ::Outputs::Speaker::SampleSource { public: void write(uint16_t address, uint8_t value) { if(address & 1) { diff --git a/Components/OPx/OPLL.hpp b/Components/OPx/OPLL.hpp index 284c9ade9..5323fbeaf 100644 --- a/Components/OPx/OPLL.hpp +++ b/Components/OPx/OPLL.hpp @@ -27,6 +27,7 @@ class OPLL: public OPLBase { /// As per ::SampleSource; provides audio output. void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); + static constexpr bool is_stereo = false; // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in // rhythm mode, but it's correct for melodic output. diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index d720ee256..820170f3b 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -13,7 +13,7 @@ namespace TI { -class SN76489: public Outputs::Speaker::SampleSource { +class SN76489: public Outputs::Speaker::SampleSource { public: enum class Personality { SN76489, diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index 55c4f4c20..2eed23a8f 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -16,7 +16,7 @@ namespace Apple::IIgs::Sound { -class GLU: public Outputs::Speaker::SampleSource { +class GLU: public Outputs::Speaker::SampleSource { public: GLU(Concurrency::AsyncTaskQueue &audio_queue); @@ -37,6 +37,7 @@ class GLU: public Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); void skip_samples(const std::size_t number_of_samples); + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index 3eabbc832..e23bcb16b 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -23,7 +23,7 @@ namespace Apple::Macintosh { Designed to be clocked at half the rate of the real hardware — i.e. a shade less than 4Mhz. */ -class Audio: public ::Outputs::Speaker::SampleSource { +class Audio: public ::Outputs::Speaker::SampleSource