Threading - How to rebuild critical processing component from GUI input while processing is ongoing?

Are JUCE applications like Synthesiser or MPESynthesiser based projects single threaded all the way through (unless you specially code them to be otherwise)?

Ie. Are interactions on the GUI and PluginEditor on the same thread as the AudioProcessor, etc?

From what I can tell it all is single threaded but I want to be sure as I am trying to figure out some strange behavior I can’t yet pin down. Thanks for any clarification.

No, audio applications are necessarily double threaded. All of the functions inside your AudioProcessor class will be called by the audio thread. Basically all other functions will be called by the message thread.

The audio thread is very delicate and requires a lot of careful programming: you can’t use locks, allocation, system calls or exceptions (or else you might get audio glitches). This makes interacting between the audio thread and the message thread very challenging. There are lots of posts about that on this forum.

1 Like

Thanks Liam. I am curious if you can instruct me on the proper way to handle the situation where:

  1. User input forces a major component of the processing chain to be rebuilt (eg. voice number change).
  2. This change and the component rebuild operation happens on the Message Thread.
  3. The PluginProcessor/Synthesiser keep calling functions like renderNextSubBlock which use the in-the-process-of-rebuilding component.

Like hypothetically, user presses a button that changes voice count or something and triggers something like this:

void AudioPlugInAudioProcessor::clearAndRebuildAllVoices() { 

	//this is the messager thread
	DBG("rebuild | IS THIS MESSAGER THREAD? " << (int)juce::MessageManager::getInstance()->isThisTheMessageThread());

	const ScopedLock voiceLock(lock);
	mMpeSynth.turnOffAllVoices(false);
	mMpeSynth.reduceNumVoices(0); //kills all voices

	mMpeSynth.INITIALIZE_CRITICAL_COMPONENT(); //MAJOR THING THAT TAKES A BIT OF TIME
	
	for (int i = 0; i < numVoices; i++) {
		customMyAddMPESynthVoice(i);
	}
}

Because this is run by the user input, it does seem to be the messager thread.

But in the mean time you have in the MPESynthesiser derived class:

void renderNextSubBlock(AudioBuffer<float>& buffer, int startSample, int numSamples) override {

		//========================
		//THIS IS AUDIO THREAD

		//simple test to see if CRITICAL_OBJECT is fully set up at present
		bool renderBlock = !(CRITICAL_OBJECT->checkIfInitialized());

		if (!renderBlock) {

			CRITICAL_OBJECT->doSomething();
			CRITICAL_OBJECT->process(startSample, numSamples, whatever);

			MPESynthesiser::renderNextSubBlock(buffer, startSample, numSamples); //call base function
		}
	}
	
}

I think this basic bool locking will at least prevent you from accessing the CRITICAL_OBJECT here when it is not yet fully initialized.

But if you start the re-building of the critical object (due to user click) half way through an already running renderNextSubBlock it will get memory errors trying to access things that no longer exist.

How would you commonly handle this?

Simplest way I could imagine though it is crude and I’m not sure if such a method exists would be to run instead something on user input like:

AudioThread.InvokeOnAudioThread(clearAndRebuildAllVoices);

I mean if you can just schedule and run the GUI triggered rebuild on the audio thread then that stops any memory access violations completely as it is single threaded.

Is this how people do it? Or what do people do? Thanks for any ideas.

Another idea I just thought of would be to instead have the GUI invoked function just set a bool that is checked at the end of each renderBlock on the audio thread, and if it set, the audio thread does the rebuild there.

ie:

void AudioPlugInAudioProcessor::clearAndRebuildAllVoices() { 

      mMpeSynth.rebuildRequired = true;
}

And then:

void renderNextSubBlock(AudioBuffer<float>& buffer, int startSample, int numSamples) override {

		//========================
		//THIS IS AUDIO THREAD

		CRITICAL_OBJECT->doSomething();
		CRITICAL_OBJECT->process(startSample, numSamples, whatever);

		MPESynthesiser::renderNextSubBlock(buffer, startSample, numSamples); //call base function

		if (rebuildRequired){
			RUN_THE_REAL_REBUILD_HERE_NOW_ON_AUDIO_THREAD();
			rebuildRequired = false;
		}
	}
}

That’s the best I can think of. You will end up blocking the audio thread but at least it is reasonably thread safe. Or what else? What do people do in these situations?

