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, z. B. das Erstellen von Audioknoten und das Decodieren von Audiodateidaten, sind Methoden von AudioContext
.
Ladetöne
Die Web Audio API verwendet für kurze bis mittlere Sounds einen AudioBuffer. 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 über 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 in request.response
gespeicherte ArrayBuffer
der Audiodateidaten asynchron decodiert, ohne den Haupt-JavaScript-Ausführungsthread zu blockieren.
Wenn decodeAudioData()
abgeschlossen 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)
ist es einfach, eine präzise Audiowiedergabe für Spiele und andere zeitkritische Anwendungen zu 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);
}
Der richtige Umgang mit der Zeit: Klänge mit Rhythmus spielen
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 auf jeder achten Note ein Hihat gespielt, bei dem Kick und Snare alle Viertel im Viertel 4/4 abwechselnd gespielt werden.
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 einmal wiederholt, anstatt der unbegrenzten Schleife, die in der Notenschrift zu sehen ist. 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 einfachsten Aktionen, die Sie mit einem Ton ausführen können, ist die Änderung der Lautstärke. Mit der Web Audio API können wir die Quelle über einen AudioGainNode an ihr Ziel weiterleiten, um die Lautstärke zu verä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 das Diagramm eingerichtet wurde, können Sie die Lautstärke programmatisch ändern. Bearbeiten Sie dazu gainNode.gain.value
so:
// Reduce the volume.
gainNode.gain.value = 0.5;
Cross-Fading zwischen zwei Tönen
Angenommen, wir haben ein etwas komplexeres Szenario, bei dem mehrere Töne abgespielt werden, aber ein Cross-Fade zwischen ihnen verwendet werden soll. Dies ist häufig bei einer DJ-ähnlichen Anwendung der Fall, in der wir zwei Plattenspieler haben und in der Lage sein möchten, von einer Tonquelle zur anderen zu schwenken.
Dazu können Sie das folgende Audiodiagramm verwenden:
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 schwebst.
Um dieses Problem zu lösen, verwenden wir eine Gleichheitskurve, bei der die entsprechenden Verstärkungskurven nicht linear sind und sich mit 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 setTimeout
für diese Planung verwenden, dies ist jedoch nicht präzise. Mit der Web Audio API können Sie die Schnittstelle AudioParam verwenden, um zukünftige Werte für Parameter wie den Verstärkungswert eines AudioGainNode
zu 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 praktische 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 Klänge von einem Audioknoten in einen anderen übertragen. Dadurch entsteht eine potenziell komplexe Prozessorkette, mit der Ihren Klangformen komplexe Effekte hinzugefügt werden.
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
- Low-Shelf-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 Verbindungspunkt wird durch den Frequenzwert bestimmt. Der Q-Faktor ist ohne Einheit und bestimmt die Form der Grafik. Die Verstärkung wirkt sich nur auf bestimmte Filter aus, z. B. auf die Tiefpass- und Peaking-Filter, aber nicht auf diesen Tiefpassfilter.
Richten wir einen einfachen Tiefpassfilter ein, um aus einem Soundsample nur die Basen 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 unter der Funktion FilterSample.changeFrequency
im obigen Quellcode-Link.
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.