Web-Audio-API: Warum komponieren, wenn Sie programmieren können?
Veröffentlicht: 2022-03-11Der allererste Entwurf für eine Web-Audio-API erschien 2011 im W3C. Obwohl Audio in Webseiten schon seit langem unterstützt wird, war bis vor kurzem kein geeigneter Weg zum Generieren von Audio aus dem Webbrowser verfügbar. Ich persönlich schreibe dies Google Chrome zu, denn im Interesse von Google wurde der Browser zum wichtigsten Teil eines Computers. Sie erinnern sich vielleicht, dass sich der Bereich des Webbrowsers nicht viel verändert hat, bis Google Chrome auftauchte. Wenn Sie in dieser Zeit Ton auf einer Webseite verwendet hätten, wäre dies eine schlechte Designentscheidung gewesen. Aber seit die Idee von Web-Experimenten aufkam, machte Web-Audio wieder Sinn. Heutzutage sind Webbrowser ein weiteres Werkzeug für den künstlerischen Ausdruck, und Video und Audio im Webbrowser spielen dabei eine entscheidende Rolle.
Die Web-Audio-API kann für einige Zwecke ziemlich schwierig zu verwenden sein, da sie sich noch in der Entwicklung befindet, aber es gibt bereits eine Reihe von JavaScript-Bibliotheken, um die Dinge zu vereinfachen. In diesem Fall werde ich Ihnen zeigen, wie Sie mit der Web Audio API beginnen, indem Sie eine Bibliothek namens Tone.js verwenden. Damit können Sie die meisten Ihrer Browser-Sound-Anforderungen abdecken, indem Sie nur die Grundlagen lernen.
Hallo Web-Audio-API
Einstieg
Wir beginnen ohne Benutzung der Bibliothek. Unser erstes Experiment besteht darin, drei Sinuswellen zu erzeugen. Da dies ein einfaches Beispiel sein wird, erstellen wir nur eine Datei namens hello.html, eine einfache HTML-Datei mit einer kleinen Menge Markup.
<!DOCTYPE html> <html> <head> <meta charset="utf‐8"> <title> Hello web audio </title> </head> <body> </body> <script> </script> </html>
Der Kern der Web Audio API ist der Audiokontext. Der Audiokontext ist ein Objekt, das alles enthält, was mit Web-Audio zu tun hat. Es wird nicht als gute Praxis angesehen, mehr als einen Audiokontext in einem einzelnen Projekt zu haben. Wir beginnen mit der Instanziierung eines Audiokontexts gemäß den Empfehlungen in der Web-Audio-API-Dokumentation von Mozilla.
var audioCtx = new (window.AudioContext || window.webkitAudioContext);
Oszillator bauen
Mit einem instanziierten Audiokontext haben Sie bereits eine Audiokomponente: audioCtx.destination. Das ist wie Ihr Lautsprecher. Um einen Ton zu erzeugen, müssen Sie ihn mit audioCtx.destination verbinden. Um nun einen Sound zu erzeugen, erstellen wir einen Oszillator:
var sine = audioCtx.createOscillator();
Großartig, aber nicht genug. Es muss auch gestartet und mit unserem audioCtx.destination verbunden werden:
sine.start(); sine.connect(audioCtx.destination);
Mit diesen vier Zeilen haben Sie eine ziemlich nervige Webseite, die einen Sinuston abspielt, aber jetzt verstehen Sie, wie Module miteinander verbunden werden können. Im folgenden Skript gibt es drei sinusförmige Töne, die mit dem Ausgang verbunden sind, jeder mit einem anderen Ton. Der Code ist sehr selbsterklärend:
//create the context for the web audio var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); //create, tune, start and connect each oscillator sinea, sineb and sinec var sinea = audioCtx.createOscillator(); sinea.frequency.value = 440; sinea.type = "sine"; sinea.start(); sinea.connect(audioCtx.destination); var sineb = audioCtx.createOscillator(); sineb.frequency.value = 523.25; sineb.type = "sine"; sineb.start(); sineb.connect(audioCtx.destination); var sinec = audioCtx.createOscillator(); sinec.frequency.value = 698.46; sinec.type = "sine"; sinec.start(); sinec.connect(audioCtx.destination);
Oszillatoren sind nicht auf Sinuswellen beschränkt, sondern können auch dreieckig, sägezahnförmig, quadratisch und kundenspezifisch geformt sein, wie im MDN angegeben.
Patch-Logik von Web-Audio
Als Nächstes werden wir unserem Orchester von Web-Audio-Komponenten ein Gain-Modul hinzufügen. Dieses Modul ermöglicht es uns, die Amplitude unserer Klänge zu ändern. Es ist vergleichbar mit einem Lautstärkeregler. Wir haben bereits die Connect-Funktion verwendet, um einen Oszillator an den Audioausgang anzuschließen. Wir können dieselbe Verbindungsfunktion verwenden, um eine beliebige Audiokomponente anzuschließen. Wenn Sie Firefox verwenden und einen Blick auf die Web-Audio-Konsole werfen, sehen Sie Folgendes:
Wenn wir die Lautstärke ändern wollen, sollte unser Patch so aussehen:
Das bedeutet, dass die Oszillatoren nicht mehr mit dem Audioziel verbunden sind, sondern mit einem Gain-Modul, und dieses Gain-Modul ist mit dem Ziel verbunden. Es ist gut, sich immer vorzustellen, dass Sie dies mit Gitarrenpedalen und Kabeln tun. Der Code wird wie folgt aussehen:
var audioCtx = new (window.AudioContext || window.webkitAudioContext) // we create the gain module, named as volume, and connect it to our var volume = audioCtx.createGain(); volume.connect(audioCtx.destination); //these sines are the same, exept for the last connect statement. //Now they are connected to the volume gain module and not to the au var sinea = audioCtx.createOscillator(); sinea.frequency.value = 440; sinea.type = "sine"; sinea.start(); sinea.connect(volume); var sineb = audioCtx.createOscillator(); sineb.frequency.value = 523.25; sineb.type = "sine"; sineb.start(); sineb.connect(volume); var sinec = audioCtx.createOscillator(); sinec.frequency.value = 698.46; sinec.type = "sine"; sinec.start(); sinec.connect(volume); volume.gain.value=0.2;
Die Lösung finden Sie unter https://github.com/autotel/simple-webaudioapi/.
GainNode ist die grundlegendste Effekteinheit, aber es gibt auch ein Delay, einen Convolver, einen biquadratischen Filter, einen Stereo-Panner, einen Waveshaper und viele andere. Sie können neue Effekte aus Bibliotheken wie Tone.js abrufen.
Wenn Sie einen dieser Soundpatches in eigenen Objekten speichern, können Sie sie nach Bedarf wiederverwenden und komplexere Orchestrierungen mit weniger Code erstellen. Dies könnte ein Thema für einen zukünftigen Beitrag sein.
Mit Tone.js die Dinge einfacher machen
Nachdem wir uns nun kurz angesehen haben, wie die einfachen Web-Audio-Module funktionieren, werfen wir einen Blick auf das großartige Web-Audio-Framework: Tone.js. Damit (und NexusUI für Benutzeroberflächenkomponenten) können wir sehr einfach interessantere Synthesizer und Sounds erstellen. Lassen Sie uns zum Ausprobieren einen Sampler erstellen und einige benutzerinteraktive Effekte darauf anwenden, und dann werden wir einige einfache Steuerelemente für dieses Beispiel hinzufügen.
Tone.js-Sampler
Wir können mit der Erstellung einer einfachen Projektstruktur beginnen:
simpleSampler |-- js |-- nexusUI.js |-- Tone.js |-- noisecollector_hit4.wav |-- sampler.html
Unsere JavaScript-Bibliotheken befinden sich im js- Verzeichnis. Für diese Demo können wir die hit4.wav-Datei von NoiseCollector verwenden, die von Freesound.org heruntergeladen werden kann.

