Zanim pojawił się element HTML5 <audio>, do odtwarzania dźwięku w internecie wymagana była wtyczka Flash lub inna. Chociaż odtwarzanie dźwięku w internecie nie wymaga już wtyczki, tag audio ma istotne ograniczenia w przypadku implementowania zaawansowanych gier i aplikacji interaktywnych.
Web Audio API to interfejs JavaScript API wysokiego poziomu służący do przetwarzania i syntezy dźwięku w aplikacjach internetowych. Celem tego interfejsu API jest uwzględnienie funkcji dostępnych w nowoczesnych silnikach dźwięku w grach oraz niektórych zadań związanych z miksowaniem, przetwarzaniem i filtrowaniem, które można znaleźć w nowoczesnych aplikacjach do produkcji dźwięku na komputery. Poniżej znajdziesz krótkie wprowadzenie do korzystania z tego zaawansowanego interfejsu API.
Pierwsze kroki z AudioContext
AudioContext służy do zarządzania wszystkimi dźwiękami i ich odtwarzania. Aby odtworzyć dźwięk za pomocą Web Audio API, utwórz co najmniej 1 źródło dźwięku i połącz je z miejscem docelowym dźwięku udostępnianym przez instancję AudioContext. To połączenie nie musi być bezpośrednie i może przechodzić przez
dowolną liczbę pośrednich AudioNodes, które działają jako moduły przetwarzania
sygnału audio. To przekierowanie jest szczegółowo
opisane w specyfikacji Web Audio specification.
Jedna instancja AudioContext może obsługiwać wiele wejść dźwięku i złożonych wykresów audio, więc w przypadku każdej tworzonej aplikacji audio będziemy potrzebować tylko jednej z nich.
Ten fragment kodu tworzy AudioContext:
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');
}
}
W przypadku starszych przeglądarek opartych na WebKit użyj prefiksu webkit, np.
webkitAudioContext.
Wiele interesujących funkcji Web Audio API, takich jak tworzenie AudioNodes i dekodowanie danych plików audio, to metody AudioContext.
Wczytywanie dźwięków
Web Audio API używa AudioBuffer do dźwięków o krótkim i średnim czasie trwania. Podstawowym sposobem jest użycie XMLHttpRequest do pobierania plików dźwiękowych.
Interfejs API obsługuje wczytywanie danych plików audio w wielu formatach, takich jak WAV, MP3, AAC, OGG i inne. Obsługa różnych formatów audio w przeglądarkach jest różna.
Ten fragment kodu pokazuje, jak wczytać próbkę dźwięku:
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();
}
Dane pliku audio są binarne (nie tekstowe), dlatego ustawiamy responseType
żądania na 'arraybuffer'. Więcej informacji o
ArrayBuffers znajdziesz w tym artykule o XHR2.
Po otrzymaniu (niezdekodowanych) danych pliku audio można je zachować do późniejszego dekodowania lub zdekodować od razu za pomocą metody AudioContext decodeAudioData(). Ta metoda pobiera ArrayBuffer danych pliku audio przechowywanych w request.response i dekoduje je asynchronicznie (nie blokując głównego wątku wykonywania JavaScriptu).
Po zakończeniu działania decodeAudioData() wywołuje wywołanie zwrotne, które udostępnia zdekodowane dane audio PCM jako AudioBuffer.
Odtwarzanie dźwięków
Po wczytaniu co najmniej 1 AudioBuffers możemy odtwarzać dźwięki. Załóżmy, że właśnie wczytaliśmy AudioBuffer z dźwiękiem szczekania psa i że wczytywanie się zakończyło. Wtedy możemy odtworzyć ten bufor za pomocą tego kodu.
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
}
Funkcję playSound() można wywoływać za każdym razem, gdy ktoś naciśnie klawisz lub kliknie coś myszą.
Funkcja noteOn(time) ułatwia planowanie precyzyjnego odtwarzania dźwięku w grach i innych aplikacjach, w których czas jest krytyczny. Aby jednak to planowanie działało prawidłowo, upewnij się, że bufory dźwięku są wstępnie wczytane.
Abstrakcja Web Audio API
Oczywiście lepiej byłoby utworzyć bardziej ogólny system wczytywania, który nie jest na stałe zakodowany do wczytywania tego konkretnego dźwięku. Istnieje wiele sposobów radzenia sobie z wieloma dźwiękami o krótkim i średnim czasie trwania, które mogą być używane w aplikacji audio lub grze. Oto jeden z nich, który korzysta z BufferLoader (nie jest częścią standardu internetowego).
Poniżej znajdziesz przykład użycia klasy BufferLoader.
Utwórzmy 2 AudioBuffers i odtwórzmy je jednocześnie, gdy tylko zostaną wczytane.
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);
}
Radzenie sobie z czasem: odtwarzanie dźwięków w rytmie
Web Audio API umożliwia programistom precyzyjne planowanie odtwarzania. Aby to zademonstrować, skonfigurujmy prostą ścieżkę rytmiczną. Prawdopodobnie najbardziej znanym wzorem perkusyjnym jest ten:
w którym hi-hat jest odtwarzany co ósemkę, a stopa i werbel – na przemian co ćwierćnutę w takcie 4/4.
Zakładając, że wczytaliśmy bufory kick, snare i hihat, kod do tego jest prosty:
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);
}
}
W tym przypadku powtarzamy tylko raz, a nie w nieskończonej pętli, jak w nutach. Funkcja playSound to metoda, która odtwarza bufor w określonym czasie:
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
Zmienianie głośności dźwięku
Jedną z najbardziej podstawowych operacji, które możesz wykonać na dźwięku, jest zmiana jego głośności. Za pomocą Web Audio API możemy przekierować źródło do miejsca docelowego przez AudioGainNode, aby manipulować głośnością:

