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