AudioWorklet vs ScriptProcessorNode in Web Audio

The Web Audio API’s AudioWorklet replaces the deprecated ScriptProcessorNode to provide high-performance, low-latency custom audio processing in JavaScript. While the legacy ScriptProcessorNode executed audio code on the browser’s main execution thread—leading to audio stuttering, dropouts, and latency—the AudioWorklet offloads audio computation to a dedicated, low-latency audio rendering thread. This shift ensures continuous, glitch-free audio processing that remains unaffected by heavy main-thread operations like UI rendering, DOM manipulation, and garbage collection.

The Flaws of ScriptProcessorNode

The ScriptProcessorNode relied on the onaudioprocess event handler, which executed on the main UI thread. This architectural limitation introduced several critical problems:

How AudioWorklet Solves Main-Thread Bottlenecks

AudioWorklet introduces a distinct separation between UI-driven logic and actual DSP processing by splitting the implementation across two components:

  1. AudioWorkletProcessor (Audio Thread): Executes inside an AudioWorkletGlobalScope on a dedicated audio rendering thread. It directly handles the audio buffers synchronously within the render cycle.
  2. AudioWorkletNode (Main Thread): Extends AudioNode and lives on the main thread, acting as the interface to connect the processor to the standard Web Audio graph and configure parameters.

By running the DSP logic in a dedicated audio thread, AudioWorklet guarantees deterministic execution time, allows for smaller buffer sizes (a default fixed quantum of 128 frames), and delivers minimal audio latency without UI interference.

Key Differences

Feature ScriptProcessorNode AudioWorklet
Execution Context Main UI Thread Dedicated Audio Worklet Thread
Thread Safety Prone to UI-induced dropouts Isolated from UI execution
Latency High (large buffer requirements) Ultra-low (fixed 128-sample block size)
Data Transfer Event-based (onaudioprocess) Shared memory / MessagePort messaging
Custom AudioParams Not supported Fully supported with sample-accurate automation

Architecture and Migration Flow

Migrating from ScriptProcessorNode to AudioWorklet requires separating the audio algorithm into an external script file and loading it asynchronously.

1. Define the Processor (Processor File)

The custom processing logic is defined in an isolated file by extending AudioWorkletProcessor and implementing the process() method:

// white-noise-processor.js
class WhiteNoiseProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    const output = outputs[0];
    output.forEach((channel) => {
      for (let i = 0; i < channel.length; i++) {
        channel[i] = Math.random() * 2 - 1;
      }
    });
    return true; // Keeps the processor alive
  }
}

registerProcessor('white-noise-processor', WhiteNoiseProcessor);

2. Register and Connect (Main Thread)

The main thread loads the processor module into the audio context and instantiates an AudioWorkletNode:

const audioContext = new AudioContext();

// Load the processor module asynchronously
await audioContext.audioWorklet.addModule('white-noise-processor.js');

// Create the AudioWorkletNode and connect it to the destination
const whiteNoiseNode = new AudioWorkletNode(audioContext, 'white-noise-processor');
whiteNoiseNode.connect(audioContext.destination);

3. Thread Communication

To send control messages or pass data between the main thread and the processor without blocking audio computation, AudioWorkletNode and AudioWorkletProcessor use the built-in bidirectional MessagePort via node.port.postMessage() and node.port.onmessage.