Tę konfigurację połączenia można uzyskać w ten sposób:
// 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);
Po skonfigurowaniu wykresu możesz programowo zmienić głośność, manipulując gainNode.gain.value w ten sposób:
// Reduce the volume.
gainNode.gain.value = 0.5;
Przenikanie między 2 dźwiękami
Załóżmy teraz, że mamy nieco bardziej złożony scenariusz, w którym odtwarzamy wiele dźwięków, ale chcemy między nimi przenikać. Jest to częsty przypadek w aplikacji podobnej do DJ-a, w której mamy 2 gramofony i chcemy móc przechodzić z jednego źródła dźwięku na drugie.
Można to zrobić za pomocą tego wykresu audio:
Aby to skonfigurować, wystarczy utworzyć 2 AudioGainNodes i połączyć każde źródło przez te węzły za pomocą funkcji takiej jak ta:
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
};
}
Przenikanie z równą mocą
Proste liniowe przenikanie powoduje spadek głośności podczas przechodzenia między próbkami.
Aby rozwiązać ten problem, używamy krzywej równej mocy, w której odpowiednie krzywe wzmocnienia są nieliniowe i przecinają się przy większej amplitudzie. Minimalizuje to spadki głośności między regionami audio, co zapewnia bardziej równomierne przenikanie między regionami, które mogą się nieznacznie różnić poziomem.
Płynne przejścia na playliście
Innym typowym zastosowaniem przenikania jest aplikacja odtwarzacza muzyki.
Gdy zmienia się utwór, chcemy wyciszyć bieżący utwór i włączyć nowy, aby uniknąć nagłego przejścia. Aby to zrobić, zaplanuj przenikanie w przyszłości. Do tego
planowania możemy użyć setTimeout, ale nie jest to precyzyjne. Za pomocą Web Audio API możemy
użyć interfejsu AudioParam do zaplanowania przyszłych wartości
parametrów, takich jak wartość wzmocnienia AudioGainNode.
Dzięki temu, mając playlistę, możemy przechodzić między utworami, planując zmniejszenie wzmocnienia w aktualnie odtwarzanym utworze i zwiększenie wzmocnienia w następnym, na krótko przed zakończeniem odtwarzania bieżącego utworu:
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);
}
Web Audio API udostępnia wygodny zestaw metod RampToValue do
stopniowej zmiany wartości parametru, takich jak
linearRampToValueAtTime i exponentialRampToValueAtTime.
Funkcję czasu przejścia można wybrać spośród wbudowanych funkcji liniowych i wykładniczych (jak powyżej), ale możesz też określić własną krzywą wartości za pomocą tablicy wartości, używając funkcji setValueCurveAtTime.
Stosowanie prostego efektu filtra do dźwięku

