Web Audio API Effects

The Web Audio API brings real-time audio signal processing natively into the browser. You can use the API in any major browser, an updated list can be found here. The Web Audio API speicifies a set of basic nodes from which JSAP effects are built.

A set of examples can be found here

The 'Hello World' of plugins

As a starting point, the following code outlines a simple gain plugin written for JSAP!

/*
    HelloWorld Gain
    This is a simple gain node with only one input and output block and one parameter
    The parameter is a gain in dB.
*/
var GainPlugin = function (factory, owner) {
    /* 
        Each plugin is passed two arguments on construction:
            1 - > Factory: The factory that built this plugin
            2 - > Owner: The SubFactory that this plugin is registered too (if given)
    */

    // This attaches the base plugin items to the Object
    BasePlugin.call(this, factory, owner);

    /* USER MODIFIABLE BEGIN */
    // Only modify between this line and the end of the object!

    // The current web audio API context is available to the plugin through the this.context object
    // We are using it to create a web audio API gain node.
    var node = this.context.createGain();

    // This defines a new parameter. The arguments passed are, in order:
    // Data Type, Name, Default value, minimum and maximum values
    // Parameters are exposed by default
    var gain_parameter = this.parameters.createNumberParameter("gain", 0, -12, 12);

    // Attaching some number conversions on the parameter to shift between dB and linear gains
    gain_parameter.translate = function (e) {
        return 20.0 * Math.log10(e);
    }
    gain_parameter.update = function (e) {
        return Math.pow(10, e / 20.0);
    }

    // Binding the parameter to the Web Audio API gain nodes' gain parameter
    gain_parameter.bindToAudioParam(node.gain);

    // Set the gain node as the input point. All connections to the plugin are made
    // to this node.
    this.addInput(node);
    // Set the gain node as the output point. All connections from the plugin are
    // made from this node
    this.addOutput(node);
    /* USER MODIFIABLE END */
}

// Also update the prototype function here!
GainPlugin.prototype = Object.create(BasePlugin.prototype);
GainPlugin.prototype.constructor = GainPlugin;
GainPlugin.prototype.name = "Gain";

The above code outlines a simple plugin, purely for wrapping a gain node. The function stored is called the Plugin Prototype, as it is the constructor function which will generate the JSAP object.

The constructor

// Empty Plugin
var Empty = function (factory, owner) {
    BasePlugin.call(this, factory, owner);
    /* USER MODIFIABLE BEGIN */
    /* USER MODIFIABLE END */
}
Empty.prototype = Object.create(BasePlugin.prototype);
Empty.prototype.constructor = Empty;
Empty.prototype.name = "Empty";

This is an empty plugin prototype and is NOT runnable code, it is only an example. The first line is the function definition where we pass in two arguments. These arguments are called factory and owner. The factory is the Factory object which constructed the plugin. The plugin uses this to communicate with the host directly, or to other plugins. The second argument, owner, is the SubFactory the plugin is currently hosted in. This will be undefined if no SubFactory was used, otherwise this will allow the plugin to understand its position in a chain and its location in the environment.

Creating Nodes

All the audio processing is performed through the Web Audio API, therefore JSAP must be built using the API's defined nodes. The API is exposed inside the plugin and can be accessed through this.context.

// Empty Plugin
var Empty = function (factory, owner) {
    BasePlugin.call(this, factory, owner);
    /* USER MODIFIABLE BEGIN */
    var gainNode = this.context.createGain();
    /* USER MODIFIABLE END */
}
Empty.prototype = Object.create(BasePlugin.prototype);
Empty.prototype.constructor = Empty;
Empty.prototype.name = "Empty";

In the above code, we've added a line to build an audio gain node. This node, as specified in the API, has one input and output connection and one parameter (the gain in linear form).

Connecting the IO

The JSAP instance must define the input and output points of the chain. Multiple input and output nodes are acceptable, as long as it is unique. For instance, the same node cannot be assigned to multiple output points. However two different nodes can! The input and outputs are added using this.addInput() and this.addOutput(). These serailly index, meaning the first call with a valid nodes adds that to in/out at position [0], the second to position [1]. For this reason, we recommend building your entire node layout first and then defining your IO at the end.

// Empty Plugin
var Empty = function (factory, owner) {
    BasePlugin.call(this, factory, owner);
    /* USER MODIFIABLE BEGIN */
    var gainNode = this.context.createGain();

    this.addInput(gainNode);
    this.addOutput(gainNode);
    /* USER MODIFIABLE END */
}
Empty.prototype = Object.create(BasePlugin.prototype);
Empty.prototype.constructor = Empty;
Empty.prototype.name = "Empty";

Basic Parameters

Creation

This will be covered in more detail in the relevant sections. The parameters must be exposed to the outside world, along with some meaningful information about them. JSAP includes a PluginParameter object from which you can build parameters:

// Empty Plugin
var Empty = function (factory, owner) {
    BasePlugin.call(this, factory, owner);
    /* USER MODIFIABLE BEGIN */
    var gainNode = this.context.createGain();

    var gain_parameter = this.parameters.createNumberParameter("gain", 0, -12, 12);

    this.addInput(gainNode);
    this.addOutput(gainNode);
    /* USER MODIFIABLE END */
}
Empty.prototype = Object.create(BasePlugin.prototype);
Empty.prototype.constructor = Empty;
Empty.prototype.name = "Empty";

In the above we build a new parameter and assign it to local variable gain_parameter using this.parameters.createParameter. The constructor arguments are:

Binding to Audio Node parameters

// Empty Plugin
var Empty = function (factory, owner) {
    BasePlugin.call(this, factory, owner);
    /* USER MODIFIABLE BEGIN */
    var gainNode = this.context.createGain();

    var gain_parameter = this.parameters.createNumberParameter("gain", 0, -12, 12);

    gain_parameter.bindToAudioParam(gainNode.gain);

    this.addInput(gainNode);
    this.addOutput(gainNode);
    /* USER MODIFIABLE END */
}
Empty.prototype = Object.create(BasePlugin.prototype);
Empty.prototype.constructor = Empty;
Empty.prototype.name = "Empty";

Simply call the parameter member function bindToAudioParam along with the Audio parameter to use. In the above example, it is binding gain_parameter onto the Web Audio API gainNode parameter gain.

Translations

There are many times when it makes sense to have to perform conversions between the data to show and data to pass into the Audio API. In our example, we want the gain to be controlled as a decibel value, however the API requires the data to be linear. The PluginParameter has two functions which you can use to bind your conversions onto: translate and update. Translate is for writing TO the audio node and update is for reading FROM the audio node. Both functions are passed one argument, the value to convert.

// Empty Plugin
var Empty = function (factory, owner) {
    BasePlugin.call(this, factory, owner);
    /* USER MODIFIABLE BEGIN */
    var gainNode = this.context.createGain();

    var gain_parameter = this.parameters.createNumberParameter("gain", 0, -12, 12);

    gain_parameter.bindToAudioParam(gainNode.gain);

    gain_parameter.translate = function (e) {
        return 20.0 * Math.log10(e);
    }
    gain_parameter.update = function (e) {
        return Math.pow(10, e / 20.0);
    }

    this.addInput(gainNode);
    this.addOutput(gainNode);
    /* USER MODIFIABLE END */
}
Empty.prototype = Object.create(BasePlugin.prototype);
Empty.prototype.constructor = Empty;
Empty.prototype.name = "Empty";

In the above example, we've attached two functions onto translate and update, both functions are then called when required.

That's it!

That is now a fully working JSAP effect! Read the rest of the documentation to get further insight into building more advanced plugins!