When using booleans between threads as flags make sure you use std::atomic<bool> for a proper thread-safe implementation.

Also, in this case I hope “rebuild” does not infer delete or new in terms of data structures/class instances, etc. because memory allocations are not something you should be doing on the audio thread, even at the end of the process block.

As much as possible you should preallocate all memory and data structures that will be used in the audio thread, and updates from the message thread should only ever update the state of these preallocated objects using thread-safe techniques.

If “rebuilding” in this case truly means deleting old objects and instantiating/allocating new ones, and this can’t be avoided, there are different techniques you can use to achieve this, but again you should not new/create/allocate anything on the audio thread.

These two videos are a great resources and should cover all of the various scenarios and techniques available for safely sharing data across realtime and non-realtime threads:

Realtime 101 Part I

Realtime 101 Part II

Well, since you asked, this is how I handle a voice number change.

In my synth I have about 100 voices for a single instrument, say a piano type synth. The voices aren’t tha big, I’d say well under 100byte each. (Remember we’re talking about voices here, not the individual sounds, which might be several 100MB each if they contain samples.

So if I think I’ll need more for an instruments I’ll just make sure there will be enough of them allocated in prepareToPlay() or wherever I do the plug-in’s initialisation. This way I never have to do it mid playing. 1000 or even 10 000 of them (voices) would hardly get noticed in the ram footprint of the plugin compared to all the shit stuff like gif-knobs or bitmapped background images some people tend to put in their plug-ins before they even get started.

So you shouldn’t need to increase the number of voices (at least not I, and you asked what other were doing, remember…)

The other way around, decreasing number of voices might however be neccessary, while not every daw/computer could handle all to many voices at the same time.

But I don’t see that would be a problem. Probably you have something like this in your synth:

handleNoteOn()
{
   if (auto voice = getFreeVoice())
   {
      voice->loadWithProperSound();
   }
   else
   {
      /* skip this note while to many voices are already playing
      * or free one already playing (note stealing)
     */
   }
}

MyVoiceClass *getFreeVoice()
{
   if (numVoicesInUse < numberUserHaveSetInGUI)
   {
      numVoicesInUse++;
      return nextFreeVoice();
   }

   return nullptr;
}

and the render function might look something like this:

void processBlock()
{
   for (i = 0 i < numVoicesInUse; ++i)
      voice[i]->loadedSound->render();
}

Then just set numberUserHaveSetInGUI in your gui to something between

lowestNumberThatsUsable < numberUserHaveSetInGUI < number allocated in prepareToplay.

I don’t even think you’d need an atomic for that.

That’s how I do it. Hope this will be of any help for you. I guess what I’m aiming at is if you need to stop the music in mid play, rethink your solution, there’s probably a better, less intrusive way to handel the change…

the ‘rebuildRequired’ flag idea is reasonable.

in the case that the rebuild is a little time-consuming, I start a dedicated thread to do that. This prevents your plugin from causing the DAW to glitch though causing the real-time thread to block (wait) a long time while the rebuild happens. It also reduces the need to allocate memory etc on the real-time thread. Clearly you would need another flag to signal to the real-time thread when the rebuild thread is complete (and it can resume normal processing).
While the rebuild is in progress the render method can output silence.

and for extra marks you can organise it so that the plugin fades out smoothly before rebuilding. 20-50ms is usually enough. This will mitigate clicks and thuds.

Thanks guys! A few points:

  • The CRITICAL_OBJECT here is actually using GPU resources and memory and there is no practical way to let it be safely accessed while it is rebuilding. There is also no practical way to resize the memory allocations etc. without just rebuilding it from scratch each time. So I simply can’t have anything touch it while it is being built.
  • It is also built exactly for the size needed of voices, etc. so there is no room to have it allocate extra resources “just in case”

Thus I think the best solution might be to have two atomic bools:

//or use shared_ptr of atomic bool - either way
std::atomic<bool> rebuildNeeded = false; //set by UI as true when needed
std::atomic<bool> buildCompleted = false; //set to true every time build is fully done

Then I can have on my user input that changes something from UI thread:

rebuildNeeded = true; //set from UI thread

And this can be actioned on my audio thread, but by starting a new thread like this:

void renderNextSubBlock(AudioBuffer<float>& buffer, int startSample, int numSamples) override {

		//========================
		//THIS IS AUDIO THREAD
		if (buildCompleted) { //only allow access to processing / critical object if build is completed

			CRITICAL_OBJECT->doSomething();
			CRITICAL_OBJECT->process(startSample, numSamples, whatever);

			MPESynthesiser::renderNextSubBlock(buffer, startSample, numSamples); //call base function

			if (rebuildNeeded){
				rebuildNeeded = false; //run only once
				buildCompleted = false; //block further access now

				//create thread and run build on this other thread
				BuildThread buildThread("buildThread");
				buildThread.run(); //or is it startThread? Or what?
			}
		}
	}
}

