Start · Sprachen · JavaScript · Referenz · AudioWorkletNode

AudioWorkletNode

Schnittstelle

Ein benutzerdefinierter <code>AudioNode</code>, dessen Audioverarbeitung in einem <code>AudioWorkletProcessor</code> im Audio-Rendering-Thread läuft.

seit JavaScript Baseline: seit 2021 in allen modernen Browsern (Ch Kategorie: webapi

Signatur

interface AudioWorkletNode extends AudioNode

Beschreibung

AudioWorkletNode ist die Seite eines selbst geschriebenen Audio-Knotens, die im Haupt-Thread lebt. Die eigentliche Signalverarbeitung übernimmt ein AudioWorkletProcessor. Er läuft im Audio-Rendering-Thread, also getrennt vom Haupt-Thread und ohne dass Rendering-Arbeit dort stört. Den Knoten verbindest du wie jeden anderen AudioNode über connect() mit dem Audio-Graphen.

Typische Einsatzfälle sind eigene Effekte (Verzerrung, Bitcrusher, Filter), Synthesizer, Pegelmesser, Tonhöhenerkennung oder das Einbinden von DSP-Code, der nach WebAssembly übersetzt wurde. AudioWorkletNode ersetzt den veralteten ScriptProcessorNode, der im Haupt-Thread lief und dadurch Aussetzer im Ton verursachte.

Der Ablauf hat immer drei Schritte:

  • Das Prozessor-Modul mit audioContext.audioWorklet.addModule(url) laden.
  • Im Modul den Prozessor mit registerProcessor(name, Klasse) registrieren.
  • Den Knoten mit new AudioWorkletNode(context, name, options) erzeugen.

Für die Kommunikation zwischen den Threads gibt es zwei Wege: node.port, einen MessagePort für beliebige Nachrichten, und node.parameters, eine AudioParamMap für automatisierbare Parameter.

Wichtige Mitglieder:

  • port (MessagePort): bidirektionaler Kanal zum Prozessor.
  • parameters (AudioParamMap): die AudioParam-Objekte, die der Prozessor in static get parameterDescriptors() festlegt.
  • onprocessorerror bzw. das Event processorerror: wird ausgelöst, wenn im Prozessor eine Ausnahme auftritt.

Parameter

Name Typ Default Beschreibung
$context Pflicht BaseAudioContext Der AudioContext oder OfflineAudioContext, zu dem der Knoten gehört.
$name Pflicht string Der Name, unter dem der Prozessor mit registerProcessor() registriert wurde. Ist der Name unbekannt, wird ein NotSupportedError geworfen.
$options.numberOfInputs number 1 Anzahl der Eingänge.
$options.numberOfOutputs number 1 Anzahl der Ausgänge.
$options.outputChannelCount number[] Kanalanzahl je Ausgang, z. B. [2] für einen Stereo-Ausgang. Die Länge des Arrays muss numberOfOutputs entsprechen.
$options.parameterData Record<string, number> {} Anfangswerte für die in parameterDescriptors festgelegten AudioParams.
$options.processorOptions any undefined Beliebige Daten, die per Structured Clone an den Konstruktor des Prozessors übergeben werden.
$options.channelCount / channelCountMode / channelInterpretation number | string 2 / "max" / "speakers" Die üblichen AudioNode-Optionen für die Verteilung der Kanäle.

Rückgabewert

Typ
AudioWorkletNode
Beschreibung
Eine neue Instanz, die mit einer neuen Instanz des registrierten AudioWorkletProcessor verbunden ist.

Beispiele

Gain-Prozessor registrieren und Knoten erzeugen

// gain-processor.js – läuft im AudioWorkletGlobalScope
class GainProcessor extends AudioWorkletProcessor {
  static get parameterDescriptors() {
    return [{ name: 'gain', defaultValue: 0.5, minValue: 0, maxValue: 1 }];
  }
  process(inputs, outputs, parameters) {
    const input = inputs[0];
    const output = outputs[0];
    const gain = parameters.gain;
    for (let ch = 0; ch < input.length; ch++) {
      for (let i = 0; i < input[ch].length; i++) {
        // gain hat Länge 1 (konstant) oder 128 (automatisiert)
        output[ch][i] = input[ch][i] * (gain.length > 1 ? gain[i] : gain[0]);
      }
    }
    return true; // Prozessor am Leben halten
  }
}
registerProcessor('gain-processor', GainProcessor);

// main.js – Haupt-Thread
const ctx = new AudioContext();
await ctx.audioWorklet.addModule('gain-processor.js');

const osc = new OscillatorNode(ctx, { frequency: 440 });
const gainNode = new AudioWorkletNode(ctx, 'gain-processor', {
  parameterData: { gain: 0.2 }
});

osc.connect(gainNode).connect(ctx.destination);
osc.start();

AudioParam automatisieren über node.parameters

const gainParam = gainNode.parameters.get('gain');

// Einblenden über 2 Sekunden
gainParam.setValueAtTime(0, ctx.currentTime);
gainParam.linearRampToValueAtTime(1, ctx.currentTime + 2);

console.log([...gainNode.parameters.keys()]);
["gain"]

Pegelmesser: Nachrichten über den MessagePort

// meter-processor.js
class MeterProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();
    this.interval = options.processorOptions.interval ?? 20;
    this.count = 0;
  }
  process([input]) {
    const ch = input[0];
    if (ch && ++this.count % this.interval === 0) {
      const rms = Math.sqrt(ch.reduce((s, v) => s + v * v, 0) / ch.length);
      this.port.postMessage({ rms });
    }
    return true;
  }
}
registerProcessor('meter', MeterProcessor);

