[Solved] VST3 and preset changes driving me nuts

Hi,

I am trying to solve a strange bug with regards to preset changes when we’re running as VST3 plugin and I’m pulling my hair out already…

As VST2, CLAP, AU everything works fine but not as VST3.

When a preset is changed, this can either be done by selecting a new preset via our built-in patch manager or just by sending a midi sysex to the plugin, we do the following:

Update parameters to the new values, parameters are derived from juce::RangedAudioParameter, we prevent to do that via setValueNotifyingHost() because when 2000+ parameters are modified some hosts don’t like that at all and go crazy (Ableton, Logic). Therefore we just use setValue()

After all parameters have been updated we call
updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true));

If I’m not wrong this should be the correct way to do it and in VST2/CLAP/AU the DAW is nicely picking up the new values and everything is nice, but not when running as VST3.

The VST3 wrapper checks if the program parameter has changed, but as we don’t have something like that (and will never have due to multimode) I patched our Juce fork to call restartComponent as soon as updateHostDisplay is called with withProgramChanged.

However, it still doesn’t work. I verified that restartComponent() is called with the flag kParamValuesChanged but neither Cubase, nor Tracktion/Waveform 13 nor Bitwig are picking up the new parameter values. The function getValue() of our parameters is never called after calling updateHostDisplay()

My current guess is that setParamNormalized should be called by Juce without doing a performEdit() after updateHostDisplay() gets called by the plugin because, according to the VST3 doc, the edit controller should have the new values before the host calls it to query the new values. But I am not super certain here.

Who can shed some light into this?

Thanks!

Okay I dug deeper and I think that is the problem indeed:

Cubase is nicely picking up the restartComponent() call, but Juce returns incorrect parameter values because the JuceVST3EditController didn’t overwrite the method getParamNormalized.

I fixed it. The problem is a conceptual difference in Juce between VST3 and other plugin formats:
Whenever a host is querying a parameter value, this ends up in a call to AudioProcessorParameter::getValue() and my guess is that this is what is supposed to happen.

However, the implementation for VST3 is different, it does not work like that because the value is cached in JuceVST3EditController::Param.

The fix requires a change in two locations:

A very small function in JuceVST3EditController::Param:

void updateParameterValue()
{
    // retrieve the current value from Juce::AudioProcessorParameter and update
    // the value stored in Vst::Parameter to ensure its up-to-date
    setNormalized(param.getValue());
}

And now the most important part, update the cached value so that the host sees the most recent value and not a possibly outdated one:

Vst::ParamValue getParamNormalized(Vst::ParamID id) override
{
	// cached parameter value might be outdated, update it before returning
	if (auto* parameter = getParameterObject (id))
	{
		if(auto* juceParam = dynamic_cast<Param*>(parameter))
			juceParam->updateParameterValue();
		return parameter->getNormalized();
	}
	return EditController::getParamNormalized(id);
}