/* ============================================================================== 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. ============================================================================== */ namespace juce { namespace dsp { /** Abstract class for the provided oversampling engines used internally in the Oversampling class. */ template class OversamplingEngine { public: //=============================================================================== OversamplingEngine (size_t newNumChannels, size_t newFactor) { numChannels = newNumChannels; factor = newFactor; } virtual ~OversamplingEngine() {} //=============================================================================== virtual SampleType getLatencyInSamples() = 0; size_t getFactor() { return factor; } virtual void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling) { buffer.setSize (static_cast (numChannels), static_cast (maximumNumberOfSamplesBeforeOversampling * factor), false, false, true); } virtual void reset() { buffer.clear(); } dsp::AudioBlock getProcessedSamples (size_t numSamples) { return dsp::AudioBlock (buffer).getSubBlock (0, numSamples); } virtual void processSamplesUp (dsp::AudioBlock &inputBlock) = 0; virtual void processSamplesDown (dsp::AudioBlock &outputBlock) = 0; protected: //=============================================================================== AudioBuffer buffer; size_t factor; size_t numChannels; }; //=============================================================================== /** 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 (size_t numChannels) : OversamplingEngine (numChannels, 1) {} ~OversamplingDummy() {} //=============================================================================== SampleType getLatencyInSamples() override { return 0.f; } void processSamplesUp (dsp::AudioBlock &inputBlock) override { jassert (inputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); jassert (inputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); 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 (dsp::AudioBlock &outputBlock) override { jassert (outputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); jassert (outputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); outputBlock.copy (OversamplingEngine::getProcessedSamples (outputBlock.getNumSamples())); } 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 (size_t numChannels, SampleType normalizedTransitionWidthUp, SampleType stopbandAttenuationdBUp, SampleType normalizedTransitionWidthDown, SampleType stopbandAttenuationdBDown) : OversamplingEngine (numChannels, 2) { coefficientsUp = *dsp::FilterDesign::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp); coefficientsDown = *dsp::FilterDesign::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown); 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; stateDown.setSize (static_cast (numChannels), static_cast (N)); stateDown2.setSize (static_cast (numChannels), static_cast (Ndiv4 + 1)); position.resize (static_cast (numChannels)); } ~Oversampling2TimesEquirippleFIR() {} //=============================================================================== SampleType getLatencyInSamples() override { return static_cast (coefficientsUp.getFilterOrder() + coefficientsDown.getFilterOrder()) * 0.5f; } void reset() override { OversamplingEngine::reset(); stateUp.clear(); stateDown.clear(); stateDown2.clear(); position.fill (0); } 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 fir = coefficientsUp.getRawCoefficients(); auto N = coefficientsUp.getFilterOrder() + 1; auto Ndiv2 = N / 2; auto numSamples = inputBlock.getNumSamples(); // Processing for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++) { 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]; // 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 (dsp::AudioBlock &outputBlock) override { jassert (outputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); jassert (outputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); // Initialization auto fir = coefficientsDown.getRawCoefficients(); auto N = coefficientsDown.getFilterOrder() + 1; auto Ndiv2 = N / 2; auto Ndiv4 = Ndiv2 / 2; auto numSamples = outputBlock.getNumSamples(); // Processing for (size_t channel = 0; channel < outputBlock.getNumChannels(); channel++) { 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]; // Output out += buf2[pos] * fir[Ndiv2]; buf2[pos] = bufferSamples[(i << 1) + 1]; samples[i] = out; // Shift data for (size_t k = 0; k < N - 2; k++) buf[k] = buf[k + 2]; // Circular buffer pos = (pos == 0 ? Ndiv4 : pos - 1); } position.setUnchecked (static_cast (channel), pos); } } private: //=============================================================================== dsp::FIR::Coefficients coefficientsUp, coefficientsDown; AudioBuffer stateUp, stateDown, stateDown2; Array 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 (size_t numChannels, SampleType normalizedTransitionWidthUp, SampleType stopbandAttenuationdBUp, SampleType normalizedTransitionWidthDown, SampleType stopbandAttenuationdBDown) : OversamplingEngine (numChannels, 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 * MathConstants::twoPi)); auto structureDown = dsp::FilterDesign::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown); dsp::IIR::Coefficients coeffsDown = getCoefficients (structureDown); latency += static_cast (-(coeffsDown.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * MathConstants::twoPi)); 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.setSize (static_cast (numChannels), coefficientsUp.size()); v1Down.setSize (static_cast (numChannels), coefficientsDown.size()); delayDown.resize (static_cast (numChannels)); } ~Oversampling2TimesPolyphaseIIR() {} //=============================================================================== SampleType getLatencyInSamples() override { return latency; } void reset() override { OversamplingEngine::reset(); v1Up.clear(); v1Down.clear(); delayDown.fill (0); } 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 coeffs = coefficientsUp.getRawDataPointer(); auto numStages = coefficientsUp.size(); auto delayedStages = numStages / 2; auto directStages = numStages - delayedStages; auto numSamples = inputBlock.getNumSamples(); // Processing for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++) { auto bufferSamples = OversamplingEngine::buffer.getWritePointer (static_cast (channel)); auto lv1 = v1Up.getWritePointer (static_cast (channel)); auto samples = inputBlock.getChannelPointer (channel); 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 (dsp::AudioBlock &outputBlock) override { jassert (outputBlock.getNumChannels() <= static_cast (OversamplingEngine::buffer.getNumChannels())); jassert (outputBlock.getNumSamples() * OversamplingEngine::factor <= static_cast (OversamplingEngine::buffer.getNumSamples())); // Initialization auto coeffs = coefficientsDown.getRawDataPointer(); auto numStages = coefficientsDown.size(); auto delayedStages = numStages / 2; auto directStages = numStages - delayedStages; auto numSamples = outputBlock.getNumSamples(); // Processing for (size_t channel = 0; channel < outputBlock.getNumChannels(); channel++) { 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)); 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] = (delay + directOut) * static_cast (0.5); delay = input; } delayDown.setUnchecked (static_cast (channel), delay); } // Snap To Zero snapToZero (false); } void snapToZero (bool snapUpProcessing) { if (snapUpProcessing) { 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++) util::snapToZero (lv1[n]); } } else { 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++) util::snapToZero (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; AudioBuffer v1Up, v1Down; Array 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 = static_cast (1) << newFactor; isMaximumQuality = newMaxQuality; type = newType; numChannels = newNumChannels; if (newFactor == 0) { numStages = 1; engines.add (new OversamplingDummy (numChannels)); } else if (type == FilterType::filterHalfBandPolyphaseIIR) { numStages = newFactor; 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); 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 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); 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)); } } } template Oversampling::~Oversampling() { engines.clear(); } //=============================================================================== template SampleType Oversampling::getLatencyInSamples() noexcept { auto latency = static_cast (0); size_t order = 1; for (size_t n = 0; n < numStages; n++) { auto& engine = *engines[static_cast (n)]; order *= engine.getFactor(); latency += engine.getLatencyInSamples() / static_cast (order); } return latency; } template size_t Oversampling::getOversamplingFactor() noexcept { return factorOversampling; } //=============================================================================== template void Oversampling::initProcessing (size_t maximumNumberOfSamplesBeforeOversampling) { jassert (! engines.isEmpty()); auto currentNumSamples = maximumNumberOfSamplesBeforeOversampling; for (size_t n = 0; n < numStages; n++) { auto& engine = *engines[static_cast (n)]; engine.initProcessing (currentNumSamples); currentNumSamples *= engine.getFactor(); } isReady = true; reset(); } template void Oversampling::reset() noexcept { jassert (! engines.isEmpty()); if (isReady) for (auto n = 0; n < engines.size(); n++) engines[n]->reset(); } template typename dsp::AudioBlock Oversampling::processSamplesUp (const dsp::AudioBlock &inputBlock) noexcept { jassert (! engines.isEmpty()); if (! isReady) return dsp::AudioBlock(); dsp::AudioBlock audioBlock = inputBlock; 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 &outputBlock) noexcept { jassert (! engines.isEmpty()); if (! isReady) return; auto currentNumSamples = outputBlock.getNumSamples(); for (size_t n = 0; n < numStages - 1; n++) currentNumSamples *= engines[static_cast (n)]->getFactor(); for (size_t n = numStages - 1; n > 0; n--) { auto& engine = *engines[static_cast (n)]; auto audioBlock = engines[static_cast (n - 1)]->getProcessedSamples (currentNumSamples); engine.processSamplesDown (audioBlock); currentNumSamples /= engine.getFactor(); } engines[static_cast (0)]->processSamplesDown (outputBlock); } template class Oversampling; template class Oversampling; } // namespace dsp } // namespace juce