Browse Source

DSP: Added bypass support to ProcessChain and ensured that all DSP processors respect the process context's bypass flag

tags/2021-05-28
hogliux 7 years ago
parent
commit
0ba6cb4ecf
10 changed files with 189 additions and 94 deletions
  1. +20
    -0
      modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h
  2. +10
    -0
      modules/juce_dsp/processors/juce_Bias.h
  3. +13
    -2
      modules/juce_dsp/processors/juce_FIRFilter.h
  4. +10
    -0
      modules/juce_dsp/processors/juce_Gain.h
  5. +11
    -1
      modules/juce_dsp/processors/juce_IIRFilter.h
  6. +12
    -6
      modules/juce_dsp/processors/juce_IIRFilter_Impl.h
  7. +24
    -10
      modules/juce_dsp/processors/juce_Oscillator.h
  8. +43
    -46
      modules/juce_dsp/processors/juce_ProcessorChain.h
  9. +35
    -26
      modules/juce_dsp/processors/juce_StateVariableFilter.h
  10. +11
    -3
      modules/juce_dsp/processors/juce_WaveShaper.h

+ 20
- 0
modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h View File

@@ -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<FloatType> (numSamples));
countdown -= numSamples;
}
}
private:
//==============================================================================
FloatType currentValue = 0, target = 0, step = 0;


+ 10
- 0
modules/juce_dsp/processors/juce_Bias.h View File

@@ -107,6 +107,16 @@ public:
auto len = inBlock.getNumSamples();
auto numChannels = inBlock.getNumChannels();
if (context.isBypassed)
{
bias.skip (static_cast<int> (len));
if (context.usesSeparateInputAndOutputBlocks())
outBlock.copy (inBlock);
return;
}
if (numChannels == 1)
{
auto* src = inBlock.getChannelPointer (0);


+ 13
- 2
modules/juce_dsp/processors/juce_FIRFilter.h View File

@@ -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;
}


+ 10
- 0
modules/juce_dsp/processors/juce_Gain.h View File

@@ -103,6 +103,16 @@ public:
auto len = inBlock.getNumSamples();
auto numChannels = inBlock.getNumChannels();
if (context.isBypassed)
{
gain.skip (static_cast<int> (len));
if (context.usesSeparateInputAndOutputBlocks())
outBlock.copy (inBlock);
return;
}
if (numChannels == 1)
{
auto* src = inBlock.getChannelPointer (0);


+ 11
- 1
modules/juce_dsp/processors/juce_IIRFilter.h View File

@@ -101,7 +101,13 @@ namespace IIR
/** Processes as a block of samples */
template <typename ProcessContext>
void process (const ProcessContext& context) noexcept;
void process (const ProcessContext& context) noexcept
{
if (context.isBypassed)
processInternal<ProcessContext, true> (context);
else
processInternal<ProcessContext, false> (context);
}
/** Processes a single sample, without any locking.
@@ -122,6 +128,10 @@ namespace IIR
//==============================================================================
void check();
/** Processes as a block of samples */
template <typename ProcessContext, bool isBypassed>
void processInternal (const ProcessContext& context) noexcept;
//==============================================================================
HeapBlock<SampleType> memory;
SampleType* state = nullptr;


+ 12
- 6
modules/juce_dsp/processors/juce_IIRFilter_Impl.h View File

@@ -69,8 +69,8 @@ void Filter<SampleType>::prepare (const ProcessSpec&) noexcept { reset(); }
template <typename SampleType>
template <typename ProcessContext>
void Filter<SampleType>::process (const ProcessContext& context) noexcept
template <typename ProcessContext, bool bypassed>
void Filter<SampleType>::processInternal (const ProcessContext& context) noexcept
{
static_assert (std::is_same<typename ProcessContext::SampleType, SampleType>::value,
"The sample-type of the IIR filter must match the sample-type supplied to this process callback");
@@ -89,6 +89,11 @@ void Filter<SampleType>::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<SampleType>::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<SampleType>::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<SampleType>::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<SampleType>::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];


+ 24
- 10
modules/juce_dsp/processors/juce_Oscillator.h View File

@@ -129,6 +129,9 @@ public:
auto numChannels = outBlock.getNumChannels();
auto baseIncrement = MathConstants<NumericType>::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<NumericType>::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);
for (size_t i = 0; i < len; ++i)
dst[i] = generator (p.advance (freq) - MathConstants<NumericType>::pi);
frequency.skip (static_cast<int> (len));
p.advance (freq * static_cast<NumericType> (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<NumericType>::pi);
}
}
phase = p;


+ 43
- 46
modules/juce_dsp/processors/juce_ProcessorChain.h View File

