Example : Simple JavaScript Web Audio Modules 2.0 Processor

Specifications :

In this example, we will use a simple JavaScript processor, to keep the code simple. But of course, you can use the Web Assembly that we've seen in the example 2. Before jumping into the code, be sure to check the Web Audio Modules API first.

Prerequisites :

To make our host communicates with the plugin, we will use the SDK of Web Audio Modules. You can either download the full source and build it on your own, or use the pre-build version.

Web Audio Modules Environment :

In this example we want to use our processor. To achieve that, we need to overwrite the WebAudioModule.

// my-wam/js

import {WebAudioModule} from "../lib/sdk/index.js";
import MyWamNode from "./wam-audio-player-node.js";


export default class MyWam extends WebAudioModule {
    /**
     * @property {Function} createAudioNode
     * @override
     * @param initialState
     * @return {Promise<MyWamNode>}
     */
    async createAudioNode(initialState) {
        await MyWamNode.addModules(this.moduleId);
        const node = new MyWamNode(this);

        // Initialize the node audio node. Register the processor in the audio context and the WAM group.
        node._initialize();
        return node;
    }
}

At this point, we can now use the audio worklet and connect to the audio node of your processor. But in our case, we will transform the processor into a processor that follows the web audio modules standards.

To use the processor, we will define the audio node. It extends WamNode.

// wam-audio-player-node.js
class MyWamNode extends WamNode {

    static async addModules(moduleId) {
        const {audioWorklet} = audioCtx;
        await super.addModules(audioCtx, moduleId);
        await addFunctionModule(audioWorklet, getProcessor, moduleId);
    }

    /* Code of the audio node
    ...
    */
}

        

We will talk about the getProcessor function afterwards while defining the wam processor. This functions will be called in the web audio modules class to register the custom processor.

Initialize the SDK :

To work with WAM plugins, we need to keep the processor in the same group as the plugins. By initializing the sdk we will get a unique host group id.

This host group id will be used when initializing the host and the plugins as follows :

// index.js

(async ()=>{

    const {default: initializeWamHost} = await import("./sdk/initializeWamHost.js");
    const [hostGroupId] = await initializeWamHost(audioCtx);

    const {default: MyWam} = await import("./my-wam.js");
    const {default: WAM1} = await import(plugin1Url);
    const {default: WAM2} = await import(plugin2Url);
    /* Code of your host
       ...
    */
})();

We can now use the audio worklet and connect to the audio node of your processor. But in our case, we will transform the processor into a processor that follows the web audio modules standards.

Creating instance of the plugins :

In the host, we can now instantiate the plugins and our wam environment.

// index.js

(async ()=>{

    /*
       ...
    */

    let wamInstance = await MyWam.createInstance(hostGroupId, audioCtx);
    let pluginInstance1 = await WAM1.createInstance(hostGroupId, audioCtx);
    let pluginInstance2 = await WAM2.createInstance(hostGroupId, audioCtx);

    node.connect(pluginInstance1._audioNode).connect(pluginInstance2._audioNode).connect(audioCtx.destination);

    /* Code of your host
       ...
    */
})();

Now that the instances are created, we can connect them into the destination. The node is our WAM environment, You can test the code with this, it will work properly, even with a simple audio worklet processor in JavaScript. But as said before, we want to customize our processor, and make it follow the WAM standards.

Custom Web Audio Modules processor :

he WAM processor is the same as pure JavaScript apart from the registering method in the Audio Worklet. In WAM the processor is registered in the GlobalScope of the Audio Worklet. To give access to the options, you will need to define a function that returns your processors as follows :

// index.js

const getProcessor = (moduleId) => {
    const audioWorkletGlobalScope = globalThis;
    const {registerProcessor} = audioWorkletGlobalScope;
    const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId);

    class MyWamProcessor extends ModuleScope.WamProcessor {
        // code of your processor goes here.
    }

    try {
        registerProcessor(moduleId, MyWamProcessor);
    } catch (error) {
        console.warn(error);
    }
    return MyWamProcessor;
}
export default getProcessor;


Conclusion :

We've created a custom processor that follows the Web Audio Modules standards. We've connected multiple plugins, without effort to the host.

But now we want to add logic inside our processor. To start with, we will see how to add parameters automation into the processor. We will try to automatize a parameter of a plugin.