diff --git a/modules/juce_dsp/juce_dsp.cpp b/modules/juce_dsp/juce_dsp.cpp index 5c50b05a3f..fd014ad2da 100644 --- a/modules/juce_dsp/juce_dsp.cpp +++ b/modules/juce_dsp/juce_dsp.cpp @@ -86,4 +86,5 @@ #include "containers/juce_AudioBlock_test.cpp" #include "frequency/juce_FFT_test.cpp" #include "processors/juce_FIRFilter_test.cpp" + #include "processors/juce_ProcessorChain_test.cpp" #endif diff --git a/modules/juce_dsp/processors/juce_ProcessorChain.h b/modules/juce_dsp/processors/juce_ProcessorChain.h index eeb3b19ae9..546fe7c074 100644 --- a/modules/juce_dsp/processors/juce_ProcessorChain.h +++ b/modules/juce_dsp/processors/juce_ProcessorChain.h @@ -63,64 +63,58 @@ class ProcessorChain { public: /** Get a reference to the processor at index `Index`. */ - template auto& get() noexcept { return std::get (processors).processor; } + template auto& get() noexcept { return std::get (processors); } /** Get a reference to the processor at index `Index`. */ - template const auto& get() const noexcept { return std::get (processors).processor; } + template const auto& get() const noexcept { return std::get (processors); } /** Set the processor at index `Index` to be bypassed or enabled. */ template - void setBypassed (bool b) noexcept { std::get (processors).isBypassed = b; } + void setBypassed (bool b) noexcept { bypassed[(size_t) Index] = b; } /** Query whether the processor at index `Index` is bypassed. */ template - bool isBypassed() const noexcept { return std::get (processors).isBypassed; } + bool isBypassed() const noexcept { return bypassed[(size_t) Index]; } /** Prepare all inner processors with the provided `ProcessSpec`. */ void prepare (const ProcessSpec& spec) { - detail::forEachInTuple ([&] (auto& item, size_t) { item.processor.prepare (spec); }, processors); + detail::forEachInTuple ([&] (auto& proc, size_t) { proc.prepare (spec); }, processors); } /** Reset all inner processors. */ void reset() { - detail::forEachInTuple ([] (auto& item, size_t) { item.processor.reset(); }, processors); + detail::forEachInTuple ([] (auto& proc, size_t) { proc.reset(); }, processors); } /** Process `context` through all inner processors in sequence. */ template void process (const ProcessContext& context) noexcept { - detail::forEachInTuple ([&] (auto& item, size_t index) noexcept + detail::forEachInTuple ([&] (auto& proc, size_t index) noexcept { if (context.usesSeparateInputAndOutputBlocks() && index != 0) { jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels()); ProcessContextReplacing replacingContext (context.getOutputBlock()); - replacingContext.isBypassed = (item.isBypassed || context.isBypassed); + replacingContext.isBypassed = (bypassed[index] || context.isBypassed); - item.processor.process (replacingContext); + proc.process (replacingContext); } else { ProcessContext contextCopy (context); - contextCopy.isBypassed = (item.isBypassed || context.isBypassed); + contextCopy.isBypassed = (bypassed[index] || context.isBypassed); - item.processor.process (contextCopy); + proc.process (contextCopy); } }, processors); } private: - template - struct ProcessorWithBypass - { - Processor processor; - bool isBypassed = false; - }; - - std::tuple...> processors; + std::tuple processors; + std::array bypassed { {} }; }; /** Non-member equivalent of ProcessorChain::get which avoids awkward diff --git a/modules/juce_dsp/processors/juce_ProcessorChain_test.cpp b/modules/juce_dsp/processors/juce_ProcessorChain_test.cpp new file mode 100644 index 0000000000..69100ddecf --- /dev/null +++ b/modules/juce_dsp/processors/juce_ProcessorChain_test.cpp @@ -0,0 +1,142 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ +namespace dsp +{ + +class ProcessorChainTest : public UnitTest +{ + template + struct MockProcessor + { + void prepare (const ProcessSpec&) noexcept { isPrepared = true; } + void reset() noexcept { isReset = true; } + + template + void process (const Context& context) noexcept + { + bufferWasClear = context.getInputBlock().getSample (0, 0) == 0; + + if (! context.isBypassed) + context.getOutputBlock().add (AddValue); + } + + bool isPrepared = false; + bool isReset = false; + bool bufferWasClear = false; + }; + +public: + ProcessorChainTest() + : UnitTest ("ProcessorChain", UnitTestCategories::dsp) {} + + void runTest() override + { + beginTest ("After calling setBypass, processor is bypassed"); + { + ProcessorChain, MockProcessor<2>> chain; + + setBypassed<0> (chain, true); + expect (isBypassed<0> (chain)); + setBypassed<0> (chain, false); + expect (! isBypassed<0> (chain)); + + setBypassed<1> (chain, true); + expect (isBypassed<1> (chain)); + setBypassed<1> (chain, false); + expect (! isBypassed<1> (chain)); + } + + beginTest ("After calling prepare, all processors are prepared"); + { + ProcessorChain, MockProcessor<2>> chain; + + expect (! get<0> (chain).isPrepared); + expect (! get<1> (chain).isPrepared); + + chain.prepare (ProcessSpec{}); + + expect (get<0> (chain).isPrepared); + expect (get<1> (chain).isPrepared); + } + + beginTest ("After calling reset, all processors are reset"); + { + ProcessorChain, MockProcessor<2>> chain; + + expect (! get<0> (chain).isReset); + expect (! get<1> (chain).isReset); + + chain.reset(); + + expect (get<0> (chain).isReset); + expect (get<1> (chain).isReset); + } + + beginTest ("After calling process, all processors contribute to processing"); + { + ProcessorChain, MockProcessor<2>> chain; + + AudioBuffer buffer (1, 1); + AudioBlock block (buffer); + ProcessContextReplacing context (block); + + block.clear(); + chain.process (context); + expectEquals (buffer.getSample (0, 0), 3.0f); + expect (get<0> (chain).bufferWasClear); + expect (! get<1> (chain).bufferWasClear); + + setBypassed<0> (chain, true); + block.clear(); + chain.process (context); + expectEquals (buffer.getSample (0, 0), 2.0f); + expect (get<0> (chain).bufferWasClear); + expect (get<1> (chain).bufferWasClear); + + setBypassed<1> (chain, true); + block.clear(); + chain.process (context); + expectEquals (buffer.getSample (0, 0), 0.0f); + expect (get<0> (chain).bufferWasClear); + expect (get<1> (chain).bufferWasClear); + + setBypassed<0> (chain, false); + block.clear(); + chain.process (context); + expectEquals (buffer.getSample (0, 0), 1.0f); + expect (get<0> (chain).bufferWasClear); + expect (! get<1> (chain).bufferWasClear); + } + } +}; + +static ProcessorChainTest processorChainUnitTest; + +} // namespace dsp +} // namespace juce