Is it safe to run new thread in processBlock?

Hello,
in my AU/VST3 plugin I have very expensive calculations that need to be done on buffer data. So I wonder if it’s good solution to make those calculations in separated thread?

Of course I know I need to provide in such case some latency to give time for another thread for calculations. But I still can imagine there could be some situation that another thread will be not in time with those calculations, and main thread would use data which are also in use in another thread. So I wonder if before run separate thread should I copy buffer data to some vector with atomic values.

Hmm… so it looks like separate thread generates many possible issues to be maintain.

I would like to ask if anyone of you have some experience with that, and could give some advices? What library to use? Is juce::Thread good solution for such things? Or is there anything better?

For any help great thanks in advance.

Best Regards.

it’s trivial to use AbstractFifo to pass data between threads.
juce::Thread is a great tool for background thread usage.
just remember to wait(5) or so at the end of your run() functions in your background thread class, otherwise you’ll cook your CPU.

Mind you, if each processBlock relies on the result of the extern thread, you get a priority inversion which means the realtime audio thread will have to wait for a lower priority thread.

Hmm… thanks for your reply.
But wait(5) in run() ???

I don’t get the idea. I thought in that way:

  1. collect data in processBlock()
  2. when it’s collected then Thread::startThread(realtimeAudioPriority)
  3. in run() on the end of calculations just call Thread::stopThread(0);. Because I need to run calculations only one time until next data is collected in processBlock(). So that’s why I don’t get the idea with wait(5)

But now I see in Juce documentation that startThread() and stopThread() is fairly heavyweight. So my solution is probably wrong. But in that case they advice to use ThreadPool instead Thread. But you’ve just told Thread is great, so not sure what I should do.

struct BackgroundThread : Thread {
    BackgroundThread(Fifo& f) : Thread("BackgroundThread"), fifo(f) {
        startThread();
    }
    ~BackgroundThread() override {
        stopThread(500);
    }

    void run() override {
        while( !threadShouldExit() ) {
            //do your processing with your fifo
            //then
            wait(5);
        }
    }

    Fifo& fifo;
};
struct YourAudioProcessor : AudioProcessor {
    //snip
    void processBlock(...) override {
        //send buffers to your background thread as needed
        fifo.push(buffer);
        //pull buffers from the background thread as needed
        fifo.pull(buffer);
    }
private:
    Fifo fifo;
    BackgroundThread backgroundThread { fifo };
};

Fifo is a class built around the juce::AbstractFifo. look at the documentation for how to use AbstractFifo with the read() and write() member functions that return ScopedReadWrite instances.

Hmm… Great thanks for example. I will test it. Super duper.

use a class like this, combined with the stuff documented here for your Fifo:

template<typename T, int Capacity>
struct Fifo
{
    AbstractFifo fifo { Capacity };
    std::array<T, Capacity> buffer;

    bool push(const T& t);
    bool pull(T& t);
};

https://docs.juce.com/master/classAbstractFifo.html#a41c705711ad487e127df59cd978ef24c

Great thanks for your reply.
But it looks in your code example like separated thread is running permanently (with 5 ms wait each iteration). But there aren’t any notification for thread that data is ready for calculations. And as I understand I should make in run() some if statement which checks if data is ready to calculations. Am I right?

But let’s say my thread has just started waiting (5 ms in your example) and after 1 ms in my processBlock() data is ready for calculations. Then the thread still need to wait 4 ms until start calculations.

I feel like there is some mismatch. Let’s say in my DAW the buffer is set to 128. So it’s about 3ms which is less than waiting in run(). But let’s say I want to make in run() the FFT which has fftSize 64. So during one buffer I need to perform calculations in run() two times. But it’s imposible while wait(5) in the run() is longer than my buffer size.

So there are two possibilities:

  1. I just don’t understand the idea which you presented;
  2. I need to avoid wait() and instead I should use some notification system which force the Thread to make calculations immediatelly and than wait for next notification.

For any help great thanks in advance.
Best Regards

I’m confused as to what problem you’re expecting a new thread to solve… If you need to perform calculations within very short increments without latency (within a buffer of 128 samples at 48k, for example), you need to perform them on the audio thread. If the results both a) rely on audio data and b) require more than a buffer’s length to process, you might be looking at a processor which couldn’t feasibly be used in real-time (although spinning up another real-time thread to perform them is possible, if difficult). If it doesn’t require audio input (say, very expensive calculation of filter coefficients, transforming an IR, rebuilding a graph, etc) then simply perform them on another thread and safely swap the result into the audio thread at the beginning of the processBlock (normally using an atomic).

If the process requires audio input, but also must be performed on another thread for another reason (it might require you to hold a lock or allocate memory, for instance), use a FIFO as described above and perform it on another thread. This isn’t a guaranteed fix, as the audio thread would still rely on the new thread processing the samples faster than they’re needed. This would probably create at least two buffers of latency, but it could easily be more. We can’t really tell you without a more clear description of what the other thread’s calculations are.

