Why is context set for every process block?

This is one thing I still don’t fully understand. In examples using AudioBLock like here it’s obvious why filter.process has to be called for every block. But why does it need to have a context wrapped around it for every call? I mean, the context is not something that is expected to change often. Why is this not handled in prepareToPlay or similar?

Is there a context struct created (and destroyed) for every call to processBlock in the example from above link:

    void processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer&) override
    {
        juce::dsp::AudioBlock<float> block (buffer);
        juce::dsp::ProcessContextReplacing<float> context (block);
        filter.process (context);
    }

Why are you worried about that? For performance? The context just stores a couple of references to the passed in AudioBlock, so constructing and destroying it locally in processBlock is pretty much “free”. Also, the reason why it can’t be constructed outside processBlock is because of those AudioBlock references, which in turn reference the AudioBuffer that is passed into processBlock, and those are not guaranteed to be the same AudioBuffer at each call. (Or the buffer might not exist yet when prepareToPlay is called.)

The context does change with every call, because every call has a different sample buffer.

a potentially different sample buffer. :wink:
Which is reason enough.
(just preventing that somebody DBG printed the pointers and found them constant…)

OK, so there’s not a new instance of dsp::ProcessContextReplacing< ContextSampleType> Struct created every call?

Just curious of how things work.

It is creating a new instance but it’s nothing to worry about, it’s just a tiny stack allocation/deallocation. (The same applies for the AudioBlocks.)

I get that the buffer needs to be wrapped to an AudioBlock for use in classes requiring AudioBlock each call but I still don’t understand why that block then also needs to be wrapped a second time just to pass info if the buffer/block’s data will be replaced or not.

I mean, the “context” being the instruction to replace data wouldn’t expect to be changed very often would it? Is there somehow a wider context that I miss here that will be new for every call? Things like channel layout, sample frequency, audio format and wether or not to replace data in the buffer seems like things that are set prior to processing…

The context is just a struct with two members:

Technically it is the same as if you were handing in two references to the AudioBlockType manually.
It is just a bit more convenient and allows to call the function and allow at compile time via template specialisation to have different implementations if the output is overwritten or not without duplicating code.
The only difference is that there is a class pointer to the struct, which is just 1 word (i.e. 4 or 8 bytes on the stack depending on the architecture).

If a host implements double buffering there would be two buffers alternating resulting with a different context every time.
And the host can call with different buffer sizes, again the context changes every time.
That scenario is very common on instrument tracks.

1 Like

OK, I’m beginning to understand the idea I think. Will probably go back and read this again when I get a better grip on these things.

Thanks.