Unexpected behaviour with dsp::ProcessorChain and ProcessContextNonReplacing

We have wrapped quite a few of our legacy dsp algorithm codebase into interfaces suitable to be used with the juce dsp module. Since a lot of them are only designed for in-place processing of float buffers, their process function are not templated but instead take a const dsp::ProcessContextReplacing<float>&. Chaining those classes, mixed with classes that have a templated processing function works well when a ProcessContextReplacing is passed to the process function of the chain.

When the dsp::ProcessorChain processes a non-replacing context, internally the first processor processes the samples non-replacing and all subsequent processors process the output buffer of that non-replacing context with a replacing one, which totally makes sense.

Now I had the situation where the first element in the chain was capable of non-replacing processing, but the second was not and I wanted to pass in a non-replacing context and I ran into quite cryptic compile errors. It took me some time to figure out that even if only the first processor in the chain will ever be called with a non-replacing process function, the compiler attempts to compile a non-replacing process version for all chained elements, because the if condition that is used in the processor chain internally to figure out if the first processor or a subsequent one is processed will be evaluated at runtime. To me this was unexpected and made an unnecessary ugly rework to one of our in-house processor classes necessary. This could be solved in the processor chain by pulling the first processor out of the tuple iterator, processing it with the context type passed in and only processing the subsequent processors in that “loop” with a hard-coded replacing context. Would you consider something like that?

Here is some example code to trigger the error:

    struct OnlyReplacing
    {
        void prepare (juce::dsp::ProcessSpec&) {}
        void process (const juce::dsp::ProcessContextReplacing<float>&) {}
        void reset() {}
    };

    juce::dsp::ProcessorChain<juce::dsp::Gain<float>, OnlyReplacing> chain;

    juce::dsp::AudioBlock<float> in, out;
    juce::dsp::ProcessContextNonReplacing<float> context (in, out);

    chain.process (context);
1 Like

Nice idea, I think this will have the side-effect of unrolling the ‘loop body’ completely so there won’t be any runtime branching inside the process call.

1 Like

I’ve added that here:

Great! Will give it a try