From 0ba6cb4ecf554fa498c573a8656028855dbc3db6 Mon Sep 17 00:00:00 2001 From: hogliux Date: Wed, 14 Feb 2018 09:07:05 +0000 Subject: [PATCH] DSP: Added bypass support to ProcessChain and ensured that all DSP processors respect the process context's bypass flag --- .../effects/juce_LinearSmoothedValue.h | 20 +++ modules/juce_dsp/processors/juce_Bias.h | 10 ++ modules/juce_dsp/processors/juce_FIRFilter.h | 15 ++- modules/juce_dsp/processors/juce_Gain.h | 10 ++ modules/juce_dsp/processors/juce_IIRFilter.h | 12 +- .../juce_dsp/processors/juce_IIRFilter_Impl.h | 18 ++- modules/juce_dsp/processors/juce_Oscillator.h | 32 +++-- .../juce_dsp/processors/juce_ProcessorChain.h | 117 +++++++++--------- .../processors/juce_StateVariableFilter.h | 101 ++++++++------- modules/juce_dsp/processors/juce_WaveShaper.h | 14 ++- 10 files changed, 222 insertions(+), 127 deletions(-) diff --git a/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h b/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h index cd39b0aa48..afb92de3d0 100644 --- a/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h +++ b/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h @@ -186,6 +186,26 @@ public: } } + //============================================================================== + /** Skip the next numSamples samples. + + This is identical to calling getNextValue numSamples times. + @see getNextValue + */ + void skip (int numSamples) noexcept + { + if (numSamples >= countdown) + { + currentValue = target; + countdown = 0; + } + else + { + currentValue += (step * static_cast (numSamples)); + countdown -= numSamples; + } + } + private: //============================================================================== FloatType currentValue = 0, target = 0, step = 0; diff --git a/modules/juce_dsp/processors/juce_Bias.h b/modules/juce_dsp/processors/juce_Bias.h index 1937af9573..61ff049f72 100644 --- a/modules/juce_dsp/processors/juce_Bias.h +++ b/modules/juce_dsp/processors/juce_Bias.h @@ -107,6 +107,16 @@ public: auto len = inBlock.getNumSamples(); auto numChannels = inBlock.getNumChannels(); + if (context.isBypassed) + { + bias.skip (static_cast (len)); + + if (context.usesSeparateInputAndOutputBlocks()) + outBlock.copy (inBlock); + + return; + } + if (numChannels == 1) { auto* src = inBlock.getChannelPointer (0); diff --git a/modules/juce_dsp/processors/juce_FIRFilter.h b/modules/juce_dsp/processors/juce_FIRFilter.h index 09d2aaa94c..4b589028f9 100644 --- a/modules/juce_dsp/processors/juce_FIRFilter.h +++ b/modules/juce_dsp/processors/juce_FIRFilter.h @@ -138,8 +138,19 @@ namespace FIR auto* fir = coefficients->getRawCoefficients(); size_t p = pos; - for (size_t i = 0; i < numSamples; ++i) - dst[i] = processSingleSample (src[i], fifo, fir, size, p); + if (context.isBypassed) + { + for (size_t i = 0; i < numSamples; ++i) + { + fifo[p] = dst[i] = src[i]; + p = (p == 0 ? size - 1 : p - 1); + } + } + else + { + for (size_t i = 0; i < numSamples; ++i) + dst[i] = processSingleSample (src[i], fifo, fir, size, p); + } pos = p; } diff --git a/modules/juce_dsp/processors/juce_Gain.h b/modules/juce_dsp/processors/juce_Gain.h index e8e206f333..e7195369a2 100644 --- a/modules/juce_dsp/processors/juce_Gain.h +++ b/modules/juce_dsp/processors/juce_Gain.h @@ -103,6 +103,16 @@ public: auto len = inBlock.getNumSamples(); auto numChannels = inBlock.getNumChannels(); + if (context.isBypassed) + { + gain.skip (static_cast (len)); + + if (context.usesSeparateInputAndOutputBlocks()) + outBlock.copy (inBlock); + + return; + } + if (numChannels == 1) { auto* src = inBlock.getChannelPointer (0); diff --git a/modules/juce_dsp/processors/juce_IIRFilter.h b/modules/juce_dsp/processors/juce_IIRFilter.h index ec048bba6b..f665ef6e1c 100644 --- a/modules/juce_dsp/processors/juce_IIRFilter.h +++ b/modules/juce_dsp/processors/juce_IIRFilter.h @@ -101,7 +101,13 @@ namespace IIR /** Processes as a block of samples */ template - void process (const ProcessContext& context) noexcept; + void process (const ProcessContext& context) noexcept + { + if (context.isBypassed) + processInternal (context); + else + processInternal (context); + } /** Processes a single sample, without any locking. @@ -122,6 +128,10 @@ namespace IIR //============================================================================== void check(); + /** Processes as a block of samples */ + template + void processInternal (const ProcessContext& context) noexcept; + //============================================================================== HeapBlock memory; SampleType* state = nullptr; diff --git a/modules/juce_dsp/processors/juce_IIRFilter_Impl.h b/modules/juce_dsp/processors/juce_IIRFilter_Impl.h index 5ef75be08c..f4f8848f66 100644 --- a/modules/juce_dsp/processors/juce_IIRFilter_Impl.h +++ b/modules/juce_dsp/processors/juce_IIRFilter_Impl.h @@ -69,8 +69,8 @@ void Filter::prepare (const ProcessSpec&) noexcept { reset(); } template -template -void Filter::process (const ProcessContext& context) noexcept +template +void Filter::processInternal (const ProcessContext& context) noexcept { static_assert (std::is_same::value, "The sample-type of the IIR filter must match the sample-type supplied to this process callback"); @@ -89,6 +89,11 @@ void Filter::process (const ProcessContext& context) noexcept auto* dst = outputBlock.getChannelPointer (0); auto* coeffs = coefficients->getRawCoefficients(); + // we need to copy this template parameter into a constexpr + // otherwise MSVC will moan that the tenary expressions below + // are constant conditional expressions + constexpr bool isBypassed = bypassed; + switch (order) { case 1: @@ -103,7 +108,8 @@ void Filter::process (const ProcessContext& context) noexcept { auto in = src[i]; auto out = in * b0 + lv1; - dst[i] = out; + + dst[i] = isBypassed ? in : out; lv1 = (in * b1) - (out * a1); } @@ -127,7 +133,7 @@ void Filter::process (const ProcessContext& context) noexcept { auto in = src[i]; auto out = (in * b0) + lv1; - dst[i] = out; + dst[i] = isBypassed ? in : out; lv1 = (in * b1) - (out * a1) + lv2; lv2 = (in * b2) - (out * a2); @@ -156,7 +162,7 @@ void Filter::process (const ProcessContext& context) noexcept { auto in = src[i]; auto out = (in * b0) + lv1; - dst[i] = out; + dst[i] = isBypassed ? in : out; lv1 = (in * b1) - (out * a1) + lv2; lv2 = (in * b2) - (out * a2) + lv3; @@ -175,7 +181,7 @@ void Filter::process (const ProcessContext& context) noexcept { auto in = src[i]; auto out = (in * coeffs[0]) + state[0]; - dst[i] = out; + dst[i] = isBypassed ? in : out; for (size_t j = 0; j < order - 1; ++j) state[j] = (in * coeffs[j + 1]) - (out * coeffs[order + j + 1]) + state[j + 1]; diff --git a/modules/juce_dsp/processors/juce_Oscillator.h b/modules/juce_dsp/processors/juce_Oscillator.h index 0363a161ea..300f5f7685 100644 --- a/modules/juce_dsp/processors/juce_Oscillator.h +++ b/modules/juce_dsp/processors/juce_Oscillator.h @@ -129,6 +129,9 @@ public: auto numChannels = outBlock.getNumChannels(); auto baseIncrement = MathConstants::twoPi / sampleRate; + if (context.isBypassed) + context.getOutputBlock().clear(); + if (frequency.isSmoothing()) { auto* buffer = rampBuffer.getRawDataPointer(); @@ -137,12 +140,15 @@ public: buffer[i] = phase.advance (baseIncrement * frequency.getNextValue()) - MathConstants::pi; - for (size_t ch = 0; ch < numChannels; ++ch) + if (! context.isBypassed) { - auto* dst = outBlock.getChannelPointer (ch); + for (size_t ch = 0; ch < numChannels; ++ch) + { + auto* dst = outBlock.getChannelPointer (ch); - for (size_t i = 0; i < len; ++i) - dst[i] = generator (buffer[i]); + for (size_t i = 0; i < len; ++i) + dst[i] = generator (buffer[i]); + } } } else @@ -150,13 +156,21 @@ public: auto freq = baseIncrement * frequency.getNextValue(); auto p = phase; - for (size_t ch = 0; ch < numChannels; ++ch) + if (context.isBypassed) { - p = phase; - auto* dst = outBlock.getChannelPointer (ch); + frequency.skip (static_cast (len)); + p.advance (freq * static_cast (len)); + } + else + { + for (size_t ch = 0; ch < numChannels; ++ch) + { + p = phase; + auto* dst = outBlock.getChannelPointer (ch); - for (size_t i = 0; i < len; ++i) - dst[i] = generator (p.advance (freq) - MathConstants::pi); + for (size_t i = 0; i < len; ++i) + dst[i] = generator (p.advance (freq) - MathConstants::pi); + } } phase = p; diff --git a/modules/juce_dsp/processors/juce_ProcessorChain.h b/modules/juce_dsp/processors/juce_ProcessorChain.h index 4566c463ea..74e119264b 100644 --- a/modules/juce_dsp/processors/juce_ProcessorChain.h +++ b/modules/juce_dsp/processors/juce_ProcessorChain.h @@ -33,88 +33,85 @@ namespace dsp namespace ProcessorHelpers // Internal helper classes used in building the ProcessorChain { template - struct GetterHelper + struct AccessHelper { template - static auto& get (ProcessorType& a) noexcept { return GetterHelper::get (a.processors); } + static auto& get (ProcessorType& a) noexcept { return AccessHelper::get (a.processors); } + + template + static void setBypassed (ProcessorType& a, bool bypassed) { AccessHelper::setBypassed (a.processors, bypassed); } }; template <> - struct GetterHelper<0> + struct AccessHelper<0> { template - static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); } + static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); } + + template + static void setBypassed (ProcessorType& a, bool bypassed) { a.isBypassed = bypassed; } }; - template - struct ChainBase + //============================================================================== + template + struct ChainElement { + void prepare (const ProcessSpec& spec) + { + processor.prepare (spec); + } + + template + void process (const ProcessContext& context) noexcept + { + if (context.usesSeparateInputAndOutputBlocks() && ! isFirst) + { + jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels()); + ProcessContextReplacing replacingContext (context.getOutputBlock()); + replacingContext.isBypassed = (isBypassed || context.isBypassed); + + processor.process (replacingContext); + } + else + { + ProcessContext contextCopy (context); + contextCopy.isBypassed = (isBypassed || context.isBypassed); + + processor.process (contextCopy); + } + } + + void reset() + { + processor.reset(); + } + + bool isBypassed = false; Processor processor; Processor& getProcessor() noexcept { return processor; } Subclass& getThis() noexcept { return *static_cast (this); } - template auto& get() noexcept { return GetterHelper::get (getThis()); } + template auto& get() noexcept { return AccessHelper::get (getThis()); } + template void setBypassed (bool bypassed) noexcept { AccessHelper::setBypassed (getThis(), bypassed); } }; - template - struct Chain : public ChainBase> + //============================================================================== + template + struct ChainBase : public ChainElement> { - using Base = ChainBase>; - - void prepare (const ProcessSpec& spec) - { - Base::processor.prepare (spec); - processors.prepare (spec); - } + using Base = ChainElement>; template - void process (ProcessContext& context) noexcept - { - Base::processor.process (context); + void process (const ProcessContext& context) noexcept { Base::process (context); processors.process (context); } + void prepare (const ProcessSpec& spec) { Base::prepare (spec); processors.prepare (spec); } + void reset() { Base::reset(); processors.reset(); } - if (context.usesSeparateInputAndOutputBlocks()) - { - jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels()); - ProcessContextReplacing replacingContext (context.getOutputBlock()); - processors.process (replacingContext); - } - else - { - processors.process (context); - } - } - - void reset() - { - Base::processor.reset(); - processors.reset(); - } - - Chain processors; + ChainBase processors; }; - template - struct Chain : public ChainBase> - { - using Base = ChainBase>; - - template - void process (ProcessContext& context) noexcept - { - Base::processor.process (context); - } - - void prepare (const ProcessSpec& spec) - { - Base::processor.prepare (spec); - } - - void reset() - { - Base::processor.reset(); - } - }; + template + struct ChainBase : public ChainElement> {}; } #endif @@ -125,7 +122,7 @@ namespace ProcessorHelpers // Internal helper classes used in building the Proc classes into a single processor which will call process() on them all in sequence. */ template -using ProcessorChain = ProcessorHelpers::Chain; +using ProcessorChain = ProcessorHelpers::ChainBase; } // namespace dsp } // namespace juce diff --git a/modules/juce_dsp/processors/juce_StateVariableFilter.h b/modules/juce_dsp/processors/juce_StateVariableFilter.h index de396c8505..be356f8560 100644 --- a/modules/juce_dsp/processors/juce_StateVariableFilter.h +++ b/modules/juce_dsp/processors/juce_StateVariableFilter.h @@ -92,6 +92,58 @@ namespace StateVariableFilter static_assert (std::is_same::value, "The sample-type of the filter must match the sample-type supplied to this process callback"); + if (context.isBypassed) + processInternal (context); + else + processInternal (context); + } + + /** Processes a single sample, without any locking or checking. + Use this if you need processing of a single value. */ + SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType sample) noexcept + { + switch (parameters->type) + { + case Parameters::Type::lowPass: return processLoop::Type::lowPass> (sample, *parameters); break; + case Parameters::Type::bandPass: return processLoop::Type::bandPass> (sample, *parameters); break; + case Parameters::Type::highPass: return processLoop::Type::highPass> (sample, *parameters); break; + default: jassertfalse; + } + + return SampleType{0}; + } + + private: + //============================================================================== + template ::Type type> + SampleType JUCE_VECTOR_CALLTYPE processLoop (SampleType sample, Parameters& state) noexcept + { + y[2] = (sample - s1 * state.R2 - s1 * state.g - s2) * state.h; + + y[1] = y[2] * state.g + s1; + s1 = y[2] * state.g + y[1]; + + y[0] = y[1] * state.g + s2; + s2 = y[1] * state.g + y[0]; + + return isBypassed ? sample : y[static_cast (type)]; + } + + template ::Type type> + void processBlock (const SampleType* input, SampleType* output, size_t n) noexcept + { + auto state = *parameters; + + for (size_t i = 0 ; i < n; ++i) + output[i] = processLoop (input[i], state); + + snapToZero(); + *parameters = state; + } + + template + void processInternal (const ProcessContext& context) noexcept + { auto&& inputBlock = context.getInputBlock(); auto&& outputBlock = context.getOutputBlock(); @@ -106,56 +158,13 @@ namespace StateVariableFilter switch (parameters->type) { - case Parameters::Type::lowPass: processBlock::Type::lowPass> (src, dst, n); break; - case Parameters::Type::bandPass: processBlock::Type::bandPass> (src, dst, n); break; - case Parameters::Type::highPass: processBlock::Type::highPass> (src, dst, n); break; + case Parameters::Type::lowPass: processBlock::Type::lowPass> (src, dst, n); break; + case Parameters::Type::bandPass: processBlock::Type::bandPass> (src, dst, n); break; + case Parameters::Type::highPass: processBlock::Type::highPass> (src, dst, n); break; default: jassertfalse; } } - /** Processes a single sample, without any locking or checking. - Use this if you need processing of a single value. */ - SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType sample) noexcept - { - switch (parameters->type) - { - case Parameters::Type::lowPass: return processLoop::Type::lowPass> (sample, *parameters); break; - case Parameters::Type::bandPass: return processLoop::Type::bandPass> (sample, *parameters); break; - case Parameters::Type::highPass: return processLoop::Type::highPass> (sample, *parameters); break; - default: jassertfalse; - } - - return SampleType{0}; - } - - private: - //============================================================================== - template ::Type type> - SampleType JUCE_VECTOR_CALLTYPE processLoop (SampleType sample, Parameters& state) noexcept - { - y[2] = (sample - s1 * state.R2 - s1 * state.g - s2) * state.h; - - y[1] = y[2] * state.g + s1; - s1 = y[2] * state.g + y[1]; - - y[0] = y[1] * state.g + s2; - s2 = y[1] * state.g + y[0]; - - return y[static_cast (type)]; - } - - template ::Type type> - void processBlock (const SampleType* input, SampleType* output, size_t n) noexcept - { - auto state = *parameters; - - for (size_t i = 0 ; i < n; ++i) - output[i] = processLoop (input[i], state); - - snapToZero(); - *parameters = state; - } - //============================================================================== std::array y; SampleType s1, s2; diff --git a/modules/juce_dsp/processors/juce_WaveShaper.h b/modules/juce_dsp/processors/juce_WaveShaper.h index bc13c4185f..6ea543ebe3 100644 --- a/modules/juce_dsp/processors/juce_WaveShaper.h +++ b/modules/juce_dsp/processors/juce_WaveShaper.h @@ -53,9 +53,17 @@ struct WaveShaper template void process (const ProcessContext& context) const noexcept { - AudioBlock::process (context.getInputBlock(), - context.getOutputBlock(), - functionToUse); + if (context.isBypassed) + { + if (context.usesSeparateInputAndOutputBlocks()) + context.getOutputBlock().copy (context.getInputBlock()); + } + else + { + AudioBlock::process (context.getInputBlock(), + context.getOutputBlock(), + functionToUse); + } } void reset() noexcept {}