Midi Mapping - How to update parameter value from processBlock?

Hi everyone,

I want to allow midi mapping for plug-in parameters, so I’d like to be able to update parameter values from the audio thread (processBlock) inside the plug-in, so that when midi messages come in, I can change the parameters that have a midi mapping associated with that midi message. The parameter change should also notify the DAW because otherwise the parameter value in the DAW will have a different value than the one in the plug-in.

I’ve read that setValueNotifyingHost should be used to notify the DAW but must only be called on the main thread. Is this true? setValue on the other hand can be called from the audio thread but doesn’t notify the DAW.

I could try a more complex approach where I’d post an async message on the main thread from the audio thread so that setValueNotifyingHost is ultimately called on the main thread. But I then expect a delay in the update rate, because ideally midi messages should update the parameter values immediately.

I’m a little stuck. I’m curious to hear what solution others came up with.

Thanks,
Jelle

setValueNotifyingHost can be called from the audio thread.

Are you sure? Reading this below makes me wonder if that’s safe…

Hmm, that is concerning. The docs don’t mention anything about that method not being safe…

setValueNotifyingHost() will indeed trigger not only the host but also your own UI listeners, so will very likely cause allocations and data races if called from the audio thread.

Since the host, at least with the current JUCE implementation, can’t receive those automation change messages in a granular/sample accurate way regardless, and at best can receive them once per buffer, I would suggest this “best currently” way:

  1. Only use setValue() during processBlock.
  2. On a timer, poll to see if the value changed and if it did notify the host (on the message thread).

On another note, definitely think if you want to allow MIDI to actually modify automation values - many plugins just let the MIDI controls override whatever is the current “live” value for the block, but those values aren’t always announced back to the host, which has its uses.

2 Likes

Perhaps that function should have been named setValueNotifyingListeners()

1 Like

this->setParameter(paramIndex,newValue);

The value must be between 0-1 no matter the range.

I load presets/states that way and it notifies the listeners aswell.

AudioProcessor::setParameter has been marked “deprecated” for a quite long time now, it shouldn’t be used.

1 Like

I can say that at least for VST3, it’s safe to call setValueNotifyingHost from the audio callback. It may not behave as you expect though. Audio thread changes do not write automation, and gesture begin/end are ignored by the wrapper already. It seems the ability of sending parameter changes from the audio thread is intended for read-only parameters, like meters. The plugin’s UI is mostly ok, as attachments are AsyncUpdaters (there are no races, but a message is posted which allocates -this happens for automation anyway).

It’s totally not safe though.

setValueNotifyingHost will call sendValueChangedMessageToListeners that will lock on it’s first line with a system call.

Also, right after that it will trigger all the listeners in your project, including those of the APVTS and ParameterAttachment. Some of those will call your UI directly, and some like ParameterAttachment will use callAsync which will both lock and allocate memory.

As far as what the host does with that notification - hard to tell, but I recall previous discussions on the forum with notes that could also cause weird behavior.

It’s just as unsafe as automation, which makes the same call on the same thread.

Not callAsync() but triggerAsyncUpdate() -to the same effect (allocating and possibly locking) but again, just like automation. APVTS::ParameterAdapter does call its listeners synchronously, as intended and documented. None of these call the UI directly -if any listener is allowed to do that, it’s a mistake, as warned by the docs of parameterValueChanged().

I can only speak for VST3 here -the wrapper saves these changes, and at the end of the callback they are added to the output IParameterChanges queue. No EditController functions are called, hence it doesn’t write automation.

I’m not recommending to set parameters on the audio thread, but since Jules made that comment the VST3 wrapper has been updated to behave as intended by the format, which allows this, with limitations. The use of AsyncUpdaters is everywhere and seemingly unavoidable -if this is unsafe, automation is too.

It isn’t, for two reasons:

  1. There’s a (yet) additional lock on the call to notify the listeners which doesn’t happen when the host changes automation.
  2. You can opt-out of the parameter listeners, and just use a timer to respond the parameters changes. This is what I do in my own code.

More on this topic in this talk by @fr810 and @dave96:

Automation is processed from JuceVST3Component::process through processParameterChanges, setValueAndNotifyIfChanged… setValueNotifyingHost. It’s the same call on the same thread, with the same ramifications.

I’m not trying to be contrarian. I just think it’s a bad idea to replicate an infrastructure that’s already provided by the framework. Anyway, I see there’s no room for it, so good evening.

Ah yes, I was not aware of that. That is quite a severe bug in the wrapper which I hope the JUCE team would handle ASAP, same with ParameterAttachment.

Well, there’s been a lot of discussion about this before, some of which you were involved in. Dave had some considerations here which I find relevant. Also Reuk here. I’m not convinced that a timer-based approach would scale well: it may end up causing more problems than it solves. I can’t say I’ve never experienced priority inversion from these things, but if it happened I’ve not noticed. Definitely not a simple issue though.

Back to the op’s, I meant to point that even if you can call setValueNotifyingHost() from the audio callback with no particular concern, it may be good to know that it won’t write automation. Not sure if that’s relevant for MIDI mapping, which I’ve never done. There are a number of threads about this, and there’s Daniel’s implementation in Plugin Gui Magic.

1 Like

I have been using the Timer approach in commercial plugins, some of them have hundreds of parameters, it does scale very well.

You can also micro optimize that solution in many ways, as I’ve suggested here:

In short: you can use a static timer so there’s only one shared for all parameters and all instances of the plugin, and you can also use a (single) atomic bool shared for all parameters so that changes on the message thread don’t engage the timer and run as they should, and most of the time the timer just checks a single bool and that’s it.

3 Likes

That sounds very reasonable. The underlying problem remains: all listeners are called synchronously for automation because in JUCE paradigm there’s a single source of truth for parameter values. This is in my opinion a mistake, and deviates from VST3 by conflating host <> processor with host <> controller communication. I think parameter changes (all of them) should reach the process callback as an argument, just like they do in VST3, and separately reach the rest of the program on the message thread, through listeners and stuff. This is a big architectural change though, that would require a lot of refactoring for everyone.

2 Likes

Yes, that’s exactly what I suggest with the timer approach.

During the processBlock, the wrapper should only call setValue(), which will set the atomic value in the parameter without triggering any listeners or system calls.

Slightly later, the timer will come in the message thread and update whatever listeners that need to be updated.

The way I’ve structured it in my code, that timer might go away completely if no listeners exist (Say, if the UI is closed).

I don’t think this is a big architectural change at all to implement. The parameters can still be stored as one atomic value and just the update order needs to be reimplmemented.

That’s a smaller change indeed, but it would limit the use of listener callbacks to the UI. Many people use the listener callbacks to update internal values in the processor, to avoid checking all parameters on every processBlock. This wouldn’t be necessary if parameter changes were passed to processBlock through an argument, as they actually are in VST3. In general, there’s a proliferation of atomics that could all disappear if changes were passed just like they are in the format, separately for each thread.

You could still write a very simple AudioParameterListener that compares the current value to the last one and calls a lambda on the audio thread. That shouldn’t be a limitation of my suggestion.

Removing atomics from parameters would improve performance a bit, but would also make almost all operations on parameters dangerous, and would require extra sync mechanisms around almost every use of the framework, including writing to the parameters from the UI, loading presets, etc, so that would require an almost entire overhaul of how JUCE-based code operates for something that is at best a micro optimization, so I’m not in favor of it.

The removal of system calls and allocation from the audio thread, however, are not a micro optimization, they’re an important requirement for pro audio software.