-
Notifications
You must be signed in to change notification settings - Fork 7
How to Use the Parameter Manager
This document provides a description of the Parameter Manager used in the WebAudioModule
SDK, and a guide to handle parameters in an WebAudioModule
with the Parameter Manager.
It is conventional for audio plugin users and hosts to schedule plugin parameter changes with an automation timeline. The WebAudio API provides the AudioParam interface, with its AtTime
methods, to allow developers to schedule sample-accurate a-rate
or buffer-accurate k-rate
automations in several ways.
It is important for an WebAudioModule
to control its parameters sample-accurately. However, the AudioParam
s exist only inside AudioNode
s, they are not constructable independently, and they do not exist in the audio thread. This is reason that WebAudioModule
API provides another interface WamParameter
for automatable parameters both in the main thread and in the audio thread. The Parameter Manager provides an implementation of the WamParameter
that uses native but customized AudioParam
to handle automation scheduling. In fact, Parameter Manager is mainly an AudioWorkletNode
that creates user defined AudioParam
s, then transform them to AudioNode
outputs or function calls.
As described in the WebAudioModule
API, the developer should declare and configure every parameters as WamParameterInfo
that are controllable and automatable by the host application, and let them accessible via WamNode
's methods, such as getParameterInfo()
. In the Parameter Manager, we consider these parameters are the WAM's exposed parameters. (see ParametersMappingConfiguratorOptions.paramsConfig
).
In a host, by automating or controlling these exposed parameters, it will as a result change the WAM's internal state. The variables to be changed in the internal state, which we call internal parameters, can be an AudioParam
or an event handler that will be called while the values change, under a certain fire rate. (see InternalParametersDescriptor
)
In some use cases, the plugin need to control multiple internal parameters with one single exposed parameters, and with different value scalings or mappings. For example, an exposed parameter mix
need to be clipped from 0 to 0.5 and be mapped to 0 and 1 for an internal parameter dry
; in the same time, it need to be clipped from 0.5 to 1 and be mapped to 1 and 0 for an internal parameter wet
. This can be done easily by declaring a paramsMapping
. (see ParametersMapping
)
By using ParamMgrFactory.create
static method, the developer will create an instance of the Parameter Manager that will automatically handle the parameters. It depends on the configuration provided with paramsConfig
, internalParamsConfig
and paramsMapping
properties of the optionsIn
argument. There are three main design patterns to declare and link the exposed parameters to the internal parameters using the Parameter Manager.
- Direct to
AudioParam
, no need to declare theparamsConfig
and theparamsMapping
, declare only theinternalParamsConfig
.
If the developer leaves the
paramsConfig
and theparamsMapping
undefined, the SDK will derive theparamsConfig
from theinternalParamsConfig
, which means they are containing same parameter names and values. TheparamsMapping
will be filled with peer to peer mappings with no value mapping.
For example:
// if audioNode.gain and audioNode.freq are AudioParams
const internalParamsConfig = {
gain: audioNode.gain,
freq: audioNode.freq
};
const paramMgr = await ParamMgrFactory.create(wam, { internalParamsConfig });
- Direct + default event listeners or
AudioParam
s, no need to declare theparamsConfig
and theparamsMapping
, declare only theinternalParamsConfig
.
If the developer declared the
internalParamsConfig
and leaves theparamsMapping
unset, the SDK will automatically make links between the exposed parameters and the internal parameters, taking account of the givingAudioParam
, or theonChange
callback with theautomationRate
.
The
paramsMapping
will be filled with peer to peer mappings with no value mapping.
For example:
const internalParamsConfig = {
enabled: {
onChange: (value, prevValue) => {
console.log(`Param "enabled" has been changed from ${prevValue} to ${value}`);
}, // callback
automationRate: 10 // 10 times/sec
},
gain: audioNode.gain // AudioParam
};
const paramMgr = await ParamMgrFactory.create(wam, { internalParamsConfig });
- Mapping + default event listeners or
AudioParam
s pattern, need to declare theparamsConfig
,internalParamsConfig
and theparamsMapping
This pattern is useful when a different mapping is needed between the internal parameters and the exposed parameters.
A value mapping can be set via
sourceRange
andtargetRange
fields. The incoming value of the exposed parameter will be firstly clipped usingsourceRange
, then the value in thesourceRange
will be remapped to thetargetRange
. If these fields remainundefined
, they will be the same as theminValue
and themaxValue
of the exposed parameter.
If one parameter name appears in both
paramsConfig
andinternalParamsConfig
, the mapping will be created automatically if it is not declared explicitly in theparamsMapping
.
Dynamically change the
paramsMapping
is possible using thesetParamsMapping
method.
For example:
const paramsConfig = {
mix: {
defaultValue: 0.5,
minValue: 0,
maxValue: 1
}
}
const internalParamsConfig = {
dryGain: dryGainNode.gain,
wetGain: wetGainNode.gain,
};
const paramsMapping = {
mix: {
dryGain: {
sourceRange: [0.5, 1],
targetRange: [1, 0],
},
wetGain: {
sourceRange: [0, 0.5],
targetRange: [0, 1],
},
},
};
const option = {
paramsConfig,
internalParamsConfig,
paramsMapping
};
const paramMgr = await ParamMgrFactory.create(wam, option);
WebAudioModule
API requires that the module's audioNode
is connectable as audio I/O, and implements the WamNode
interface. As a developer, one can use the Parameter Manager to act as the WamNode
interface, and use another AudioNode
to act as the audio I/O by creating a CompositeAudioNode
. We provide a prototype of the CompositeAudioNode
in the Parameter Manager folder.
To get it work with the Parameter Manager, see this example:
import WebAudioModule from 'wamsdk/src/WebAudioModule';
import ParamMgrFactory from 'wamsdk/src/ParamMgr/ParamMgrFactory';
import CompositeAudioNode from 'wamsdk/src/ParamMgr/CompositeAudioNode';
class MyCompositeAudioNode extends CompositeAudioNode {
setup(output, paramMgr) {
this.connect(output, 0, 0);
this._wamNode = paramMgr;
this._output = output;
}
}
export default class MyWam extends WebAudioModule {
//... other settings
async createAudioNode(initialState) {
const gainNode = new GainNode(this.audioContext);
const compositeNode = new MyCompositeAudioNode(this.audioContext);
const internalParamsConfig = {
gain: gainNode.gain
};
const paramMgrNode = await ParamMgrFactory.create(this, { internalParamsConfig });
compositeNode.setup(gainNode, paramMgrNode);
if (initialState) compositeNode.setState(initialState);
return compositeNode;
}
}