VST3PluginInstance synchronisation problem

juce based host and a juce based plugin (latest tip)

I have noticed the following problem: under certain circumstances the plugin seems to be initialised with its default parameter values and not from its own parameters which are stored in the binary state.

The initial parameter changes do come from the audio-callback, from a queue while in the same time or earlier the binary state will be applied from the message-thread (which contains the actual saved parameter values)

depending on who is later, wins

Should it be reconsidered the VST3-processor format, when a binary-state is applied to a plugin, that the audio callback may never happened, and so possible existing parameter changes are applied before?

I tried to delay the first process() in the VST3 wrapper, before the parameter changes are updated, to reconstruct the issue.

I think the situation happens when there is a non optimal timely interaction between initialising the plugin with setStateInformation setting the parameters and the first audio call back with its processed parameter changes.

First I thought the problem is because I reconstruct the parameter in setStateInformation via setValue() (which at least worked for the last 20 years) instead setValueNotifyingHost ()

After replacing it with setValueNotifiying host the situation is better, but sometimes there still will processed Parameter changes with the initial parameter value ??

I suspect that these come from these suspicious cachedParamValues?? procedure in the vst3 plugin format

cachedParamValues.ifSet ([&] (Steinberg::int32 index, float value)
{
        inputParameterChanges->set (cachedParamValues.getParamID (index), value);
 });

the I looked into CachedParamValues which uses some kind of FloatCache?!? wtf :exploding_head:

I think there is some kind of concurrency problem in there but I cannot name it because its to confusing.

Fact is, setValueNotifiying not magically changes all cached parameter states inside the host or plugin, can that be?

@reuk I tagging you here, because I see that that you added the FloatCache. My instinct tells me that something is wrong here, but i can’t prove it.

BTW: I don’t have a example how to securely reconstruct the problem, you might want to try to add a delay before the first processed parameter changes in the wrapper, and applying the plugin state at the same time

I’ve been looking into this and so far I haven’t been able to repro the issue, even with a 5-second delay before running the VST3 processAudio function. Are you able to reproduce this with any of the JUCE demo projects (demo plugins, AudioPluginHost)? If you can find a repro using the JUCE examples, that would make it much easier to find a fix.

What’s happening on the hosting side? Are you just creating an instance of the plugin and then calling setStateInformation, or are you modifying parameter values from the host too? Is there anything special about the plugin you’re loading (does it apply parameter values using an async updater or similar inside setStateInformation)?

After initialisation, the float cache will hold the default parameter values, and after loading a preset the float cache should hold the parameter values from the preset. I think the only way the float cache could “hold on” to old parameter values is if the setStateInformation call failed to update the float cache somehow. This doesn’t seem to be possible, because setComponentStateAndResetParameters updates every entry in the float cache during the call to setStateInformation. For this reason I think it’s unlikely that the float cache is the cause of the problem. That being said, there are a lot of moving parts here so it’s possible I overlooked something.

Could you add some logging, or cache the current stack trace when setValue is called in your plugin? On runs where the incorrect state is applied, the final call to setValue must be using incorrect values. It would be useful to know whether the incorrect values are being set during the audio callback, or whether they’re being set by some faulty async code on the main thread.

Thanks!
Just a short answer for now, I will look more into it tomorrow.
My host is using more or less the same code as the plugin host example and doesn’t modify the parameters in any way.

The problem does not happen every-time, so this is not easy to debug.

The wrong setValues are coming from the audio callback, there is one loop in the wrapper that processes the parameter changes before every callback.

My naive thought is that the hosting side notice the parameter change, reapply the old cached parameter, before setStateInformation has ended, and the cache has wrong state because is read and written and read at the same time, but this is pure speculation

Why is there a need to cache the values anyway, isn’t it where this kind of issues begin?

I try to look tomorrow more closely

I thought a bit more about this, and I might be able to see the race. If the first audio callback (which includes default parameter values) happens at exactly the same time as the preset is loaded, there’s a chance that this sequence of events happens:

  • On the audio thread, the cachedParamValues.ifSet (... statement runs, transferring all cached parameter values into the queues of input parameter changes.
  • On the main thread, we start setting the preset state, and get as far as calling holder->component->setState (componentStream) which sets the plugin state.
  • On the audio thread, we now pass the input parameter queues into the plugin’s process function, which sets the parameter values back to their default values.
  • On the main thread, we continue on in setStateInformation, calling setComponentStateAndResetParameters. Now, when we call editController->getParamNormalized we get the default parameter values back, so our float cache ends up reflecting the default parameter values.

By strategically placing some WaitableEvents I can force this call sequence and confirm that it produces the undesirable results. This patch contains the changes necessary to force the bad call order:

0002-Add-waitable-events-to-force-pathalogical-ordering.patch (2.1 KB)

I think the root of the problem is that the host shouldn’t be passing the parameter values into the process function after loading a new plugin state. Could you try applying this patch and see whether you can reproduce the problem? It seems to resolve the issue here, even with the additional WaitableEvents.

0001-VST3-Host-Avoid-re-sending-parameter-values-after-lo.patch (3.0 KB)

The JUCE parameter API provides a function AudioProcessorParameter::getValue() which may be called from any thread, and which must be wait-free. However, the VST3 API only allows current parameter values to be queried from the IEditController, and all IEditController functions must be called on the UI thread. It is impossible to implement the required JUCE API directly in terms of the VST3 API, and instead we must maintain a separate thread-safe cache of all the parameter values which can be safely read from any thread.

Brilliant! Tomorrow morning I wasn’t able to reproduce the exact problem but with the 0002-patch I could reconstruct it too. With the 0001-patch it looks like the problem is solved.

I still have trouble to understand the whole editController stuff, is this a third storage besides the cache and the plugin-processor? And shouldn’t update setState synchronously update the cache, and set the flag, so that the parameters will be updated with the next processBlock iteration?

Sort of. VST3 plugins are split between a Processor and an Editor, where the Processor and Editor are expected to keep separate copies of all parameter values. The host is responsible for keeping the Processor and Editor in sync. The editController controls the plugin editor - when the user interacts with the editor, the edit controller will report any parameter changes. The edit controller can also be queried for the current values of all parameters according to the editor.

The official documentation says that the state restoration should happen like this:

When the states are restored, the host passes the processor state to both the processor and the controller (Steinberg::Vst::IEditController::setComponentState). A host must always pass that state to the processor first. The controller then has to synchronize its parameters to this state (but must not perform any IComponentHandler callbacks).
After restoring a state, the host rescans the parameters (asking the controller) in order to update its internal representation.

In JUCE’s case, the final sentence of that quote corresponds to the loop in setComponentStateAndResetParameters which queries each of the controller’s parameter values and updates the host’s parameters. The mistake we were making was that JUCE was interpreting these as ‘new’ parameter changes, so they were being fed into the next audio callback. I think the fix is to just update the cached value, and to avoid setting the flag which would cause the parameter to be re-applied to the processor.

Thanks for the explanation and taking the time.

I there a reason why parameter changes from the plugin are reapplied before the next processes-callback to the plugin? I guess it makes only sense if GUI and processor are separated.

This is the way VST3 is designed to work. The editor and processor are allowed (and recommended) to be completely separated, so when the editor makes a change to a parameter the host is responsible for communicating this change to the processor.

The fix is now merged to develop:

1 Like