Multithreaded AudioProcessorGraph Source-Code


#1

I want to share my experience with a multiprocessor version of audio-processor graph, i think its a good start to build an “official” version for juce/T4.

My version uses the same method names as the original version, so it should no big problem to replace it with the original version.
I think is a good idea to create an abstract version of AudioProcessorGraph/Node, than it would be easy to switch between a single- and multicore version.
Basic data-structures like connections could be inherited. (the friend-class relationship with “Node” is may not elegant for inheritance)

Up to now i ignored all midi-functionality, so it may not work properly with midi. Also there is a recursion-check for the graph missing.

Functionality:
It uses a simple self organizing, but tested and good working structure, based on the “ideal” assumption that every node has its own CPU.
Because you never now, when a AudioProcesser has completed its block-processing, most pre-sorting of processing-operations would be redundant.
Thats why i have chosen this structure.

Basically the worker threads (number of CPUs-1) just iterate through the audioProcesser-nodes and look what they “can do”.

“node=nodes_workingCopy[ (++nextNodeIteritater) % nodes_workingCopy.size() ];”

The AudioDevice-Callback-Thread also do processing, but never! does any OS-implicied waiting (like WaitableEvent/Sleep)

Every node has references to other nodes, which are connected via inputs and outputs, and uses a atomic number to stores its “numberOfProcessedInputs”.
After a node has processed (“AudioProcessorGraphMultiProcessor::Node::process()”) it increase the “numberOfProcessedInputs” of nodes which are connected via outputs.
So when a the “numberOfProcessedInputs” reached “requiredInputs.size()” , the worker-thread collects all audio-input-data into the nodes own audio-buffer and does the processing.

The orginal AudioProcessorgraph uses MM-Callback-locks to prevent threading issues, instead i use the critical Section “reconfigurationLock” to make it safe for any thread to call a method.

EDIT:
New file location:


#2

I’d be interested in seeing a usable version of this through a rehashed plugin host that I can pull.


#3

As long you don’t use any midi-connections it should be usable. Currently i use it for my own internal processing (without Plugin Host), where i have no need for midi.


#4

files now available on github


#5

Not tested yet but it looks like one of the most usefull pieces of Juce user-written code ever ! Good job :smiley:
The inheritance thing is also a very good idea. Do you plan to make midi work eventually ?


#6

i wouldn’t describe it as “rock solid” but it works for me. My secret hope is that Jules uses this as inspiration to create something similar which he use as base for the whole tracktion mixing engine, and contribute the source-code back to juce. :wink:


#7

[size=150]Brothers, let’s join hands and pray then ;)[/size]

I’m gonna have a look at your code and check if it can be made more solid by using a higher level, bullet proof, abstraction, like Intel TBB (opensource and cross platform !)


#8

Revisiting my modded version of chkn's multithreaded graph.

And I'm wondering if the wait event on line 861 is a good idea

if (nothingHappenedCounter>=nodes_workingCopy.size())
{
if (!thisIsAudioCallbackThread)
{
waitEvent.wait(1);// Don't waste CPU Power, when nothing is to do - wait a little bit unless another thread has something finished
};
nothingHappenedCounter=0;
}

As far as I can tell this would prevent the thread from running for 1 ms, but that is very long if your buffersize is 64 samples.

Not saying this is wrong, just asking if it might be.

The reason I'm asking is that I'm hunting down some very rear dropouts, (like once every 48 hours)


#9

i would say once in 48 hours is surprisingly good for 64 buffer size ;-)

But anyway, i have to look in my code, not sure i why i choose a waitable event those times, maybe a solution based on a Thread::yield() would be better)

Without waiting the threads would heavily spin and waste cpu-power


#10

Well, I'm aiming for "never" @ 64, as in live audio that one buffer underrun is pretty obvious to the 800+ people attending the show.

 

When I look at it again, see that the wait is probably ok.

The wait event will be signaled / reset by other threads and the audio callback thread, long before the ms times out.

Sorry for the wild goose chaise.