I’ve been chasing a jumpy slider value [see video below]. I initially thought it’s performance related, but the root cause seems to the behaviour of juce::ParameterAttachment (specifically juce::SliderParameterAttachment).
When the slider is not connected to the APVTS (i.e. juce::SliderParameterAttachment is not created), the slider value moves smootly.
This issue reproduces on macOS, JUCE 8.0.1 VST3 in Ableton 10 and 12. It works OK in Standalone and AU.
It seems that sometimes on slider drags, ParameterAttachment::parameterValueChanged is called from both the message thread and audio thread. Based on the newValue I’m seeing in parameterValueChanged my suspicion is that the call from the audio thread is the one that is making the slider look jumpy. Is this expected? I would expect a call from the audio thread only on automation.
I was able to reproduce this message thread + audio thread callback on a basic AudioPlugin example, although the jumpiness is not that visible.
No success. It’s actually worse than I thought as it’s also happening with juce::ButtonParameterAttachment. (again, only VST3 on Ableton - have not tested other DAWs)
Add a juce::AudioParameterBool to APVTS and initialise to false
Connect a juce::ToggleButton to the parameter above with juce::ButtonParameterAttachment
Set a breakpoint in ParameterAttachment::parameterValueChanged
Click on the button to toggle its state.
Breakpoint is triggered on message thread with a value of 1 (true) [see Image 1]
Breakpoint is triggered again audio thread with a value of 0 (false) [see Image 2]
Toggle’s state is still false
I click on the toggle again and turns on but breakpoint is not triggered
Very strange. I can repro this reliably in my plugin but not in the toy example. In the toy example only the juce::SliderParameterAttachment reproduces.
when you set the value to the host via setValueNotifyingHost(), it’s possible that it results in a reaction from the host to the plug-in that is indistinguishable from a regular change to an automated parameter initiated by the host.
It would be very nice if parameterValueChanged() were called with a flag indicating whether the change was initiated by the host (for example a change in automation envelope), or if it was initiated by the plug-in UI (in which case it may probably be ignored by the plug-in UI because it’s already expected to represent the correct value)
It’s weird because I’m pretty sure we use APVTS exactly as in the tutorial but the bug is obvious in the world’s most used DAW. I suppose JUCE team would have seen it by the time.
I was also wondering if the value is percieved as different at the end of the audio callback — it should only be sending values that fail equality since 2021ish?
Maybe worth sticking a breakpoint at that equality test in processParameterChanges — you may need to add the companion withValueFromStringFunction function so that normalization/denormalization produces the same value when translating back and forth?..
I’m naive as why this logic exists in the first place for VST, would love to understand if it’s spec-pleasing or “ensures audio callback always gets parameter changes in a timely way” or…
i experienced this issue with ableton in an uncommon situation (a VST3 wrapper for an AUv3 plugin) and i had to do something like this in the VST3 to get it working (ymmv, this was essentially proxying changes from host ↔ vst3 ↔ auv3):
// in processor
juce::ThreadLocalValue<bool> inAudioProcessorParameterChangedCallback;
void parameterChanged(const juce::String ¶meterID, float newValue) {
// we only want to listen for changes that come from the host so we can pass
// them on. however - it's possible to end up here in the
// audioProcessorParameterChanged callback when we call
// setValueNotifyingHost(). the inAudioProcessorParameterChangedCallback flag
// is an attempt to avoid this loop (change from slider in our editor ->
// audioProcessorParameterChanged -> setValueNotifyingHost -> parameterChanged
// -> setValue on on processor -> audioProcessorParameterChanged) see also the
// similar "inParameterChangedCallback" code in the au wrappers
if (inAudioProcessorParameterChangedCallback.get()) {
inAudioProcessorParameterChangedCallback = false;
return;
}
// otherwise this handles changes from host -> processor
}
void audioProcessorParameterChanged(
juce::AudioProcessor *, int parameterIndex, float newValue) {
if (getParameters()[parameterIndex] != nullptr) {
inAudioProcessorParameterChangedCallback = true;
getParameters()[parameterIndex]->setValueNotifyingHost(newValue);
}
}
It seems the jumpiness gets worse as the frame rate drops. I have been improving the frame rate of my plugin, which makes the VST3 jumpiness barely noticeable, but it’s still there. I’ll save myself the ParameterAttachment fork for now…
I also found a workaround that I don’t understand at all:
I was calling setEnabled() within this attachment’s callback — if I instead put that call in a juce::MessageManager::callAsync — there’s no problem whatsoever and I only get a single call to my attachment instead of 3!
Maybe there’s a timing constraint or ordering issue with the additional VST3 parameter callbacks? Maybe @reuk has ideas…
This is definitely a bug in Ableton Live ( I logged events from the DAW which confirmed that parameter updates are arriving out-of-order).
I reported it on March 14 2024. Ableton responded:
Re: Ableton Support #2890650: Ableton Live VST3 plugin automation bug (jitter).
QA Agent (Ableton Support)
Apr 15, 2024, 06:00 PDT
Hello Jef,
we’re aware of this problem. It’s filed in our bugs backlog and there’s progress on this currently, but it’s not yet clear when a fix would be ready to ship.
Thanks Jeff, that’s helpful. I just heard this exact same thing from another Ableton source.
I guess that means there’s a big chunk of at least Ableton 12 versions that can be expected to work this way and will need to be mitigated (I haven’t tested Ableton 11).
It is interesting to me that the issue is only present when I call something synchronously on the message thread.
Aha, this explains my trouble with my parameter link mechanism with Ableton and VST3.
Is there anything new from the Ableton side?
PS: I guess the behaviour is part of the VST3 specification where a plugin can multiple GUIs which are separated from the processor model, but makes no sense in terms of JUCE where GUI and Parameter access the same underlying parameter value.