VST pitfalls with parameter synchronization

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Jakob / Cableguys wrote:
We'd still have the problem of parameter values toggling back and forth. See my example in the very first post: the parameter might be moved by the user from 0.1 to 0.2 to 0.3, and will temporarily jump back to 0.2 when the host calls <setParameter> and echoes the parameter change, and then a little later again to 0.3.
This wouldn't happen if you let setParameter... set the parameter and the GUI "only" notify the host of the changes.

Post

bitwise wrote:
Jakob / Cableguys wrote:
We'd still have the problem of parameter values toggling back and forth. See my example in the very first post: the parameter might be moved by the user from 0.1 to 0.2 to 0.3, and will temporarily jump back to 0.2 when the host calls <setParameter> and echoes the parameter change, and then a little later again to 0.3.
This wouldn't happen if you let setParameter... set the parameter and the GUI "only" notify the host of the changes.
Indeed, this would be fine for all hosts which send the parameter change back to the plugin. But hosts don't have to, so we can't rely on this.. if a host doesn't send the parameter change to the plugin we'd never get aware of the parameter change.

Post

In this case, you could use a timer that sets the parameter when the host doesn't do it. You would only need this timer for a short time. As soon as the vst "learns" that the host doesn't set the parameter, the gui starts setting the value immediately.

Perhaps the hosts you refer to, expect the parameter to be flagged as automatable.

Post

bitwise wrote:In this case, you could use a timer that sets the parameter when the host doesn't do it. You would only need this timer for a short time. As soon as the vst "learns" that the host doesn't set the parameter, the gui starts setting the value immediately.

Perhaps the hosts you refer to, expect the parameter to be flagged as automatable.
Good approach, definitely worth a try!

Post

Jakob / Cableguys wrote: We'd still have the problem of parameter values toggling back and forth. See my example in the very first post: the parameter might be moved by the user from 0.1 to 0.2 to 0.3, and will temporarily jump back to 0.2 when the host calls <setParameter> and echoes the parameter change, and then a little later again to 0.3.
When the GUI sets a parameter record a timestamp, current system time for example, and ignore any incoming changes for that parameter from the host unless they occur maybe 1 second after that. Essentially block host automation while the use has his hands on the control.

You could override SetParameter & SetParameterAutomated and have all the logic in there. You just need an extra array with the timestamp for each parameter.

In fact you probably dont need an array, just a single timestamp and parameter index, and just ignore the last changed parameter. The user cant move the mouse that quickly that you'd be likely to have more that one being wiggled in relevant amount of time.

Should work i think.
Chris Jones
www.sonigen.com

Post

First, about the host echoing the parameter changes - this is really rude from the host. I would simply ignore any parameter changes coming from the host while you are automating the same parameter on the UI. Normally if you call beginEdit() and endEdit(), the host should not send any parameter changes for this parameter.

Second, about setParameter() and threads. setParameter() can be called from:
- The UI thread when tweaking values and calling setParameterAutomated.
- The Audio thread when the host plays back automation.

if setParameter() is not atomic, then processReplacing() can be dealing with incomplete (or, in the example of filter coefficients, invalid) data.

You can make setParameter() atomic using a mutex, however this leads to potential audio dropouts, if the audio thread is waiting on the mutex while the UI thread is sending automation data.

The other option is to use a lock-free queue system. This ensures the audio thread is never waiting for the UI, at the expense of potential latency in processing UI events.

You will need 2 lock-free ring buffers: one for processing changes from the UI on the audio thread, and one for notifying the UI when the host sends automation on the audio thread. You will need to override setParameterAutomated(), so that it doesn't call setParameter(), but instead pushes the parameter change on the queue.

Post

You don't need either mutexes or queues if you make setParameter .. set a variable (single write to an aligned address is atomic anyway).

Post

mystran wrote:You don't need either mutexes or queues if you make setParameter .. set a variable (single write to an aligned address is atomic anyway).
Exactly.

Post

AdmiralQuality wrote:
mystran wrote:You don't need either mutexes or queues if you make setParameter .. set a variable (single write to an aligned address is atomic anyway).
Exactly.
It's not about making the write to a parameter (if that's what you mean?) atomic, but of making sure that the parameter is not changed during process. And about avoiding to get "old" values sent back from the host which are already outdated.

Post

Big Tick wrote:First, about the host echoing the parameter changes - this is really rude from the host. I would simply ignore any parameter changes coming from the host while you are automating the same parameter on the UI. Normally if you call beginEdit() and endEdit(), the host should not send any parameter changes for this parameter.
Good idea, I will check if <beginEdit> and <endEdit> will keep the host from sending updates!
Big Tick wrote:..

The other option is to use a lock-free queue system. This ensures the audio thread is never waiting for the UI, at the expense of potential latency in processing UI events.

You will need 2 lock-free ring buffers: one for processing changes from the UI on the audio thread, and one for notifying the UI when the host sends automation on the audio thread. You will need to override setParameterAutomated(), so that it doesn't call setParameter(), but instead pushes the parameter change on the queue.
Yes, this (or something similar) is what I thought to do in the first place, but it seemed so overkill.

Post

Jakob / Cableguys wrote:
AdmiralQuality wrote:
mystran wrote:You don't need either mutexes or queues if you make setParameter .. set a variable (single write to an aligned address is atomic anyway).
Exactly.
It's not about making the write to a parameter (if that's what you mean?) atomic, but of making sure that the parameter is not changed during process. And about avoiding to get "old" values sent back from the host which are already outdated.
And that's why you copy the parameter at the start of your process function. Then it can't change on you.