Tone.js stellt seine Funktionalitäten über Player-Objekte bereit. Die grundlegende Fähigkeit des Objekts besteht darin, ein Sample zu laden und es entweder in einer Schleife oder einmal abzuspielen. Unser erster Schritt hier ist das Erstellen eines Player-Objekts in einer „sampler“-Variablen innerhalb der Datei sampler.html:
<!doctype html> <html> <head> <title> Sampler </title> <script type="text/javascript" src="js/nexusUI.js" ></script> <script type="text/javascript" src="js/Tone.js" ></script> <script> var sampler = new Tone.Player("noisecollector_hit4.wav", function() { console.log("samples loaded"); }); </script> </head> <body> </body> </html>
Beachten Sie, dass der erste Parameter des Player-Konstruktors der Name der WAV-Datei und der zweite eine Callback-Funktion ist. WAV ist nicht der einzige unterstützte Dateityp, und die Kompatibilität hängt mehr vom Webbrowser als von der Bibliothek ab. Die Rückruffunktion wird ausgeführt, wenn der Player das Laden des Samples in seinen Puffer beendet hat.
Wir müssen auch unseren Sampler an den Ausgang anschließen. Der Weg von Tone.js dazu ist:
sampler.toMaster();
… wobei sampler ein Tone.Player-Objekt ist, nach Zeile 10. Die toMaster-Funktion ist eine Abkürzung für connect(Tone.Master).
Wenn Sie Ihren Webbrowser bei geöffneter Entwicklerkonsole öffnen, sollten Sie die Meldung „Beispiele geladen“ sehen, die darauf hinweist, dass der Player korrekt erstellt wurde. An dieser Stelle möchten Sie vielleicht das Beispiel hören. Dazu müssen wir der Webseite eine Schaltfläche hinzufügen und sie so programmieren, dass sie das Sample abspielt, sobald sie gedrückt wird. Wir werden eine NexusUI-Schaltfläche im Körper verwenden:
<canvas nx="button"></canvas>
Sie sollten jetzt eine abgerundete Schaltfläche sehen, die im Dokument gerendert wird. Um es so zu programmieren, dass es unser Beispiel abspielt, fügen wir einen NexusUI-Listener hinzu, der so aussieht:
button1.on('*',function(data) { console.log("button pressed!"); })
Etwas Herausragendes an NexusUI ist, dass es eine globale Variable für jedes NexusUI-Element erstellt. Sie können NexusUI so einstellen, dass dies nicht geschieht, und diese Variablen stattdessen nur in nx.widgets[] haben, indem Sie nx.globalWidgets auf „false“ setzen. Hier werden wir nur ein paar Elemente erstellen, also bleiben wir einfach bei diesem Verhalten.
Genau wie in jQuery können wir diese .on-Ereignisse setzen, und das erste Argument ist der Ereignisname. Hier weisen wir nur dem, was mit der Taste gemacht wird, eine Funktion zu. Dies wird als „*“ geschrieben. Sie können mehr über Ereignisse für jedes Element in der NexusUI-API erfahren. Um das Sample abzuspielen, anstatt Meldungen zu protokollieren, wenn wir die Taste drücken, sollten wir die Startfunktion unseres Samplers ausführen.
nx.onload = function() { button1.on('*',function(data) { console.log("button pressed!"); sampler.start(); }); }
Beachten Sie auch, dass der Listener in einen Onload-Callback geht. NexusUI-Elemente werden im Zeichenbereich gezeichnet, und Sie können nicht darauf verweisen, bis nx die Funktion onload aufruft. Genauso wie Sie es mit DOM-Elementen in jQuery tun würden.
Das Ereignis wird bei gedrückter Maustaste und beim Loslassen ausgelöst. Wenn Sie möchten, dass es nur beim Drücken ausgelöst wird, müssen Sie auswerten, ob event.press gleich eins ist.
Damit sollten Sie eine Taste haben, die das Sample bei jedem Drücken abspielt. Wenn Sie sampler.retrigger auf true setzen, können Sie das Sample unabhängig davon abspielen, ob es abgespielt wird oder nicht. Andernfalls müssen Sie warten, bis das Sample beendet ist, um es erneut auszulösen.
Anwenden von Effekten
Mit Tone.js können wir ganz einfach eine Verzögerung erzeugen:
var delay= new Tone.FeedbackDelay("16n",0.5).toMaster();
Das erste Argument ist die Verzögerungszeit, die wie hier gezeigt in Notenschrift geschrieben werden kann. Der zweite ist der Nasspegel, also die Mischung zwischen dem Originalklang und dem darauf einwirkenden Klang. Bei Delays will man in der Regel kein 100%iges Wet, weil Delays in Bezug auf den Originalsound interessant sind, und das Wet allein als beides zusammen nicht sehr ansprechend ist.
Der nächste Schritt besteht darin, unseren Sampler vom Master zu trennen und ihn stattdessen an das Delay anzuschließen. Optimieren Sie die Leitung, in der der Sampler mit dem Master verbunden ist:
sampler.connect(delay);
Probieren Sie die Taste jetzt erneut aus und sehen Sie den Unterschied.
Als Nächstes fügen wir dem Hauptteil unseres Dokuments zwei Zifferblätter hinzu:
<canvas nx="dial"></canvas> <canvas nx="dial"></canvas>
Und wir wenden die Werte der Drehregler mit dem NexusUIlistener auf den Verzögerungseffekt an:
dial1.on('*',function(data) { delay.delayTime.value=data.value; }) dial2.on('*',function(data) { delay.feedback.value=data.value; })
Die Parameter, die Sie für jedes Ereignis optimieren können, finden Sie in der Tone.js-Dokumentation. Für Verspätung ist es hier. Jetzt können Sie das Beispiel ausprobieren und die Verzögerungsparameter mit den Drehreglern der NexusUI anpassen. Dieser Vorgang kann problemlos mit jedem NexusUI-Element durchgeführt werden und ist nicht nur auf Effekte beschränkt. Versuchen Sie beispielsweise auch, ein weiteres Zifferblatt hinzuzufügen und seinen Listener wie folgt hinzuzufügen:
dial3.on('*',function(data) { sampler.playbackRate=data.value; })
Sie finden diese Dateien unter github.com/autotel/simpleSampler
Fazit
Als ich diese APIs durchging, fühlte ich mich überwältigt von all den Möglichkeiten und Ideen, die mir in den Sinn kamen. Der große Unterschied zwischen dieser Implementierung von Audio und den traditionellen Implementierungen von digitalem Audio liegt nicht im Audio selbst, sondern im Kontext. Hier gibt es keine neuen Syntheseverfahren. Die Innovation besteht vielmehr darin, dass Audio- und Musikproduktion jetzt auf Webtechnologien treffen.
Ich persönlich beschäftige mich mit elektronischer Musik, und in diesem Bereich gab es schon immer dieses Paradoxon der Mehrdeutigkeit zwischen dem tatsächlichen Aufführen von Musik und dem Drücken von Play zu einem aufgenommenen Track. Wenn Sie wirklich elektronische Live-Musik machen wollen, müssen Sie in der Lage sein, Ihre eigenen performativen Werkzeuge oder „Musik machenden Roboter“ für die Live-Improvisation zu erstellen. Aber wenn die Darbietung elektronischer Musik zu einem einfachen Anpassen von Parametern in vorgefertigten Musikalgorithmen wird, dann kann auch das Publikum in diesen Prozess einbezogen werden. Ich habe an kleinen Experimenten bezüglich dieser Integration von Web und Audio für Crowdsourcing-Musik gearbeitet, und vielleicht werden wir bald Partys besuchen, bei denen die Musik vom Publikum über ihre Smartphones kommt. Schließlich unterscheidet es sich nicht sehr von rhythmischen Jams, die wir vielleicht in den Höhlenzeitaltern genossen haben.
Weiterführende Literatur im Toptal Engineering Blog:
- WebAssembly/Rust-Tutorial: Perfekte Audioverarbeitung
- MIDI-Tutorial: Erstellen von browserbasierten Audioanwendungen, die von MIDI-Hardware gesteuert werden