Hey Jimmy,
first of all, let me thank you for writing these blog posts.
They are insanely helpful and got me started writing CLAP plugins in Rust! I now have my own framework on top of clack and it's all because you gave me a good starting point for this library, so thanks a ton. :)
Unfortunately, there is one major issue with what you're teaching in Part 2 - you are putting plugin state into the Shared struct, and wrap it in an RwLock to make it Send and Sync as per the requirements.
Using mutexes/locks is, however, not real-time safe! The audio thread must never wait for anything, especially not for a lower-priority thread such as the main thread. For a detailed explanation, please refer to this amazing article on real-time safety, paragraph "Locking": http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
There are two alternatives:
-
using two lock-free queues (e.g. ringbuf) to transmit state changes between threads, and polling for changes in process and flush on the audio thread, and on_main_thread and flush on the main thread. You create the queues in activate and store the respective senders/receivers on the main and audio thread struct, and destroy them again in deactivate. In this scenario, there's no need for a Shared struct at all. This is what I am doing in my plugins.
-
using atomic floats (e.g. via the atomic_float crate) for each property, and storing them at the top level of your Shared struct. This is the variant that's a lot easier to implement in your existing code, but you'll have to get rid of your Envelope struct.
I think it would be good to amend your article to avoid teaching wrong patterns to less experienced developers.
Feel free to ask if you have any questions or if I can help in any way :)
Best,
Marius
Hey Jimmy,
first of all, let me thank you for writing these blog posts.
They are insanely helpful and got me started writing CLAP plugins in Rust! I now have my own framework on top of clack and it's all because you gave me a good starting point for this library, so thanks a ton. :)
Unfortunately, there is one major issue with what you're teaching in Part 2 - you are putting plugin state into the
Sharedstruct, and wrap it in anRwLockto make itSendandSyncas per the requirements.Using mutexes/locks is, however, not real-time safe! The audio thread must never wait for anything, especially not for a lower-priority thread such as the main thread. For a detailed explanation, please refer to this amazing article on real-time safety, paragraph "Locking": http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
There are two alternatives:
using two lock-free queues (e.g.
ringbuf) to transmit state changes between threads, and polling for changes inprocessandflushon the audio thread, andon_main_threadandflushon the main thread. You create the queues inactivateand store the respective senders/receivers on the main and audio thread struct, and destroy them again indeactivate. In this scenario, there's no need for aSharedstruct at all. This is what I am doing in my plugins.using atomic floats (e.g. via the
atomic_floatcrate) for each property, and storing them at the top level of yourSharedstruct. This is the variant that's a lot easier to implement in your existing code, but you'll have to get rid of yourEnvelopestruct.I think it would be good to amend your article to avoid teaching wrong patterns to less experienced developers.
Feel free to ask if you have any questions or if I can help in any way :)
Best,
Marius