And if you have a big coefficient calculation to do, do it there as well (only when the parameter has changed from what it last was, of course).

You can use a single, atomic variable to select from two different versions of whatever larger structure your process needs. One your process is currently using, the other is free to be constructed by setParameter. When setParameter is finished building whatever needed to be built based on the parameter(s) that changed, it sets an atomic flag to tell the process to switch over.

Post

AdmiralQuality wrote:
Jakob / Cableguys wrote:
AdmiralQuality wrote:
mystran wrote:You don't need either mutexes or queues if you make setParameter .. set a variable (single write to an aligned address is atomic anyway).
Exactly.
It's not about making the write to a parameter (if that's what you mean?) atomic, but of making sure that the parameter is not changed during process. And about avoiding to get "old" values sent back from the host which are already outdated.
And that's why you copy the parameter at the start of your process function. Then it can't change on you.
+1. I use the same approach as AdmiralQuality. I don't use locks for parameters that map to a float or integer value. I consider them atomic. In most cases my audio engine code doesn't care if these atomic parameters change mid process. The parameter is copied when it can't change mid process without something blowing up. Often the copying doesn't need to be explicit and happens as part of how my audio engine works.

For example, filter cut-off maps to a single float value (ranging from 15hz to 18khz). The filter cut-off parameter can change anytime and the audio engine is notified of changes as soon as they occur. Internally the audio engine is running at two rates. Audio rate and control rate. The filter cut-off change is ignored until the next 'control rate' update when the filter co-efficients are updated together. In this way the filter cut-off can change any time and the filter co-efficents are guaranteed to be updated together.


Shannon

Post

Slightly OT: I've recently been working on my plugin parameter code. It's helped to think about plugin parameters as using the Model-Veiw-Controller pattern.

A plugin will have an object that stores the canonical state of all parameters, the 'Model'.

A plugin has a number of 'Plugin Parameter' objects that function as 'Controllers'. Each 'Plugin Parameter' object represents one parameter. (Filter Cutoff, LFO Rate, etc.) As expected with the MVC pattern, 'Plugin Parameter' objects are the only things that read and write data from the the 'Model'.

A plugin has a number of 'Views'. The GUI and the audio engine are two obvious examples. I also have a "MIDI automation" view (parameters that have been linked to MIDI CC's for MIDI parameter automation).

The 'views' only have access to the 'controller' and function independently and are unaware of each other.

My code originally bundled the 'Controller' and 'Model' code together into one class. Ie. the plugin parameter object also stored the state of the parameter. This works with simple plugins but becomes a hindrance with some other plugins.

One of the difficulties of using the MVC pattern is finding ways to keep all the 'views' in sync with the 'model'. My code uses a ad hoc collection of techniques.

The GUI will update all parameters when the GUI is opened. Some parameters are updated on a timer. Some parameter changes will send a 'windows message' to the GUI to indicate a change.

I break with the MVC pattern to update the audio engine. My plugin parameter objects update the audio engine directly. (In a way the audio engine is a second model that mirrors the first.') I've chosen to do this because I want parameter changes to be audible ASAP. It also saves the audio engine from constantly checking for parameter changes.


Shannnon

Post

very angry mobster wrote: +1. I use the same approach as AdmiralQuality. I don't use locks for parameters that map to a float or integer value. I consider them atomic. In most cases my audio engine code doesn't care if these atomic parameters change mid process. The parameter is copied when it can't change mid process without something blowing up. Often the copying doesn't need to be explicit and happens as part of how my audio engine works.
So you're doing something like this ?

Code: Select all

processReplacing(...) {
   float newCutoff = getParameter(CUTOFF);
   if (newCutoff != this->prevCutoff) {
		this->prevCutoff = newCutoff;
		recomputeFilterCoeffs();
   }
   ...
}

Post

Big Tick wrote:
very angry mobster wrote: +1. I use the same approach as AdmiralQuality. I don't use locks for parameters that map to a float or integer value. I consider them atomic. In most cases my audio engine code doesn't care if these atomic parameters change mid process. The parameter is copied when it can't change mid process without something blowing up. Often the copying doesn't need to be explicit and happens as part of how my audio engine works.
So you're doing something like this ?

Code: Select all

processReplacing(...) {
   float newCutoff = getParameter(CUTOFF);
   if (newCutoff != this->prevCutoff) {
		this->prevCutoff = newCutoff;
		recomputeFilterCoeffs();
   }
   ...
}
That's essentially what I do, though with a bit of indirection.

Basically I have an "rtUpdate()" method that does something like the above for all parameters (storing just a copy of the whole array basically), and records which ones have changed (and also whether any of them changed). So "multi-dependent" stuff (like recompute filter if either cutoff or Q changed) are still possible.

Code: Select all

// eg something like this..
if(params.rtUpdate())
{
   // at least something changed if rtUpdate() returned true
   if(params.changed(P_FCUTOFF) || params.changed(P_FRESO))
   {
      // do a recompute using the internal values
      // that were cloned by rtUpdate above
      filter.recompute(
         params.getInternal(P_FCUTOFF),
         params.getInternal(P_FRESO));
   }
}
// .. rest of process()
You might want to tag the "externally accessible" parameter array (ie what getParameter() and setParameter() use) as volatile just to be sure (though in practice it's unlikely to matter much).

Post Reply

Return to “DSP and Plugin Development”