From 609d81d75d4d9bb386e3cabe016ef43cc2b4cdc7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 9 Feb 2024 14:25:40 -0500 Subject: [PATCH] Distinguish sources of samples and of whole buffers. --- Components/6560/6560.hpp | 4 +- Components/AY38910/AY38910.hpp | 4 +- Components/AudioToggle/AudioToggle.hpp | 4 +- Components/KonamiSCC/KonamiSCC.hpp | 4 +- Components/OPx/Implementation/OPLBase.hpp | 4 +- Components/SN76489/SN76489.hpp | 4 +- Machines/Apple/AppleIIgs/Sound.hpp | 4 +- Machines/Apple/Macintosh/Audio.hpp | 4 +- Machines/Atari/2600/TIASound.hpp | 4 +- Machines/Electron/SoundGenerator.hpp | 4 +- Machines/Enterprise/Dave.hpp | 4 +- Machines/MSX/MSX.cpp | 2 +- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 2 +- .../Clock Signal.xcodeproj/project.pbxproj | 4 +- .../{SampleSource.hpp => BufferSource.hpp} | 94 ++++++++++++++++--- .../Speaker/Implementation/CompoundSource.hpp | 4 +- Outputs/Speaker/Speaker.hpp | 1 - 17 files changed, 107 insertions(+), 44 deletions(-) rename Outputs/Speaker/Implementation/{SampleSource.hpp => BufferSource.hpp} (52%) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 24d8e5479..a2535674e 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -12,12 +12,12 @@ #include "../../Concurrency/AsyncTaskQueue.hpp" #include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" namespace MOS::MOS6560 { // audio state -class AudioGenerator: public Outputs::Speaker::SampleSource { +class AudioGenerator: public Outputs::Speaker::BufferSource { public: AudioGenerator(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 58eb4ec80..2f30f61d9 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -8,7 +8,7 @@ #pragma once -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" #include "../../Reflection/Struct.hpp" @@ -66,7 +66,7 @@ enum class Personality { This AY has an attached mono or stereo mixer. */ -template class AY38910: public ::Outputs::Speaker::SampleSource, stereo> { +template class AY38910: public ::Outputs::Speaker::BufferSource, stereo> { public: /// Creates a new AY38910. AY38910(Personality, Concurrency::AsyncTaskQueue &); diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp index b93ea4cae..c8e962742 100644 --- a/Components/AudioToggle/AudioToggle.hpp +++ b/Components/AudioToggle/AudioToggle.hpp @@ -8,7 +8,7 @@ #pragma once -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" namespace Audio { @@ -16,7 +16,7 @@ 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::BufferSource { public: Toggle(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index 8d2986e85..7da684a73 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -8,7 +8,7 @@ #pragma once -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" namespace Konami { @@ -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::BufferSource { 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 569e970a2..1890646a9 100644 --- a/Components/OPx/Implementation/OPLBase.hpp +++ b/Components/OPx/Implementation/OPLBase.hpp @@ -8,12 +8,12 @@ #pragma once -#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../../Concurrency/AsyncTaskQueue.hpp" namespace Yamaha::OPL { -template class OPLBase: public ::Outputs::Speaker::SampleSource { +template class OPLBase: public ::Outputs::Speaker::BufferSource { public: void write(uint16_t address, uint8_t value) { if(address & 1) { diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index cab4469e1..ec35fb06b 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -8,12 +8,12 @@ #pragma once -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" namespace TI { -class SN76489: public Outputs::Speaker::SampleSource { +class SN76489: public Outputs::Speaker::BufferSource { public: enum class Personality { SN76489, diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index dece2832d..c65e9ceb9 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -12,11 +12,11 @@ #include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../Concurrency/AsyncTaskQueue.hpp" -#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" namespace Apple::IIgs::Sound { -class GLU: public Outputs::Speaker::SampleSource { // TODO: isn't this stereo? +class GLU: public Outputs::Speaker::BufferSource { // TODO: isn't this stereo? public: GLU(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index d7cbcaae0..09781c5fa 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -10,7 +10,7 @@ #include "../../../Concurrency/AsyncTaskQueue.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" -#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" #include #include @@ -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::BufferSource { public: Audio(Concurrency::AsyncTaskQueue &task_queue); diff --git a/Machines/Atari/2600/TIASound.hpp b/Machines/Atari/2600/TIASound.hpp index fdbbc3bfd..c1cace014 100644 --- a/Machines/Atari/2600/TIASound.hpp +++ b/Machines/Atari/2600/TIASound.hpp @@ -8,7 +8,7 @@ #pragma once -#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../../Concurrency/AsyncTaskQueue.hpp" namespace Atari2600 { @@ -17,7 +17,7 @@ namespace Atari2600 { // will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38. constexpr int CPUTicksPerAudioTick = 2; -class TIASound: public Outputs::Speaker::SampleSource { +class TIASound: public Outputs::Speaker::BufferSource { public: TIASound(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Machines/Electron/SoundGenerator.hpp b/Machines/Electron/SoundGenerator.hpp index 666c4c0c8..3222c68da 100644 --- a/Machines/Electron/SoundGenerator.hpp +++ b/Machines/Electron/SoundGenerator.hpp @@ -8,12 +8,12 @@ #pragma once -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" namespace Electron { -class SoundGenerator: public ::Outputs::Speaker::SampleSource { +class SoundGenerator: public ::Outputs::Speaker::BufferSource { public: SoundGenerator(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index a3e369251..8e5b70622 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -13,7 +13,7 @@ #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" #include "../../Numeric/LFSR.hpp" -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" namespace Enterprise::Dave { @@ -26,7 +26,7 @@ enum class Interrupt: uint8_t { /*! Models the audio-production subset of Dave's behaviour. */ -class Audio: public Outputs::Speaker::SampleSource { +class Audio: public Outputs::Speaker::BufferSource { public: Audio(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 8d3ea168d..f4e02b8f2 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -41,7 +41,7 @@ #include "../../Outputs/Log.hpp" #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" -#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../Configurable/StandardOptions.hpp" #include "../../ClockReceiver/ForceInline.hpp" diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 62406a629..c20e43fff 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -28,7 +28,7 @@ #include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" -#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp" #include "../../../Storage/Tape/Tape.hpp" #include "../../../Storage/Tape/Parsers/Spectrum.hpp" diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 09dd756a7..3babbde3e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1533,7 +1533,7 @@ 4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = ""; }; 4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = ""; }; 4B683B002727BE6F0043E541 /* Amiga Blitter Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Amiga Blitter Tests"; sourceTree = ""; }; - 4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = ""; }; + 4B698D1A1FE768A100696C91 /* BufferSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BufferSource.hpp; sourceTree = ""; }; 4B69DEB52AB79E4F0055B217 /* Instruction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Instruction.cpp; sourceTree = ""; }; 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = ""; }; 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = ""; }; @@ -4004,7 +4004,7 @@ isa = PBXGroup; children = ( 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */, - 4B698D1A1FE768A100696C91 /* SampleSource.hpp */, + 4B698D1A1FE768A100696C91 /* BufferSource.hpp */, 4B770A961FE9EE770026DC70 /* CompoundSource.hpp */, ); path = Implementation; diff --git a/Outputs/Speaker/Implementation/SampleSource.hpp b/Outputs/Speaker/Implementation/BufferSource.hpp similarity index 52% rename from Outputs/Speaker/Implementation/SampleSource.hpp rename to Outputs/Speaker/Implementation/BufferSource.hpp index 36afe40f7..e212e2ef8 100644 --- a/Outputs/Speaker/Implementation/SampleSource.hpp +++ b/Outputs/Speaker/Implementation/BufferSource.hpp @@ -22,7 +22,7 @@ namespace Outputs::Speaker { by the template parameter to LowpassSpeaker. */ template -class SampleSource { +class BufferSource { public: /*! Indicates whether this component will write stereo samples. @@ -32,13 +32,7 @@ class SampleSource { /*! Should write the next @c number_of_samples to @c target. */ - void get_samples(std::size_t number_of_samples, typename SampleT::type *target) { - const auto &source = *static_cast(this); - while(number_of_samples--) { - *target = source.level(); - ++target; - } - } + 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 @@ -46,10 +40,7 @@ class SampleSource { 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) { - if constexpr (&SourceT::advance == &SampleSource::advance) { - return; - } + void skip_samples(std::size_t number_of_samples) { typename SampleT::type scratch_pad[number_of_samples]; get_samples(number_of_samples, scratch_pad); } @@ -59,9 +50,7 @@ class SampleSource { fill the target with zeroes; @c false if a call might return all zeroes or might not. */ - bool is_zero_level() const { return false; } - auto level() const { return typename SampleT::type(); } - void advance() {} + bool is_zero_level() const { return false; } /*! Sets the proper output range for this sample source; it should write values @@ -82,4 +71,79 @@ class SampleSource { double average_output_peak() const { return 1.0; } }; +/// +template +struct SampleSource: public BufferSource { + public: + void get_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(); + ++target; + } + } else { + std::size_t c = 0; + + // Fill in the tail of any pa3rtially-captured level. + auto level = source.level(); + while(c < number_of_samples && master_divider_ != divider) { + target[c] = level; + ++c; + ++master_divider_; + } + source.advance(); + + // Provide all full levels. + int whole_steps = (number_of_samples - c) / divider; + while(whole_steps--) { + std::fill(&target[c], &target[c + divider], source.level()); + c += divider; + source.advance(); + } + + // 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; + } + } + + // TODO: use a concept here, when C++20 filters through. + // + // Until then: sample sources should implement this. + auto level() const { + return typename SampleT::type(); + } + void advance() {} + + private: + int master_divider_{}; +}; + } diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index 16d33657f..6bc77220a 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -8,7 +8,7 @@ #pragma once -#include "SampleSource.hpp" +#include "BufferSource.hpp" #include #include @@ -31,7 +31,7 @@ template constexpr bool is_stereo() { An owner may optionally assign relative volumes. */ template class CompoundSource: - public Outputs::Speaker::SampleSource, ::Outputs::Speaker::is_stereo()> { + public Outputs::Speaker::BufferSource, ::Outputs::Speaker::is_stereo()> { private: template class CompoundSourceHolder { public: diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp index 5195b4f77..9bbb91821 100644 --- a/Outputs/Speaker/Speaker.hpp +++ b/Outputs/Speaker/Speaker.hpp @@ -14,7 +14,6 @@ namespace Outputs::Speaker { -// Shorthands. using MonoSample = int16_t; struct StereoSample { #if TARGET_RT_BIG_ENDIAN