I’m not sure of the proper name for this. Maybe “transient” properties? But I’m wondering what the proper way to deal properties that need to shared between the editor and processor, and kept in sync, but do not need to be serialized or undo/redo support, etc.
And example might be a step sequencer that displays the step that is currently being played. The editor and the processor both need to know what step we’re on, and we want them to be in sync. But it makes no sense for the current step to be serialized out or tracked by the undo manager.
What’s the pattern for dealing with this? Make a ValueTree somewhere that holds just these “transient” properties?
Generally speaking you also don’t want to read/write into a ValueTree in the processor: VT is meant to be used in the message thread only, and touching it in the processor thread will cause all kinds of threading mayhem.
For values you want to share between the processor and editor, look at using atomics for simple values, and lock free FIFO for more complex data structures. Quite a lot of discussions on the forum about that.
So just a stand-alone AudioParameterInt or AudioParameterFloat or whatnot then? The part I care most about is the Listener to keep the UI up-to-date.
I get not wanting to write from the audio thread (when I need to store something, I stash it in a pre-allocated juce::Array with CriticalSection, then trigger AsyncUpdater to deal with it on the message thread. Maybe this is wrong?).
But isn’t reading values unavoidable if they’re going to do anything useful? Currently I’m using getRawParameterValue to get a pointer to the atomic and load()ing that. Is this just waiting to bite me in the ass? I’m having trouble picturing an alternative, so thank you for any help/advice/scorn to get me going in the right direction
AudioParameterInt, etc are internally atomic, so they’re safe to use in both threads already.
Locks in general aren’t good from the audio thread.
But if you have a ValueTree under the lock isn’t really locking anything, as the ValueTree listeners will be called now in threads you’re not expecting, not to mention you very well might be writing into data that’s right in the middle of reading in the other threads.
So, first order of business is to not use a ValueTree anywhere near the audio thread. Use some data structure you control that has no listeners that trigger actions on other threads.
Then, you can use some sync mechanism to ensure thread safety, with the most common one being a lock free FIFO - search that term in the forum for many, many discussions.
Hi @jemmons, I don’t know your level of experience so apologies if you know it all already but this video is really good for more detail on solid ways to share data across the audio & message thread:
Hmm… I might watch it a third time for myself…
Trying to apply your situation to their flow chart, it seems like a few atomic member variables not contained in a value tree or parameter would do the trick. You wouldn’t have the ‘listener’ functionality but I think eyalamir’s point is that this functionality could result in functions not being called on the thread you want.
Thanks for the link! This whole series has been a fantastic watch.
I guess I’m confused about a few things now based on comments above. Maybe using this talk as a framework I can express them better.
First, I’ll occasionally want to write values out from my audio thread that I’ll want to be stored as part of the state of the my plugin. I think the process should be:
write the values from the audio thread into a lock-free FIFO
get to the message thread (where it’s safe to block). I do this via AsyncUpdater.
consume the FIFO values on the message thread and write them to the APVTS where it can lock and block all it wants.
Second, I’ll occasionally want to share values between my audio thread and message thread that are not stored as part of my plugin’s state.
since these are small (individual ints) a std::atomic<int> should do this lock-free.
It would be nice to have my UI listen to these values, though. So an AudioParameterInt would be preferable.
But I can’t find any info in the docs about its atomicity or locking characteristics. Am I looking in the wrong place?
Regardless, setting this from the audio thread could result in the listener being called on the audio thread. Should I deal with this by having the listener handler do nothing but kick to the message thread?
If not, what pattern do people use to trigger behavior in a non-audio thread from the audio thread?
Third, I want to read one or two values into my audio thread from my plugin’s state.
I could do this via an std::atomic<float> — I don’t need the Listener of AudioProcessParameter since processBlock is always “polling” as it were.
But the APVTS where these values are stored is backed by RangedAudioParameters anyway, right? If those are already atomic and lock free (a big if. Again, would love to learn how I should be determining that), “caching” values into a std::atomic just to be consumed by the audio thread feels like reinventing APVTS to some extent.
So is there anything wrong with using getRawParameterValue(...).load() from the audio thread? Is there blocking that could happen there that I’m not foreseeing?
Finally, I’m sure all of this has been discussed a hundred times, but I’m not finding the right resource to look it up in/proper search terms to find it. So thanks for y’all’s patience, and also if you could point me where I should be looking (that video, for example, is gold), it’s appreciated!