Then the build function can be in the Thread:

class BuildThread : Thread {

	void run() override {
		REBUILDCRITICALOBJECT();
		buildComplete = true;
		signalThreadShouldExit(); //kill the thread as it is now done
	} 

I have never worked with the Thread class but as far as I can see that is how it works, right? You derive from it and override the run function then you run the run function or startThread function to make it go and do the signalThreadShouldExit() at the end of that if you want the thread dead?

I think this should be safe and it more successfully blocks any rendering access to the critial object while it is not built. It also only costs two bool checks per render block which is not bad at all (insignificant). And it won’t lag the audio thread.

Thanks again for your help and any ideas.

I’m not the most experienced person here, but I can note a few things.

The correct thing to call here would be startThread(). run() would just call the function from the calling thread, without starting the new thread. For a use case like this, it would probably be easier just to use std::async, so that you can run the whole thing from a lambda without having to worry about setting up new types.

However, a solution like this is never going to be suitable for the audio thread, as it involves all sorts of locking and system calls under the hood. Even if you fade out your own audio buffer as Jeff suggested, this won’t help for all of the other plugins that the audio thread is responsible for, and it is likely that you will end up missing a deadline.

The videos that Cymatic shared does cover situations like this, and you should look at those in detail. Also check out the associated github here.

Edit: it might be that a CAS loop (Compare And Swap) is your best option. Build your new resource off thread and then swap it in on a CAS loop on the audio thread when it is ready.

Thanks. I tested my idea as I wrote it and it at least prevents crashing, but I was just using run() so still likely running the build on the audio thread without realizing it. I will try startThread.

I just read here:

https://www.ccoderun.ca/programming/doxygen/juce/classjuce_1_1Thread.html#a4998cf4e1dfe357230f6448ed9458b0a

startThread() [1/2]

void juce::Threa.d::startThread ( )

Starts the thread running.

This will cause the thread’s run() method to be called by a new thread. If this thread is already running, startThread() won’t do anything.

This should take the burden off the audio thread altogether, right?

I mean the whole point of Thread is to run it on a new thread which will not be the audio thread.

I can appreciate there might be more seamless methods but it does not need to be perfect for my use, only not crashing which I have solved with everyone’s input, so thanks.

I don’t think you’re on the right track here. Just by calling startThread(), you’re already invoking a bunch of mutexes and system calls that aren’t real-time safe. If do don’t believe me, dig into it in a debugger and see for yourself. Even if you could get around this, you’d still have the problem of locking around the resources that you are re-building. I.e. your new thread has re-built the thing, but how do you get it back onto the audio thread without locking?

I think a better approach would be for your GUI input to trigger the thing to be rebuilt on the GUI thread itself, then to swap it into the audio thread using a CAS loop. I’ve never done this before so I can’t help with the details, but this seems to be what Fabian recommended in the aforementioned talk. Others here will be able to help you further.

A smaller point:

This won’t work because buildThread exists on the stack, so as soon as it goes out of scope it’s going to crash. It’s for this reason that std::async would be much easier in this case. But as mentioned above, any sort of thread launch on the audio thread is a bad idea to begin with.

I would generally advice against using these atomic booleans as a way to synchronise audio and ui thread. It is recommended relatively often and it is working well in maybe 9/10 (or 99/100) cases, but most of the time, you don’t think of every race condition. It is also not exactly scalable when your application grows more complex.
As a concrete situation where these booleans are most likely to fail: consider the possibility of updates from the UI happening very fast. The audio thread might overwrite the second rebuild request while performing the first (or something similar etc.)

For synchronisation, I’d recommend to look at three relatively easy patterns:

