CLAP: The New Audio Plug-in Standard (by U-he, Bitwig and others)

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

diroxe7660 wrote: Sat Aug 31, 2024 4:39 pm - When turning off sync and enabling the HPF/LPF, changes to the filters sometimes take a really long time (several seconds) to apply.

Edit: I somehow managed to break the filter or the cutoff slider by rapidly changing cutoff freq.
Thanks so much for trying out my plugin. Unfortunately, I've not been able to reproduce these problems so far. I'll keep trying.

I'm wondering if these problems are somehow related to me putting locks around parameter changes, both in the UI thread and the process thread. I saw locks being used in some of the example code for CLAP, so I decided to do the same. Back in my VST days, I mostly didn't use locks. The one time I did, I wound up taking them out because of complaints that it caused stuttering (I probably didn't implement them correctly). I'm not sure how locks could cause such a long delay in parameter changes taking effect, but it's the first thing that comes to mind. I'll investigate further.

Post

Which clap example code uses locks? Most of the examples I know use lock free data structures to coordinate the ui and audio threads

Post

Leslie Sanford wrote: Sat Aug 31, 2024 11:38 pmThe one time I did, I wound up taking them out because of complaints that it caused stuttering (I probably didn't implement them correctly). I'm not sure how locks could cause such a long delay in parameter changes taking effect, but it's the first thing that comes to mind. I'll investigate further.
This is most likely because of what is known as "priority inversion" where a low-priority thread (eg. UI) takes a lock and then get's suspended for one reason or another. A high-priority thread (eg. DSP) then tries to take the lock, but ends up waiting for the low-priority thread to release it, effectively demoting the high-priority thread to a lower priority during the wait, because it can't progress until the low-priority thread is scheduled.

In strict realtime systems it's even possible to construct deadlocks out of priority inversions (which is why they sometimes support a concept known as priority inheritance, but that's not something you should expect on the average desktop platform), but fortunately that won't happen on your average desktop system; the low-priority thread will get CPU time eventually, but it might take long enough for the high-priority thread to miss it's target and drop an audio buffer.

The only reliable solution is to redesign so that the DSP thread never needs to wait for a lock that is potentially held by a lower priority thread. One can do this either by using wait-free queues (which by definition never force any thread to wait), or one can do this by using "trylock()" style primitive (that fails rather than waiting) on the high-priority thread if it's acceptable to simply skip the critical section if we cannot get the lock immediately (but note that looping a trylock() is even worse than just waiting).

Most of the time wait-free queues are preferable. The "trylock()" approach is quite situational, because if the UI thread is expected to take the lock often, then trylock() is also expected to fail fairly often and whether this is acceptable needs to be evaluated case-by-case... and frankly the situations where trylock()-style approach is least likely to cause issues thends to be the cases where it's also fairly easy to just use wait-free queues (or even atomic variables) instead.

Post

baconpaul wrote: Sun Sep 01, 2024 11:21 am Which clap example code uses locks? Most of the examples I know use lock free data structures to coordinate the ui and audio threads
I thought it was more than one example, but it's just the first one that was linked to from the Clap site:

https://cleveraudio.org/developers-getting-started/

Specifically:

https://nakst.gitlab.io/tutorial/clap-part-2.html

I think this is using traditional locking as far as I understand. A mutex is acquired in one thread, e.g. the UI thread, and other threads must wait till it is released.

Post

Leslie Sanford wrote: Sun Sep 01, 2024 2:02 pm I think this is using traditional locking as far as I understand. A mutex is acquired in one thread, e.g. the UI thread, and other threads must wait till it is released.
The problem is that most of the world ignores the concept of priorities.

In a non-realtime situation, it is essentially sufficient that there are no race conditions. In a realtime situation, correctness additionally requires that no thread ever waits for the progress of a lower priority thread.

In a realtime situation you simply cannot ignore priorities (in a hard real-time system that's sometimes enough to cause a deadlock), but most multi-threading tutorials, programming courses and even the C++ standard assume that priorities don't exist.

The short version is that if you're sharing a mutex between threads of different priorities, the code is simply incorrect.

Post

mystran wrote: Sun Sep 01, 2024 3:43 pm The problem is that most of the world ignores the concept of priorities.
Hi, mystran. Thank you for the detailed posts. It's been a while since I've been active here, and it's nice to see a familiar face.

Your posts have given me a lot to think about. I understand now why a naive use of a mutex in this context isn't appropriate. My current thinking is to limit actual parameter changes, e.g. updating delay times, etc., to the process thread. The UI thread merely marks that a change has happened (using a 'gesture' array). The remaining task is making modifying this array thread safe in a 'lock-free' manner.
Last edited by Leslie Sanford on Sun Sep 01, 2024 8:35 pm, edited 1 time in total.

Post

Yeah take a look at the clap saw demo. That, like most claps, has a pair of non allocating lock free queues it uses to inter communicate between the ui and dsp thread

Like mystran said if you lock on the Audi thread and there’s contention you will get a stutter. And if there’s guaranteed to never be contention why are you locking. So it is (almost) always wrong and always sub optimal

Post

Isn't it best to only update parameters on a concurrent thread? The DSP thread should not be waiting for any updates to occur, they should already be completed.

Post

camsr wrote: Sun Sep 01, 2024 5:48 pm Isn't it best to only update parameters on a concurrent thread? The DSP thread should not be waiting for any updates to occur, they should already be completed.
My thinking is that you'd treat the UI events the same as the events coming from the host that you're processing in the process function. You're synching the host to the UI events at the beginning of the process function (at least that's what I'm seeing in the saw demo). It seems like a good place to update the plugin with changes from the UI, e.g. updating fillter coefficients, etc. But I could easily be wrong and should become more familiar with CLAP before making any conclusions.

Post

A little point I wanna nitpick: a "lock-free" algorithm is one that guarantees that at least some thread makes progress, while a "wait-free" algorithm is one that guarantees that all threads make progress. You typically want a "wait-free" queue here.

In practical terms, the difference is actually sort of important for real-time purposes, because a "lock-free" algorithm is allowed to do things like spin threads in retry-loops potentially forever as long as there's always some thread that eventually succeeds, where as "wait-free" algorithms must give each thread a bounded worst-case no matter what other threads might or might not be doing and the "bounded worst-case" is basically another definition for "realtime."

The distinction is not trivial: a wait-free single-producer-single-consumer queue (which is probably what you want; typically one for DSP to GUI and another one for GUI to DSP) has the downside that it's necessarily fixed size, so you need to deal with the possibility of the queue occasionally being full. If we relax the requirement to "lock-free" then we could build the queue as a linked list, allocating more nodes as we go, but besides losing the "wait-free" property, we also need to deal with the node allocation somehow... and we still have the problem that if we're not draining the queue fast enough the queue could potentially grow arbitrarily large.

Post

Yeah that’s a good distinction. I meant “fixed ring buffer for objects with copy semantics and atomics for reader and writer position” for “lock free queue”. Juce has one as does lots of open source projects.

Post

camsr wrote: Sun Sep 01, 2024 5:48 pm Isn't it best to only update parameters on a concurrent thread? The DSP thread should not be waiting for any updates to occur, they should already be completed.
Clap handles all events on a single thread in a single queue and that thread is the audio thread (with one minor technical exception that param events can get notified on the main thread when no audio is running)

So most claps when they get a param update if a ui is attached pushes an update to a non-mutexed queue, and similarly when you drag a knob you push onto a similar structure that the audio thread then reads and presents to the host.

https://github.com/surge-synthesizer/cl ... o.cpp#L332 and surrounding may help.

Post

Has anyone had any trouble implementing a bypass parameter with Reaper?

What I'm finding is that when bypassing (unchecking the checkbox in the plugin list) my plugin in Reaper, I don't receive a CLAP_EVENT_PARAM_VALUE event, so my plugin doesn't know that it's been bypassed.

However, when I re-enable the plugin (check the checkbox), I receive a CLAP_EVENT_PARAM_VALUE event indicating that the bypass parameter is now disabled, i.e. the plugin is no longer bypassed.

Additionally, sending bypass parameter changes to the host only seems to work sporadically.

I'm not having this problem with Bitwig Studio. Bypass parameter changes work 100% of the time in both directions (to/from host).

EDIT: Reaper version info: v7.16 - May 21 2024 on Windows 10

Post

Quick question. In studying the template code from the CLAP SDK, it looks like a single compiled CLAP plugin can build several plugins. You provide the host with a factory which tells the host how many plugins are available. The host can then request that it build instances of the plugins by passing the plugin ID to the create_plugin function in clap_plugin_factory.

In my simple delay plugin, there's only one plugin available. But I'm about to begin writing some other plugins, e.g. chorus, phase shifter, etc., and now I'm wondering if I should make them part of the same project. That's to say, the compiled CLAP file would tell the host that there are x number plugins available and create instances of them when requested by the host to do so.

Has anyone done this? How do the resulting plugins show up in the host? Are the listed as though they are separate plugins?

I can see the attractiveness of this approach. I'm creating simple free (when they're released) plugins and having a single file that gives you all of them would be kind of cool.

Thanks for any info.

Post

Yes I’ve done this. Yes they show up in the host also. In many cases it is convenient - I’m thinking in the next version of surge of bundling the synth and effects plugin this way and already do it for some test projects we have going

Auv2 and vst3 have the same capability and the wrapper projects multi plugin claps into multi plugin other formats too and since the other formats already support this idiom the hosts have no problem

True story - my first try at airwindows as a clap used this to make one flap with 384 unique plugins. Worked great. Not very reasonable ux.

Post Reply

Return to “DSP and Plugin Development”