Web Audio API umożliwia przesyłanie dźwięku z jednego węzła audio do drugiego, tworząc potencjalnie złożony łańcuch procesorów, które dodają złożone efekty do Twoich dźwięków.
Jednym ze sposobów jest umieszczenie BiquadFilterNodes między źródłem dźwięku a miejscem docelowym. Ten typ węzła audio może wykonywać różne filtry niskiego rzędu, które można wykorzystać do tworzenia korektorów graficznych, a nawet bardziej złożonych efektów, głównie związanych z wybieraniem, które części widma częstotliwości dźwięku mają być podkreślone, a które stłumione.
Obsługiwane typy filtrów:
- Filtr dolnoprzepustowy
- Filtr górnoprzepustowy
- Filtr pasmowy
- Filtr dolnopółkowy
- Filtr górnopółkowy
- Filtr szczytowy
- Filtr wycinający
- Filtr przepuszczający wszystkie częstotliwości
Wszystkie filtry zawierają parametry określające pewną ilość wzmocnienia, częstotliwość, przy której ma być stosowany filtr, oraz współczynnik jakości. Filtr dolnoprzepustowy zachowuje niższy zakres częstotliwości, ale odrzuca wysokie częstotliwości. Punkt odcięcia jest określany przez wartość częstotliwości, a współczynnik Q jest bezwymiarowy i określa kształt wykresu. Wzmocnienie wpływa tylko na niektóre filtry, takie jak filtry dolnopółkowe i szczytowe, a nie na ten filtr dolnoprzepustowy.
Skonfigurujmy prosty filtr dolnoprzepustowy, aby wyodrębnić tylko basy z próbki dźwięku:
// 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);
Ogólnie rzecz biorąc, elementy sterujące częstotliwością należy dostosować tak, aby działały w skali logarytmicznej, ponieważ ludzki słuch działa na tej samej zasadzie (czyli A4 to 440 Hz, a A5 to 880 Hz). Więcej informacji znajdziesz w funkcji FilterSample.changeFrequency w kodzie źródłowym, do którego link znajduje się powyżej.
Na koniec pamiętaj, że przykładowy kod umożliwia łączenie i rozłączanie filtra, co powoduje dynamiczną zmianę wykresu AudioContext. Możemy odłączyć AudioNodes od wykresu, wywołując node.disconnect(outputNumber).
Aby na przykład przekierować wykres z filtra na połączenie bezpośrednie, możemy zrobić tak:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Dalsze słuchanie
Omówiliśmy podstawy interfejsu API, w tym wczytywanie i odtwarzanie próbek audio. Utworzyliśmy wykresy audio z węzłami wzmocnienia i filtrami oraz zaplanowaliśmy dźwięki i zmiany parametrów audio, aby włączyć niektóre popularne efekty dźwiękowe. W tym momencie możesz już tworzyć ciekawe aplikacje audio w internecie.
Jeśli szukasz inspiracji, wielu programistów stworzyło już świetne projekty za pomocą Web Audio API. Oto niektóre z moich ulubionych:
- AudioJedit, narzędzie do łączenia dźwięków w przeglądarce, które używa trwałych linków SoundCloud.
- ToneCraft, sekwencer dźwięku, w którym dźwięki są tworzone przez układanie bloków 3D.
- Plink, gra do wspólnego tworzenia muzyki, która korzysta z Web Audio i Web Sockets.