How to create several threads compute different contribution of the final AudioBuffer?

Hi everyone,

I am currently developing a synth that has its processing part coded in Faust (you don’t need to understand Faust to help me :wink: ). The plugin consist of 9 oscillators, each having some basic parameters (like : gain, pan, active/notActive, waveform type, etc…) and several effects (like : distortion, compression, etc… ). The Faust part has already been coded, which means I have a cpp file that contains a class representing a single oscillator.

What I do for the moment, is that I create 9 instances of the class, I compute the sound that each oscillator make and then I add them up. Like so :

void FAUST::processBlock(juce::AudioBuffer<float>& buffer, int totalNumOutputChannels)
{
    juce::ScopedNoDenormals noDenormals;
    int numSamples = buffer.getNumSamples();
    float** o1 = osc11Faust.processBlock(numSamples);
    float** o2 = osc12Faust.processBlock(numSamples);
    ...
    float** o9 = osc33Faust.processBlock(numSamples);
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        for(int i=0; i<buffer.getNumSamples(); i++)
        {
            *buffer.getWritePointer(channel,i) = o1[channel][i] + o2[channel][i]; + o3[channel][i]
                                                 + o4[channel][i] + o5[channel][i] + o6[channel][i]
                                                 + o7[channel][i] + o8[channel][i] + o9[channel][i];
        }
    }
}

The issue is that there is a super annoying clicking at the start of a chord when I press more than 2 notes at the same time. Since the results of the different oscillators are independent, I though it would be a good idea to make them run on different threads. I’ve looked at the juce::Thread class and what I’ve come to understand from that is that I should make all the oscijFaust objects inherit from Thread and put the function processBlock(int numSamples) in the run function. But then how can I know when each process has finished? And how can I share the float** between all the threads?

I am also interested in knowing if the threads are actually going the help me get rid of the clicking. If someone has another idea of how I could do I would very much appreciate.

Romain.

Are you sure the clicking is because of CPU overload? Maybe there’s just some problem in the voices handling, their envelopes or the voices are too loud at the attack stage when summed together?

Faust returns a buffer from its processBlock method? Hopefully it doesn’t actually allocate those buffers in the audio thread! Can you check how that part is implemented in the code?

Using threads to parallelize processing in the audio callback is not usually a good idea. There will rarely be enough actual work for the threads to do, compared to the overhead of synchronizing the threads. (Like you already mentioned, you have to have some kind of mechanism that lets you know all the threads are ready with their work and that you can mix the results.)

Edit : also the classic : are you testing a release or debug build?

2 Likes

Thank you for your answer! I was indeed in debug mode, I didn’t know there was such a difference in the CPU load.

The buffers are initialized in prepareToPlay like so :

void RODAG::FAUSTOSCILLATOR::prepareToPlay(double sampleRate, int samplesPerBlock)
{
    outputs = new float*[2];
    for (int channel = 0; channel < 2; ++channel)
    {
        outputs[channel] = new float[samplesPerBlock];
    }
}

And during the processBlock call, the elements of outputs are replaced by the audio samples.

Thanks again for your help.

Clicking in the start of the notes is most commonly a sample mismanagement problem rather than a CPU overload one. If it was your CPU overload you would hear it during the whole sound not only in the beginning. If you print or log your output buffer you’ll probably see snippets of 0’s in the beggining of a new note/end of previosu one, or just numbers over 1.0 or under -1.0 if that’s the case. A pretty easy way to find out is using only 2-3 oscillators instead of 9, but I bet the problem will persist. Said so, going for multithreaded audio even if everything is working fine is asking for trouble, I’d first try to solve this in a single audio thread.