  1. Atomic values: good for single parameter values like gain. Everything that fits into a primitive type. Major benefit: it is very easy to use, hard to get wrong and costs are relatively low (no system calls). Don’t use atomics to pass pointers to bigger structures around, without being absolutely sure of what you are doing. The problem with pointers ultimately surfaces when you think about destruction at the correct moment (and the cherry on top: make sure the audio thread is not the one calling std::free)
  2. Read-Copy-Update (RCU): great pattern to sync data structures. Here is a ready to use implementation (the author also has a talk on YouTube explaining what he did). I won’t go into more detail here, as there are a lot of resources (also in this forum) on that matter. Snapshots/Include/Snapshots.h at main · PalmerHogen/Snapshots · GitHub
  3. RCU has a disadvantege when it comes to the “copy” step. If your data structure is big, it might be an expensive operation to maybe change a single value. This is where you might want to use a queue of updates. In essence, you tell your audio thread what exactly has to change without passing an entirely new copy. Here is a great talk on that topic: https://www.youtube.com/watch?v=lb8b1SYy73Q It is also how JUCE implemented their sampler demo: JUCE/examples/Plugins/SamplerPluginDemo.h at master · juce-framework/JUCE · GitHub The command queue downsides are, that it is only working properly with two threads: one to push changes, one to pull and execute changes. If you have two threads pushing, the execution order might get tangled and does not make sense anymore (especially with topographic changes to your structure). RCU on the other hand has the ability to report on the updater, that it was not the one committing the update (a different thread got there first), so the updater (UI) can have a second chance to repeat the RCU in it’s entirety with the additional data from the other updater.
1 Like

Creating a new thread every time uses a lot of resources. I would use JUCE::ThreadPool with one thread initialized. This way you also get a job queue for free and you don’t have overlapping tasks/threads out of the box. I think this already eliminates a lot of problems.

Edit:
You can use lambdas to trigger jobs. No need for classes that derive from Juce::Thread.

1 Like

If you can accept fails, i.e., unprocessed several blocks when updating the object, you could try lock a spin lock on the audio thread and lock it when you updating the object (on the background thread). Otherwise, you may consider the CAS or RCU approach.

Here is another great talk on RCU:

And a continued video on real-time 101:

Correct. I had to put the BuildThread as a private variable but otherwise it has worked great. I have had zero crashes or problems since doing this a few days ago now.

This is not something I need to waste a tonne of energy on as it is a heavily resource intensive process to rebuild the object and you will not have ongoing smooth playback during this process no matter what, nor is it needed or expected.

I think the odds of the atomic booleans failing is also extraordinarily low and I am not launching a NASA shuttle. I don’t need to scale it up as this is the only thing I need to lock like this.

The point is that only the audio thread knows when it is safe for me to lock the rendering process out from accessing the object and start the rebuild. Thus the audio thread must be the one to do both of these things. I don’t really see a simple or easy way around this besides the Thread (which took minimal effort to implement), and so far it has worked perfectly without any failures or bugs.

If I don’t want to create a new thread each time, I can keep that thread alive and use it as needed as suggested by @kunz , but it doesn’t seem to be an issue at this point for my purposes at least. Bigger things to do.

@kunz I am just curious what you mean by:

You can use lambdas to trigger jobs. No need for classes that derive from Juce::Thread

I am not familiar with that. In C# we have Task.Run() to run functions easily on a new thread. I presume this is something similar. Can you elaborate or share a link or few lines of code to illustrate? Is this a way to run something on another thread you mean? Thanks.

I’m also from the c# world :slight_smile: Async await is a lot more straightforward there. You don’t need to use Task.Run() for network or database access in c#. Most libraries already come with IO-bound async functions.

I also like to keep things simple. Because of this I often use the juce library for stuff like this.
They often have some nice additional functionality like a way to tell the thread to cancel or if you want to make sure that only one tasks runs at once. Also, you don’t have to care about deconstruction.

No need to blow up the code with new classes. Here is how you could use the juce::ThreadPool:

threadPool->addJob([this, file, padIndex, mappingIndex]
                               { processor.loadMetadata(file, padIndex, mappingIndex); });

I meant something like this where you can pass a method instead of a thread class:

threadPool->addJob([this, file, padIndex, mappingIndex]
                               { processor.loadMetadata(file, padIndex, mappingIndex); });

You have much less code this way.