How to guarantee a function won't be interrupted by another thread?

I’m currently working on a filter plugin with the filter cutoff frequency controlled by a slider. Every time the slider is moved, I need to recalculate my filter using the new cutoff frequency. The function that calculates the filter looks something like this:

for (auto i = 0; i < filterSize; ++i)
{
filter[i] = /some index dependent value/
}
filter[filterSize/2] = /specific index independent value/

The statement outside the loop is necessary to avoid a NaN result.

This works fine the first time is runs but I’m having some issues when this function is called as a result of the slider changing.

I’ve made my plugin editor inherit from Slider::Listener, have added it as the slider listener and have overriden sliderValueChanged:

void sliderValueChanged(juce::Slider* slider)
{
if (slider == &cutoffFreqSlider)
{
float cutoff = cutoffFreqSlider.getValue();
processor.getFilter().setCutoffFreq(cutoff);
}
}

The call to setCutoffFreq also includes a call to the filter calculation function but this time when the function runs it is interupted immediately after the loop finishes by AudioDeviceManager::audioDeviceIOCallback on the WASAPI thread, which in turn calls my processBlock function before the NaN value is dealt with.

My main question, is there any way to guarantee my filter calculation function fully executes without interruption?

As a secondary question, I noticed that my filter calculation function is running on the Juce Message Thread. Is this a problem? Is this because it is called by sliderValueChanged on the editor side rather than the processor side?

I’m not well versed in multithreading so apologies if I’ve left out any important details.

edit: changed an incorrect variable name

  1. You can use critical sections and scoped locks to ensure that functions aren’t interrupted by other threads.

something like:

In Header:

CriticalSection mLock;

In CPP:

{ / * begin scope */
   /* lock is entered for duration of current scope */
   ScopedLock l(mLock);

} /* end scope / lock release */
  1. The thread which calls the function is always the thread which will execute the function.

There’s a lot of ways to go about doing things, but generally what people would do is update the parameter from the editor, and then check for changes at the start of the process block / or as often as desired in the audio processing code and update the coefficients from the audio thread.

If you don’t notice performance bottlenecks dragging the knob then this may work well if you have no modulation in your audio lines, but generally you’ll be doing more calculations then needed as the knob is going to be calling value changed a lot as it’s being dragged

It sounds like that is exactly what I was looking for!

So all I would need to do is pass a CriticalSection to a ScopedLock and the start of my function? I don’t have to worry about manually releasing the lock or anything?

I did intend to wrap the cutoff frequency in some kind of parameter object but I wasn’t sure how to properly implement a function without just checking for updates every time processBlock is called. I suppose AudioProcessorParameter can help with that though, I seem to remember that class having a dedicated listener.

Thanks for the reply, its really helpful.

Yep! The ScopedLock will release, just create it at the start. It can be helpful to wrap it in extra braces if you’re doing other things in the function to ensure it’s held for as short a time as possible.

For the processBlock, I usually just store the current value, and if the new value != current -> process the changes.

This way it’s not recalculating if no changes have been made. I generally don’t like that historical state checking either, but in that cause I’ve found it results in the minimal amount of required processing as opposed to processing on every value change.

Not sure if it is clear from the answer, but 1) you need to put the ScopedLock in both threads that want to access the data. and 2) you do not want that other thread to be the audio callback thread (ie. processBlock). Taking a lock in the audio thread is a recipe for problems.

I see. I just modified my filter to include CriticalSection crit as a member and added ScopedLock(crit) and the start of my function and noticed it was still being interrupted by the audio thread, I think your first point may explain why that is still happening.

If I can’t - or shouldn’t - use locks on the audio thread, does this mean my only option is to make sure my function is called from the audio thread itself?

I’m not an expert on threading, but this primer is worth a read:

Cardinal Rule #1:

Don’t hold locks on the audio thread.

1 Like

I’m giving it a read now and it seems very useful, thanks!

also, if this is that actual line you are using, you are not taking the lock for the scope of the function, because you are not declaring an actual ScopedLock object, so the lock is only active on that line.

ScopedLock sl (crit);

something like that is what you want

My mistake, the actual code I’m using is ScopedLock lock(crit);

Yes, you can guarantee that your DSP stuff (e.g. filter calculations) fully executes without interruption by putting it on the audio thread. There are other options to recalculate your DSP stuff on a background thread, but it gets complicated.

Given that you talk about using a Slider::Listener, I’m guessing you’re not using AudioProcessorValueTreeState to manage your parameters – otherwise, you could use a SliderAttachment instead (the approach outlined in the APVTS tutorial). And then, in your processBlock method (which runs on the audio thread) you could fetch the parameter value from the APVTS and recalculate your DSP stuff.

That sounds like that would solve my problem. I thought I could get away with ignoring proper parameter management until I got the DSP working but obviously I can’t separate them like that.

Thank you all for the responses, I’ve learned a lot!

You can separate proper parameter management for a bit, if you’re just trying to complete a proof of concept for now. For a really quick and dirty solution, give your Processor a public std::atomic<float> filterCutoff member, and change your slider callback to:

void sliderValueChanged(juce::Slider* slider)
{
  if (slider == &cutoffFreqSlider)
  {
    processor.filterCutoff = cutoffFreqSlider.getValue();
  }
}

Then in your processBlock, run the DSP calculation before processing audio:

 myFilter.setCutoffFreq (filterCutoff);

So now the std::atomic<float> is the “go-between” to share that value between message and audio threads, and you wait until you’re on the audio thread to alter the actual DSP object.

Note that for an efficient implementation, you’d want to add a bunch of other stuff, like store a previousFilterCutoff value so you could compare old and new values, and thereby avoid recalculating DSP stuff if there was no change.