Clarification of parameter handling in VST 3

Hi,

I’ve just been through the VST 3 docs again regarding parameter handling and just wanted to confirm my understanding of the following is correct:

(Sorry for the long post, but there’s a lot of detail/edge cases here that would be nice to have clarified.)

  1. When a plugin’s edit controller wants to change a parameter, it notifies the host via performEdit on the main UI thread. The host can optionally record that value for automation, but must pass it to the processor which it does on the next audio thread cycle via the “parameters-in queue” of the IAudioProcessor::process() call.

  2. When a host wants to edit a parameter value from a host based UI it should notify the plugin’s edit controller on the UI thread, via IEditController::setParamNormalized and it should notify the processor on the next audio thread cycle (via process() again)

  3. When a host wants to edit a parameter in response to automation (say from an automation track or via a MIDI assignment) from the audio thread it passes the parameter value to the audio processor via process(), and later the main UI thread notifies the edit controller via setParamNormalized (and may do so at a slower rate - not every value needs to be passed to the edit controller)

  4. When an audio processor wants to change a parameter value it can use IConnectionPoint to send a message to the edit controller, but should not do so on the real-time audio thread because the host may have it directly connected to the edit controller and this could have an impact on real-time performance. Instead the processor should use a timer or secondary thread to send the parameter changes to the edit controller and never directly to the host. In response the edit controller should notify the host of the new parameter value via performEdit(), but must do so on the main UI thread.

  5. Callbacks to performEdit on threads other than the main UI thread should be ignored.

  6. In any of the above cases, if the audio engine isn’t running or the plugin is inactive and a parameter change needs to be passed to the audio processor, it should be queued and sent on the first audio cycle when that plugin’s processing resumes.

And some questions:

  1. I’ve noticed some plugin audio processor’s pick up on parameter changes even though they’re only sent to the edit controller. ie: I’ve disabled passing parameters via process() from my host but the processor still seems to see them. Is that expected? I think Juce based plugins might do this. (I guess they might be passing them via IConnectionPoint - don’t know).

  2. Is it ever valid for a plugin’s processor and edit controller to communicate directly or should that always be done via the host. It seems that’s the intention of the VST 3 spec, but I suspect some plugins might play fast and loose with that rule. Does “kDistributable” have any impact on this rule?

  3. This might contradict point 8 but as an alternative to approach 4 when an audio processor wants to notify a host of a parameter change, is it valid for the edit controller to pull parameter values directly from the audio processor (say via a shared in-memory structure) on a UI thread timer and then notify the host via performEdit?

10.After setting a parameter value via setParamNormalized, can the host assume the value stuck or should the host query the parameter for it’s new value in case the plugin rounded or snapped it. Can/should the host rely on a performEdit callback in response to setParamNormalized (I suspect not)

Any feedback greatly appreciated.

Brad

  1. When a plugin’s edit controller wants to change a parameter, it notifies the host via performEdit on the main UI thread. The host can optionally record that value for automation, but must pass it to the processor which it does on the next audio thread cycle via the “parameters-in queue” of the IAudioProcessor::process() call.

Yes, and additionally the plugin has to call beginEdit () at first, then at the end endEdit () (UI Thread)

  1. When a host wants to edit a parameter value from a host based UI it should notify the plugin’s edit controller on the UI thread, via IEditController::setParamNormalized and it should notify the processor on the next audio thread cycle (via process() again)

Yes, additionally the host could call (if the interface IEditControllerHostEditing is supported by the plugin) beginEditFromHost () and endEditFromHost ()

  1. When a host wants to edit a parameter in response to automation (say from an automation track or via a MIDI assignment) from the audio thread it passes the parameter value to the audio processor via process(), and later the main UI thread notifies the edit controller via setParamNormalized (and may do so at a slower rate - not every value needs to be passed to the edit controller)

Yes, the host has the responsibility to send the changes to the edit controller at the right time, using any delay compensation information.

  1. When an audio processor wants to change a parameter value it can use IConnectionPoint to send a message to the edit controller, but should not do so on the real-time audio thread because the host may have it directly connected to the edit controller and this could have an impact on real-time performance. Instead the processor should use a timer or secondary thread to send the parameter changes to the edit controller and never directly to the host. In response the edit controller should notify the host of the new parameter value via performEdit(), but must do so on the main UI thread.