@@ -33,88 +33,85 @@ namespace dsp
namespace ProcessorHelpers // Internal helper classes used in building the ProcessorChain
{
template <int arg>
struct GetterHelper
struct AccessHelper
{
template <typename ProcessorType>
static auto& get (ProcessorType& a) noexcept { return GetterHelper<arg - 1>::get (a.processors); }
};
static auto& get (ProcessorType& a) noexcept { return AccessHelper<arg - 1>::get (a.processors); }
template <>
struct GetterHelper<0>
{
template <typename ProcessorType>
static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); }
static void setBypassed (ProcessorType& a, bool bypassed) { AccessHelper<arg - 1>::setBypassed (a.processors, bypassed); }
};
template <typename Processor, typename Subclass>
struct ChainBase
template <>
struct AccessHelper<0>
{
Processor processor;
Processor& getProcessor() noexcept { return processor; }
Subclass& getThis() noexcept { return *static_cast<Subclass*> (this); }
template <typename ProcessorType>
static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); }
template <int arg> auto& get() noexcept { return GetterHelper<arg>::get (getThis()); }
template <typename ProcessorType>
static void setBypassed (ProcessorType& a, bool bypassed) { a.isBypassed = bypassed; }
};
template <typename FirstProcessor, typename... SubsequentProcessors>
struct Chain : public ChainBase<FirstProcessor, Chain<FirstProcessor, SubsequentProcessors...>>
//==============================================================================
template <bool isFirst, typename Processor, typename Subclass>
struct ChainElement
{
using Base = ChainBase<FirstProcessor, Chain<FirstProcessor, SubsequentProcessors...>>;
void prepare (const ProcessSpec& spec)
{
Base::processor.prepare (spec);
processors.prepare (spec);
processor.prepare (spec);
}
template <typename ProcessContext>
void process (ProcessContext& context) noexcept
void process (const ProcessContext& context) noexcept
{
Base::processor.process (context);
if (context.usesSeparateInputAndOutputBlocks())
if (context.usesSeparateInputAndOutputBlocks() && ! isFirst)
{
jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels());
ProcessContextReplacing<typename ProcessContext::SampleType> replacingContext (context.getOutputBlock());
processors.process (replacingContext);
replacingContext.isBypassed = (isBypassed || context.isBypassed);
processor.process (replacingContext);
}
else
{
processors.process (context);
ProcessContext contextCopy (context);
contextCopy.isBypassed = (isBypassed || context.isBypassed);
processor.process (contextCopy);
}
}
void reset()
{
Base::processor.reset();
processors.reset();
processor.reset();
}
Chain<SubsequentProcessors...> processors;
bool isBypassed = false;
Processor processor;
Processor& getProcessor() noexcept { return processor; }
Subclass& getThis() noexcept { return *static_cast<Subclass*> (this); }
template <int arg> auto& get() noexcept { return AccessHelper<arg>::get (getThis()); }
template <int arg> void setBypassed (bool bypassed) noexcept { AccessHelper<arg>::setBypassed (getThis(), bypassed); }
};
template <typename ProcessorType>
struct Chain<ProcessorType> : public ChainBase<ProcessorType, Chain<ProcessorType>>
//==============================================================================
template <bool isFirst, typename FirstProcessor, typename... SubsequentProcessors>
struct ChainBase : public ChainElement<isFirst, FirstProcessor, ChainBase<isFirst, FirstProcessor, SubsequentProcessors...>>
{
using Base = ChainBase<ProcessorType, Chain<ProcessorType>>;
using Base = ChainElement<isFirst, FirstProcessor, ChainBase<isFirst, FirstProcessor, SubsequentProcessors...>>;
template <typename ProcessContext>
void process (ProcessContext& context) noexcept
{
Base::processor.process (context);
}
void prepare (const ProcessSpec& spec)
{
Base::processor.prepare (spec);
}
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(); }
void reset()
{
Base::processor.reset();
}
ChainBase<false, SubsequentProcessors...> processors;
};
template <bool isFirst, typename ProcessorType>
struct ChainBase<isFirst, ProcessorType> : public ChainElement<isFirst, ProcessorType, ChainBase<isFirst, ProcessorType>> {};
}
#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 <typename... Processors>
using ProcessorChain = ProcessorHelpers::Chain<Processors...>;
using ProcessorChain = ProcessorHelpers::ChainBase<true, Processors...>;
} // namespace dsp
} // namespace juce

+ 35
- 26
modules/juce_dsp/processors/juce_StateVariableFilter.h View File

