Multithreaded audio processor graph


#1

A multithreaded audio processor graph : do we have a chance to see that one day ? :)

 


#2

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


#3

Is it just a matter of replacing Juce's AudioProcessorGraph by your version ? In that case I'd be interested ...


#4

 

The link is not to my code, but yes, it can replace the standard graph 1-1


#5

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


#6

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  )

 


#7

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)


#8

Great job here guys ! :D Can't wait to see the code !! I hope Jules will consider integrating it to Juce !


#9

​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.


#10

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


#11

An approach that avoids the locks might be:

  • the audio callback thread iterates over the nodes looking for ones ready for processing (using numberOfProcessedInputs)
  • when it finds one it schedules it on a worker thread via that thread's lock-free queue

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.


#12

cool!


#13

>>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)

 

 


#14

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();
    };


#15

More about…Thread Locking

Naggar