javascriptaudioworkletprocessor

using await or wait promise in AudioWorkletProcessor.process function


How to using await keyword or wait the promise in the AudioWorkletProcessor.process function?

I have some tasks such as running machine learning model inference and retrieve the results that requires async function, but I can't use it in the process function of the AudioWorkletProcessor.

Here is a simple AudioWorkletProcessor test page (host it in a web server instead of open it directly, otherwise the javascript blob will be blocked due to the security policy).

<!doctype html>
<html>
    <head>
        <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
    </head>
    <div class="p-3 mb-2 bg-slate-100 w-1/3 rounded text-sm">
        <input id="volume-meter" class="w-full bg-white"
            type="range" min="0" max="100" value="0" step="1" disabled>
    </div>
    <button id="button-start" disabled>START</button>
    <script>
        const audioContext = new AudioContext();
        
        // the code in "volume-meter-processor.js" since error happens when addModule directly 
        let process_code = `
        const SMOOTHING_FACTOR = 0.8;
        const FRAME_PER_SECOND = 60;
        const FRAME_INTERVAL = 1 / FRAME_PER_SECOND;
        
        class VolumeMeter extends AudioWorkletProcessor {
        constructor() {
          super();
          this._lastUpdate = currentTime;
          this._volume = 0;
        }
        calculateRMS(inputChannelData) {
          let sum = 0;
          for (let i = 0; i < inputChannelData.length; i++) {
            sum += inputChannelData[i] * inputChannelData[i];
          }
          let rms = Math.sqrt(sum / inputChannelData.length);
          this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR);
        }
        test_async = async function(a,b){
            return a+b;
        }
        process(inputs, outputs) {
          
          const inputChannelData = inputs[0][0];
          if (currentTime - this._lastUpdate > FRAME_INTERVAL) {
            this.calculateRMS(inputChannelData);
            this.port.postMessage(this._volume);
            console.log(inputs)
            this._lastUpdate = currentTime;
          }
          let c = await this.test_async(3,5);
          return true;
        }
        }
        registerProcessor("volume-meter", VolumeMeter);
        `;
        
        
        const startAudio = async (context, meterElement) => {
          let blob = new Blob([process_code], {type: 'application/javascript'});
          //await context.audioWorklet.addModule('volume-meter-processor.js');
          await context.audioWorklet.addModule(URL.createObjectURL(blob));
          const mediaStream = await navigator.mediaDevices.getUserMedia({audio: true});
          const micNode = context.createMediaStreamSource(mediaStream);
          const volumeMeterNode = new AudioWorkletNode(context, 'volume-meter');   
          volumeMeterNode.port.onmessage = ({data}) => {
            meterElement.value = data * 500;
          };
          micNode.connect(volumeMeterNode).connect(context.destination);
        };
        
        // A simplem onLoad handler. It also handles user gesture to unlock the audio
        // playback.
        window.addEventListener('load', async () => {
          const buttonEl = document.getElementById('button-start');
          const meterEl = document.getElementById('volume-meter');
          buttonEl.disabled = false;
          meterEl.disabled = false;
          buttonEl.addEventListener('click', async () => {
            await startAudio(audioContext, meterEl);
            audioContext.resume();
            buttonEl.disabled = true;
            buttonEl.textContent = 'Playing...';
          }, false);
        });
    </script>
</html>

I want to use await keyword and wait the result in the process function.

This is my attemps, but non of these works.

Attemp 1:

process(inputs, outputs) {
  const inputChannelData = inputs[0][0];
  if (currentTime - this._lastUpdate > FRAME_INTERVAL) {
    this.calculateRMS(inputChannelData);
    this.port.postMessage(this._volume);
    console.log(inputs)
    this._lastUpdate = currentTime;
  }
  let c = await this.test_async(3,5);
  return true;
}

This attemp failed with error: SyntaxError: Unexpected reserved word

Attemp 2:

async process(inputs, outputs) {
  const inputChannelData = inputs[0][0];
  if (currentTime - this._lastUpdate > FRAME_INTERVAL) {
      this.calculateRMS(inputChannelData);
      this.port.postMessage(this._volume);
      console.log(inputs)
      this._lastUpdate = currentTime;
  }
  let c = await this.test_async(3,5);
  return true;
}

The webpage simply stuck and not process any microphone input

Attemp 3

process(inputs, outputs) {
  const inputChannelData = inputs[0][0];
  if (currentTime - this._lastUpdate > FRAME_INTERVAL) {
      this.calculateRMS(inputChannelData);
      this.port.postMessage(this._volume);
      console.log(inputs)
      this._lastUpdate = currentTime;
  }
  let c = 0;
  this.test_async(3,5).then(value => {
     c = value;
  })
  while (c === 0){
    //This while loop consumes a lot of CPU!!!
    console.log("waiting promise")
  }
  console.log("promise done, c=", c)
  return true;
}

Attemp 3 fails due to the test_async are never been called.

How do I use the async function in the AudioWorkletProcessor.process function?


Solution

  • Simply can't.

    This document https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process indicates the AudioWorkletProcessor.process() method is called synchronously from the audio rendering thread so that it is not possible to wait promise from another async function in the AudioWorkletProcessor.process() method.