Start · Sprachen · JavaScript · Referenz · AudioWorkletProcessor

AudioWorkletProcessor

Schnittstelle

Basisklasse für eigenen Audioverarbeitungscode, der im Audio-Rendering-Thread läuft und die Ausgabe eines <code>AudioWorkletNode</code> berechnet.

seit JavaScript Baseline: alle modernen Browser (Chrome 66+, Firef Kategorie: webapi

Signatur

interface AudioWorkletProcessor

Beschreibung

AudioWorkletProcessor ist die Basisklasse, von der du in einem AudioWorklet-Modul eigene Prozessoren ableitest. Der Code läuft nicht im Hauptthread, sondern im AudioWorkletGlobalScope direkt im Audio-Rendering-Thread. Dadurch verarbeitest du Samples mit niedriger Latenz und ohne Aussetzer, die durch Layout, Garbage Collection oder andere Arbeit im Hauptthread entstehen würden. Das Interface ersetzt den veralteten ScriptProcessorNode.

Ein Prozessor wird im Worklet-Modul per registerProcessor(name, Klasse) registriert. Im Hauptthread lädst du das Modul mit audioContext.audioWorklet.addModule(url) und erzeugst anschließend einen new AudioWorkletNode(context, name, options). Dabei wird automatisch eine Instanz deines Prozessors erstellt. Den Konstruktor rufst du nie selbst auf, sonst wird ein TypeError geworfen.

Die zentrale Methode ist process(inputs, outputs, parameters). Sie wird für jeden Render-Block aufgerufen, in der Regel mit 128 Sample-Frames. inputs und outputs sind Arrays von Eingängen bzw. Ausgängen. Jeder davon ist ein Array von Kanälen, und jeder Kanal ist ein Float32Array. Über den statischen Getter parameterDescriptors definierst du eigene AudioParams, die im Hauptthread über node.parameters erreichbar sind. Mit der Eigenschaft port (ein MessagePort) tauschst du Nachrichten in beide Richtungen mit dem Knoten aus.

Der Rückgabewert von process() legt die Lebensdauer fest. Mit true bleibt der Prozessor aktiv. Mit false darf ihn der Browser abbauen, sobald keine aktiven Eingänge mehr anliegen. Quellknoten wie Oszillatoren geben daher meist true zurück, reine Effekte oft false.

Parameter

Name Typ Default Beschreibung
$options AudioWorkletNodeOptions Wird dem Konstruktor des Prozessors automatisch übergeben. Es sind die Optionen aus new AudioWorkletNode(), z. B. numberOfInputs, numberOfOutputs, outputChannelCount, parameterData und processorOptions (beliebige eigene Daten, die per Structured Clone übertragen werden).
$inputs Pflicht Float32Array[][] Parameter von process(): inputs[n][kanal][sample]. Ist ein Eingang nicht verbunden, hat er 0 Kanäle.
$outputs Pflicht Float32Array[][] Parameter von process(): Ausgabepuffer, die du beschreibst. Sie sind mit Nullen vorbelegt.
$parameters Pflicht Object<string, Float32Array> Parameter von process(): aktuelle Werte der eigenen AudioParams. Das Array hat die Länge 128 (a-rate mit Automation) oder 1 (Wert im ganzen Block konstant).

Beispiele

Einfacher Gain-Prozessor mit eigenem AudioParam (gain-processor.js)

// gain-processor.js – läuft im AudioWorkletGlobalScope
class GainProcessor extends AudioWorkletProcessor {
  static get parameterDescriptors() {
    return [{ name: 'gain', defaultValue: 0.5, minValue: 0, maxValue: 1, automationRate: 'a-rate' }];
  }

  process(inputs, outputs, parameters) {
    const input = inputs[0];
    const output = outputs[0];
    const gain = parameters.gain;

    for (let ch = 0; ch < input.length; ch++) {
      const inCh = input[ch];
      const outCh = output[ch];
      for (let i = 0; i < inCh.length; i++) {
        // Länge 1 = konstanter Wert im ganzen Block
        outCh[i] = inCh[i] * (gain.length > 1 ? gain[i] : gain[0]);
      }
    }
    return true;
  }
}

registerProcessor('gain-processor', GainProcessor);

Prozessor im Hauptthread laden und verwenden

const ctx = new AudioContext();

const start = async () => {
  await ctx.audioWorklet.addModule('gain-processor.js');

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

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

  // Eigenen AudioParam automatisieren
  const gain = gainNode.parameters.get('gain');
  gain.setValueAtTime(0, ctx.currentTime);
  gain.linearRampToValueAtTime(0.8, ctx.currentTime + 2);

  osc.start();
};

// AudioContext erst nach einer Nutzeraktion starten (Autoplay-Richtlinie)
document.querySelector('#play').addEventListener('click', start, { once: true });

Pegelmesser mit Kommunikation über port

// meter-processor.js
class MeterProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();
    this.interval = options.processorOptions?.interval ?? 20; // Blöcke
    this.count = 0;
    this.active = true;
    this.port.onmessage = (e) => {
      if (e.data === 'stop') this.active = false;
    };
  }

  process(inputs) {
    const ch = inputs[0][0];
    if (ch && ++this.count >= this.interval) {
      let sum = 0;
      for (let i = 0; i < ch.length; i++) sum += ch[i] * ch[i];
      this.port.postMessage({ rms: Math.sqrt(sum / ch.length), time: currentTime });
      this.count = 0;
    }
    return this.active;
  }
}
registerProcessor('meter-processor', MeterProcessor);

// --- Hauptthread ---
// await ctx.audioWorklet.addModule('meter-processor.js');
// const meter = new AudioWorkletNode(ctx, 'meter-processor', {
//   processorOptions: { interval: 10 }
// });
// meter.port.onmessage = ({ data }) => console.log(data.rms.toFixed(3));
// source.connect(meter);
0.212 0.198 0.205

// Wichtig · Fallstricke

  • Kein new im Hauptthread: Der Prozessor wird nur durch new AudioWorkletNode() erzeugt. Rufst du im eigenen Konstruktor super() nicht auf, ist this.port nicht verfügbar.
  • Echtzeitregeln: Erzeuge in process() keine Objekte oder Arrays, denn die Garbage Collection kann Knackser verursachen. Vermeide außerdem blockierende Arbeit und häufige postMessage-Aufrufe. Legst du Puffer im Konstruktor an, verwende sie wieder.
  • Blockgröße: Gehe nicht fest von 128 Frames aus, sondern nutze channel.length. Künftige Versionen der Spezifikation erlauben andere Render-Quanten.
  • Nicht verbundene Eingänge haben 0 Kanäle, inputs[0][0] ist dann undefined. Prüfe das immer vorher.
  • Einschränkungen im Worklet-Scope: Es gibt kein DOM, kein fetch und keine Timer wie setTimeout. Zur Verfügung stehen currentTime, currentFrame, sampleRate und registerProcessor.
  • Sicherer Kontext: audioWorklet gibt es nur über HTTPS bzw. localhost. Das Modul unterliegt CORS und der CSP (script-src/worker-src).
  • Fehler: Eine Ausnahme in process() löst am Knoten das Ereignis processorerror aus. Danach gibt der Knoten nur noch Stille aus.
  • Für große Datenmengen ohne Kopieren eignet sich ein SharedArrayBuffer. Dafür muss die Seite cross-origin-isolated sein (COOP/COEP-Header).