Signatur
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): dieAudioParam-Objekte, die der Prozessor instatic get parameterDescriptors()festlegt.onprocessorerrorbzw. das Eventprocessorerror: 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
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()]);
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);
// Wichtig · Fallstricke
- Sicherer Kontext:
AudioWorkletgibt es nur über HTTPS oder auflocalhost. Das Modul wird wie ein ES-Modul geladen und unterliegt CORS sowie der CSP-Direktivescript-src. - Reihenfolge: Warte auf das Promise von
addModule(), bevor du den Knoten erzeugst. Sonst wirft der Konstruktor einenInvalidStateErroroderNotSupportedError. - Autoplay: Ein
AudioContextstartet oft im Zustand"suspended". Rufectx.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,postMessagebei jedem Block und lange Schleifen, sonst entstehen Knackser. Im Worklet-Scope gibt es wederfetchnoch 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 dutruezurückgeben. - Parameter-Arrays: Ein Wert in
parametershat 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
processorerrorwird ausgelöst. - Viele Nutzdaten lassen sich effizient mit
SharedArrayBufferübertragen. Dafür braucht die Seite Cross-Origin-Isolation über die Header COOP und COEP.