Loading SamplerSound in SamplerPlugin

I’m looking at the SamplerPlugin::handleAsyncUpdate() and where it loads new SamplerSounds when state changes.

SamplerPlugin::handleAsyncUpdate() 
{

    {
        const juce::ScopedLock sl (lock);
        allNotesOff();
        soundList.swapWith (newSounds);

        sourceMediaChanged();
    }
}
Then in the audio process thread...

void SamplerPlugin::applyToBuffer (const PluginRenderContext& fc)
{
    if (fc.destBuffer != nullptr)
    {
        SCOPED_REALTIME_CHECK

        const juce::ScopedLock sl (lock);
       //play samplersounds....

}

From looking at these two functions, handleAyncUpdate will happen on the message thread and when the new SamplerSounds are finished loading from the audio files, a lock is grabbed and the SamplerSounds are swapped.
Then, on the audio processing thread before any processing is attempted, it also grabs the lock this preventing the samplerSounds from being swapped out mid process block.

  1. The message thread could potentially block the audio processing thread, if the swap takes too long, but in practice this is not likely.
  2. The audio processing thread can block the message thread, but this would be momentary and not noticeably by the user.

@dave96 I’ve read and watched all your talks on real time audio and wondering if this lock method used in Tracktion is fine to use in a VST plugin context?

It’s not the best way to do it. The best way would be a lock-free queue or atomic pointer swap but they come with their own lifetime problems.

The main issue with the above is the potential priority inversion if the audio thread is waiting for the message thread to complete.

To be honest though, in this case where you’re loading new samples, in the situation where there are new samples pending, you can’t continue to play the old ones so it’s probably best to use a spin mutex and then a try_lock from the audio thread. If you can’t get the lock, just emit silence. The next time round you’ll probably be able to use the new samples and carry on. That way the message thread will only ever wait for the audio thread which is fine.

@dave96, another idea I was having is to use RefCountedObjectPointers…one on the AudioThread and one on the MessageThread, giving a simple lock free queue with only one place.

When handleAsyncUpdate is called, the MessageThread pointers are swapped, with the AudioThread pointer still retaining the old samples and thus avoiding yanking samples mid audio process.

Then at the end of the process block I set the AudioThread pointer to the newly loaded MessageThread pointer and the old samples are released.

Does this seem like a good approach? The only thing is if there is an overhead associated with the ref count going to zero on the AudioThread and then releasing the object?

Thanks for the help!