Web-Audio-Updates in Chrome 49

Chris Wilson
Chris Wilson

In Chrome wird die Unterstützung der Web Audio API kontinuierlich und unbemerkt verbessert. In Chrome 49 (Betaversion seit Februar 2016, voraussichtlich stabile Version im März 2016) haben wir mehrere Funktionen aktualisiert, um die Spezifikation zu erfüllen, und einen neuen Knoten hinzugefügt.

decodeAudioData() gibt jetzt ein Versprechen zurück

Die Methode decodeAudioData() von AudioContext gibt jetzt eine Promise zurück, wodurch eine Promise-basierte asynchrone Musterverarbeitung möglich ist. Die Methode decodeAudioData() hat immer Erfolg- und Fehler-Callback-Funktionen als Parameter akzeptiert:

context.decodeAudioData( arraybufferData, onSuccess, onError);

Jetzt kannst du stattdessen die Standard-Promise-Methode verwenden, um die asynchrone Dekodierung von Audiodaten zu verarbeiten:

context.decodeAudioData( arraybufferData ).then(
        (buffer) => { /* store the buffer */ },
        (reason) => { console.log("decode failed! " + reason) });

In einem einzelnen Beispiel sieht das zwar etwas umständlicher aus, aber Promises machen die asynchrone Programmierung einfacher und konsistenter. Aus Gründen der Abwärtskompatibilität werden die Rückruffunktionen „Success“ und „Error“ gemäß der Spezifikation weiterhin unterstützt.

OfflineAudioContext unterstützt jetzt suspend() und resume()

Auf den ersten Blick mag es seltsam erscheinen, suspend() für einen OfflineAudioContext zu verwenden. Schließlich wurde suspend() zu AudioContext hinzugefügt, um die Audiohardware in den Standby-Modus versetzen zu können. Das ist in Szenarien, in denen in einen Puffer gerendert wird, sinnlos. Dafür ist natürlich OfflineAudioContext da. Der Sinn dieser Funktion besteht jedoch darin, jeweils nur einen Teil eines „Werts“ zu berechnen, um die Speichernutzung zu minimieren. Sie können weitere Knoten erstellen, während ein Rendervorgang pausiert ist.

Beethovens Mondscheinsonate enthält beispielsweise etwa 6.500 Noten. Jede „Note“ wird wahrscheinlich in mindestens ein paar Audiograph-Knoten zerlegt (z.B. einen AudioBuffer und einen Gain-Knoten). Wenn du die gesamten siebeneinhalb Minuten mit OfflineAudioContext in einen Puffer rendern möchtest, solltest du wahrscheinlich nicht alle diese Knoten gleichzeitig erstellen. Stattdessen können Sie sie in Zeitblöcken erstellen:

var context = new OfflineAudioContext(2, length, sampleRate);
scheduleNextBlock();
context.startRendering().then( (buffer) => { /* store the buffer */ } );

function scheduleNextBlock() {
    // create any notes for the next blockSize number of seconds here
    // ...

    // make sure to tell the context to suspend again after this block;
    context.suspend(context.currentTime + blockSize).then( scheduleNextBlock );

    context.resume();
}

So können Sie die Anzahl der Knoten minimieren, die zu Beginn des Renderings vorab erstellt werden müssen, und den Speicherbedarf verringern.

IIRFilterNode

Die Spezifikation enthält einen Knoten für Audiophile, die ihre eigene genau spezifizierte unendliche Impulsantwort erstellen möchten: den IIRFilterNode. Dieser Filter ergänzt den BiquadFilterNode, ermöglicht aber eine vollständige Angabe der Filterantwortparameter (anstelle der BiquadFilterNode-AudioParams für Typ, Frequenz, Q usw.). Mit der IIRFilterNode können Filter genau spezifiziert werden, die zuvor nicht erstellt werden konnten, z. B. Filter mit einer Ordnung. Die Verwendung des IIRFilterNodes erfordert jedoch ein fundiertes Wissen über die Funktionsweise von IIR-Filtern. Außerdem können sie nicht wie BiquadFilterNodes geplant werden.

Vorherige Änderungen

Ich möchte auch einige Verbesserungen erwähnen, die bereits implementiert wurden: In Chrome 48 wurde die BiquadFilter-Knotenautomatisierung mit der Audiorate ausgeführt. Die API hat sich dadurch nicht geändert, aber deine Filtersweeps klingen jetzt noch glatter. Außerdem haben wir in Chrome 48 die AudioNode.connect()-Methode um eine Verknüpfung erweitert, indem wir den Knoten zurückgeben, mit dem eine Verbindung hergestellt wird. So lassen sich leichter Knotenketten erstellen, wie in diesem Beispiel:

sourceNode.connect(gainNode).connect(filterNode).connect(context.destination);

Das war's fürs Erste.