Parameter API Design Requirements
Plugin parameter support is achieved through
remidy::PluginParameterSupport class which is accessible
via parameters().
Parameter API Differences Between Formats
- VST3 and CLAP: parameter values are stored as
double. AU parameters arefloat. LV2 parameters are practicallyfloat(lilv Atom does not supportdoublevalues). - AU: nominally they have stable parameter ID integer, but an infamous
fact around AudioUnit is that Apple Logic Pro and GarageBand fail to
handle parameter stability by assuming "index" in the parameter list as
stable. JUCE team and their plugin developers try hard to workaround
this issue by introducing
versionHint. We should not fail like Logic Pro and GarageBand and always identify parameters by ID, not index. - LV2: there are multiple different ways to define "parameters" and we
will have to support both...
- Traditional approach: define
lv2:ControlPorts within thelv2:Plugin. They havelv2:index,lv2:name, andlv2:symbol. - Modern approach: define global
lv2:Parameters, and specify them aspatch:readableorpatch:writablewithin thelv2:Plugin. The actual parameters will be passed aslv2:Atoms. They haverdfs:label,rdfs:range, andpg:group. Note that there is no numeric ID. - They both can have
lv2:default,lv2:minimum,lv2:maximum,lv2:portProperty, andunit:unit.
- Traditional approach: define
LV2 brings in the following design principles:
- There is no numeric stable parameter ID. Parameters ID by index is instant and cannot be used in persistable states.
- There may be read-only parameters and write-only parameters (if that makes sense).
- Retrieving parameter values may not work (they may be write-only, or we don't have parameter value store - even if we store any parameter changes there may be parameters without initial default values.)
VST3 and CLAP brings in the following design principles:
- Since VST3 parameter access is achieved via
IEditControllerAPI which involves GUI thread,PluginParameterSupporthas abool accessRequiresMainThread()property getter function. VST3 returnstrue. - The same goes for CLAP where
count()andget_info()inclap_plugin_paramsare marked as[main-thread].
Plain vs. Normalized
In VST3 every parameter operation is expected to be within 0.0–1.0, with conversion to and from normalized values. AU, LV2, and CLAP expect plain values.
The public API in PluginParameterSupport expects plain
double values. They are explicitly named as such.
UAPMD needs to convert the values betwee double and
uint32_t.
Normalized-to-Plain conversion is not always available
Whereas VST3 IEditController provides
normalizedParamToPlain(), it is not always implemented
correctly. VST3SDK EditController implements this function
as to just return the source value by default.
Plugins like Dexed does not implement these functions while it converts parameters in 0.0..1.0 to the plain value when it is converted to string.
Automatable
We support only "automatable" parameters in some use cases. Namely, non-automatable parameters are not displayed on uapmd-app and not mapped to MIDI-CI AllCtrlList property.
- AU parameters can be
kAudioUnitParameterFlag_NonRealTime - VST3 parameters can be
kCanAutomate - CLAP parameters can be
CLAP_PARAM_IS_AUTOMATABLE - LV2: still wondering, maybe "expensive" parameters had better be excluded...
For example, Tracktion "Collective" contains a lot of NonRealTime parameters that I have no idea what they are for (they are unnamed).
Sample-accurate Parameter Changes
All VST3, AU, LV2, and CLAP supports sample-accurate parameter
changes. Those setParameter() calls that come with non-zero
timestamp are "enqueued" for the next audio processing.
- VST3: use
IParameterChanges - AU: use
AudioUnitScheduleParameters - LV2: use
Atom_Sequence(unsupported for ControlPort-based parameters) - CLAP: use event streams in
clap_process_t
Per-Note Controllers (parameters)
VST3, AU, and CLAP supports per-note parameter controllers.
- VST3: use
INoteExpressionController - AU: use parameters with group, channel, or note scope? It is very unclear from the API documentation.
- LV2: not supported
- CLAP: use
clap_event_param_value_twithkey.
It should be noted that per-note controllers are defined totally differently from normal parameters. VST3, AU, and MIDI 2.0 UMP define them as such. The only exception is CLAP, which defines parameters in unified way. We return the same list of per-note parameters as normal parameters.
The way how VST3 and AU support per-note controllers (parameters) is different from CLAP also in that they may return different set of parameter definitions for each channel (also may differ from "global"). AU even goes further and they may return different set of parameter IDs for each note. This makes constructing static list of parameters at configuring connections almost impossible.
Extraneous CC roundtrip to parameters
One of the problem mapping from parameters to Assignable Controllers is that VST3 parameters are often mapped from MIDI CCs for each channel, due to fundamental VST3 design problem that does not support MIDI messages.
If we map every single parameter to Assignable Controller, it feels extraneous to further map them again. But CC-to-parameter mappings happen at plugin side, while Assignable Controllers to parameter mappings happen at the host side. We cannot guess which parameter should map to which CC to send (while we can "guess" by parameter names).
Dynamic parameter list updates
Some plugins dynamically updates their parameter list (e.g. BYOD
VST3/AUv2/CLAP, AUv3 Mela, etc.) and when they happen the plugin
typically have to be "restarted" by the host (e.g.
IComponentHandler::restartComponent() in VST3,
clap_host.request_restart() in CLAP).
The way how we trigger this component refresh should not be
in the public API, but it is currently exposed as
PluginParameterSupport::refreshAllParameterMetadata(). Each
plugin format overrides it by triggering update events that are
registered as in
PluginParameterSupport::parameterMetadataChangeEvent
members. It is exposed to the users (here its notify()
should not also be exposed, likewise...).
We support the entire parameter refresh in uapmd-app.
LV2 partially supports this concept as Dynamic Manifest, but there does not seem any normative way to notify the manifest updates. It is also known to not work well.