// main.js
const ctx = new AudioContext();
await ctx.audioWorklet.addModule('meter-processor.js');
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mic = ctx.createMediaStreamSource(stream);

const meter = new AudioWorkletNode(ctx, 'meter', {
  numberOfOutputs: 0,
  processorOptions: { interval: 10 }
});
meter.port.onmessage = ({ data }) => console.log('RMS:', data.rms.toFixed(3));
meter.onprocessorerror = (e) => console.error('Fehler im Prozessor', e);

mic.connect(meter);
RMS: 0.042 RMS: 0.051 …

// Wichtig · Fallstricke

  • Sicherer Kontext: AudioWorklet gibt es nur über HTTPS oder auf localhost. Das Modul wird wie ein ES-Modul geladen und unterliegt CORS sowie der CSP-Direktive script-src.
  • Reihenfolge: Warte auf das Promise von addModule(), bevor du den Knoten erzeugst. Sonst wirft der Konstruktor einen InvalidStateError oder NotSupportedError.
  • Autoplay: Ein AudioContext startet oft im Zustand "suspended". Rufe ctx.resume() in einem Handler für eine Nutzeraktion auf.
  • Echtzeit-Regeln im Prozessor: process() bekommt Blöcke mit 128 Samples und muss schnell zurückkehren. Vermeide dort Speicherzuweisungen, postMessage bei jedem Block und lange Schleifen, sonst entstehen Knackser. Im Worklet-Scope gibt es weder fetch noch DOM.
  • Lebensdauer: Liefert process() false, darf der Browser den Prozessor beenden, sobald keine Eingänge mehr aktiv sind. Gibt es keinen Eingang, etwa bei einem Generator, musst du true zurückgeben.
  • Parameter-Arrays: Ein Wert in parameters hat die Länge 1, wenn er im Block konstant ist, und sonst die Länge 128. Behandle beide Fälle.
  • Fehler: Wirft der Prozessor eine Ausnahme, gibt der Knoten danach nur noch Stille aus, und das Event processorerror wird ausgelöst.
  • Viele Nutzdaten lassen sich effizient mit SharedArrayBuffer übertragen. Dafür braucht die Seite Cross-Origin-Isolation über die Header COOP und COEP.