Yes, it is one way to do it. An another way, for example for VuMeter parameter, the plugin could export the parameter as normal parameter and send them back in the process call into the outputParameterChanges queue. The host will them at the right time to the edit Controller (see Again example).

  1. Callbacks to performEdit on threads other than the main UI thread should be ignored.

Yes.

  1. In any of the above cases, if the audio engine isn’t running or the plugin is inactive and a parameter change needs to be passed to the audio processor, it should be queued and sent on the first audio cycle when that plugin’s processing resumes.

There is a way to flush parameters when the process is not called, by calling explicitly process with numChannels and numSamples equal zero.

1 Like
  1. I’ve noticed some plugin audio processor’s pick up on parameter changes even though they’re only sent to the edit controller. ie: I’ve disabled passing parameters via process() from my host but the processor still seems to see them. Is that expected? I think Juce based plugins might do this. (I guess they might be passing them via IConnectionPoint - don’t know).

Using IConnectionPoint interface for communication between edit Controller and Processor is the way to use.

  1. Is it ever valid for a plugin’s processor and edit controller to communicate directly or should that always be done via the host. It seems that’s the intention of the VST 3 spec, but I suspect some plugins might play fast and loose with that rule. Does “kDistributable” have any impact on this rule?

Direct communication is not recommended, some plugins are based on the SingleComponentEffect (where edit controller and processor is the same instance) for convenient issues but not recommended, in this case the flag kDistributable indicates that a well separated (edit/processor) plugin could
be edit on one computer and process on an another, the host has to pass the messages between them.

  1. This might contradict point 8 but as an alternative to approach 4 when an audio processor wants to notify a host of a parameter change, is it valid for the edit controller to pull parameter values directly from the audio processor (say via a shared in-memory structure) on a UI thread timer and then notify the host via performEdit?

hum…pulling could be done using IConnectionPoint … The plugin could use shared memory (in this case not kDistributable) if it has no other solution.

10.After setting a parameter value via setParamNormalized, can the host assume the value stuck or should the host query the parameter for it’s new value in case the plugin rounded or snapped it. Can/should the host rely on a performEdit callback in response to setParamNormalized (I suspect not)

If the host set a value (setParamNormalized) which is refused or constrained on the plugin side, the plugin could/should call performEdit with the new value.

1 Like

I don’t have authoritative answers since I did not write the VST SDK, but as the dev for jamba (https://jamba.dev) I do have some hands on experience.

For #1, you can do

beginEdit(paramID);
performEdit(paramID, );
performEdit(paramID, );
performEdit(paramID, );
performEdit(paramID, );
endEdit(paramID);

The use case is for example onMouseDown, you call beginEdit… onMouseMove you call performEdit and onMouseUp (or onMouseCancel) you call endEdit (in my case this is encapsulated in an object creation/destruction so that I don’t have to think about beginEdit/endEdit).

The way I have seen DAW handle this is that you don’t get every single value coming from performEdit being inserted into the undo history. Example, you move a gain knob… although the gain changes as you move the knob only when you release the mouse will the value be entered in the undo list, so that if you hit undo, you don’t get 25 positions of the knob…

For #4, I have been mostly using outputParameterChanges and here is how I do it: jamba/RTParameter.cpp at v3.2.4 · pongasoft/jamba · GitHub. This happens during the process method so on the RT thread.
You can also send messages from the UI thread (so need to be done from a timer), using AudioEffect::sendMessage.

Hope this helps
Yan

Thanks for the quick answers!

Of course… even though I’ve implemented that in my host, I completely forgot about it.

I remember reading about that approach and while that’s how I implemented it initially I found some plugins crashed. Debugging a plugin under Cubase Elements it seems to not do this and instead sends the parameter changes on the next audio cycle.

Really? I wasn’t expecting that answer. So the performEdit callback would happen during the setParamNormalized call? Do plugins actually do this because I haven’t seen it mentioned anywhere in the docs - although I guess it’s fairly benign if they didn’t.

Thanks. As mentioned above, I was aware of this approach but forgot about it.

Hi, how does getParamID() know what ID it should get and for which parameter?
outputParameterChanges,
How does it run during the process method?
Is ProcessData &oData from the processor?

This VST3 is written in a way that I just don’t understand… I cannot see the flow of things…
Do you have a flow diagram of sorts so that I can trace the flow of data and parameters?

1 Like

Did you read the VST3 doc about parameter?