From bd6ca234cbc3226daf52cfdb1f6c7b83bc1e91a8 Mon Sep 17 00:00:00 2001 From: hogliux Date: Wed, 23 Aug 2017 16:15:58 +0100 Subject: [PATCH] Added Oversampling processor to DSP module --- modules/juce_dsp/juce_dsp.cpp | 1 + modules/juce_dsp/juce_dsp.h | 1 + .../juce_dsp/processors/juce_Oversampling.cpp | 672 ++++++++++++++++++ .../juce_dsp/processors/juce_Oversampling.h | 135 ++++ 4 files changed, 809 insertions(+) create mode 100644 modules/juce_dsp/processors/juce_Oversampling.cpp create mode 100644 modules/juce_dsp/processors/juce_Oversampling.h diff --git a/modules/juce_dsp/juce_dsp.cpp b/modules/juce_dsp/juce_dsp.cpp index e8667ebd2a..fd9dea4cc1 100644 --- a/modules/juce_dsp/juce_dsp.cpp +++ b/modules/juce_dsp/juce_dsp.cpp @@ -55,6 +55,7 @@ namespace juce { #include "processors/juce_FIRFilter.cpp" #include "processors/juce_IIRFilter.cpp" + #include "processors/juce_Oversampling.cpp" #include "maths/juce_SpecialFunctions.cpp" #include "maths/juce_Matrix.cpp" #include "maths/juce_LookupTable.cpp" diff --git a/modules/juce_dsp/juce_dsp.h b/modules/juce_dsp/juce_dsp.h index 086e8d1808..5a2cc076a5 100644 --- a/modules/juce_dsp/juce_dsp.h +++ b/modules/juce_dsp/juce_dsp.h @@ -213,6 +213,7 @@ namespace juce #include "processors/juce_FIRFilter.h" #include "processors/juce_Oscillator.h" #include "processors/juce_StateVariableFilter.h" + #include "processors/juce_Oversampling.h" #include "frequency/juce_FFT.h" #include "frequency/juce_Convolution.h" #include "frequency/juce_Windowing.h" diff --git a/modules/juce_dsp/processors/juce_Oversampling.cpp b/modules/juce_dsp/processors/juce_Oversampling.cpp new file mode 100644 index 0000000000..c5d2a570e2 --- /dev/null +++ b/modules/juce_dsp/processors/juce_Oversampling.cpp @@ -0,0 +1,672 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +//=============================================================================== +/** Abstract class for the provided oversampling engines used internally in + the Oversampling class. +*/ +template +class OversamplingEngine +{ +public: + //=============================================================================== + OversamplingEngine (size_t newFactor) { factor = newFactor; } + virtual ~OversamplingEngine() {} + + //=============================================================================== + virtual SampleType getLatencyInSamples() = 0; + size_t getFactor() { return factor; } + + virtual void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling) + { + buffer.setSize (1, static_cast (maximumNumberOfSamplesBeforeOversampling * factor)); + } + + virtual void reset() + { + buffer.clear(); + } + + SampleType* getProcessedSamples() { return buffer.getWritePointer (0); } + size_t getNumProcessedSamples() { return static_cast (buffer.getNumSamples()); } + + virtual void processSamplesUp (SampleType *samples, size_t numSamples) = 0; + virtual void processSamplesDown (SampleType *samples, size_t numSamples) = 0; + +protected: + //=============================================================================== + AudioBuffer buffer; + size_t factor; +}; + + +//=============================================================================== +/** Dummy oversampling engine class which simply copies and pastes the input + signal, which could be equivalent to a "one time" oversampling processing. +*/ +template +class OversamplingDummy : public OversamplingEngine +{ +public: + //=============================================================================== + OversamplingDummy() : OversamplingEngine(1) {} + ~OversamplingDummy() {} + + //=============================================================================== + SampleType getLatencyInSamples() override + { + return 0.f; + } + + void processSamplesUp (SampleType *samples, size_t numSamples) override + { + auto bufferSamples = this->buffer.getWritePointer (0); + + for (size_t i = 0; i < numSamples; i++) + bufferSamples[i] = samples[i]; + } + + void processSamplesDown (SampleType *samples, size_t numSamples) override + { + auto bufferSamples = OversamplingEngine::buffer.getWritePointer (0); + + for (size_t i = 0; i < numSamples; i++) + samples[i] = bufferSamples[i]; + } + +private: + //=============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OversamplingDummy) +}; + +//=============================================================================== +/** + 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. +*/ +template +class Oversampling2TimesEquirippleFIR : public OversamplingEngine +{ +public: + //=============================================================================== + Oversampling2TimesEquirippleFIR (SampleType normalizedTransitionWidthUp, + SampleType stopbandAttenuationdBUp, + SampleType normalizedTransitionWidthDown, + SampleType stopbandAttenuationdBDown) : OversamplingEngine (2) + { + coefficientsUp = *dsp::FilterDesign::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp); + coefficientsDown = *dsp::FilterDesign::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown); + + auto 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)); + } + + ~Oversampling2TimesEquirippleFIR() {} + + //=============================================================================== + SampleType getLatencyInSamples() override + { + return static_cast (coefficientsUp.getFilterOrder() + coefficientsDown.getFilterOrder()); + } + + void reset() override + { + OversamplingEngine::reset(); + + stateUp.clear(); + stateDown.clear(); + stateDown2.clear(); + + position = 0; + } + + void processSamplesUp (SampleType *samples, size_t numSamples) override + { + // 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; + + // Processing + 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]; + + // 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]; + } + } + + void processSamplesDown (SampleType *samples, size_t numSamples) override + { + // 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; + + // Processing + for (size_t i = 0; i < numSamples; i++) + { + // Input + buf[N - 1] = bufferSamples[2 * 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]; + + // Output + out += buf2[position] * fir[Ndiv2]; + buf2[position] = bufferSamples[2 * i + 1]; + + samples[i] = out; + + // Shift data + for (size_t k = 0; k < N - 2; k++) + buf[k] = buf[k + 2]; + + // Circular buffer + position = (position == 0 ? Ndiv4 - 1 : position - 1); + } + } + +private: + //=============================================================================== + dsp::FIR::Coefficients coefficientsUp, coefficientsDown; + AudioBuffer stateUp, stateDown, stateDown2; + size_t position; + + //=============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesEquirippleFIR) +}; + + +//=============================================================================== +/** Oversampling engine class performing 2 times oversampling using the Filter + Design IIR Polyphase Allpass Cascaded method. The resulting filter is minimum + phase, and provided with a method to get the exact resulting latency. +*/ +template +class Oversampling2TimesPolyphaseIIR : public OversamplingEngine +{ +public: + //=============================================================================== + Oversampling2TimesPolyphaseIIR (SampleType normalizedTransitionWidthUp, + SampleType stopbandAttenuationdBUp, + SampleType normalizedTransitionWidthDown, + SampleType stopbandAttenuationdBDown) : OversamplingEngine (2) + { + auto structureUp = dsp::FilterDesign::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp); + dsp::IIR::Coefficients coeffsUp = getCoefficients (structureUp); + latency = static_cast (-(coeffsUp.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * 2 * double_Pi)); + + auto structureDown = dsp::FilterDesign::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown); + dsp::IIR::Coefficients coeffsDown = getCoefficients (structureDown); + latency += static_cast (-(coeffsDown.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * 2 * double_Pi)); + + for (auto i = 0; i < structureUp.directPath.size(); i++) + coefficientsUp.add (structureUp.directPath[i].coefficients[0]); + + for (auto i = 1; i < structureUp.delayedPath.size(); i++) + coefficientsUp.add (structureUp.delayedPath[i].coefficients[0]); + + for (auto i = 0; i < structureDown.directPath.size(); i++) + coefficientsDown.add (structureDown.directPath[i].coefficients[0]); + + for (auto i = 1; i < structureDown.delayedPath.size(); i++) + coefficientsDown.add (structureDown.delayedPath[i].coefficients[0]); + + v1Up.resize (coefficientsUp.size()); + v1Down.resize (coefficientsDown.size()); + } + + ~Oversampling2TimesPolyphaseIIR() {} + + //=============================================================================== + SampleType getLatencyInSamples() override + { + return latency; + } + + void reset() override + { + OversamplingEngine::reset(); + + v1Up.fill (0); + v1Down.fill (0); + delayDown = 0; + } + + void processSamplesUp (SampleType *samples, size_t numSamples) override + { + // 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; + + // Processing + for (size_t i = 0; i < numSamples; i++) + { + // 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; + } + + // Snap To Zero + snapToZero (true); + + } + + void processSamplesDown (SampleType *samples, size_t numSamples) override + { + // 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; + + // Processing + for (size_t i = 0; i < numSamples; i++) + { + // 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] = (delayDown + directOut) * static_cast (0.5); + delayDown = input; + } + + // Snap To Zero + snapToZero (false); + } + + void snapToZero (bool snapUpProcessing) + { + if (snapUpProcessing) + { + auto lv1 = v1Up.getRawDataPointer(); + auto numStages = coefficientsUp.size(); + + for (auto n = 0; n < numStages; n++) + JUCE_SNAP_TO_ZERO (lv1[n]); + } + else + { + auto lv1 = v1Down.getRawDataPointer(); + auto numStages = coefficientsDown.size(); + + for (auto n = 0; n < numStages; n++) + JUCE_SNAP_TO_ZERO (lv1[n]); + } + } + +private: + //=============================================================================== + /** This function calculates the equivalent high order IIR filter of a given + polyphase cascaded allpass filters structure. + */ + const dsp::IIR::Coefficients getCoefficients (typename dsp::FilterDesign::IIRPolyphaseAllpassStructure &structure) const + { + dsp::Polynomial numerator1 ({ static_cast (1.0) }); + dsp::Polynomial denominator1 ({ static_cast (1.0) }); + dsp::Polynomial numerator2 ({ static_cast (1.0) }); + dsp::Polynomial denominator2 ({ static_cast (1.0) }); + + dsp::Polynomial temp; + + for (auto n = 0; n < structure.directPath.size(); n++) + { + auto *coeffs = structure.directPath.getReference (n).getRawCoefficients(); + + if (structure.directPath[n].getFilterOrder() == 1) + { + temp = dsp::Polynomial ({ coeffs[0], coeffs[1] }); + numerator1 = numerator1.getProductWith (temp); + + temp = dsp::Polynomial ({ static_cast (1.0), coeffs[2] }); + denominator1 = denominator1.getProductWith (temp); + } + else + { + temp = dsp::Polynomial ({ coeffs[0], coeffs[1], coeffs[2] }); + numerator1 = numerator1.getProductWith (temp); + + temp = dsp::Polynomial ({ static_cast (1.0), coeffs[3], coeffs[4] }); + denominator1 = denominator1.getProductWith (temp); + } + } + + for (auto n = 0; n < structure.delayedPath.size(); n++) + { + auto *coeffs = structure.delayedPath.getReference (n).getRawCoefficients(); + + if (structure.delayedPath[n].getFilterOrder() == 1) + { + temp = dsp::Polynomial ({ coeffs[0], coeffs[1] }); + numerator2 = numerator2.getProductWith (temp); + + temp = dsp::Polynomial ({ static_cast (1.0), coeffs[2] }); + denominator2 = denominator2.getProductWith (temp); + } + else + { + temp = dsp::Polynomial ({ coeffs[0], coeffs[1], coeffs[2] }); + numerator2 = numerator2.getProductWith (temp); + + temp = dsp::Polynomial ({ static_cast (1.0), coeffs[3], coeffs[4] }); + denominator2 = denominator2.getProductWith (temp); + } + } + + dsp::Polynomial numeratorf1 = numerator1.getProductWith (denominator2); + dsp::Polynomial numeratorf2 = numerator2.getProductWith (denominator1); + dsp::Polynomial numerator = numeratorf1.getSumWith (numeratorf2); + dsp::Polynomial denominator = denominator1.getProductWith (denominator2); + + dsp::IIR::Coefficients coeffs; + + coeffs.coefficients.clear(); + auto inversion = static_cast (1.0) / denominator[0]; + + for (auto i = 0; i <= numerator.getOrder(); i++) + coeffs.coefficients.add (numerator[i] * inversion); + + for (auto i = 1; i <= denominator.getOrder(); i++) + coeffs.coefficients.add (denominator[i] * inversion); + + return coeffs; + } + + //=============================================================================== + Array coefficientsUp, coefficientsDown; + SampleType latency; + + Array v1Up, v1Down; + SampleType delayDown; + + //=============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesPolyphaseIIR) +}; + + +//=============================================================================== +template +Oversampling::Oversampling (size_t newNumChannels, size_t newFactor, FilterType newType, bool newMaxQuality) +{ + jassert (newFactor >= 0 && newFactor <= 4 && newNumChannels > 0); + + factorOversampling = 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; + } + 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); + + engines.add (new Oversampling2TimesPolyphaseIIR (tw1, -75.f + 10.f * n, tw2, -70.f + 10.f * 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); + + engines.add (new Oversampling2TimesEquirippleFIR (tw1, -90.f + 10.f * n, tw2, -70.f + 10.f * n)); + } + } +} + +template +Oversampling::~Oversampling() +{ + engines.clear(); +} + +//=============================================================================== +template +SampleType Oversampling::getLatencyInSamples() noexcept +{ + auto latency = static_cast (0); + auto order = 1; + + for (size_t n = 0; n < numStages; n++) + { + auto& engine = *engines[static_cast (n)]; + + order *= engine.getFactor(); + latency += engine.getLatencyInSamples() / std::pow (static_cast (2), static_cast (order)); + } + + return latency; +} + +template +size_t Oversampling::getOversamplingFactor() noexcept +{ + return factorOversampling; +} + +//=============================================================================== +template +void Oversampling::initProcessing (size_t maximumNumberOfSamplesBeforeOversampling) +{ + jassert (engines.size() > 0); + + for (size_t channel = 0; channel < numChannels; channel++) + { + auto currentNumSamples = maximumNumberOfSamplesBeforeOversampling; + auto offset = numStages * channel; + + for (size_t n = 0; n < numStages; n++) + { + auto& engine = *engines[static_cast (n + offset)]; + + engine.initProcessing (currentNumSamples); + currentNumSamples *= engine.getFactor(); + } + } + isReady = true; + + reset(); +} + +template +void Oversampling::reset() noexcept +{ + jassert (engines.size() > 0); + + if (isReady) + for (auto n = 0; n < engines.size(); n++) + engines[n]->reset(); +} + +template +typename dsp::AudioBlock Oversampling::getProcessedSamples() +{ + 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; + + for (size_t n = 0; n < numStages; n++) + { + auto& engine = *engines[static_cast (n + offset)]; + engine.processSamplesUp (dataSamples, currentNumSamples); + + currentNumSamples *= engine.getFactor(); + dataSamples = engine.getProcessedSamples(); + } + } +} + +template +void Oversampling::processSamplesDown (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++) + { + auto currentNumSamples = block.getNumSamples(); + auto offset = numStages * channel; + + for (size_t n = 0; n < numStages - 1; n++) + currentNumSamples *= engines[static_cast (n + offset)]->getFactor(); + + for (size_t n = numStages - 1; n > 0; n--) + { + auto& engine = *engines[static_cast (n + offset)]; + + auto dataSamples = engines[static_cast (n + offset - 1)]->getProcessedSamples(); + engine.processSamplesDown (dataSamples, currentNumSamples); + + currentNumSamples /= engine.getFactor(); + } + + engines[static_cast (offset)]->processSamplesDown (block.getChannelPointer (channel), currentNumSamples); + } + +} + +template class Oversampling; +template class Oversampling; diff --git a/modules/juce_dsp/processors/juce_Oversampling.h b/modules/juce_dsp/processors/juce_Oversampling.h new file mode 100644 index 0000000000..0bcc7357a8 --- /dev/null +++ b/modules/juce_dsp/processors/juce_Oversampling.h @@ -0,0 +1,135 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +template +class OversamplingEngine; + +//=============================================================================== +/** + A processing class performing multi-channel oversampling. + + It can be configured to do 2 times, 4 times, 8 times or 16 times oversampling + using a multi-stage approach, either polyphase allpass IIR filters or FIR + filters for the filtering, and reports successfully the latency added by the + filter stages. + + The principle of oversampling is to increase the sample rate of a given + non-linear process, to prevent it from creating aliasing. Oversampling works + by upsampling N times the input signal, processing the upsampling signal + with the increased internal sample rate, and downsample the result to get + back the original processing sample rate. + + Choose between FIR or IIR filtering depending on your needs in term of + latency and phase distortion. With FIR filters, the phase is linear but the + latency is maximum. With IIR filtering, the phase is compromised around the + Nyquist frequency but the phase is minimum. + + @see FilterDesign. +*/ +template +class JUCE_API Oversampling +{ +public: + /** The type of filter that can be used for the oversampling processing. */ + enum FilterType + { + filterHalfBandFIREquiripple = 0, + filterHalfBandPolyphaseIIR, + numFilterTypes + }; + + //=============================================================================== + /** + Constructor of the oversampling class. All the processing parameters must be + provided at the creation of the oversampling object. + + Note : you might want to create a class heriting from Oversampling with a + different constructor if you need more control on what happens in the process. + + @param numChannels the number of channels to process with this object + @param factor the processing will perform 2 ^ factor times oversampling + @param type the type of filter design employed for filtering during + oversampling + @param isMaxQuality if the oversampling is done using the maximum quality, + the filters will be more efficient, but the CPU load will + increase as well + */ + Oversampling (size_t numChannels, size_t factor, FilterType type, bool isMaxQuality = true); + + /** Destructor. */ + ~Oversampling(); + + //=============================================================================== + /** Returns the latency in samples of the whole processing. Use this information + 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. + */ + SampleType getLatencyInSamples() noexcept; + + /** Returns the current oversampling factor. */ + size_t getOversamplingFactor() noexcept; + + //=============================================================================== + /** Must be called before any processing, to set the buffer sizes of the internal + buffers of the oversampling processing. + */ + void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling); + + /** 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; + + /** 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. + */ + dsp::AudioBlock getProcessedSamples(); + + /** 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; + +private: + //=============================================================================== + bool isMaximumQuality; + size_t factorOversampling, numStages; + FilterType type; + size_t numChannels; + + //=============================================================================== + bool isReady = false; + + OwnedArray> engines; + + //=============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling) +};