@@ -92,25 +92,10 @@ namespace StateVariableFilter
static_assert (std::is_same<typename ProcessContext::SampleType, SampleType>::value,
"The sample-type of the filter must match the sample-type supplied to this process callback");
auto&& inputBlock = context.getInputBlock();
auto&& outputBlock = context.getOutputBlock();
// This class can only process mono signals. Use the ProcessorDuplicator class
// to apply this filter on a multi-channel audio stream.
jassert (inputBlock.getNumChannels() == 1);
jassert (outputBlock.getNumChannels() == 1);
auto n = inputBlock.getNumSamples();
auto* src = inputBlock .getChannelPointer (0);
auto* dst = outputBlock.getChannelPointer (0);
switch (parameters->type)
{
case Parameters<NumericType>::Type::lowPass: processBlock<Parameters<NumericType>::Type::lowPass> (src, dst, n); break;
case Parameters<NumericType>::Type::bandPass: processBlock<Parameters<NumericType>::Type::bandPass> (src, dst, n); break;
case Parameters<NumericType>::Type::highPass: processBlock<Parameters<NumericType>::Type::highPass> (src, dst, n); break;
default: jassertfalse;
}
if (context.isBypassed)
processInternal<true, ProcessContext> (context);
else
processInternal<false, ProcessContext> (context);
}
/** Processes a single sample, without any locking or checking.
@@ -119,9 +104,9 @@ namespace StateVariableFilter
{
switch (parameters->type)
{
case Parameters<NumericType>::Type::lowPass: return processLoop<Parameters<NumericType>::Type::lowPass> (sample, *parameters); break;
case Parameters<NumericType>::Type::bandPass: return processLoop<Parameters<NumericType>::Type::bandPass> (sample, *parameters); break;
case Parameters<NumericType>::Type::highPass: return processLoop<Parameters<NumericType>::Type::highPass> (sample, *parameters); break;
case Parameters<NumericType>::Type::lowPass: return processLoop<false, Parameters<NumericType>::Type::lowPass> (sample, *parameters); break;
case Parameters<NumericType>::Type::bandPass: return processLoop<false, Parameters<NumericType>::Type::bandPass> (sample, *parameters); break;
case Parameters<NumericType>::Type::highPass: return processLoop<false, Parameters<NumericType>::Type::highPass> (sample, *parameters); break;
default: jassertfalse;
}
@@ -130,7 +115,7 @@ namespace StateVariableFilter
private:
//==============================================================================
template <typename Parameters<NumericType>::Type type>
template <bool isBypassed, typename Parameters<NumericType>::Type type>
SampleType JUCE_VECTOR_CALLTYPE processLoop (SampleType sample, Parameters<NumericType>& state) noexcept
{
y[2] = (sample - s1 * state.R2 - s1 * state.g - s2) * state.h;
@@ -141,21 +126,45 @@ namespace StateVariableFilter
y[0] = y[1] * state.g + s2;
s2 = y[1] * state.g + y[0];
return y[static_cast<size_t> (type)];
return isBypassed ? sample : y[static_cast<size_t> (type)];
}
template <typename Parameters<NumericType>::Type type>
template <bool isBypassed, typename Parameters<NumericType>::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<type> (input[i], state);
output[i] = processLoop<isBypassed, type> (input[i], state);
snapToZero();
*parameters = state;
}
template <bool isBypassed, typename ProcessContext>
void processInternal (const ProcessContext& context) noexcept
{
auto&& inputBlock = context.getInputBlock();
auto&& outputBlock = context.getOutputBlock();
// This class can only process mono signals. Use the ProcessorDuplicator class
// to apply this filter on a multi-channel audio stream.
jassert (inputBlock.getNumChannels() == 1);
jassert (outputBlock.getNumChannels() == 1);
auto n = inputBlock.getNumSamples();
auto* src = inputBlock .getChannelPointer (0);
auto* dst = outputBlock.getChannelPointer (0);
switch (parameters->type)
{
case Parameters<NumericType>::Type::lowPass: processBlock<isBypassed, Parameters<NumericType>::Type::lowPass> (src, dst, n); break;
case Parameters<NumericType>::Type::bandPass: processBlock<isBypassed, Parameters<NumericType>::Type::bandPass> (src, dst, n); break;
case Parameters<NumericType>::Type::highPass: processBlock<isBypassed, Parameters<NumericType>::Type::highPass> (src, dst, n); break;
default: jassertfalse;
}
}
//==============================================================================
std::array<SampleType, 3> y;
SampleType s1, s2;


+ 11
- 3
modules/juce_dsp/processors/juce_WaveShaper.h View File

@@ -53,9 +53,17 @@ struct WaveShaper
template <typename ProcessContext>
void process (const ProcessContext& context) const noexcept
{
AudioBlock<FloatType>::process (context.getInputBlock(),
context.getOutputBlock(),
functionToUse);
if (context.isBypassed)
{
if (context.usesSeparateInputAndOutputBlocks())
context.getOutputBlock().copy (context.getInputBlock());
}
else
{
AudioBlock<FloatType>::process (context.getInputBlock(),
context.getOutputBlock(),
functionToUse);
}
}
void reset() noexcept {}


Loading…
Cancel
Save