From f1d62982063dbf69a2a4880fa266976e82d13134 Mon Sep 17 00:00:00 2001 From: hogliux Date: Wed, 6 Sep 2017 10:17:09 +0100 Subject: [PATCH] DSP: Fixed multiple issues with the DSP Oversampling class and updated DSP module plug-in demo code accordingly --- .../DSPModulePluginDemo_SharedCode.vcxproj | 6 + ...PModulePluginDemo_StandalonePlugin.vcxproj | 6 + .../DSPModulePluginDemo_VST.vcxproj | 6 + .../DSP module plugin demo.jucer | 4 +- .../Source/PluginEditor.cpp | 24 +- .../Source/PluginEditor.h | 4 +- .../Source/PluginProcessor.cpp | 44 +- .../Source/PluginProcessor.h | 4 + .../juce_dsp/processors/juce_Oversampling.cpp | 462 ++++++++++-------- .../juce_dsp/processors/juce_Oversampling.h | 17 +- 10 files changed, 345 insertions(+), 232 deletions(-) diff --git a/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_SharedCode.vcxproj b/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_SharedCode.vcxproj index c7b52988c6..f977707fd1 100644 --- a/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_SharedCode.vcxproj +++ b/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_SharedCode.vcxproj @@ -101,6 +101,9 @@ true $(IntDir)\DSPModulePluginDemo.bsc + + if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll" + @@ -143,6 +146,9 @@ true $(IntDir)\DSPModulePluginDemo.bsc + + if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll" + diff --git a/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_StandalonePlugin.vcxproj b/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_StandalonePlugin.vcxproj index 793416403c..96b3e8a143 100644 --- a/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_StandalonePlugin.vcxproj +++ b/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_StandalonePlugin.vcxproj @@ -104,6 +104,9 @@ true $(IntDir)\DSPModulePluginDemo.bsc + + if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll" + @@ -147,6 +150,9 @@ true $(IntDir)\DSPModulePluginDemo.bsc + + if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll" + diff --git a/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_VST.vcxproj b/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_VST.vcxproj index dfd834034f..374cb6fed4 100644 --- a/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_VST.vcxproj +++ b/examples/DSP module plugin demo/Builds/VisualStudio2015/DSPModulePluginDemo_VST.vcxproj @@ -104,6 +104,9 @@ true $(IntDir)\DSPModulePluginDemo.bsc + + if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll" + @@ -147,6 +150,9 @@ true $(IntDir)\DSPModulePluginDemo.bsc + + if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll" + diff --git a/examples/DSP module plugin demo/DSP module plugin demo.jucer b/examples/DSP module plugin demo/DSP module plugin demo.jucer index 67e37a1318..7ee8df1779 100644 --- a/examples/DSP module plugin demo/DSP module plugin demo.jucer +++ b/examples/DSP module plugin demo/DSP module plugin demo.jucer @@ -59,10 +59,10 @@ + warningsAreErrors="0" postbuildCommand="if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll""/> + useRuntimeLibDLL="0" postbuildCommand="if "$(ProjectName)"=="$(SolutionName)_VST" copy /Y "$(TargetDir)\$(TargetName).dll" "c:\vstplugins\musical entropy\$(TargetName).dll""/> diff --git a/examples/DSP module plugin demo/Source/PluginEditor.cpp b/examples/DSP module plugin demo/Source/PluginEditor.cpp index 982fe6cd93..6573a34e12 100644 --- a/examples/DSP module plugin demo/Source/PluginEditor.cpp +++ b/examples/DSP module plugin demo/Source/PluginEditor.cpp @@ -123,6 +123,10 @@ DspModulePluginDemoAudioProcessorEditor::DspModulePluginDemoAudioProcessorEditor cabinetSimButton.addListener (this); cabinetSimButton.setButtonText (processor.cabinetSimParam->name); + addAndMakeVisible (oversamplingButton); + oversamplingButton.addListener (this); + oversamplingButton.setButtonText (processor.oversamplingParam->name); + //============================================================================== setSize (600, 400); } @@ -172,9 +176,13 @@ void DspModulePluginDemoAudioProcessorEditor::resized() //============================================================================== auto buttonSlice = bounds.removeFromTop (30); - cabinetSimButton.setSize (200, bounds.getHeight()); + cabinetSimButton.setSize (200, buttonSlice.getHeight()); cabinetSimButton.setCentrePosition (buttonSlice.getCentre()); - bounds.removeFromTop (15); + bounds.removeFromTop(5); + + buttonSlice = bounds.removeFromTop (30); + oversamplingButton.setSize(200, buttonSlice.getHeight()); + oversamplingButton.setCentrePosition(buttonSlice.getCentre()); } //============================================================================== void DspModulePluginDemoAudioProcessorEditor::comboBoxChanged (ComboBox* box) @@ -198,3 +206,15 @@ void DspModulePluginDemoAudioProcessorEditor::comboBoxChanged (ComboBox* box) processor.cabinetTypeParam->operator= (index); } } + +void DspModulePluginDemoAudioProcessorEditor::buttonClicked (Button* button) +{ + if (button == &cabinetSimButton) + { + processor.cabinetSimParam->operator= (cabinetSimButton.getToggleState()); + } + else if (button == &oversamplingButton) + { + processor.oversamplingParam->operator= (oversamplingButton.getToggleState()); + } +} diff --git a/examples/DSP module plugin demo/Source/PluginEditor.h b/examples/DSP module plugin demo/Source/PluginEditor.h index d2a36b8bbd..ab43e1fbe9 100644 --- a/examples/DSP module plugin demo/Source/PluginEditor.h +++ b/examples/DSP module plugin demo/Source/PluginEditor.h @@ -86,7 +86,7 @@ public: private: //============================================================================== void comboBoxChanged (ComboBox*) override; - void buttonClicked (Button*) override { processor.cabinetSimParam->operator= (cabinetSimButton.getToggleState()); } + void buttonClicked (Button*) override; //============================================================================== DspModulePluginDemoAudioProcessor& processor; @@ -94,7 +94,7 @@ private: ScopedPointer inputVolumeSlider, outputVolumeSlider, lowPassFilterFreqSlider, highPassFilterFreqSlider; ComboBox stereoBox, slopeBox, waveshaperBox, cabinetTypeBox; - ToggleButton cabinetSimButton; + ToggleButton cabinetSimButton, oversamplingButton; Label inputVolumeLabel, outputVolumeLabel, lowPassFilterFreqLabel, highPassFilterFreqLabel, stereoLabel, slopeLabel, waveshaperLabel, diff --git a/examples/DSP module plugin demo/Source/PluginProcessor.cpp b/examples/DSP module plugin demo/Source/PluginProcessor.cpp index bd531c000c..8e9ac479ac 100644 --- a/examples/DSP module plugin demo/Source/PluginProcessor.cpp +++ b/examples/DSP module plugin demo/Source/PluginProcessor.cpp @@ -38,6 +38,9 @@ DspModulePluginDemoAudioProcessor::DspModulePluginDemoAudioProcessor() waveShapers { {std::tanh}, {dsp::FastMathApproximations::tanh} }, clipping { clip } { + // Oversampling 2 times with IIR filtering + oversampling = new dsp::Oversampling (2, 1, dsp::Oversampling::filterHalfBandPolyphaseIIR, false); + addParameter (inputVolumeParam = new AudioParameterFloat ("INPUT", "Input Volume", { 0.f, 60.f, 0.f, 1.0f }, 0.f, "dB")); addParameter (highPassFilterFreqParam = new AudioParameterFloat ("HPFREQ", "Pre Highpass Freq.", { 20.f, 20000.f, 0.f, 0.5f }, 20.f, "Hz")); addParameter (lowPassFilterFreqParam = new AudioParameterFloat ("LPFREQ", "Post Lowpass Freq.", { 20.f, 20000.f, 0.f, 0.5f }, 20000.f, "Hz")); @@ -50,12 +53,11 @@ DspModulePluginDemoAudioProcessor::DspModulePluginDemoAudioProcessor() "Cassette recorder cabinet" }, 0)); addParameter (cabinetSimParam = new AudioParameterBool ("CABSIM", "Cabinet Sim", false)); + addParameter (oversamplingParam = new AudioParameterBool ("OVERS", "Oversampling", false)); addParameter (outputVolumeParam = new AudioParameterFloat ("OUTPUT", "Output Volume", { -40.f, 40.f, 0.f, 1.0f }, 0.f, "dB")); cabinetType.set (0); - - } DspModulePluginDemoAudioProcessor::~DspModulePluginDemoAudioProcessor() @@ -82,8 +84,6 @@ void DspModulePluginDemoAudioProcessor::prepareToPlay (double sampleRate, int sa auto channels = static_cast (jmin (getMainBusNumInputChannels(), getMainBusNumOutputChannels())); dsp::ProcessSpec spec { sampleRate, static_cast (samplesPerBlock), channels }; - updateParameters(); - lowPassFilter.prepare (spec); highPassFilter.prepare (spec); @@ -92,6 +92,11 @@ void DspModulePluginDemoAudioProcessor::prepareToPlay (double sampleRate, int sa convolution.prepare (spec); cabinetType.set (-1); + + oversampling->initProcessing (static_cast (samplesPerBlock)); + + updateParameters(); + reset(); } void DspModulePluginDemoAudioProcessor::reset() @@ -99,6 +104,7 @@ void DspModulePluginDemoAudioProcessor::reset() lowPassFilter.reset(); highPassFilter.reset(); convolution.reset(); + oversampling->reset(); } void DspModulePluginDemoAudioProcessor::releaseResources() @@ -115,20 +121,34 @@ void DspModulePluginDemoAudioProcessor::process (dsp::ProcessContextReplacing oversampledBlock; + + setLatencySamples (audioCurrentlyOversampled ? roundFloatToInt (oversampling->getLatencyInSamples()) : 0); + + if (audioCurrentlyOversampled) + oversampledBlock = oversampling->processSamplesUp (context.getInputBlock()); + + dsp::ProcessContextReplacing waveshaperContext = audioCurrentlyOversampled ? dsp::ProcessContextReplacing (oversampledBlock) : context; + // Waveshaper processing, for distortion generation, thanks to the input gain // The fast tanh can be used instead of std::tanh to reduce the CPU load auto waveshaperIndex = waveshaperParam->getIndex(); if (isPositiveAndBelow (waveshaperIndex, (int) numWaveShapers) ) { - waveShapers[waveshaperIndex].process (context); + waveShapers[waveshaperIndex].process (waveshaperContext); if (waveshaperIndex == 1) - clipping.process(context); + clipping.process (waveshaperContext); - context.getOutputBlock() *= 0.7f; + waveshaperContext.getOutputBlock() *= 0.7f; } + // Downsampling + if (audioCurrentlyOversampled) + oversampling->processSamplesDown (context.getOutputBlock()); + // Post-lowpass filtering lowPassFilter.process (context); @@ -209,13 +229,20 @@ bool DspModulePluginDemoAudioProcessor::producesMidi() const //============================================================================== void DspModulePluginDemoAudioProcessor::updateParameters() { + auto newOversampling = oversamplingParam->get(); + if (newOversampling != audioCurrentlyOversampled) + { + audioCurrentlyOversampled = newOversampling; + oversampling->reset(); + } + + //============================================================================== auto inputdB = Decibels::decibelsToGain (inputVolumeParam->get()); auto outputdB = Decibels::decibelsToGain (outputVolumeParam->get()); if (inputVolume.getGainLinear() != inputdB) inputVolume.setGainLinear (inputdB); if (outputVolume.getGainLinear() != outputdB) outputVolume.setGainLinear (outputdB); - dsp::IIR::Coefficients::Ptr newHighPassCoeffs, newLowPassCoeffs; auto newSlopeType = slopeParam->getIndex(); if (newSlopeType == 0) @@ -246,6 +273,7 @@ void DspModulePluginDemoAudioProcessor::updateParameters() } cabinetIsBypassed = ! cabinetSimParam->get(); + } //============================================================================== diff --git a/examples/DSP module plugin demo/Source/PluginProcessor.h b/examples/DSP module plugin demo/Source/PluginProcessor.h index 2a8fd42ef0..4574cb33a7 100644 --- a/examples/DSP module plugin demo/Source/PluginProcessor.h +++ b/examples/DSP module plugin demo/Source/PluginProcessor.h @@ -88,6 +88,7 @@ public: AudioParameterChoice* cabinetTypeParam; AudioParameterBool* cabinetSimParam; + AudioParameterBool* oversamplingParam; private: //============================================================================== @@ -103,6 +104,9 @@ private: dsp::Gain inputVolume, outputVolume; + ScopedPointer> oversampling; + bool audioCurrentlyOversampled = false; + Atomic cabinetType; bool cabinetIsBypassed = false; diff --git a/modules/juce_dsp/processors/juce_Oversampling.cpp b/modules/juce_dsp/processors/juce_Oversampling.cpp index 5748ec272d..a6349966ac 100644 --- a/modules/juce_dsp/processors/juce_Oversampling.cpp +++ b/modules/juce_dsp/processors/juce_Oversampling.cpp @@ -34,7 +34,12 @@ class OversamplingEngine { public: //=============================================================================== - OversamplingEngine (size_t newFactor) { factor = newFactor; } + OversamplingEngine (size_t newNumChannels, size_t newFactor) + { + numChannels = newNumChannels; + factor = newFactor; + } + virtual ~OversamplingEngine() {} //=============================================================================== @@ -43,7 +48,7 @@ public: virtual void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling) { - buffer.setSize (1, static_cast (maximumNumberOfSamplesBeforeOversampling * factor)); + buffer.setSize (static_cast (numChannels), static_cast (maximumNumberOfSamplesBeforeOversampling * factor), false, false, true); } virtual void reset() @@ -51,16 +56,19 @@ public: buffer.clear(); } - SampleType* getProcessedSamples() { return buffer.getWritePointer (0); } - size_t getNumProcessedSamples() { return static_cast (buffer.getNumSamples()); } + dsp::AudioBlock getProcessedSamples (size_t numSamples) + { + return dsp::AudioBlock (buffer).getSubBlock (0, numSamples); + } - virtual void processSamplesUp (SampleType *samples, size_t numSamples) = 0; - virtual void processSamplesDown (SampleType *samples, size_t numSamples) = 0; + virtual void processSamplesUp (dsp::AudioBlock &inputBlock) = 0; + virtual void processSamplesDown (dsp::AudioBlock &outputBlock) = 0; protected: //=============================================================================== AudioBuffer buffer; size_t factor; + size_t numChannels; }; @@ -73,7 +81,7 @@ class OversamplingDummy : public OversamplingEngine { public: //=============================================================================== - OversamplingDummy() : OversamplingEngine(1) {} + OversamplingDummy (size_t numChannels) : OversamplingEngine (numChannels, 1) {} ~OversamplingDummy() {} //=============================================================================== @@ -82,20 +90,22 @@ public: return 0.f; } - void processSamplesUp (SampleType *samples, size_t numSamples) override + void processSamplesUp (dsp::AudioBlock &inputBlock) override { - auto bufferSamples = this->buffer.getWritePointer (0); + jassert (inputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); + jassert (inputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); - for (size_t i = 0; i < numSamples; i++) - bufferSamples[i] = samples[i]; + for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++) + OversamplingEngine::buffer.copyFrom (static_cast (channel), 0, + inputBlock.getChannelPointer (channel), static_cast (inputBlock.getNumSamples())); } - void processSamplesDown (SampleType *samples, size_t numSamples) override + void processSamplesDown (dsp::AudioBlock &outputBlock) override { - auto bufferSamples = OversamplingEngine::buffer.getWritePointer (0); + jassert (outputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); + jassert (outputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); - for (size_t i = 0; i < numSamples; i++) - samples[i] = bufferSamples[i]; + outputBlock.copy (OversamplingEngine::getProcessedSamples (outputBlock.getNumSamples())); } private: @@ -104,8 +114,7 @@ private: }; //=============================================================================== -/** - Oversampling engine class performing 2 times oversampling using the Filter +/** Oversampling engine class performing 2 times oversampling using the Filter Design FIR Equiripple method. The resulting filter is linear phase, symmetric, and has every two samples but the middle one equal to zero, leading to specific processing optimizations. @@ -115,21 +124,26 @@ class Oversampling2TimesEquirippleFIR : public OversamplingEngine { public: //=============================================================================== - Oversampling2TimesEquirippleFIR (SampleType normalizedTransitionWidthUp, + Oversampling2TimesEquirippleFIR (size_t numChannels, + SampleType normalizedTransitionWidthUp, SampleType stopbandAttenuationdBUp, SampleType normalizedTransitionWidthDown, - SampleType stopbandAttenuationdBDown) : OversamplingEngine (2) + SampleType stopbandAttenuationdBDown) : OversamplingEngine (numChannels, 2) { coefficientsUp = *dsp::FilterDesign::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp); coefficientsDown = *dsp::FilterDesign::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown); - auto N = coefficientsDown.getFilterOrder() + 1; + auto N = coefficientsUp.getFilterOrder() + 1; + stateUp.setSize (static_cast (numChannels), static_cast (N)); + + N = coefficientsDown.getFilterOrder() + 1; auto Ndiv2 = N / 2; auto Ndiv4 = Ndiv2 / 2; - stateUp.setSize (1, static_cast (coefficientsUp.getFilterOrder() + 1)); - stateDown.setSize (1, static_cast (N)); - stateDown2.setSize (1, static_cast (Ndiv4)); + stateDown.setSize (static_cast (numChannels), static_cast (N)); + stateDown2.setSize (static_cast (numChannels), static_cast (Ndiv4 + 1)); + + position.resize (static_cast (numChannels)); } ~Oversampling2TimesEquirippleFIR() {} @@ -137,7 +151,7 @@ public: //=============================================================================== SampleType getLatencyInSamples() override { - return static_cast (coefficientsUp.getFilterOrder() + coefficientsDown.getFilterOrder()); + return static_cast (coefficientsUp.getFilterOrder() + coefficientsDown.getFilterOrder()) * 0.5f; } void reset() override @@ -148,83 +162,103 @@ public: stateDown.clear(); stateDown2.clear(); - position = 0; + position.fill (0); } - void processSamplesUp (SampleType *samples, size_t numSamples) override + void processSamplesUp (dsp::AudioBlock &inputBlock) override { + jassert (inputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); + jassert (inputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); + // Initialization - auto bufferSamples = OversamplingEngine::buffer.getWritePointer (0); auto fir = coefficientsUp.getRawCoefficients(); - auto buf = stateUp.getWritePointer (0); - auto N = coefficientsUp.getFilterOrder() + 1; auto Ndiv2 = N / 2; + auto numSamples = inputBlock.getNumSamples(); // Processing - for (size_t i = 0; i < numSamples; i++) + for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++) { - // Input - buf[N - 1] = 2 * samples[i]; + auto bufferSamples = OversamplingEngine::buffer.getWritePointer (static_cast (channel)); + auto buf = stateUp.getWritePointer (static_cast (channel)); + auto samples = inputBlock.getChannelPointer (channel); + + for (size_t i = 0; i < numSamples; i++) + { + // Input + buf[N - 1] = 2 * samples[i]; - // Convolution - auto out = static_cast (0.0); - for (size_t k = 0; k < Ndiv2; k += 2) - out += (buf[k] + buf[N - k - 1]) * fir[k]; + // Convolution + auto out = static_cast (0.0); + for (size_t k = 0; k < Ndiv2; k += 2) + out += (buf[k] + buf[N - k - 1]) * fir[k]; - // Outputs - bufferSamples[i << 1] = out; - bufferSamples[(i << 1) + 1] = buf[Ndiv2 + 1] * fir[Ndiv2]; + // Outputs + bufferSamples[i << 1] = out; + bufferSamples[(i << 1) + 1] = buf[Ndiv2 + 1] * fir[Ndiv2]; - // Shift data - for (size_t k = 0; k < N - 2; k+=2) - buf[k] = buf[k + 2]; + // Shift data + for (size_t k = 0; k < N - 2; k += 2) + buf[k] = buf[k + 2]; + } } } - void processSamplesDown (SampleType *samples, size_t numSamples) override + void processSamplesDown (dsp::AudioBlock &outputBlock) override { + jassert (outputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); + jassert (outputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); + // Initialization - auto bufferSamples = OversamplingEngine::buffer.getWritePointer (0); auto fir = coefficientsDown.getRawCoefficients(); - auto buf = stateDown.getWritePointer (0); - auto buf2 = stateDown2.getWritePointer (0); - auto N = coefficientsDown.getFilterOrder() + 1; auto Ndiv2 = N / 2; auto Ndiv4 = Ndiv2 / 2; + auto numSamples = outputBlock.getNumSamples(); // Processing - for (size_t i = 0; i < numSamples; i++) + for (size_t channel = 0; channel < outputBlock.getNumChannels(); channel++) { - // Input - buf[N - 1] = bufferSamples[2 * i]; + auto bufferSamples = OversamplingEngine::buffer.getWritePointer (static_cast (channel)); + auto buf = stateDown.getWritePointer (static_cast (channel)); + auto buf2 = stateDown2.getWritePointer (static_cast (channel)); + auto samples = outputBlock.getChannelPointer (channel); + auto pos = position.getUnchecked (static_cast (channel)); + + for (size_t i = 0; i < numSamples; i++) + { + // Input + buf[N - 1] = bufferSamples[i << 1]; - // Convolution - auto out = static_cast (0.0); - for (size_t k = 0; k < Ndiv2; k += 2) - out += (buf[k] + buf[N - k - 1]) * fir[k]; + // Convolution + auto out = static_cast (0.0); + for (size_t k = 0; k < Ndiv2; k += 2) + out += (buf[k] + buf[N - k - 1]) * fir[k]; - // Output - out += buf2[position] * fir[Ndiv2]; - buf2[position] = bufferSamples[2 * i + 1]; + // Output + out += buf2[pos] * fir[Ndiv2]; + buf2[pos] = bufferSamples[(i << 1) + 1]; - samples[i] = out; + samples[i] = out; - // Shift data - for (size_t k = 0; k < N - 2; k++) - buf[k] = buf[k + 2]; + // Shift data + for (size_t k = 0; k < N - 2; k++) + buf[k] = buf[k + 2]; + + // Circular buffer + pos = (pos == 0 ? Ndiv4 : pos - 1); + } - // Circular buffer - position = (position == 0 ? Ndiv4 - 1 : position - 1); + position.setUnchecked (static_cast (channel), pos); } + } private: //=============================================================================== dsp::FIR::Coefficients coefficientsUp, coefficientsDown; AudioBuffer stateUp, stateDown, stateDown2; - size_t position; + Array position; //=============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesEquirippleFIR) @@ -241,10 +275,11 @@ class Oversampling2TimesPolyphaseIIR : public OversamplingEngine { public: //=============================================================================== - Oversampling2TimesPolyphaseIIR (SampleType normalizedTransitionWidthUp, + Oversampling2TimesPolyphaseIIR (size_t numChannels, + SampleType normalizedTransitionWidthUp, SampleType stopbandAttenuationdBUp, SampleType normalizedTransitionWidthDown, - SampleType stopbandAttenuationdBDown) : OversamplingEngine (2) + SampleType stopbandAttenuationdBDown) : OversamplingEngine (2, numChannels) { auto structureUp = dsp::FilterDesign::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp); dsp::IIR::Coefficients coeffsUp = getCoefficients (structureUp); @@ -266,8 +301,9 @@ public: for (auto i = 1; i < structureDown.delayedPath.size(); i++) coefficientsDown.add (structureDown.delayedPath[i].coefficients[0]); - v1Up.resize (coefficientsUp.size()); - v1Down.resize (coefficientsDown.size()); + v1Up.setSize (static_cast (numChannels), coefficientsUp.size()); + v1Down.setSize (static_cast (numChannels), coefficientsDown.size()); + delayDown.resize (static_cast (numChannels)); } ~Oversampling2TimesPolyphaseIIR() {} @@ -282,95 +318,113 @@ public: { OversamplingEngine::reset(); - v1Up.fill (0); - v1Down.fill (0); - delayDown = 0; + v1Up.clear(); + v1Down.clear(); + delayDown.fill (0); } - void processSamplesUp (SampleType *samples, size_t numSamples) override + void processSamplesUp (dsp::AudioBlock &inputBlock) override { + jassert (inputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); + jassert (inputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); + // Initialization - auto bufferSamples = OversamplingEngine::buffer.getWritePointer (0); auto coeffs = coefficientsUp.getRawDataPointer(); - auto lv1 = v1Up.getRawDataPointer(); - auto numStages = coefficientsUp.size(); auto delayedStages = numStages / 2; auto directStages = numStages - delayedStages; + auto numSamples = inputBlock.getNumSamples(); // Processing - for (size_t i = 0; i < numSamples; i++) + for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++) { - // Direct path cascaded allpass filters - auto input = samples[i]; - for (auto n = 0; n < directStages; n++) - { - auto alpha = coeffs[n]; - auto output = alpha * input + lv1[n]; - lv1[n] = input - alpha * output; - input = output; - } + auto bufferSamples = OversamplingEngine::buffer.getWritePointer (static_cast (channel)); + auto lv1 = v1Up.getWritePointer (static_cast (channel)); + auto samples = inputBlock.getChannelPointer (channel); - // Output - bufferSamples[i << 1] = input; - - // Delayed path cascaded allpass filters - input = samples[i]; - for (auto n = directStages; n < numStages; n++) + for (size_t i = 0; i < numSamples; i++) { - auto alpha = coeffs[n]; - auto output = alpha * input + lv1[n]; - lv1[n] = input - alpha * output; - input = output; + // Direct path cascaded allpass filters + auto input = samples[i]; + for (auto n = 0; n < directStages; n++) + { + auto alpha = coeffs[n]; + auto output = alpha * input + lv1[n]; + lv1[n] = input - alpha * output; + input = output; + } + + // Output + bufferSamples[i << 1] = input; + + // Delayed path cascaded allpass filters + input = samples[i]; + for (auto n = directStages; n < numStages; n++) + { + auto alpha = coeffs[n]; + auto output = alpha * input + lv1[n]; + lv1[n] = input - alpha * output; + input = output; + } + + // Output + bufferSamples[(i << 1) + 1] = input; } - - // Output - bufferSamples[(i << 1) + 1] = input; } // Snap To Zero snapToZero (true); - } - void processSamplesDown (SampleType *samples, size_t numSamples) override + void processSamplesDown (dsp::AudioBlock &outputBlock) override { + jassert (outputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); + jassert (outputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); + // Initialization - auto bufferSamples = OversamplingEngine::buffer.getWritePointer (0); auto coeffs = coefficientsDown.getRawDataPointer(); - auto lv1 = v1Down.getRawDataPointer(); - auto numStages = coefficientsDown.size(); auto delayedStages = numStages / 2; auto directStages = numStages - delayedStages; + auto numSamples = outputBlock.getNumSamples(); // Processing - for (size_t i = 0; i < numSamples; i++) + for (size_t channel = 0; channel < outputBlock.getNumChannels(); channel++) { - // Direct path cascaded allpass filters - auto input = bufferSamples[i << 1]; - for (auto n = 0; n < directStages; n++) - { - auto alpha = coeffs[n]; - auto output = alpha * input + lv1[n]; - lv1[n] = input - alpha * output; - input = output; - } - auto directOut = input; + auto bufferSamples = OversamplingEngine::buffer.getWritePointer (static_cast (channel)); + auto lv1 = v1Down.getWritePointer (static_cast (channel)); + auto samples = outputBlock.getChannelPointer (channel); + auto delay = delayDown.getUnchecked (static_cast (channel)); - // Delayed path cascaded allpass filters - input = bufferSamples[(i << 1) + 1]; - for (auto n = directStages; n < numStages; n++) + for (size_t i = 0; i < numSamples; i++) { - auto alpha = coeffs[n]; - auto output = alpha * input + lv1[n]; - lv1[n] = input - alpha * output; - input = output; + // Direct path cascaded allpass filters + auto input = bufferSamples[i << 1]; + for (auto n = 0; n < directStages; n++) + { + auto alpha = coeffs[n]; + auto output = alpha * input + lv1[n]; + lv1[n] = input - alpha * output; + input = output; + } + auto directOut = input; + + // Delayed path cascaded allpass filters + input = bufferSamples[(i << 1) + 1]; + for (auto n = directStages; n < numStages; n++) + { + auto alpha = coeffs[n]; + auto output = alpha * input + lv1[n]; + lv1[n] = input - alpha * output; + input = output; + } + + // Output + samples[i] = (delay + directOut) * static_cast (0.5); + delay = input; } - // Output - samples[i] = (delayDown + directOut) * static_cast (0.5); - delayDown = input; + delayDown.setUnchecked (static_cast (channel), delay); } // Snap To Zero @@ -381,19 +435,25 @@ public: { if (snapUpProcessing) { - auto lv1 = v1Up.getRawDataPointer(); - auto numStages = coefficientsUp.size(); + for (auto channel = 0; channel < OversamplingEngine::buffer.getNumChannels(); channel++) + { + auto lv1 = v1Up.getWritePointer (channel); + auto numStages = coefficientsUp.size(); - for (auto n = 0; n < numStages; n++) - JUCE_SNAP_TO_ZERO (lv1[n]); + for (auto n = 0; n < numStages; n++) + JUCE_SNAP_TO_ZERO (lv1[n]); + } } else { - auto lv1 = v1Down.getRawDataPointer(); - auto numStages = coefficientsDown.size(); + for (auto channel = 0; channel < OversamplingEngine::buffer.getNumChannels(); channel++) + { + auto lv1 = v1Down.getWritePointer (channel); + auto numStages = coefficientsDown.size(); - for (auto n = 0; n < numStages; n++) - JUCE_SNAP_TO_ZERO (lv1[n]); + for (auto n = 0; n < numStages; n++) + JUCE_SNAP_TO_ZERO (lv1[n]); + } } } @@ -478,8 +538,8 @@ private: Array coefficientsUp, coefficientsDown; SampleType latency; - Array v1Up, v1Down; - SampleType delayDown; + AudioBuffer v1Up, v1Down; + Array delayDown; //=============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesPolyphaseIIR) @@ -492,43 +552,53 @@ Oversampling::Oversampling (size_t newNumChannels, size_t newFactor, { jassert (newFactor >= 0 && newFactor <= 4 && newNumChannels > 0); - factorOversampling = (size_t) 1 << newFactor; + factorOversampling = static_cast (1) << newFactor; isMaximumQuality = newMaxQuality; type = newType; numChannels = newNumChannels; if (newFactor == 0) { - for (size_t channel = 0; channel < numChannels; channel++) - engines.add (new OversamplingDummy()); - numStages = 1; + engines.add (new OversamplingDummy (numChannels)); } else if (type == FilterType::filterHalfBandPolyphaseIIR) { numStages = newFactor; - for (size_t channel = 0; channel < numChannels; channel++) - for (size_t n = 0; n < numStages; n++) - { - auto tw1 = (isMaximumQuality ? 0.10f : 0.12f); - auto tw2 = (isMaximumQuality ? 0.12f : 0.15f); + for (size_t n = 0; n < numStages; n++) + { + auto twUp = (isMaximumQuality ? 0.10f : 0.12f) * (n == 0 ? 0.5f : 1.f); + auto twDown = (isMaximumQuality ? 0.12f : 0.15f) * (n == 0 ? 0.5f : 1.f); - engines.add (new Oversampling2TimesPolyphaseIIR (tw1, -75.f + 10.f * n, tw2, -70.f + 10.f * n)); - } + auto gaindBStartUp = (isMaximumQuality ? -75.f : -65.f); + auto gaindBStartDown = (isMaximumQuality ? -70.f : -60.f); + auto gaindBFactorUp = (isMaximumQuality ? 10.f : 8.f); + auto gaindBFactorDown = (isMaximumQuality ? 10.f : 8.f); + + engines.add (new Oversampling2TimesPolyphaseIIR (numChannels, + twUp, gaindBStartUp + gaindBFactorUp * n, + twDown, gaindBStartDown + gaindBFactorDown * n)); + } } else if (type == FilterType::filterHalfBandFIREquiripple) { numStages = newFactor; - for (size_t channel = 0; channel < numChannels; channel++) - for (size_t n = 0; n < numStages; n++) - { - auto tw1 = (isMaximumQuality ? 0.10f : 0.12f); - auto tw2 = (isMaximumQuality ? 0.12f : 0.15f); + for (size_t n = 0; n < numStages; n++) + { + auto twUp = (isMaximumQuality ? 0.10f : 0.12f) * (n == 0 ? 0.5f : 1.f); + auto twDown = (isMaximumQuality ? 0.12f : 0.15f) * (n == 0 ? 0.5f : 1.f); - engines.add (new Oversampling2TimesEquirippleFIR (tw1, -90.f + 10.f * n, tw2, -70.f + 10.f * n)); - } + auto gaindBStartUp = (isMaximumQuality ? -90.f : -70.f); + auto gaindBStartDown = (isMaximumQuality ? -70.f : -60.f); + auto gaindBFactorUp = (isMaximumQuality ? 10.f : 8.f); + auto gaindBFactorDown = (isMaximumQuality ? 10.f : 8.f); + + engines.add (new Oversampling2TimesEquirippleFIR (numChannels, + twUp, gaindBStartUp + gaindBFactorUp * n, + twDown, gaindBStartDown + gaindBFactorDown * n)); + } } } @@ -550,7 +620,7 @@ SampleType Oversampling::getLatencyInSamples() noexcept auto& engine = *engines[static_cast (n)]; order *= engine.getFactor(); - latency += engine.getLatencyInSamples() / std::pow (static_cast (2), static_cast (order)); + latency += engine.getLatencyInSamples() / static_cast (order); } return latency; @@ -568,18 +638,14 @@ void Oversampling::initProcessing (size_t maximumNumberOfSamplesBefo { jassert (engines.size() > 0); - for (size_t channel = 0; channel < numChannels; channel++) - { - auto currentNumSamples = maximumNumberOfSamplesBeforeOversampling; - auto offset = numStages * channel; + auto currentNumSamples = maximumNumberOfSamplesBeforeOversampling; - for (size_t n = 0; n < numStages; n++) - { - auto& engine = *engines[static_cast (n + offset)]; + for (size_t n = 0; n < numStages; n++) + { + auto& engine = *engines[static_cast (n)]; - engine.initProcessing (currentNumSamples); - currentNumSamples *= engine.getFactor(); - } + engine.initProcessing (currentNumSamples); + currentNumSamples *= engine.getFactor(); } isReady = true; @@ -597,75 +663,49 @@ void Oversampling::reset() noexcept } template -typename dsp::AudioBlock Oversampling::getProcessedSamples() +typename dsp::AudioBlock Oversampling::processSamplesUp (const dsp::AudioBlock &inputBlock) noexcept { jassert (engines.size() > 0); - Array arrayChannels; - - for (size_t channel = 0; channel < numChannels; channel++) - arrayChannels.add (engines[static_cast (((channel + 1) * numStages) - 1)]->getProcessedSamples()); - - auto numSamples = engines[static_cast (numStages - 1)]->getNumProcessedSamples(); - auto block = dsp::AudioBlock (arrayChannels.getRawDataPointer(), numChannels, numSamples); - - return block; -} - -template -void Oversampling::processSamplesUp (dsp::AudioBlock &block) noexcept -{ - jassert (engines.size() > 0 && block.getNumChannels() <= numChannels); - if (! isReady) - return; - - for (size_t channel = 0; channel < jmin (numChannels, block.getNumChannels()); channel++) - { - SampleType* dataSamples = block.getChannelPointer (channel); - auto currentNumSamples = block.getNumSamples(); - auto offset = numStages * channel; + return dsp::AudioBlock(); - for (size_t n = 0; n < numStages; n++) - { - auto& engine = *engines[static_cast (n + offset)]; - engine.processSamplesUp (dataSamples, currentNumSamples); + dsp::AudioBlock audioBlock = inputBlock; - currentNumSamples *= engine.getFactor(); - dataSamples = engine.getProcessedSamples(); - } + for (size_t n = 0; n < numStages; n++) + { + auto& engine = *engines[static_cast (n)]; + engine.processSamplesUp (audioBlock); + audioBlock = engine.getProcessedSamples (audioBlock.getNumSamples() * engine.getFactor()); } + + return audioBlock; } template -void Oversampling::processSamplesDown (dsp::AudioBlock &block) noexcept +void Oversampling::processSamplesDown (dsp::AudioBlock &outputBlock) noexcept { - jassert (engines.size() > 0 && block.getNumChannels() <= numChannels); + jassert (engines.size() > 0); if (! isReady) return; - for (size_t channel = 0; channel < jmin (numChannels, block.getNumChannels()); channel++) - { - auto currentNumSamples = block.getNumSamples(); - auto offset = numStages * channel; - - for (size_t n = 0; n < numStages - 1; n++) - currentNumSamples *= engines[static_cast (n + offset)]->getFactor(); + auto currentNumSamples = outputBlock.getNumSamples(); - for (size_t n = numStages - 1; n > 0; n--) - { - auto& engine = *engines[static_cast (n + offset)]; + for (size_t n = 0; n < numStages - 1; n++) + currentNumSamples *= engines[static_cast (n)]->getFactor(); - auto dataSamples = engines[static_cast (n + offset - 1)]->getProcessedSamples(); - engine.processSamplesDown (dataSamples, currentNumSamples); + for (size_t n = numStages - 1; n > 0; n--) + { + auto& engine = *engines[static_cast (n)]; - currentNumSamples /= engine.getFactor(); - } + auto audioBlock = engines[static_cast (n - 1)]->getProcessedSamples (currentNumSamples); + engine.processSamplesDown (audioBlock); - engines[static_cast (offset)]->processSamplesDown (block.getChannelPointer (channel), currentNumSamples); + currentNumSamples /= engine.getFactor(); } + engines[static_cast (0)]->processSamplesDown (outputBlock); } template class Oversampling; diff --git a/modules/juce_dsp/processors/juce_Oversampling.h b/modules/juce_dsp/processors/juce_Oversampling.h index 0bcc7357a8..8b6da27c0c 100644 --- a/modules/juce_dsp/processors/juce_Oversampling.h +++ b/modules/juce_dsp/processors/juce_Oversampling.h @@ -87,6 +87,9 @@ public: in your main processor to compensate the additional latency involved with the oversampling, for example with a dry / wet functionality, and to report the latency to the DAW. + + Note : the latency might not be integer, so you might need to round its value + or to compensate it properly in your processing code. */ SampleType getLatencyInSamples() noexcept; @@ -102,21 +105,21 @@ public: /** Resets the processing pipeline, ready to oversample a new stream of data. */ void reset() noexcept; - /** Must be called to perform the upsampling, prior to any oversampled processing. */ - void processSamplesUp (dsp::AudioBlock &block) noexcept; + /** Must be called to perform the upsampling, prior to any oversampled processing. - /** Can be called to access to the oversampled input signal, to perform any non- - linear processing which needs the higher sample rate. Don't forget to set - the sample rate of that processing to N times the original sample rate. + Returns an AudioBlock referencing the oversampled input signal, which must be + used to perform the non-linear processing which needs the higher sample rate. + Don't forget to set the sample rate of that processing to N times the original + sample rate. */ - dsp::AudioBlock getProcessedSamples(); + dsp::AudioBlock processSamplesUp (const dsp::AudioBlock &inputBlock) noexcept; /** Must be called to perform the downsampling, after the upsampling and the non-linear processing. The output signal is probably delayed by the internal latency of the whole oversampling behaviour, so don't forget to take this into account. */ - void processSamplesDown (dsp::AudioBlock &block) noexcept; + void processSamplesDown (dsp::AudioBlock &outputBlock) noexcept; private: //===============================================================================