Tips and Tricks for Plugin Authoring
To author your algorithm as an audio plugin, you must conform to the audio plugin API. When authoring audio plugins in the MATLAB® environment, keep these common pitfalls and best practices in mind.
To learn more about audio plugins in general, see Audio Plugins in MATLAB.
Avoid Disrupting the Event Queue in MATLAB
When the Audio Test Bench runs an audio plugin, it sequentially:
Calls the reset method
Sets tunable properties associated with parameters
Calls the process method
While running, the Audio Test Bench calls
in a loop the process method and then the set
methods
for tuned properties. The plugin API does not specify the order that
the tuned properties are set.
It is possible to disrupt the normal methods timing by interrupting the event queue. Common
ways to accidentally interrupt the event queue include using a plot
or drawnow
function.
Note
plot
and drawnow
are
only available in the MATLAB environment. plot
and drawnow
cannot
be included in generated plugins. See Separate Code for Features Not Supported for Plugin Generation for
more information.
In the following code snippet, the gain applied to the left
and right channels is not the same if the associated Gain
parameter
is tuned during the call to process:
... L = plugin.Gain*in(:,1); drawnow R = plugin.Gain*in(:,2); out = [L,R]; ...
The author interrupts the event queue in the code snippet, causing
the set
methods of properties associated with parameters
to be called while the process method is in the middle of execution.
Depending on your processing algorithm, interrupting the event
queue can lead to inconsistent and buggy behavior. Also, the set
method
might not be explicit, which can make the issue difficult to track
down. Possible fixes for the problem of event queue disruption include
saving properties to local variables, and moving the queue disruption
to the beginning or end of the process method.
Save Properties to Local Variables
You can save tunable property values to local variables at the start of your processing. This technique ensures that the values used during the process method are not updated within a single call to process. Because accessing the value of a local variable is cheaper than accessing the value of a property, saving properties to local variables that are accessed multiple times is a best practice.
... gain = plugin.Gain; L = gain*in(:,1); drawnow R = gain*in(:,2); out = [L,R]; ...
Move Queue Disruption to Bottom or Top of Process Method
You can move the disruption to the event queue to the bottom or top of the process method. This technique ensures that property values are not updated in the middle of the call.
... L = plugin.Gain*in(:,1); R = plugin.Gain*in(:,2); out = [L,R]; drawnow ...
Separate Code for Features Not Supported for Plugin Generation
The MATLAB environment offers functionality not supported
for plugin generation. You can mark code to ignore during plugin generation
by placing it inside a conditional statement by using coder.target
(MATLAB Coder).
... if coder.target('MATLAB') ... end ...
generateAudioPlugin
, code inside the statement if
coder.target('MATLAB')
is ignored.For example, timescope
is
not enabled for code generation. If you run the following plugin in MATLAB,
you can use the visualize function to open a time scope that plots
the input and output power per frame.
Implement Reset Correctly
A common error in audio plugin authoring is misusing the reset method. Valid uses of the reset method include:
Clearing state
Passing down calls to reset to component objects
Updating properties which depend on sample rate
Invalid use of the reset method includes setting the value of any properties associated with parameters. Do not use your reset method to set properties associated with parameters to their initial conditions. Directly setting a property associated with a parameter causes the property to be out of sync with the parameter. For example, the following plugin is an example of incorrect use of the reset method.
classdef badReset < audioPlugin properties Gain = 1; end properties (Constant) PluginInterface = audioPluginInterface(audioPluginParameter('Gain')); end methods function out = process(plugin,in) out = in*plugin.Gain; end function reset(plugin) % <-- Incorrect use of reset method. plugin.Gain = 1; % <-- Never set values of a property that is end % associated with a plugin parameter. end end
Implement Plugin Composition Correctly
If your plugin is composed of other plugins, then you must pass
down the sample rate and calls to reset to the component plugins.
Call setSampleRate
in
the reset method to pass down the sample rate to the component plugins.
To tune parameters of the component plugins, create an audio plugin
interface in the composite plugin for tunable parameters of the component
plugins. Then pass down the values in the set
methods
for the associated properties. The following is an example of plugin
composition that was constructed using best practices.
Plugin Composition Using Basic Plugins
Plugin composition using System objects has these key differences from plugin composition using basic plugins.
Immediately call
setup
on your component System object™ after it is constructed. Construction and setup of the component object occurs inside the constructor of the composite plugin.If your component System object requires sample rate information, then it has a sample rate property. Set the sample rate property in the reset method.
Address "A set method for a non-Dependent property should not access another property" Warning in Plugin
It is recommended that you suppress the warning when authoring audio plugins.
The following code snippet follows the plugin authoring best practice for processing changes
in parameter property
Cutoff
.
classdef highpassFilter < audioPlugin ... properties (Constant) PluginInterface = audioPluginInterface( ... audioPluginParameter('Cutoff', ... 'Label','Hz',... 'Mapping',{'log',20,2000})); end methods function y = process(plugin,x) [y,plugin.State] = filter(plugin.B,plugin.A,x,plugin.State); end function set.Cutoff(plugin,val) plugin.Cutoff = val; [plugin.B,plugin.A] = highpassCoeffs(plugin,val,getSampleRate(plugin)); % <<<< warning occurs here end end ... end
The highpassCoeffs
function might be expensive,
and should be called only when necessary. You do not want to call highpassCoeffs
in
the process method, which runs in the real-time audio processing loop.
The logical place to call highpassCoeffs
is in set.Cutoff
.
However, mlint
shows a warning
for this practice. The warning is intended to help you avoid initialization
order issues when saving and loading classes. See Avoid Property Initialization Order Dependency for
more details. The solution recommended by the warning is to create
a dependent property with a get
method and compute
the value there. However, following the recommendation complicates
the design and pushes the computation back into the real-time processing
method, which you are trying to avoid.
You might also incur the warning when correctly implementing plugin composition. For an example of a correct implementation of composition, see Implement Plugin Composition Correctly.
Use System Object That Does Not Support Variable-Size Signals
The audio plugin API requires audio plugins to support variable-size inputs and outputs. For a partial list of System objects that support variable-size signals, see Variable-Size Signal Support DSP System Objects. You might encounter issues if you attempt to use objects that do not support variable-size signals in your plugin.
For example, dsp.AnalyticSignal
does not support variable-size signals. The BrokenAnalyticSignalTransformer
plugin
uses a dsp.AnalyticSignal
object incorrectly and
fails the validateAudioPlugin
test
bench:
validateAudioPlugin BrokenAnalyticSignalTransformer
Checking plug-in class 'BrokenAnalyticSignalTransformer'... passed. Generating testbench file 'testbench_BrokenAnalyticSignalTransformer.m'... done. Running testbench... Error using dsp.AnalyticSignal/parenReference Changing the size on input 1 is not allowed without first calling the release() method. Error in BrokenAnalyticSignalTransformer/process (line 13) analyticSignal = plugin.Transformer(in); Error in testbench_BrokenAnalyticSignalTransformer (line 61) o1 = process(plugin, in(:,1)); Error in validateAudioPlugin
See BrokenAnalyticSignalTransformer
Code
If you want to use the functionality of a System object that does not support variable-size signals, you can buffer the input and output of the System object, or always call the object with one sample.
Always Call the Object with One Sample
You can create a loop around your call to an object. The loop iterates for the number of samples in your variable frame size. The call to the object inside the loop is always a single sample.
Note
Depending on your implementation and the particular object, calling an object sample by sample in a loop might result in significant computational cost.
Buffer Input and Output of Object
You can buffer the input to your object to a consistent frame
size, and then buffer the output of your object back to the original
frame size. The dsp.AsyncBuffer
System object is
well-suited for this task.
Note
Use of the asynchronous buffering object forces a minimum latency of your specified frame size.
Using Enumeration Parameter Mapping
It is often useful to associate a property with a set of strings
or character vectors. However, restrictions on plugin generation require
cached values, such as property values, to have a static size. To
work around this issue, you can use a separate enumeration class that
maps the strings to the enumerations, as described in the audioPluginParameter
documentation.
Alternatively, if you want to avoid writing an enumeration class and keep all your code in one file, you can use a dependent property to map your parameter names to a set of values. In this scenario, you map your enumeration value to a value that you can cache.