Hello zac, great thanks for your reply.
I want to make various things. For example pitch shifter, or transforming IR with long reverb impulse responses, etc.
I know how to use fftw library, and it is very fast and handy for such tasks like mine tasks. And it is fast enough to perform calculations directly in processBlock() on my new computer but I am afraid about older and slower machines.

And I was advised to perform such heavy calculations in separated thread.
And actually the main reason is I just want to understand how to properly use separated thread and synchronise it with processBlock().

Once again: I don’t understand why should I use wait() in the run() of separated thread.
Of course I understand the reason which matkatmusic mentioned and which is “to not cook my CPU”.
But wait() also means that that after wait the run() will be called again and again independently on what happen in processBlock(). So I think it would be more reasonable to call run() only when processBlock() tell that in some way to another thread.

In other words I imagine it like that:

  1. processBlock() works normally and collects the samples which are needed for calculations in mySeparatedThread::run(). But mySeparatedThread::run() should do nothing until samples are collected.
  2. When there are enough samples collected then processBlock should say to mySeparatedThread: “Hey man!!! I have enough sample so please perform calculations as soon as possible”.
  3. and now mySeparatedThread::run() should perform calculations once and stop working, and waiting for next portion of samples. But until we don’t know exactly when next portion of samples will be ready we shouldn’t wait(5), instead we should wait until processBlock() tell us “Hey I have next portion of samples”

That’s why I don’t get the idea to wait 5ms.

Okay. Let’s talk about this on a case-by-case basis:

While this task is potentially costly, it is also (probably) something that needs to happen in response to parameter changes, not in response to audio input. You can do this entirely on the message thread, in a number of ways (timer, changeListener from the parameterChanged(), changeListener from processBlock(), etc), modifying the IR and using a std::atomic<T*> to exchange the modified IR in a safe way.

This is different. If you can’t find or write an algorithm which is safe to run on the audio thread, don’t make a pitch shifter! You can/should/will need to still use a FIFO depending on the type of pitch shifting and the algorithm you use.

I think you’re misunderstanding more here though. Most importantly, it seems like you think the thread will do something beginning at the top of every call to processBlock().

There is no way to safely do this. The best way to emulate it is to make the thread check for jobs at an interval. If you want it to be 2.5ms instead of 5, that’s fine. But you can’t have the new thread synchronously called to do something from the audio thread.

With that understanding, you’ll notice that

isn’t possible. But you can do this:

  1. add a std::atomic (or atomic flag) to your processor, and a reference to your processor on the calc thread
  2. in processBlock(), once enough samples are queued, set the atomic to true.

and meanwhile

  1. run() on your calc thread loops.
  2. in every loop, it checks if the atomic is set, if so, it sets it to false and does some math.
  3. wait for a little while so that it doesn’t constantly check the bool forever.

If you need to queue samples up rather than simply set a flag, you can replace “set the atomic to true.” with “pushes samples to the fifo” and “it checks if the atomic is set, if so, it sets it to false” with “it checks if the right number of samples are in the fifo and, if so, pops them”. Then you’d need a similar synchronization method to get the samples back into the audio thread.

5ms is a pretty reasonable amount of time between checks for most of these sorts of things.

1 Like

Great great great thanks for your informative reply.

But what about to use wait(-1) in the run(), and in processBlock() just call notify() when data is ready for calculations.

I still have problem to imagine how both threads are synchronised when wait(someInt) is used. I know and understand that both threads can’t be called synchronously. But then shouldn’t we synchronise them in some way manualy?

When I use wait(2) it means the run() has it’s own repetition rate, which is exactly two milliseconds and it’s completely independent from processBlock() which has repetition rate dependent on buffer size set in plugin host. And that total independency in repetition rate between run() and processBlock() is something that is disturbing me.

So I wonder if wouldn’t it be more reasonable to use wait(-1) in the run(), and in processBlock() just call notify() when data is ready for calculations. Isn’t it?

Best Regards

Well there are two major flaws with this:

  1. Thread::notify() would only notify the waiting thread if wait(-1) had already been called. That means if you have a buffer still processing and have another ready to process, it might not get processed.

  2. More importantly, Thread::notify() needs to signal a WaitableEvent, which acquires a lock. This is unacceptable on the audio thread.

Other things to note:

processBlock() doesn’t have a “repetition rate”. It could be called on ten minutes of audio all in 30 seconds. It just has to process the samples when asked to, regardless of when in time it was asked to process.

Waiting for a period between checks for whether there are new samples to process is the only way to have these two threads doing these tasks without locks. If you want to make the waiting period shorter, go for it, but I’d guess that the only difference would be higher CPU usage.