diff --git a/.github/workflows/build-teensy.yml b/.github/workflows/build-teensy.yml index 121bbc37f..1c30af49c 100644 --- a/.github/workflows/build-teensy.yml +++ b/.github/workflows/build-teensy.yml @@ -55,8 +55,8 @@ jobs: - name: Build run: | cc=~/.platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-g++ - $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -c modules/common/chowdsp_core/chowdsp_core.cpp - $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -c modules/dsp/chowdsp_buffers/chowdsp_buffers.cpp - $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -c modules/dsp/chowdsp_dsp_data_structures/chowdsp_dsp_data_structures.cpp - $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -c modules/dsp/chowdsp_dsp_utils/chowdsp_dsp_utils.cpp - $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -c modules/dsp/chowdsp_math/chowdsp_math.cpp + $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -DJUCE_MODULE_AVAILABLE_chowdsp_data_structures=1 -c modules/common/chowdsp_core/chowdsp_core.cpp + $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -DJUCE_MODULE_AVAILABLE_chowdsp_data_structures=1 -c modules/dsp/chowdsp_buffers/chowdsp_buffers.cpp + $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -DJUCE_MODULE_AVAILABLE_chowdsp_data_structures=1 -c modules/dsp/chowdsp_dsp_data_structures/chowdsp_dsp_data_structures.cpp + $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -DJUCE_MODULE_AVAILABLE_chowdsp_data_structures=1 -c modules/dsp/chowdsp_dsp_utils/chowdsp_dsp_utils.cpp + $cc -DCORE_TEENSY=1 --std=c++17 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 --specs=nosys.specs -Imodules/common -Imodules/dsp -DJUCE_MODULE_AVAILABLE_chowdsp_buffers=1 -DJUCE_MODULE_AVAILABLE_chowdsp_data_structures=1 -c modules/dsp/chowdsp_math/chowdsp_math.cpp diff --git a/examples/SimpleEQ/CMakeLists.txt b/examples/SimpleEQ/CMakeLists.txt index 8a3c441ee..85f507c7e 100644 --- a/examples/SimpleEQ/CMakeLists.txt +++ b/examples/SimpleEQ/CMakeLists.txt @@ -15,3 +15,7 @@ target_sources(SimpleEQ PluginEditor.cpp FilterPlots.cpp ) +target_compile_definitions(SimpleEQ + PRIVATE + CHOWDSP_BUFFER_MAX_NUM_CHANNELS=2 +) diff --git a/examples/SimpleEQ/SimpleEQPlugin.cpp b/examples/SimpleEQ/SimpleEQPlugin.cpp index a68aa56a9..3da96bcdb 100644 --- a/examples/SimpleEQ/SimpleEQPlugin.cpp +++ b/examples/SimpleEQ/SimpleEQPlugin.cpp @@ -63,7 +63,7 @@ void SimpleEQPlugin::processAudioBlock (juce::AudioBuffer& buffer) // any thread, but we'll do it on the audio thread here. setEQParams(); - if (state.params.linPhaseMode.get()) + if (state.params.linPhaseMode->get()) { // Linear phase mode is on: processing the linear phase EQ here! linPhaseEQ.processBlock (buffer); diff --git a/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferView.h b/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferView.h index 68210eeda..fbe1afa38 100644 --- a/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferView.h +++ b/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferView.h @@ -1,5 +1,9 @@ #pragma once +#if JUCE_MODULE_AVAILABLE_chowdsp_data_structures +#include +#endif + namespace chowdsp { /** @@ -372,4 +376,18 @@ template >> || (std::is_const_v && detail::is_static_buffer_v, xsimd::batch>)>> BufferView (BufferType&, Ts...) -> BufferView>; #endif // ! CHOWDSP_NO_XSIMD + +#if JUCE_MODULE_AVAILABLE_chowdsp_data_structures +template +BufferView make_temp_buffer (ArenaAllocatorView arena, int num_channels, int num_samples) +{ + std::array channel_pointers {}; + for (size_t ch = 0; ch < static_cast (num_channels); ++ch) + { + channel_pointers[ch] = arena.allocate (num_samples, SIMDUtils::defaultSIMDAlignment); + jassert (channel_pointers[ch] != nullptr); + } + return { channel_pointers.data(), num_channels, num_samples }; +} +#endif } // namespace chowdsp diff --git a/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.cpp b/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.cpp index 6f262799b..b944c0322 100644 --- a/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.cpp +++ b/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.cpp @@ -99,6 +99,14 @@ void SmoothedBufferValue::process (int numSample } } +template +void SmoothedBufferValue::process (int numSamples, ArenaAllocatorView alloc) +{ + bufferData = alloc.allocate (numSamples, bufferAlignment); + jassert (bufferData != nullptr); // arena allocator is out of memory! + process (numSamples); +} + template void SmoothedBufferValue::process (FloatType value, int numSamples) { @@ -117,6 +125,14 @@ void SmoothedBufferValue::process (FloatType val bufferData[n] = smoother.getNextValue(); } +template +void SmoothedBufferValue::process (FloatType value, int numSamples, ArenaAllocatorView alloc) +{ + bufferData = alloc.allocate (numSamples, bufferAlignment); + jassert (bufferData != nullptr); // arena allocator is out of memory! + process (value, numSamples); +} + template class SmoothedBufferValue; template class SmoothedBufferValue; template class SmoothedBufferValue; diff --git a/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h b/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h index d82a8da6c..132e1009d 100644 --- a/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h +++ b/modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h @@ -79,12 +79,7 @@ class SmoothedBufferValue */ void process (int numSamples); - void process (int numSamples, ArenaAllocatorView alloc) - { - bufferData = alloc.allocate (numSamples, bufferAlignment); - jassert (bufferData != nullptr); // arena allocator is out of memory! - process (numSamples); - } + void process (int numSamples, ArenaAllocatorView alloc); /** * Process smoothing for the input value. @@ -92,12 +87,7 @@ class SmoothedBufferValue */ void process (FloatType value, int numSamples); - void process (FloatType value, int numSamples, ArenaAllocatorView alloc) - { - bufferData = alloc.allocate (numSamples, bufferAlignment); - jassert (bufferData != nullptr); // arena allocator is out of memory! - process (value, numSamples); - } + void process (FloatType value, int numSamples, ArenaAllocatorView alloc); /** Returns a pointer to the current smoothed buffer. */ [[nodiscard]] const FloatType* getSmoothedBuffer() const { return bufferData; } diff --git a/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.cpp b/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.cpp index ac6eeeccb..92d4ef955 100644 --- a/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.cpp +++ b/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.cpp @@ -1,14 +1,21 @@ namespace chowdsp { template -void BypassProcessor>>::prepare (const juce::dsp::ProcessSpec& spec, bool onOffParam) +void BypassProcessor>>::prepare (const juce::dsp::ProcessSpec& spec, bool onOffParam, bool useInternalBuffer) { prevOnOffParam = onOffParam; - fadeBuffer.setMaxSize ((int) spec.numChannels, (int) spec.maximumBlockSize); + if (useInternalBuffer) + fadeBuffer.setMaxSize ((int) spec.numChannels, (int) spec.maximumBlockSize); } template -bool BypassProcessor>>::processBlockIn (const BufferView& block, bool onOffParam) +bool BypassProcessor>>::processBlockIn (const BufferView& block, + bool onOffParam, + std::optional arena) { if (! onOffParam && ! prevOnOffParam) { @@ -17,15 +24,21 @@ bool BypassProcessor (*arena, block.getNumChannels(), block.getNumSamples()); + else + fadeBufferView = BufferView { fadeBuffer, 0, block.getNumSamples(), 0, block.getNumChannels() }; + + BufferMath::copyBufferData (block, fadeBufferView); } return true; } template -void BypassProcessor>>::processBlockOut (const BufferView& block, bool onOffParam) +void BypassProcessor>>::processBlockOut (const BufferView& block, bool onOffParam) { auto fadeOutputBuffer = [onOffParam] (auto* blockPtr, const auto* fadePtr, const int startSample, const int numSamples) { @@ -68,7 +81,7 @@ void BypassProcessor -void BypassProcessor>>::prepare (const juce::dsp::ProcessSpec& spec, bool onOffParam) +void BypassProcessor>>::prepare (const juce::dsp::ProcessSpec& spec, + bool onOffParam, + bool useInternalBuffer) { prevOnOffParam = onOffParam; - fadeBuffer.setMaxSize ((int) spec.numChannels, (int) spec.maximumBlockSize); + if (useInternalBuffer) + fadeBuffer.setMaxSize ((int) spec.numChannels, (int) spec.maximumBlockSize); compDelay.prepare (spec); // sample rate does not matter } template -void BypassProcessor>>::setLatencySamples (int delaySamples) +void BypassProcessor>>::setLatencySamples (int delaySamples) { setLatencySamplesInternal ((NumericType) delaySamples); } template -void BypassProcessor>>::setLatencySamples (NumericType delaySamples) +void BypassProcessor>>::setLatencySamples (NumericType delaySamples) { static_assert (! std::is_same_v, "Attempting to set non-integer latency value without using delay interpolation!"); setLatencySamplesInternal (delaySamples); } template -void BypassProcessor>>::setLatencySamplesInternal (NumericType delaySamples) +void BypassProcessor>>::setLatencySamplesInternal (NumericType delaySamples) { if (juce::approximatelyEqual (delaySamples, prevDelay)) return; @@ -115,7 +139,11 @@ void BypassProcessor -bool BypassProcessor>>::processBlockIn (const BufferView& block, bool onOffParam) +bool BypassProcessor>>::processBlockIn (const BufferView& block, + bool onOffParam, + std::optional arena) { enum class DelayOp { @@ -161,9 +189,13 @@ bool BypassProcessor (*arena, block.getNumChannels(), block.getNumSamples()); + else + fadeBufferView = BufferView { fadeBuffer, 0, block.getNumSamples(), 0, block.getNumChannels() }; + + BufferMath::copyBufferData (block, fadeBufferView); + doDelayOp (fadeBufferView, compDelay, DelayOp::Pop); if (onOffParam && latencySampleCount < 0) latencySampleCount = (int) compDelay.getDelay(); @@ -177,7 +209,9 @@ bool BypassProcessor -void BypassProcessor>>::processBlockOut (const BufferView& block, bool onOffParam) +void BypassProcessor>>::processBlockOut (const BufferView& block, bool onOffParam) { auto fadeOutputBuffer = [onOffParam] (auto* blockPtr, const auto* fadePtr, const int startSample, const int numSamples) { @@ -222,7 +256,7 @@ void BypassProcessor -int BypassProcessor>>::getFadeStartSample (const int numSamples) +int BypassProcessor>>::getFadeStartSample (const int numSamples) { if (latencySampleCount <= 0) { diff --git a/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.h b/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.h index 614519446..a66525bb4 100644 --- a/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.h +++ b/modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_BypassProcessor.h @@ -22,11 +22,11 @@ namespace chowdsp * } * ``` */ -template +template class BypassProcessor; template -class BypassProcessor>> +class BypassProcessor>> { public: using NumericType = SampleTypeHelpers::NumericType; @@ -40,14 +40,14 @@ class BypassProcessor& buffer, bool onOffParam); + bool processBlockIn (const BufferView& buffer, bool onOffParam, std::optional arena = std::nullopt); /** * Call this at the end of your processBlock(). @@ -59,12 +59,13 @@ class BypassProcessor fadeBuffer; + BufferView fadeBufferView; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BypassProcessor) }; template -class BypassProcessor>> +class BypassProcessor>> { public: using NumericType = SampleTypeHelpers::NumericType; @@ -78,7 +79,7 @@ class BypassProcessor& buffer, bool onOffParam); + bool processBlockIn (const BufferView& buffer, bool onOffParam, std::optional arena = std::nullopt); /** * Call this at the end of your processBlock(). @@ -116,6 +117,7 @@ class BypassProcessor fadeBuffer; + BufferView fadeBufferView; DelayLine compDelay { 1 << 18 }; // max latency = 2^18 = 262144 samples NumericType prevDelay {}; diff --git a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.cpp b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.cpp index 49b2fc93a..5a0822ea8 100644 --- a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.cpp +++ b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.cpp @@ -39,9 +39,6 @@ void EQBandBase::prepare (const juce::dsp::Proces { fs = (NumericType) spec.sampleRate; - fadeBuffer.setMaxSize ((int) spec.numChannels, (int) spec.maximumBlockSize); - fadeBuffer.clear(); - TupleHelpers::forEachInTuple ( [spec] (auto& filter, size_t) { @@ -58,7 +55,7 @@ void EQBandBase::prepare (const juce::dsp::Proces for (auto* smoother : { &freqSmooth, &qSmooth, &gainSmooth }) { - smoother->prepare (spec.sampleRate, (int) spec.maximumBlockSize); + smoother->prepare (spec.sampleRate, (int) spec.maximumBlockSize, false); smoother->setRampLength (0.05); } @@ -278,24 +275,26 @@ void EQBandBase::fadeBuffers (const FloatType* fa } template -void EQBandBase::processBlock (const BufferView& buffer) noexcept +void EQBandBase::processBlock (const BufferView& buffer, ArenaAllocatorView arena) noexcept { const auto numChannels = buffer.getNumChannels(); const auto numSamples = buffer.getNumSamples(); + const auto frame = arena.create_frame(); - freqSmooth.process (freqHzHandle, numSamples); - qSmooth.process (qHandle, numSamples); - gainSmooth.process (gainHandle, numSamples); + freqSmooth.process (freqHzHandle, numSamples, arena); + qSmooth.process (qHandle, numSamples, arena); + gainSmooth.process (gainHandle, numSamples, arena); const auto needsFade = filterType != prevFilterType; + BufferView fadeBuffer {}; if (needsFade) { - fadeBuffer.setCurrentSize (numChannels, numSamples); + fadeBuffer = make_temp_buffer (arena, numChannels, numSamples); BufferMath::copyBufferData (buffer, fadeBuffer); } TupleHelpers::forEachInTuple ( - [this, &buffer] (auto& filter, size_t filterIndex) + [this, &buffer, &fadeBuffer] (auto& filter, size_t filterIndex) { if ((int) filterIndex == filterType) { @@ -320,62 +319,4 @@ void EQBandBase::processBlock (const BufferView -template -void EQBandBase::process (const ProcessContext& context) noexcept -{ - const auto& inputBlock = context.getInputBlock(); - auto& block = context.getOutputBlock(); - const auto numChannels = block.getNumChannels(); - const auto numSamples = (int) block.getNumSamples(); - - jassert (inputBlock.getNumChannels() == numChannels); - jassert (inputBlock.getNumSamples() == (size_t) numSamples); - - freqSmooth.process (freqHzHandle, numSamples); - qSmooth.process (qHandle, numSamples); - gainSmooth.process (gainHandle, numSamples); - - // the filters will need to do in-place processing anyway, so let's just copy the blocks here - if (context.usesSeparateInputAndOutputBlocks()) - block.copyFrom (inputBlock); - - if (context.isBypassed) - { - reset(); - return; - } - - const auto needsFade = filterType != prevFilterType; - if (needsFade) - BufferMath::copyBufferData (block, fadeBuffer, 0, numSamples, 0, numChannels); - - TupleHelpers::forEachInTuple ( - [this, &block] (auto& filter, size_t filterIndex) - { - if ((int) filterIndex == filterType) - { - processFilterChannel (filter, block); - } - else if ((int) filterIndex == prevFilterType) - { - processFilterChannel (filter, fadeBuffer); - filter.reset(); - } - }, - filters); - - if (needsFade) - { - for (size_t channel = 0; channel < numChannels; ++channel) - { - auto* blockPtr = block.getChannelPointer (channel); - fadeBuffers (blockPtr, fadeBuffer.getReadPointer ((int) channel), blockPtr, numSamples); - } - } - - prevFilterType = filterType; -} - } // namespace chowdsp::EQ diff --git a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.h b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.h index 6daca56eb..eaf395308 100644 --- a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.h +++ b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQBand.h @@ -58,11 +58,7 @@ class EQBandBase void reset(); /** Processes an buffer of samples. */ - void processBlock (const BufferView& buffer) noexcept; - - /** Processes an audio context */ - template - void process (const ProcessContext& context) noexcept; + void processBlock (const BufferView& buffer, ArenaAllocatorView arena) noexcept; private: template @@ -93,8 +89,6 @@ class EQBandBase NumericType fs = NumericType (44100.0); - Buffer fadeBuffer; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EQBandBase) }; diff --git a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.cpp b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.cpp index 656957341..265b39436 100644 --- a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.cpp +++ b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.cpp @@ -51,8 +51,14 @@ void EQProcessor::prepare (const juce::dsp::Pro for (size_t i = 0; i < numBands; ++i) { bands[i].prepare (spec); - bypasses[i].prepare (spec, onOffs[i]); + bypasses[i].prepare (spec, onOffs[i], false); } + + const auto paddedChannelSize = Math::ceiling_divide (static_cast (spec.maximumBlockSize), SIMDUtils::defaultSIMDAlignment) * SIMDUtils::defaultSIMDAlignment; + const auto requiredMemoryBytes = paddedChannelSize * sizeof (FloatType) * 3 // per-band smoothed values + + paddedChannelSize * spec.numChannels * sizeof (FloatType) * 2 // per-band fade and bypass buffers + + 32; // extra padding + arena.reset (requiredMemoryBytes); } template @@ -67,10 +73,14 @@ void EQProcessor::processBlock (const BufferVie { for (size_t i = 0; i < numBands; ++i) { - if (! bypasses[i].processBlockIn (block, onOffs[i])) + const auto frame = arena.create_frame(); + if (! bypasses[i].processBlockIn (block, onOffs[i], arena)) + { + bands[i].reset(); continue; + } - bands[i].processBlock (block); + bands[i].processBlock (block, arena); bypasses[i].processBlockOut (block, onOffs[i]); } diff --git a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.h b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.h index 0c8fe17ab..a2605a3ee 100644 --- a/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.h +++ b/modules/dsp/chowdsp_eq/EQ/chowdsp_EQProcessor.h @@ -64,6 +64,8 @@ class EQProcessor std::array, numBands> bypasses; std::array onOffs = { false }; + ArenaAllocator<> arena; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EQProcessor) }; } // namespace chowdsp::EQ diff --git a/modules/dsp/chowdsp_filters/LowerOrderFilters/chowdsp_IIRFilter.h b/modules/dsp/chowdsp_filters/LowerOrderFilters/chowdsp_IIRFilter.h index 9b191016a..c7594f81a 100644 --- a/modules/dsp/chowdsp_filters/LowerOrderFilters/chowdsp_IIRFilter.h +++ b/modules/dsp/chowdsp_filters/LowerOrderFilters/chowdsp_IIRFilter.h @@ -39,7 +39,7 @@ class IIRFilter if constexpr (maxChannelCount == dynamicChannelCount) z.resize (numChannels); else - jassert (numChannels <= maxChannelCount); + jassert (numChannels <= static_cast (maxChannelCount)); } /** Reset filter state */ diff --git a/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EQFilterPlots.h b/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EQFilterPlots.h index 2211de965..f2b6d0184 100644 --- a/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EQFilterPlots.h +++ b/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EQFilterPlots.h @@ -273,8 +273,8 @@ struct HigherOrderLPFPlot : EQFilterPlot private: LPF1Plot extraPlot; - std::array plots; - static constexpr auto butterQVals = chowdsp::QValCalcs::butterworth_Qs(); + std::array plots {}; + static constexpr auto butterQVals = QValCalcs::butterworth_Qs(); }; /** Plotting helper for higher-order HPFs. */ @@ -318,7 +318,7 @@ struct HigherOrderHPFPlot : EQFilterPlot private: HPF1Plot extraPlot; - std::array plots; - static constexpr auto butterQVals = chowdsp::QValCalcs::butterworth_Qs(); + std::array plots {}; + static constexpr auto butterQVals = QValCalcs::butterworth_Qs(); }; } // namespace chowdsp::EQ diff --git a/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.cpp b/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.cpp index 99b6190c3..f121a68e5 100644 --- a/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.cpp +++ b/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.cpp @@ -2,142 +2,154 @@ namespace chowdsp::EQ { -EqualizerPlot::EqualizerPlot (int numBands, SpectrumPlotParams&& plotParams) - : SpectrumPlotBase (std::move (plotParams)), - filterPlots ((size_t) numBands), - filtersActiveFlags ((size_t) numBands, true) +template +EqualizerPlot::EqualizerPlot (SpectrumPlotParams&& plotParams) + : SpectrumPlotBase (std::move (plotParams)) { + std::fill (filtersActiveFlags.begin(), filtersActiveFlags.end(), true); for (auto& plot : filterPlots) - plot.plot = std::make_unique(); + plot.plot.template emplace(); } -void EqualizerPlot::setFilterType (int bandIndex, EQPlotFilterType type) +template +void EqualizerPlot::setFilterType (int bandIndex, EQPlotFilterType type) { auto& plot = filterPlots[(size_t) bandIndex].plot; + auto& plotType = filterPlots[(size_t) bandIndex].type; + if (plotType.has_value() && *plotType == type) + return; + + plotType = type; switch (type) { case EQPlotFilterType::LPF1: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::LPF2: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::LPF3: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF4: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF5: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF6: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF7: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF8: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF9: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF10: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF11: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::LPF12: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF1: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::HPF2: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::HPF3: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF4: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF5: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF6: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF7: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF8: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF9: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF10: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF11: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::HPF12: - plot = std::make_unique>(); + plot.template emplace>(); break; case EQPlotFilterType::BPF2: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::Bell: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::Notch: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::LowShelf: - plot = std::make_unique(); + plot.template emplace(); break; case EQPlotFilterType::HighShelf: - plot = std::make_unique(); + plot.template emplace(); break; default: - plot = std::make_unique(); + plot.template emplace(); break; } } -void EqualizerPlot::setFilterActive (int bandIndex, bool isActive) +template +void EqualizerPlot::setFilterActive (int bandIndex, bool isActive) { filtersActiveFlags[(size_t) bandIndex] = isActive; } -bool EqualizerPlot::getFilterActive (int bandIndex) const +template +bool EqualizerPlot::getFilterActive (int bandIndex) const { return filtersActiveFlags[(size_t) bandIndex]; } -void EqualizerPlot::setCutoffParameter (int bandIndex, float cutoffHz) +template +void EqualizerPlot::setCutoffParameter (int bandIndex, float cutoffHz) { filterPlots[(size_t) bandIndex].plot->setCutoffFrequency (cutoffHz); } -void EqualizerPlot::setQParameter (int bandIndex, float qValue) +template +void EqualizerPlot::setQParameter (int bandIndex, float qValue) { filterPlots[(size_t) bandIndex].plot->setQValue (qValue); } -void EqualizerPlot::setGainDBParameter (int bandIndex, float gainDB) +template +void EqualizerPlot::setGainDBParameter (int bandIndex, float gainDB) { filterPlots[(size_t) bandIndex].plot->setGainDecibels (gainDB); } -void EqualizerPlot::updateFilterPlotPath (int bandIndex) +template +void EqualizerPlot::updateFilterPlotPath (int bandIndex) { const auto width = getWidth(); if (width == 0 || getHeight() == 0) @@ -168,7 +180,8 @@ void EqualizerPlot::updateFilterPlotPath (int bandIndex) repaint(); } -void EqualizerPlot::updateMasterFilterPlotPath() +template +void EqualizerPlot::updateMasterFilterPlotPath() { const auto width = getWidth(); if (width == 0 || getHeight() == 0) @@ -184,10 +197,10 @@ void EqualizerPlot::updateMasterFilterPlotPath() activePlots.push_back (&filterPlot.plotData); } - const auto getPointForXCoord = [this, &activePlots = std::as_const (activePlots)] (int x) -> juce::Point + const auto getPointForXCoord = [this, ¤tActivePlots = std::as_const (activePlots)] (int x) -> juce::Point { float magLinear = 1.0f; - for (auto* plotData : activePlots) + for (auto* plotData : currentActivePlots) { if (plotData->size() <= (size_t) x) break; @@ -204,15 +217,114 @@ void EqualizerPlot::updateMasterFilterPlotPath() masterFilterPlotPath.lineTo (getPointForXCoord (x)); } -void EqualizerPlot::resized() +template +void EqualizerPlot::resized() { for (int i = 0; i < (int) filterPlots.size(); ++i) updateFilterPlotPath (i); } -const juce::Path& EqualizerPlot::getPath (int bandIndex) const +template +const juce::Path& EqualizerPlot::getPath (int bandIndex) const { jassert (juce::isPositiveAndBelow (bandIndex, (int) filterPlots.size())); return filterPlots[(size_t) bandIndex].plotPath; } + +//================================================================================================= +#if JUCE_MODULE_AVAILABLE_chowdsp_plugin_state && JUCE_MODULE_AVAILABLE_chowdsp_eq +template +EqualizerPlotWithParameters::EqualizerPlotWithParameters (ParameterListeners& listeners, + EQ::StandardEQParameters& eqParameters, + const std::function& filterTypeMap, + SpectrumPlotParams&& plotParams) + : EqualizerPlot (std::move (plotParams)), + eqParams (eqParameters) +{ + for (const auto [index, bandParams] : enumerate (eqParams.eqParams)) + { + const auto bandIndex = (int) index; + callbacks += + { + listeners.addParameterListener ( + bandParams.freqParam, + ParameterListenerThread::MessageThread, + [this, bandIndex] + { + updateFreqParameter (bandIndex); + EqualizerPlot::updateFilterPlotPath (bandIndex); + }), + listeners.addParameterListener ( + bandParams.qParam, + ParameterListenerThread::MessageThread, + [this, bandIndex] + { + updateQParameter (bandIndex); + EqualizerPlot::updateFilterPlotPath (bandIndex); + }), + listeners.addParameterListener ( + bandParams.gainParam, + ParameterListenerThread::MessageThread, + [this, bandIndex] + { + updateGainDBParameter (bandIndex); + EqualizerPlot::updateFilterPlotPath (bandIndex); + }), + listeners.addParameterListener ( + bandParams.typeParam, + ParameterListenerThread::MessageThread, + [this, bandIndex, filterTypeMap] + { + updateFilterType (bandIndex, filterTypeMap); + EqualizerPlot::updateFilterPlotPath (bandIndex); + }), + listeners.addParameterListener ( + bandParams.onOffParam, + ParameterListenerThread::MessageThread, + [this, bandIndex] + { + updateFilterOnOff (bandIndex); + EqualizerPlot::updateFilterPlotPath (bandIndex); + }) + }; + + updateFilterType (bandIndex, filterTypeMap); + updateFilterOnOff (bandIndex); + } +} + +template +void EqualizerPlotWithParameters::updateFreqParameter (int bandIndex) +{ + EqualizerPlot::setCutoffParameter (bandIndex, eqParams.eqParams[(size_t) bandIndex].freqParam->get()); +} + +template +void EqualizerPlotWithParameters::updateQParameter (int bandIndex) +{ + EqualizerPlot::setQParameter (bandIndex, eqParams.eqParams[(size_t) bandIndex].qParam->get()); +} + +template +void EqualizerPlotWithParameters::updateGainDBParameter (int bandIndex) +{ + EqualizerPlot::setGainDBParameter (bandIndex, eqParams.eqParams[(size_t) bandIndex].gainParam->get()); +} + +template +void EqualizerPlotWithParameters::updateFilterType (int bandIndex, const std::function& filterTypeMap) // NOSONAR +{ + const auto& bandTypeParam = eqParams.eqParams[(size_t) bandIndex].typeParam; + EqualizerPlot::setFilterType (bandIndex, filterTypeMap (bandTypeParam->getIndex())); + updateFreqParameter (bandIndex); + updateQParameter (bandIndex); + updateGainDBParameter (bandIndex); +} + +template +void EqualizerPlotWithParameters::updateFilterOnOff (int bandIndex) +{ + EqualizerPlot::setFilterActive (bandIndex, eqParams.eqParams[(size_t) bandIndex].onOffParam->get()); +} +#endif } // namespace chowdsp::EQ diff --git a/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.h b/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.h index f4a5cc363..f8f118a33 100644 --- a/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.h +++ b/modules/gui/chowdsp_visualizers/SpectrumPlots/chowdsp_EqualizerPlot.h @@ -39,10 +39,11 @@ enum class EQPlotFilterType }; /** UI component for plotting EQ filter frequency responses. */ +template class EqualizerPlot : public SpectrumPlotBase { public: - explicit EqualizerPlot (int numBands, SpectrumPlotParams&& params = {}); + explicit EqualizerPlot (SpectrumPlotParams&& plotParams); /** Sets the filter type for a given band. */ void setFilterType (int bandIndex, EQPlotFilterType type); @@ -78,118 +79,44 @@ class EqualizerPlot : public SpectrumPlotBase struct BandPlotInfo { - std::unique_ptr plot; - juce::Path plotPath; + LocalPointer plot {}; + std::optional type {}; + juce::Path plotPath {}; std::vector plotData {}; }; - std::vector filterPlots; - juce::Path masterFilterPlotPath; + std::array filterPlots {}; + juce::Path masterFilterPlotPath {}; - std::vector filtersActiveFlags; + std::array filtersActiveFlags {}; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EqualizerPlot) }; #if JUCE_MODULE_AVAILABLE_chowdsp_plugin_state && JUCE_MODULE_AVAILABLE_chowdsp_eq template -class EqualizerPlotWithParameters : public EqualizerPlot +class EqualizerPlotWithParameters : public EqualizerPlot { public: - EqualizerPlotWithParameters (chowdsp::ParameterListeners& listeners, - chowdsp::EQ::StandardEQParameters& eqParameters, + EqualizerPlotWithParameters (ParameterListeners& listeners, + EQ::StandardEQParameters& eqParameters, const std::function& filterTypeMap, - SpectrumPlotParams&& plotParams = {}) - : EqualizerPlot (numBands, std::move (plotParams)), - eqParams (eqParameters) - { - for (const auto [index, bandParams] : chowdsp::enumerate (eqParams.eqParams)) - { - const auto bandIndex = (int) index; - callbacks += - { - listeners.addParameterListener ( - bandParams.freqParam, - chowdsp::ParameterListenerThread::MessageThread, - [this, bandIndex] - { - updateFreqParameter (bandIndex); - updateFilterPlotPath (bandIndex); - }), - listeners.addParameterListener ( - bandParams.qParam, - chowdsp::ParameterListenerThread::MessageThread, - [this, bandIndex] - { - updateQParameter (bandIndex); - updateFilterPlotPath (bandIndex); - }), - listeners.addParameterListener ( - bandParams.gainParam, - chowdsp::ParameterListenerThread::MessageThread, - [this, bandIndex] - { - updateGainDBParameter (bandIndex); - updateFilterPlotPath (bandIndex); - }), - listeners.addParameterListener ( - bandParams.typeParam, - chowdsp::ParameterListenerThread::MessageThread, - [this, bandIndex, filterTypeMap] - { - updateFilterType (bandIndex, filterTypeMap); - updateFilterPlotPath (bandIndex); - }), - listeners.addParameterListener ( - bandParams.onOffParam, - chowdsp::ParameterListenerThread::MessageThread, - [this, bandIndex] - { - updateFilterOnOff (bandIndex); - updateFilterPlotPath (bandIndex); - }) - }; - - updateFilterType (bandIndex, filterTypeMap); - updateFilterOnOff (bandIndex); - } - } + SpectrumPlotParams&& plotParams = {}); private: - void updateFreqParameter (int bandIndex) - { - setCutoffParameter (bandIndex, eqParams.eqParams[(size_t) bandIndex].freqParam->get()); - } + void updateFreqParameter (int bandIndex); + void updateQParameter (int bandIndex); + void updateGainDBParameter (int bandIndex); + void updateFilterType (int bandIndex, const std::function& filterTypeMap); // NOSONAR + void updateFilterOnOff (int bandIndex); - void updateQParameter (int bandIndex) - { - setQParameter (bandIndex, eqParams.eqParams[(size_t) bandIndex].qParam->get()); - } + EQ::StandardEQParameters& eqParams; - void updateGainDBParameter (int bandIndex) - { - setGainDBParameter (bandIndex, eqParams.eqParams[(size_t) bandIndex].gainParam->get()); - } - - void updateFilterType (int bandIndex, const std::function& filterTypeMap) // NOSONAR - { - const auto& bandTypeParam = eqParams.eqParams[(size_t) bandIndex].typeParam; - setFilterType (bandIndex, filterTypeMap (bandTypeParam->getIndex())); - updateFreqParameter (bandIndex); - updateQParameter (bandIndex); - updateGainDBParameter (bandIndex); - } - - void updateFilterOnOff (int bandIndex) - { - setFilterActive (bandIndex, eqParams.eqParams[(size_t) bandIndex].onOffParam->get()); - } - - chowdsp::EQ::StandardEQParameters& eqParams; - - chowdsp::ScopedCallbackList callbacks; + ScopedCallbackList callbacks {}; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EqualizerPlotWithParameters) }; #endif } // namespace chowdsp::EQ + +#include "chowdsp_EqualizerPlot.cpp" diff --git a/modules/gui/chowdsp_visualizers/chowdsp_visualizers.cpp b/modules/gui/chowdsp_visualizers/chowdsp_visualizers.cpp index 11bd561a1..97f96dda4 100644 --- a/modules/gui/chowdsp_visualizers/chowdsp_visualizers.cpp +++ b/modules/gui/chowdsp_visualizers/chowdsp_visualizers.cpp @@ -1,7 +1,6 @@ #include "chowdsp_visualizers.h" #include "SpectrumPlots/chowdsp_SpectrumPlotBase.cpp" -#include "SpectrumPlots/chowdsp_EqualizerPlot.cpp" #include "SpectrumPlots/chowdsp_GenericFilterPlotter.cpp" #include "WaveshaperPlot/chowdsp_WaveshaperPlot.cpp" diff --git a/tests/dsp_tests/chowdsp_dsp_utils_test/BypassTest.cpp b/tests/dsp_tests/chowdsp_dsp_utils_test/BypassTest.cpp index 41a2007f3..2759fdba7 100644 --- a/tests/dsp_tests/chowdsp_dsp_utils_test/BypassTest.cpp +++ b/tests/dsp_tests/chowdsp_dsp_utils_test/BypassTest.cpp @@ -10,10 +10,20 @@ constexpr float delaySamp = 5.0f; } // namespace template -void processFunc (const chowdsp::BufferView& buffer, BypassType& bypass, std::atomic* onOffParam, FuncType&& blockFunc) +void processFunc (const chowdsp::BufferView& buffer, + BypassType& bypass, + std::atomic* onOffParam, + chowdsp::ArenaAllocatorView* arena, + FuncType&& blockFunc) { + std::optional arena_frame; + if (arena != nullptr) + arena_frame.emplace (*arena); + auto onOff = bypass.toBool (onOffParam); - if (! bypass.processBlockIn (buffer, onOff)) + if (! bypass.processBlockIn (buffer, + onOff, + arena == nullptr ? std::optional { std::nullopt } : *arena)) return; blockFunc (buffer); @@ -73,59 +83,79 @@ TEST_CASE ("Bypass Test", "[dsp][misc]") static constexpr int bufferTestNIters = 5; SECTION ("Audio Buffer Test") { - chowdsp::BypassProcessor bypass; - std::atomic onOffParam { 0.0f }; - bypass.prepare ({ fs, (juce::uint32) nSamples, 1 }, bypass.toBool (&onOffParam)); - - chowdsp::Buffer buffer (1, bufferTestNIters * nSamples); - buffer.clear(); - for (int i = 0; i < bufferTestNIters; ++i) + auto arena = chowdsp::ArenaAllocator> {}; + auto arenaView = chowdsp::ArenaAllocatorView { arena }; + for (auto& arenaPtr : std::vector { nullptr, &arenaView }) { - chowdsp::BufferView subBuffer { buffer, i * nSamples, nSamples }; - processFunc (subBuffer, - bypass, - &onOffParam, - [] (const chowdsp::BufferView& block) - { - for (auto [ch, data] : chowdsp::buffer_iters::channels (block)) - juce::FloatVectorOperations::add (data.data(), 1.0f, data.size()); - }); - onOffParam.store (1.0f - onOffParam.load()); - } + chowdsp::BypassProcessor bypass; + std::atomic onOffParam { 0.0f }; + bypass.prepare ({ fs, + (juce::uint32) nSamples, + 1 }, + bypass.toBool (&onOffParam), + arenaPtr == nullptr); + + chowdsp::Buffer buffer (1, bufferTestNIters * nSamples); + buffer.clear(); + for (int i = 0; i < bufferTestNIters; ++i) + { + chowdsp::BufferView subBuffer { buffer, i * nSamples, nSamples }; + processFunc (subBuffer, + bypass, + &onOffParam, + arenaPtr, + [] (const chowdsp::BufferView& block) + { + for (const auto& [ch, data] : chowdsp::buffer_iters::channels (block)) + juce::FloatVectorOperations::add (data.data(), 1.0f, data.size()); + }); + onOffParam.store (1.0f - onOffParam.load()); + } - checkForClicks (buffer.getReadPointer (0), bufferTestNIters * nSamples, 0.005f, "Audio Buffer has clicks!"); + checkForClicks (buffer.getReadPointer (0), bufferTestNIters * nSamples, 0.005f, "Audio Buffer has clicks!"); + } } static constexpr int delayTestNIters = 8; SECTION ("Audio Buffer Delay Test") { - chowdsp::BypassProcessor bypass; - std::atomic onOffParam { 0.0f }; - bypass.prepare ({ fs, (juce::uint32) nSamples, 1 }, bypass.toBool (&onOffParam)); - bypass.setLatencySamples (delaySamp); - - chowdsp::DelayLine delay { 2048 }; - delay.prepare ({ fs, (juce::uint32) nSamples, 1 }); - delay.setDelay (delaySamp); - - chowdsp::Buffer buffer (1, delayTestNIters * nSamples); - createPulseTrain (buffer.getWritePointer (0), delayTestNIters * nSamples, pulseSpace); - for (int i = 0; i < delayTestNIters; ++i) + auto arena = chowdsp::ArenaAllocator> {}; + auto arenaView = chowdsp::ArenaAllocatorView { arena }; + for (auto& arenaPtr : std::vector { nullptr, &arenaView }) { - chowdsp::BufferView subBuffer { buffer, i * nSamples, nSamples }; - processFunc (subBuffer, - bypass, - &onOffParam, - [&] (const chowdsp::BufferView& block) - { - delay.processBlock (block); - }); - - if (i % 2 != 0) - onOffParam.store (1.0f - onOffParam.load()); - } - delay.free(); + chowdsp::BypassProcessor bypass; + std::atomic onOffParam { 0.0f }; + bypass.prepare ({ fs, + (juce::uint32) nSamples, + 1 }, + bypass.toBool (&onOffParam), + arenaPtr == nullptr); + bypass.setLatencySamples (delaySamp); + + chowdsp::DelayLine delay { 2048 }; + delay.prepare ({ fs, (juce::uint32) nSamples, 1 }); + delay.setDelay (delaySamp); + + chowdsp::Buffer buffer (1, delayTestNIters * nSamples); + createPulseTrain (buffer.getWritePointer (0), delayTestNIters * nSamples, pulseSpace); + for (int i = 0; i < delayTestNIters; ++i) + { + chowdsp::BufferView subBuffer { buffer, i * nSamples, nSamples }; + processFunc (subBuffer, + bypass, + &onOffParam, + arenaPtr, + [&] (const chowdsp::BufferView& block) + { + delay.processBlock (block); + }); + + if (i % 2 != 0) + onOffParam.store (1.0f - onOffParam.load()); + } + delay.free(); - checkPulseSpacing (buffer.getReadPointer (0), delayTestNIters * nSamples, pulseSpace); + checkPulseSpacing (buffer.getReadPointer (0), delayTestNIters * nSamples, pulseSpace); + } } }