How can I import a module into an AudioWorkletProcessor that is changed elsewhere?

3k Views Asked by At

I'm trying to change a value that is used by an AudioWorkletProcessor from another module, but from the context of the AudioWorkletProcessor, the value doesn't change and just stays the same. But from the module that modifies the data, when queried upon the data has in fact changed there. It's like there is a completely separate instance/state of the module that holds the data (functions.js) for the modifier (main.js) and the reader (audio-processor.js)

Here we have audio-processor.js

import { sawWave } from "/src/functions.js";

var tick = 0

class AudioProcessor extends AudioWorkletProcessor {
    process(inputs, outputs, parameters) {
        const output = outputs[0]
        output.forEach(channel => {

            // emphasis on sawWave function
            var value = sawWave(tick) * 0.1;
            for (let i = 0; i < channel.length; i++) {
                channel[i] = value
            }
        });
        tick++
        return true
    }
}

registerProcessor('audio-processor', AudioProcessor)

Here we have functions.js, which is contains sawWave() which is imported into audio-processor.js

let variables = {
    // emphasis on this variable
    sawWaveFrequency: 1
}

export function setSawFrequency(freq) {
    variables.sawWaveFrequency = freq
}

export function sawWave(tick) {
    console.log(variables.sawWaveFrequency)
    // this function uses the variable defined above and is imported by audio-processor.js
    // variables.sawWaveFrequency doesn't change for audio-processor.js when modified from main.js
    // so the output will will always be as if variables.sawWaveFrequency = 1
    return tick * variables.sawWaveFrequency % 10;
}

Here we have main.js, which handles input from a HTML page

import { setSawFrequency } from "/src/functions.js";

var audioContext
var whiteNoiseNode

async function init() {
    audioContext = new AudioContext()
    await audioContext.audioWorklet.addModule("/src/audio-processor.js")
    whiteNoiseNode = new AudioWorkletNode(audioContext, 'audio-processor')
}

function play() {
    whiteNoiseNode.connect(audioContext.destination)
}

function main() {
    document.querySelector("#play").onclick = play;
    document.querySelector("#init").onclick = init;

    document.querySelector("#slider-mult").oninput = function() {
        // this is supposed to change the value, but only does so in the context of main.js and not audio-processor.js
        setSawFrequency(this.value);
    }
}

main();

EDIT: I take it you're only supposed to use AudioWorkletProcessor.port and the parameters parameter in the process function to communicate with it?

2

There are 2 best solutions below

0
On BEST ANSWER

You're already on the right track. To change a value inside your AudioWorkletProcessor you can either use a custom AudioParam or send a message over the MessagePort.

The reason why your code doesn't work is that you technically end up with two instances of the same module. The AudioWorkletProcessor runs on a different thread and has no access to the modules that are loaded on the main thread. Therefore /src/functions.js gets loaded twice. One instance is living on the main thread and the other one is living on the audio thread. Each of them doesn't know that the other exists.

0
On

You can also use a SharedArrayBuffer

Create the shared memory object in one module.

  const length = 100;
  const size = Int32Array.BYTES_PER_ELEMENT * length;
  this.sab = new SharedArrayBuffer(size);
  this.sharedArray = new Int32Array(this.sab);

Send it to AudioWorkletProcessor

  this.worker.port.postMessage({
      sab: this.sab
  });

In the AudioWorkletProcessor

this.port.onmessage = event => {
      this.sharedArray = new Int32Array(event.data.sab);
}

Now both modules share the same array (this.sharedArray).