Vor dem HTML5-Element <audio>
war Flash oder ein anderes Plug-in erforderlich, um die Stille im Web zu durchbrechen. Für Audio im Web ist zwar kein Plug-in mehr erforderlich, das Audio-Tag bringt jedoch erhebliche Einschränkungen bei der Implementierung komplexer Spiele und interaktiver Anwendungen mit sich.
Die Web Audio API ist eine hochrangige JavaScript API zur Verarbeitung und Synthese von Audio in Webanwendungen. Ziel dieser API ist es, Funktionen moderner Audio-Engines für Spiele und einige der Funktionen zum Mischen, Bearbeiten und Filtern von Audioinhalten in modernen Desktop-Anwendungen zur Audioproduktion zu integrieren. Im Folgenden finden Sie eine kurze Einführung in die Verwendung dieser leistungsstarken API.
Erste Schritte mit AudioContext
Mit einem AudioContext können alle Töne verwaltet und abgespielt werden. Wenn Sie mit der Web Audio API einen Ton erzeugen möchten, erstellen Sie eine oder mehrere Tonquellen und verbinden Sie sie mit dem Tonziel, das von der AudioContext
-Instanz bereitgestellt wird. Diese Verbindung muss nicht direkt sein und kann beliebig viele Zwischen-AudioNodes durchlaufen, die als Verarbeitungsmodule für das Audiosignal dienen. Dieses Routing wird in der Web Audio-Spezifikation ausführlicher beschrieben.
Eine einzelne Instanz von AudioContext
kann mehrere Audioeingaben und komplexe Audio-Grafiken unterstützen. Wir benötigen also nur eine davon für jede Audioanwendung, die wir erstellen.
Im folgenden Snippet wird ein AudioContext
erstellt:
var context;
window.addEventListener('load', init, false);
function init() {
try {
context = new AudioContext();
}
catch(e) {
alert('Web Audio API is not supported in this browser');
}
}
Verwenden Sie für ältere WebKit-basierte Browser das Präfix webkit
, wie bei webkitAudioContext
.
Viele der interessanten Funktionen der Web Audio API wie das Erstellen von Audioknoten und das Decodieren von Audiodateidaten sind Methoden von AudioContext
.
Ladetöne
Die Web Audio API verwendet einen AudioBuffer für kurze bis mittellange Töne. Der grundlegende Ansatz besteht darin, XMLHttpRequest zum Abrufen von Audiodateien zu verwenden.
Die API unterstützt das Laden von Audiodateidaten in mehreren Formaten wie WAV, MP3, AAC, OGG und anderen. Die Browserunterstützung für verschiedene Audioformate variiert.
Im folgenden Snippet wird das Laden eines Audiosamples veranschaulicht:
var dogBarkingBuffer = null;
var context = new AudioContext();
function loadDogSound(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
dogBarkingBuffer = buffer;
}, onError);
}
request.send();
}
Die Audiodateidaten sind binär (kein Text). Daher setzen wir die responseType
der Anfrage auf 'arraybuffer'
. Weitere Informationen zu ArrayBuffers
finden Sie in diesem Artikel zu XHR2.
Nachdem die (nicht decodierten) Audiodateidaten empfangen wurden, können sie zur späteren Decodierung gespeichert oder sofort mit der AudioContext-Methode decodeAudioData()
decodiert werden. Bei dieser Methode wird die ArrayBuffer
der in request.response
gespeicherten Audiodateidaten asynchron decodiert, ohne den Haupt-JavaScript-Ausführungsthread zu blockieren.
Wenn decodeAudioData()
fertig ist, wird eine Callback-Funktion aufgerufen, die die decodierten PCM-Audiodaten als AudioBuffer
bereitstellt.
Wiedergabe von Tönen
Sobald mindestens eine AudioBuffers
geladen ist, können wir Töne abspielen. Angenommen, wir haben gerade eine AudioBuffer
mit dem Gebell eines Hundes geladen und das Laden ist abgeschlossen. Diesen Puffer können wir dann mit dem folgenden Code abspielen.
var context = new AudioContext();
function playSound(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.noteOn(0); // play the source now
}
Diese playSound()
-Funktion kann jedes Mal aufgerufen werden, wenn jemand eine Taste drückt oder mit der Maus etwas anklickt.
Mit der Funktion noteOn(time)
können Sie die Audiowiedergabe für Spiele und andere zeitkritische Anwendungen ganz einfach planen. Damit diese Planung jedoch ordnungsgemäß funktioniert, müssen Ihre Audio-Puffer vorab geladen werden.
Web Audio API abstrahieren
Natürlich wäre es besser, ein allgemeineres Ladesystem zu erstellen, das nicht hartcodiert ist, um diesen bestimmten Ton zu laden. Es gibt viele Ansätze, um mit den vielen kurzen bis mittellangen Tönen umzugehen, die in einer Audioanwendung oder einem Spiel verwendet werden. Hier ist eine Möglichkeit mit einem BufferLoader (kein Teil des Webstandards).
Im Folgenden finden Sie ein Beispiel für die Verwendung der Klasse BufferLoader
.
Erstellen wir zwei AudioBuffers
und spielen sie nach dem Laden gleichzeitig ab.
window.onload = init;
var context;
var bufferLoader;
function init() {
context = new AudioContext();
bufferLoader = new BufferLoader(
context,
[
'../sounds/hyper-reality/br-jam-loop.wav',
'../sounds/hyper-reality/laughter.wav',
],
finishedLoading
);
bufferLoader.load();
}
function finishedLoading(bufferList) {
// Create two sources and play them both together.
var source1 = context.createBufferSource();
var source2 = context.createBufferSource();
source1.buffer = bufferList[0];
source2.buffer = bufferList[1];
source1.connect(context.destination);
source2.connect(context.destination);
source1.noteOn(0);
source2.noteOn(0);
}
Umgang mit der Zeit: Töne mit Rhythmus abspielen
Mit der Web Audio API können Entwickler die Wiedergabe genau planen. Zur Veranschaulichung erstellen wir einen einfachen Rhythmus-Track. Das wohl bekannteste Drumkit-Muster ist das folgende:
Dabei wird in einem 4/4-Takt jede Achtelnote mit einem Hi-Hat und Kick und Snare abwechselnd jede Viertelnote gespielt.
Angenommen, wir haben die Puffer kick
, snare
und hihat
geladen, ist der Code dazu ganz einfach:
for (var bar = 0; bar < 2; bar++) {
var time = startTime + bar * 8 * eighthNoteTime;
// Play the bass (kick) drum on beats 1, 5
playSound(kick, time);
playSound(kick, time + 4 * eighthNoteTime);
// Play the snare drum on beats 3, 7
playSound(snare, time + 2 * eighthNoteTime);
playSound(snare, time + 6 * eighthNoteTime);
// Play the hi-hat every eighth note.
for (var i = 0; i < 8; ++i) {
playSound(hihat, time + i * eighthNoteTime);
}
}
Hier wird nur eine Wiederholung verwendet, anstatt der unbegrenzten Schleife in der Notenschrift. Die Funktion playSound
ist eine Methode, mit der ein Puffer zu einer bestimmten Zeit wiedergegeben wird:
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
Lautstärke eines Tons ändern
Eine der grundlegendsten Aktionen, die Sie mit einem Ton ausführen können, ist die Änderung der Lautstärke. Mit der Web Audio API können wir unsere Quelle über einen AudioGainNode an ihr Ziel weiterleiten, um die Lautstärke zu ändern:
So richten Sie die Verbindung ein:
// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);
Nachdem die Grafik eingerichtet wurde, können Sie die Lautstärke programmatisch ändern, indem Sie gainNode.gain.value
so bearbeiten:
// Reduce the volume.
gainNode.gain.value = 0.5;
Cross-Fading zwischen zwei Tönen
Angenommen, wir haben ein etwas komplexeres Szenario, bei dem wir mehrere Töne abspielen, aber einen Cross-Fade zwischen ihnen verwenden möchten. Dies ist ein häufiges Szenario in einer DJ-ähnlichen Anwendung, in der wir zwei Plattenspieler haben und von einer Audioquelle zur anderen schwenken möchten.
Das kann mit dem folgenden Audio-Graphen erfolgen:
Dazu erstellen wir einfach zwei AudioGainNodes und verbinden jede Quelle über die Knoten mithilfe einer Funktion wie dieser:
function createSource(buffer) {
var source = context.createBufferSource();
// Create a gain node.
var gainNode = context.createGainNode();
source.buffer = buffer;
// Turn on looping.
source.loop = true;
// Connect source to gain.
source.connect(gainNode);
// Connect gain to destination.
gainNode.connect(context.destination);
return {
source: source,
gainNode: gainNode
};
}
Crossfading mit gleicher Leistung
Bei einem naiven linearen Crossfade kommt es zu einem Lautstärkeabfall, wenn du zwischen den Samples wechselst.
Um dieses Problem zu beheben, verwenden wir eine Kurve mit gleicher Leistung, bei der die entsprechenden Verstärkungskurven nicht linear sind und sich bei einer höheren Amplitude schneiden. Dadurch werden Lautstärkeeinbrüche zwischen Audioregionen minimiert, was zu einem gleichmäßigeren Crossfade zwischen Regionen führt, die sich in der Lautstärke leicht unterscheiden können.
Crossfading in Playlists
Eine weitere gängige Anwendung für einen Crossfader ist in einer Musikplayer-App.
Wenn ein Song wechselt, soll der aktuelle Titel ausgeblendet und der neue eingeschwenkt werden, um einen abrupten Übergang zu vermeiden. Dazu musst du einen Crossfade in der Zukunft planen. Wir könnten zwar setTimeout
für die Planung verwenden, das ist aber nicht genau. Mit der Web Audio API können wir über die AudioParam-Schnittstelle zukünftige Werte für Parameter wie den Verstärkungswert eines AudioGainNode
planen.
So können wir bei einer Playlist zwischen den Titeln wechseln, indem wir kurz vor dem Ende des aktuellen Titels eine Lautstärkesenkung für den gerade abgespielten Titel und eine Lautstärkeerhöhung für den nächsten Titel planen:
function playHelper(bufferNow, bufferLater) {
var playNow = createSource(bufferNow);
var source = playNow.source;
var gainNode = playNow.gainNode;
var duration = bufferNow.duration;
var currTime = context.currentTime;
// Fade the playNow track in.
gainNode.gain.linearRampToValueAtTime(0, currTime);
gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
// Play the playNow track.
source.noteOn(0);
// At the end of the track, fade it out.
gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
// Schedule a recursive track change with the tracks swapped.
var recurse = arguments.callee;
ctx.timer = setTimeout(function() {
recurse(bufferLater, bufferNow);
}, (duration - ctx.FADE_TIME) - 1000);
}
Die Web Audio API bietet eine Reihe praktischer RampToValue
-Methoden, mit denen sich der Wert eines Parameters wie linearRampToValueAtTime
und exponentialRampToValueAtTime
schrittweise ändern lässt.
Sie können die Funktion für die Übergangszeit aus den integrierten linearen und exponentiellen Funktionen auswählen (wie oben). Sie können aber auch mithilfe der Funktion setValueCurveAtTime
eine eigene Wertkurve über ein Wertearray angeben.
Einen einfachen Filtereffekt auf einen Ton anwenden
Mit der Web Audio API können Sie Audio von einem Audioknoten an einen anderen weiterleiten und so eine potenziell komplexe Kette von Prozessoren erstellen, um Ihren Klangformen komplexe Effekte hinzuzufügen.
Eine Möglichkeit dazu besteht darin, BiquadFilterNodes zwischen die Audioquelle und das Ziel zu platzieren. Dieser Audioknoten kann eine Vielzahl von Filtern niedriger Ordnung ausführen, die zum Erstellen von grafischen Equalizern und sogar komplexeren Effekten verwendet werden können. Dabei geht es hauptsächlich darum, auszuwählen, welche Teile des Frequenzspektrums eines Tons hervorgehoben und welche unterdrückt werden sollen.
Folgende Filtertypen werden unterstützt:
- Tiefpassfilter
- Hochpassfilter
- Band pass filter
- Tiefpass-Filter
- Hochpassfilter
- Peaking-Filter
- Kerbfilter
- Filter „Alle Karten/Tickets“
Alle Filter enthalten Parameter, mit denen die Verstärkung, die Häufigkeit der Filteranwendung und ein Qualitätsfaktor festgelegt werden können. Der Tiefpassfilter behält den unteren Frequenzbereich bei, verwirft aber hohe Frequenzen. Der Achsenabschnitt wird durch den Frequenzwert bestimmt. Der Q-Faktor ist ohne Maßeinheit und bestimmt die Form des Diagramms. Die Verstärkung wirkt sich nur auf bestimmte Filter aus, z. B. auf die Tiefpass- und Spitzenfilter, nicht aber auf diesen Tiefpassfilter.
Erstellen wir einen einfachen Tiefpassfilter, um nur die Bässe aus einem Audiobeispiel zu extrahieren:
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);
Im Allgemeinen müssen die Frequenzregler so angepasst werden, dass sie auf einer logarithmischen Skala funktionieren, da das menschliche Gehör nach demselben Prinzip funktioniert (A4 ist 440 Hz und A5 ist 880 Hz). Weitere Informationen finden Sie im Abschnitt zur Funktion FilterSample.changeFrequency
im oben verlinkten Quellcode.
Im Beispielcode können Sie den Filter verbinden und trennen, um die AudioContext-Grafik dynamisch zu ändern. Wir können Audioknoten durch Aufrufen von node.disconnect(outputNumber)
von der Grafik trennen.
Wenn wir beispielsweise die Route des Graphen von einem Filter zu einer direkten Verbindung umleiten möchten, können wir Folgendes tun:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Weitere Informationen
Wir haben die Grundlagen der API behandelt, einschließlich des Ladens und Abspielens von Audiosamples. Wir haben Audio-Grafiken mit Verstärkungsknoten und Filtern erstellt und Töne und Audioparameter angepasst, um einige gängige Audioeffekte zu ermöglichen. Jetzt können Sie loslegen und coole Web-Audioanwendungen erstellen.
Wenn Sie nach Inspiration suchen, haben viele Entwickler bereits beeindruckende Projekte mit der Web Audio API erstellt. Zu meinen Favoriten gehören:
- AudioJedit, ein Tool zum Schneiden von Audio im Browser, das SoundCloud-Permalinks verwendet.
- ToneCraft, ein Sound-Sequencer, mit dem Klänge durch Stapeln von 3D-Blöcken erstellt werden.
- Plink, ein kollaboratives Musikspiel mit Web Audio und Web Sockets.