Plugin demo, thread safety


#1

Hello guys,

I am looking at the juce audio plugin demo and got a bit confused about the
way parameters are sent from the GUI to the processor in a seemingly hazardous way.

As I understand it, the AudioProcessorEditor holds a pointer to the PluginAudioProcessor, so
when a slider is moved it calls the setParameterNotifyingHost() method in the PluginAudioProcessor. But hey, that isn’t thread safe is it? What happens if something in the audio callback uses the parameters while they are being changed?

I’m new to threads so some advice from the pros would be great :slight_smile:
Cheers!


#2

Well, it’s very much up to you to pass the data in whatever way is appropriate for your app…

If you’ve just got a bunch of floats or ints that the audio code is referring to, then it may be enough to make them volatile, and just let your audio code pick up the changes as they happen. Or if it’s important that a whole bank of values is updated atomically, you might put them in a structure and use an atomic pointer to swap between banks without blocking. It’s best to avoid CriticalSections in the audio thread if possible, and use atomics to get around it, but really it all depends on how your data needs to behave.


#3

Hi Jules, thank you for helping me!! :smiley:

The atomic pointer thing sounds very interesting, please tell me more! How can the atomic pointer used for swaping between parameter banks? I don’t really get it :oops: .

The Atomic class has changed since Juce v1.51, right?


#4

If I have two sets of parameters, one being used by the audio callback (pAudio) and one being changed by the GUI (pGUI), I can swap pointers when the new parameters from the GUI controls have been stored to pGUI. Fine. But what if the swap occurs in the middle of the audio callback? Then a combination of old and new parameters might be used, leading to instability and various evils.

A workaround might be to set a flag=1 atomically when the audio callback is using parameters. But then the parameter update callback in the GUI thread must wait until the flag=0 before swaping pointers. Is it ok to wait there?

What about the risk of getting another callback going to work on the pGUI parameters before the previous callback have finished? Like if the host does a change right after a knob have been twisted? Is it necessary to protect the pGUI parameters?

Is setParameterNotifyingHost() in the audio thread or the GUI thread? What is in the audio thread apart from the audio callback?

Ok, I hope that someone has time to answer my questions. Help and comments are much appreciated!

Now its time for tea and Juce. Cheers!


#5

A neat way to do this is with a ReferenceCountedObjectPtr - these are atomic, so if you can do stuff like this:

[code]ReferenceCountedObjectPtr sharedSettingsObject;

void audioThread()
{
ReferenceCountedObjectPtr localSettings (*sharedSettingsObject);

localSettings->getBlahblah1..
localSettings->getBlahblah2..

}

void changeSomeSettings() // non-audio thread
{
ReferenceCountedObjectPtr newSettings (new Settings (sharedSettingsObject));
newSettings->setBlahblah1…
newSettings->setBlahblah2…

sharedSettings = newSettings;

}[/code]


#6

Beautiful! :smiley: What a nice solution! The only drawback I can think of with that approach is the copying of the whole settings object, but it happens outside the audio thread so it shouldn’t be a problem for small sets of parameters… Many thanks Jules!!


#7

Hello again! I have a few more questions:

  1. Is it a thread safe operation to copy a ReferenceCountedObjectPtr? What if a ReferenceCountedObjectPtr is being copied ptrA = ptrB; and someone is trying to use ptrA while the referencedObject member of ptrA is midway through the change, wouldn’t that spell disaster? Here is the code for “=” in ReferenceCountedObjectPtr (I don’t understand it completely but I thought it might help answer my question):

[code]
/** Changes this pointer to point at a different object.

    The reference count of the old object is decremented, and it might be
    deleted if it hits zero. The new object's count is incremented.
*/
ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& operator= (const ReferenceCountedObjectPtr<ReferenceCountedObjectClass>& other)
{
    ReferenceCountedObjectClass* const newObject = other.referencedObject;

    if (newObject != referencedObject)
    {
        if (newObject != 0)
            newObject->incReferenceCount();

        ReferenceCountedObjectClass* const oldObject = referencedObject;
        referencedObject = newObject;

        if (oldObject != 0)
            oldObject->decReferenceCount();
    }

    return *this;
}[/code]
  1. Is it safe to assume that AudioProcessor::setParameter and AudioProcessor::processBlock will never execute at the same time, ie that they occur sequentially within the same thread?

#8
  1. Yes, that should be fine - it’s only a pointer and reference count that are being changed.
  2. Nope, you can pretty much guarantee that those two functions will be called at the same time!