1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-22 01:34:21 +00:00

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

This commit is contained in:
hogliux 2018-02-14 09:07:05 +00:00
parent e55e87ee0e
commit 0ba6cb4ecf
10 changed files with 222 additions and 127 deletions

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;

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);

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

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);

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;

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];

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);
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);
for (size_t i = 0; i < len; ++i)
dst[i] = generator (p.advance (freq) - MathConstants<NumericType>::pi);
}
}
phase = p;

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 <typename ProcessorType>
static void setBypassed (ProcessorType& a, bool bypassed) { AccessHelper<arg - 1>::setBypassed (a.processors, bypassed); }
};
template <>
struct GetterHelper<0>
struct AccessHelper<0>
{
template <typename ProcessorType>
static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); }
static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); }
template <typename ProcessorType>
static void setBypassed (ProcessorType& a, bool bypassed) { a.isBypassed = bypassed; }
};
template <typename Processor, typename Subclass>
struct ChainBase
//==============================================================================
template <bool isFirst, typename Processor, typename Subclass>
struct ChainElement
{
void prepare (const ProcessSpec& spec)
{
processor.prepare (spec);
}
template <typename ProcessContext>
void process (const ProcessContext& context) noexcept
{
if (context.usesSeparateInputAndOutputBlocks() && ! isFirst)
{
jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels());
ProcessContextReplacing<typename ProcessContext::SampleType> 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<Subclass*> (this); }
template <int arg> auto& get() noexcept { return GetterHelper<arg>::get (getThis()); }
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 FirstProcessor, typename... SubsequentProcessors>
struct Chain : public ChainBase<FirstProcessor, Chain<FirstProcessor, SubsequentProcessors...>>
//==============================================================================
template <bool isFirst, typename FirstProcessor, typename... SubsequentProcessors>
struct ChainBase : public ChainElement<isFirst, FirstProcessor, ChainBase<isFirst, FirstProcessor, SubsequentProcessors...>>
{
using Base = ChainBase<FirstProcessor, Chain<FirstProcessor, SubsequentProcessors...>>;
void prepare (const ProcessSpec& spec)
{
Base::processor.prepare (spec);
processors.prepare (spec);
}
using Base = ChainElement<isFirst, FirstProcessor, ChainBase<isFirst, FirstProcessor, SubsequentProcessors...>>;
template <typename ProcessContext>
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<typename ProcessContext::SampleType> replacingContext (context.getOutputBlock());
processors.process (replacingContext);
}
else
{
processors.process (context);
}
}
void reset()
{
Base::processor.reset();
processors.reset();
}
Chain<SubsequentProcessors...> processors;
ChainBase<false, SubsequentProcessors...> processors;
};
template <typename ProcessorType>
struct Chain<ProcessorType> : public ChainBase<ProcessorType, Chain<ProcessorType>>
{
using Base = ChainBase<ProcessorType, Chain<ProcessorType>>;
template <typename ProcessContext>
void process (ProcessContext& context) noexcept
{
Base::processor.process (context);
}
void prepare (const ProcessSpec& spec)
{
Base::processor.prepare (spec);
}
void reset()
{
Base::processor.reset();
}
};
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

View file

@ -92,6 +92,58 @@ 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");
if (context.isBypassed)
processInternal<true, ProcessContext> (context);
else
processInternal<false, ProcessContext> (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<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;
}
return SampleType{0};
}
private:
//==============================================================================
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;
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<size_t> (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<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();
@ -106,56 +158,13 @@ namespace StateVariableFilter
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;
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;
}
}
/** 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<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;
default: jassertfalse;
}
return SampleType{0};
}
private:
//==============================================================================
template <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;
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<size_t> (type)];
}
template <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);
snapToZero();
*parameters = state;
}
//==============================================================================
std::array<SampleType, 3> y;
SampleType s1, s2;

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 {}