Listening to parameters vs listening to ValueTree properties

Hi @kunz, thanks for your answer!

I’m also using a class that holds the atomic values used in the DSP processing, but I’m updating them directly if possible, even while processing.

Do you mean that you are not even checking if something’s changed in your plugin parameters when you update the DSP internal atomics ? Like, if your have a biquad filter, processBlock will recompute the coeffs for every block ? I agree this is of course a very safe and hassle-free path to sample-accurate automation (in the event that JUCE allows it one day). I’m also very much interested in sample-accurate automation, but I was hoping there would be a sample-accurate way to check if a parameter has changed. What I was worrying about in my previous post was that some designs would not even be block-accurate, which seems terrible.

Here’s my understanding so far:

  1. juce::ValueTree is not updated synchronously, so registering a juce::ValueTree::Listener to the state tree of the APVTS may not provide callbacks that are called fast enough to set an atomic before the next processBlock. In that sense it is not even block-accurate, at least this seems to be what @daniel was suggesting. Since updates are based on a juce::Timer running on the main thread, audibly delayed DSP updates could be heard if the buffer is large. The worst part is that the presence of these delays would depend on what’s running in the DAW overall (since the main thread is shared amongst all plugins).
  2. Now, on the other hand, from the docs, juce::AudioProcessorParameter::Listener is called synchronously when a parameter changes, so this could actually be used to update an atomic that warns of parameter changes. This would be block-accurate (and even sample-accurate) unless I’m missing something. More precisely, I could register all my parameters with a listener of this type, providing them with a common lambda that just switches a global atomic flag to true. However, if other listeners are registered to the same parameter, it’s unclear to me in which order the callbacks are triggered.
  3. The implementation of juce::AudioProcessorValueTreeState::Listener seems to be not completely disclosed. Is it only relying on juce::AudioProcessorParameter::Listener ? Several posts like Best practices for AudioProcessorValueTreeState and child components seem to suggest that it’s not the case and these two types of Listeners will actually react differently to parameter changes. This is also suggested by the fact that the docs for parameterValueChanged in juce::AudioProcessorParameter::Listener explicitly warn that the callback code must be fast enough because it’s called synchronously, while the docs for parameterChanged of juce::AudioProcessorValueTreeState::Listener do not suggest anything. Does anyone know the final answer to this ?

I’m still discovering all this so I might make it unnecessarily complicated, but I’m still at early stages of development so I kinda want to find the right design patterns. Right now it seems that the safest option is to create my own structs of parameter references that I can pass around and build a parameter system revolving solely around juce::AudioProcessorParameter::Listener and synchronous calls (no timer). AFAIK, the only timers needed are for UI updates that happen at the end of the chain, e.g. in the parameter attachment helper classes for UI widgets, we do need some async behaviour to avoid updating the UI state from the audio thread in case the parameters were changed from automation.

I will still have the APVTS lying at the root of my plugin for state serialization, but I will not use its helper functions for parameter listening, because they might be implemented with a particular type of compromise in mind and may involve a hidden timer. Again, I might be wrong or overthink all of this, happy to learn more!