Where to recalculate coefficients?

I was taking a look at the DSPmodules plugin example as I wanted to find out in an EQ plugin where and what is the most efficient way to execute IIR filter coefficients computing.

Following the example, a very simple implementation of gain and an IIR stereo filter coefficient computing woluld be:

void PluginProcessor::valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&)
{
    requiresUpdate.store (true);
    DBG("prop changed");
}

void PluginProcessor::update()
{
    float gValue = *apvts.getRawParameterValue("gain");
    processorChain.get<0>().setGainDecibels(gValue);
    
    float fValue = *apvts.getRawParameterValue("freq");
    *processorChain.get<1>().state = juce::dsp::IIR::ArrayCoefficients<float>::makeHighPass  (getSampleRate(), fValue);
    
    requiresUpdate.store (false);
}

And then the update function would be called in the processor like:

void PluginProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    
    if (requiresUpdate.load())
        update();

    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    
    const auto numChannels = juce::jmax(totalNumInputChannels, totalNumOutputChannels);

    auto inoutBlock = juce::dsp::AudioBlock<float> (buffer).getSubsetChannelBlock (0, (size_t) numChannels);
    processorChain.process (juce::dsp::ProcessContextReplacing<float> (inoutBlock));
}

Is this the correct way and place to recalculate the IIR coefficients ? Or shoud they be calculated somehwere else ?

Generally speaking, that’s correct, however you must consider the creation of the coefficients likely allocates memory, which is considered a no no on the audio thread.

I believe the usual thing to do here is have them created on another thread, then once they are made, swap them on the audio thread.

It is correct and very very safe (AFAIK juce::dsp::IIR::ArrayCoefficients<float> won’t allocate memory). However, I would recommend requireUpdate.exchange(false) instead of a pair of load() and store(). And be aware that the automation speed is limited to once per block if you do so.
If you want to calculate coefficients on another thread, you have to be careful as it can go wrong easily. The following videos might be helpful:

CAS method:

RCU method:

Don’ think it does either, but I wouldn’t consider the above safe.

You are not protected against the following case:

  • UI/Automation sets values
  • Audio Thread pulls values half way
  • UI/Automation sets values
  • Audio thread pulls the rest

Since the audio thread had the last say, the update boolean is false, even though the first half of the values is still outdated but won’t be updated on the next processBlock. Also (maybe not a problem here, but could be in a different case) you are now working with an invalid set of values. First half from the first values, second half from the second value update.

Thank you @Fandusss, @Rincewind and @zsliu98 for this great insights. I’m wondering then why in the Plugin DSP module example it is actually done in the “update in AudioProcessBlock fashion”.

I used to use this valueTreePropertyChanged method myself until I realized it doesn’t work in offline rendering mode. It appears to run off a timer and in offline mode the audio will be rendered faster than in realtime mode, and so any automated parameter changes will happen too slowly.

To fix this you can add an isNonRealtime() check but it’s kind of a hack:

bool expected = true;
if (isNonRealtime() || requiresUpdate.compare_exchange_strong(expected, false)) {
    update();
}

In your setStateInformation function you should add requiresUpdate.store (true); as well.

In your update() it’s also a good idea not to use apvts.getRawParameterValue() to read the parameters since that does a slow look-up by parameter name. Instead, use pointers to the AudioParameterFloat objects.

1 Like

update in AudioProcessBlock fashion is indeed real-time safe. However, from my point of view, using a pair of load and store is wrong (cause as @Rincewind suggested, it can miss a update, maybe someone from JUCE can check this one?).

That’s why I suggest exchange (or compare_exchange_strong, see @kerfuffle post, which is more efficient, intuitively).

BTW, I have been using parameterChanged instead of valueTreePropertyChanged.