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:
parent
e55e87ee0e
commit
0ba6cb4ecf
10 changed files with 222 additions and 127 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue