Reading/writing values lock free to/from processBlock

I thought I could do it without per-sample but that seems to be a misconception from my side. Seems to me and from the answers here that it’s not really possible (without going inside those classes and making changes…). But it’s great to have cleared this out.

I’m also not using the juce dsp classes for filtering. But it looks that every filter also has a method to process a single sample (processSample).

https://docs.juce.com/master/classdsp_1_1IIR_1_1Filter.html#a751edfeb3193b7739c5b2f47168c7027

Yes, but then I would need to take apart the whole AudioBlock into single samples anyway so it seems quite pointless if the idea is to work on a per block basis. :slightly_smiling_face:

Revisiting this, just wondering…
The idea with swap was to have two pointers to arrays of coefficients.

pointer1: currently in use
pointer2: idle

And then when new coefficients are needed I would do:

calculate new coefficients outside process block
pointer2 := newly calculated coefficients for the IIR.

And then the idea was that using swap (pointer1, pointer2) in the beginning of process block just before assigning to the filter (which I will have to do in any case). I thought it would make the operation safe and no allocations would be needed in that action. But maybe unique_ptr is not the right type here? I’m not familiar with the safest and most effective way to do this in C++.

But what are you doing if values change again while you switch the pointers? You may still have some edge cases that aren’t thread-safe.

OK, so the swap is not thread safe? Could it be made thread safe without locking? Just curious, it seems I’m pretty close… :slightly_smiling_face:

When the swap is complete they can at least not be corrupted during assignment to the filter.

The pointer swap may be thread-safe. Maybe you need to use atomic pointers for this. But the values in the array are still not thread-safe:

  1. You change ptr2 array from the UI Thread.
  2. You change ptr2 again, the same time you swap to ptr2 in the processing block → you may read a corrupt filter state.

Don’t understand me wrong. You might never notice a problem in real life. But it isn’t thread safe and may lead to hard-to-find bugs or audio clicks.

Yeah I see what you mean in general, but I’m not sure how the values could change in this particuilar case.

The the result from the IIR-coefficient calculation is passed as a whole in an array which is assigned to the unique_ptr that’s not in use, after the swap there’s no acces to them from “outside”. Before the swap I guess the pointer can be reassigned but that would be from a new complete set of coefficients so shouldn’t be corrpupt.

I think… :thinking:

You are assuming, that the audio thread directly picks up on the swap. But imagine two swaps being scheduled before the audio thread picks them up. So there is access from the “outside”

Another major downside from this two pointer approach is, that you don’t guarantee that the coefficients don’t change while the audio thread is working with them. That’s no problem when you read them only once form the atomic pointer at the start of processing a block (what you should do anyway to keep the memory barriers to a minimum) but it’s hard to keep track of these kind of constraints.
What I would advice you to do is: use one atomic_shared_ptr (or atomic<*type> of you want to use a different memory structure) and swap that pointer only on the audio thread. This way the audio thread always knows when stuff is changing and the outside can happily read from it (e.g. to display the values currently processed) without having to worry that the object you are reading suddenly gets destroyed while the audio thread changes the coefficients.

If you don’t need the “happily reading” part, you can skip the hole atomic part and just make sure to set the values on the audio thread.

The SamplerPluginDemo has this kind of fifo command queue to schedule a command (e.g. set new coefficients) from outside and the audio thread executes these commands on thread as soon as it comes along. The only thing you have to change is to replace the CriticalSection with a SpinLock.

One note about atomic_shared_ptrs: very new and not all compilers support those currently. If those are not an option, you can use a SpinLock at that point two.

OK thanks everyone. I will need to digest this now to get the full picture.

I should probably not dig deeper into this but just to mention, the swap is inside the process block and the coefficients pointed to are consumed immediately by assignment to the filter state. So this particuilar problem won’t happen.

But I see there will be a queue situation on the “outside”.

I completely feel you. One tries to learn something and you want to do everything “the right way”, and then you see compromises everywhere.
For instance the changes from audio thread to the GUI, where juce uses an AsyncUpdater.
You have to find your own way and figure out, where you want to compromise and where you create a complex system.

One great talk is from @dave96 on the JUCE meetup a while ago:

It is good to go in steps, being aware that there are problems that might or might not require solving later.

Great link!