Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do Web Audio API events run in a separate thread?

I'm particularly interested in the onaudioprocess of the ScriptProcessorNode (until recently called JavaScriptNode). It is an event listener which is called periodically for audio processing. Does it run in a separate thread?

I'd want to feed the data to a circular buffer and process it outside of this callback so I don't hog the CPU. I can use web workers for the async processing, but AFAIK I'd need a different implementation of the ring buffer in the case of different threads.

Is there a way to test this?

like image 882
Davorin Avatar asked Nov 08 '12 11:11

Davorin


2 Answers

All JavaScript is single-threaded, synchronously executed. Any asynchronous stuff is done via events, which add their handlers to the task queue - to be executed when the current task is finished.

To use separate threads, you would need an environment like WebWorkers - every thread has its own execution context (global scope) and task queue; communication between them is done via events.

As the onaudioprocess handler seems to live in the same scope as the DOM, it is quite unlikely that it runs in its own thread. If you really have a computationally intensive task that makes your page unresponsive, you should use a WebWorker into which you feed the audio events:

myScriptProcessorNode.onaudioprocess = myWebWorker.postMessage;
like image 77
Bergi Avatar answered Nov 15 '22 15:11

Bergi


With Bergi's solution, you're going to run into issues with the structured clone algorithm not being able to copy read only parameters in the audioProcessingEvent. What you need to do is break out the parts you need from the event that are cloneable, and pass them over to your worker in a different data structure like so:

_onAudioProcess(audioProcessingEvent) {
  const {inputBuffer, outputBuffer} = audioProcessingEvent;
  // The output buffer contains the samples that will be modified and
  // eventually played, so we need to keep a reference to it.
  this._outputBuffer = outputBuffer;
  const numChannels = inputBuffer.numberOfChannels;

  const inputChannels =
    Array.from({length: numChannels}, (i) => {
      return inputBuffer.getChannelData(i);
    });

  this._worker.postMessage({
    command: 'DO_STUFF',
    inputChannels: inputChannels,
  });
}

You're also going to need to access the reference to your outputBuffer in setMessageHandler, in order to copy the processed data back to be eventually played by the user.

like image 1
BrandonM Avatar answered Nov 15 '22 15:11

BrandonM