Start · Sprachen · JavaScript · Referenz · AudioWorklet

AudioWorklet

Schnittstelle

Stellt eigene Audioverarbeitungsskripte bereit, die im Audio-Rendering-Thread mit sehr niedriger Latenz laufen; erreichbar über <code>audioContext.audioWorklet</code>.

seit JavaScript Baseline: alle modernen Browser (seit 2021; Chrome Kategorie: webapi

Signatur

interface AudioWorklet extends Worklet

Beschreibung

Das Interface AudioWorklet gehört zur Web Audio API und erbt von Worklet. Du erzeugst es nicht selbst. Jeder BaseAudioContext hat eine eigene Instanz, die du über die Eigenschaft audioContext.audioWorklet erreichst. Mit der geerbten Methode addModule(moduleURL) lädst du ein JavaScript-Modul in den AudioWorkletGlobalScope. Dieser Bereich läuft im selben Thread wie das Audio-Rendering.

Im geladenen Modul leitest du eine Klasse von AudioWorkletProcessor ab und meldest sie mit registerProcessor(name, klasse) an. Die Methode process(inputs, outputs, parameters) wird für jeden Render-Block aufgerufen, standardmäßig mit 128 Sample-Frames. Im Haupt-Thread erzeugst du danach einen AudioWorkletNode mit demselben Namen und bindest ihn wie jeden anderen Knoten in den Audiographen ein.

AudioWorklet ist der Nachfolger des veralteten ScriptProcessorNode. Weil die Verarbeitung nicht im Haupt-Thread läuft, stören Layout, Garbage Collection im UI oder aufwendiges JavaScript die Audioausgabe nicht. Typische Einsätze sind eigene Effekte (Verzerrung, Filter, Bitcrusher), Synthesizer, Pegelmesser, Analyse in Echtzeit sowie DSP-Code in WebAssembly.

Haupt-Thread und Prozessor tauschen Daten über AudioParams (festgelegt über static get parameterDescriptors()) oder über den MessagePort in node.port bzw. this.port aus.

Parameter

Name Typ Default Beschreibung
$moduleURL Pflicht string | URL Parameter der geerbten Methode addModule(): die URL des Moduls mit den Prozessor-Klassen. Sie wird relativ zum Dokument aufgelöst.
$options { credentials?: 'omit' | 'same-origin' | 'include' } { credentials: 'same-origin' } Optionen für addModule(). Sie legen fest, ob beim Laden des Moduls Zugangsdaten (z. B. Cookies) mitgeschickt werden.

Beispiele

Prozessor-Modul: weißes Rauschen mit Lautstärke-Parameter

// datei: noise-processor.js (läuft im AudioWorkletGlobalScope)
class NoiseProcessor extends AudioWorkletProcessor {
  static get parameterDescriptors() {
    return [{ name: 'gain', defaultValue: 0.2, minValue: 0, maxValue: 1 }];
  }

  process(inputs, outputs, parameters) {
    const output = outputs[0];
    const gain = parameters.gain;
    for (const channel of output) {
      for (let i = 0; i < channel.length; i++) {
        // Der Parameter kann ein Wert pro Block oder pro Sample sein
        const g = gain.length > 1 ? gain[i] : gain[0];
        channel[i] = (Math.random() * 2 - 1) * g;
      }
    }
    return true; // Prozessor aktiv halten
  }
}

registerProcessor('noise-processor', NoiseProcessor);

Modul laden und AudioWorkletNode verwenden

// Haupt-Thread, z. B. in einem Klick-Handler (Autoplay-Richtlinie)
const startButton = document.querySelector('#start');

startButton.addEventListener('click', async () => {
  const ctx = new AudioContext();
  await ctx.audioWorklet.addModule('noise-processor.js');

  const noise = new AudioWorkletNode(ctx, 'noise-processor');
  noise.connect(ctx.destination);

  // Lautstärke über den AudioParam sanft ändern
  const gain = noise.parameters.get('gain');
  gain.setValueAtTime(0.2, ctx.currentTime);
  gain.linearRampToValueAtTime(0, ctx.currentTime + 2);

  console.log('Rauschen läuft, Samplerate:', ctx.sampleRate);
});
Rauschen läuft, Samplerate: 48000

Pegelmesser mit MessagePort-Kommunikation

// Prozessor-Code als Blob-URL, damit keine eigene Datei nötig ist
const code = `
class MeterProcessor extends AudioWorkletProcessor {
  constructor() { super(); this.frames = 0; }
  process(inputs) {
    const input = inputs[0][0];
    if (input) {
      let sum = 0;
      for (const s of input) sum += s * s;
      this.frames += input.length;
      if (this.frames >= sampleRate / 10) { // etwa 10-mal pro Sekunde
        this.port.postMessage(Math.sqrt(sum / input.length));
        this.frames = 0;
      }
    }
    return true;
  }
}
registerProcessor('meter', MeterProcessor);`;

const url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' }));

const startMeter = async () => {
  const ctx = new AudioContext();
  await ctx.audioWorklet.addModule(url);
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const mic = ctx.createMediaStreamSource(stream);
  const meter = new AudioWorkletNode(ctx, 'meter');
  meter.port.onmessage = ({ data }) => console.log('RMS:', data.toFixed(3));
  mic.connect(meter);
};

startMeter();
RMS: 0.012 RMS: 0.087 …

// Wichtig · Fallstricke

  • Sicherer Kontext: AudioWorklet steht nur über HTTPS bzw. localhost zur Verfügung. Auf unsicheren Seiten ist audioContext.audioWorklet undefined.
  • Reihenfolge: Du darfst new AudioWorkletNode(ctx, name) erst aufrufen, nachdem das Promise von addModule() erfüllt ist. Sonst wird ein InvalidStateError geworfen.
  • Echtzeit-Code: In process() keine Speicherzuweisungen (neue Arrays/Objekte), kein postMessage bei jedem Block und keine blockierenden Operationen. Das löst Garbage Collection aus und führt zu Knacksern. Puffer legst du besser einmal im Konstruktor an.
  • Eingeschränkter Scope: Im AudioWorkletGlobalScope gibt es kein DOM, kein fetch, kein setTimeout und kein XMLHttpRequest. Verfügbar sind unter anderem sampleRate, currentTime, currentFrame und registerProcessor.
  • Rückgabewert von process(): Mit true bleibt der Prozessor aktiv. Bei false darf der Browser ihn beenden, sobald keine Eingänge mehr verbunden sind.
  • Parameter: Das Array eines AudioParams hat Länge 1 (konstant im Block) oder 128 (automatisiert). Beide Fälle musst du behandeln.
  • Autoplay: Einen AudioContext legst du am besten in einem Nutzer-Ereignis an oder setzt ihn dort mit resume() fort.
  • Kompatibilität: Wird von allen modernen Browsern unterstützt. ScriptProcessorNode ist veraltet und sollte nur als Rückfallebene für sehr alte Browser dienen. Für SharedArrayBuffer zwischen den Threads ist Cross-Origin-Isolation nötig (COOP/COEP-Header).