A multithreaded audio processor graph : do we have a chance to see that one day ? :)
A multithreaded audio processor graph : do we have a chance to see that one day ? :)
I have used this:
https://github.com/jchkn/ckjucetools/tree/master/AudioProcessorGraphMultiThreaded
And modified it a bit to support midi and my specific needs.
There are other post on the forum on the same topic
Is it just a matter of replacing Juce's AudioProcessorGraph by your version ? In that case I'd be interested ...
The link is not to my code, but yes, it can replace the standard graph 1-1
Can you share what you did to support MIDI?
Perhaps we can combine this with Graeme’s changes which are working great (see thread: http://www.juce.com/forum/topic/optimisations-renderingopssequencecalculator)
Cheers,
Rail
well my modified version of the audioprocessorgraph was a proof-of-concept, and i'm happy to see that someone did a full working version of it. Would happy to see the code ;)
I'm came to the conclusion the current preordering algorithm makes no sense in a multithread context ( i wrote something as explanation here http://www.juce.com/forum/topic/multithreaded-audioprocessorgraph-source-code , which does not mean that any pre-sorting is needless or can be used to optimize the algorithm )
I´ll share my version when I get my head above water here.
As I recall the midi part was pretty easy to do.
I also modded it a bit so that it does processes nodes that are not patched to output. (For example a node that only has an input)
Great job here guys ! :D Can't wait to see the code !! I hope Jules will consider integrating it to Juce !
Hi chkn,
i read your code and found it really smart, for 2 reasons :
1. it manages obviously the multithreading, as the title said, (and that is nice !)
and 2. the repetitive way the graph is traversed with your waiting flags system enables (with a little work) to update connections or nodes without recalculating the all graph with buildRenderingSequence(), simply locally, parents to children, without even stoping threads or locking anything... which is a more dynamic AudioProcessorGraph than the orginal... !
i'll have to work a little bit on it but at the moment it looks to work well, and the midi is a simple corollary of the audio buffer, it works the same...
Well thanks! i'll share this soon.
Interesting stuff. I see the use of ScopedTryLock within AudioProcessorGraphMultiThreaded::Node::process. Isn't using mutexes in a real time thread a no-no?
See http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
An approach that avoids the locks might be:
If you can find a lock-free wait-free queue that supports multiple consumers (I don't know of one) you could use a single FIFO, which would be better.
I suppose the disadvantage of this is that the worker threads would spin (busy-wait). I don't know of a way around that in the lock-free world.
cool!
>>I suppose the disadvantage of this is that the worker threads would spin (busy-wait).
Yes, thats the point, and if all cpu-kernels would spin (i think with some kind of compare-set-bit operation) it would be unbelievable inefficient.
And modern CPUs may throtteling the clock for energy/temperature then.
If somebody implements something cool i would be happy, but at some point there needs to be some-kind of synchronization.
But from a practical point of view: All my attempts to measure problems through performance intrusion failed, why? Modern CPUs are just to fast.
Also it seems the thread- scedulers of modern OSs are highly optimized.
The real bottleneck were always, ram-read/write-access (or ram allocation, especially on windows)
EDIT: I removed the code because I must admit that it was not fully operational at this point... (And nobody gave any advice about it!) For the Midi management in Chkn's code: 1. add the midiBuffer member in the Node class
ScopedPointer<MidiBuffer> midiBuffer;
2. Init. in the Node::prepare method:
midiBuffer=new MidiBuffer(); midiBuffer->clear();
3. add this in the Node::process method (before "processor->processBlock(*buffer, *midiBuffer);")
bool unwritten=true; int o =AudioProcessorGraphMultiThreaded::midiChannelIndex; for (int i=0; iinputConnection->destChannelIndex==o) { if (ri->inputConnection->sourceChannelIndex==o) { if (unwritten) { midiBuffer->clear(); midiBuffer->addEvents(*ri->node->midiBuffer,0,buffer->getNumSamples(),0); unwritten=false; } else { midiBuffer->addEvents(*ri->node->midiBuffer,0,buffer->getNumSamples(),0); }; } else { DBG("Impossible Connection: Source Channel "+String(ri->inputConnection->sourceChannelIndex)+" is higher than available channels "+String(buffer->getNumChannels())); } }; } if (unwritten) { midiBuffer->clear(); };
More about…Thread Locking
Naggar