До появления элемента <audio> в HTML5 для воспроизведения звука на веб-страницах требовался Flash или другой плагин. Хотя для воспроизведения аудио в интернете больше не требуется плагин, тег audio накладывает значительные ограничения на реализацию сложных игр и интерактивных приложений.
Web Audio API – это высокоуровневый JavaScript API для обработки и синтеза аудио в веб-приложениях. Цель этого API – предоставить возможности, которые есть в современных аудиодвижках для игр, а также некоторые функции микширования, обработки и фильтрации, доступные в современных приложениях для создания аудио на компьютерах. Ниже приведено краткое введение в работу с этим мощным API.
Начало работы с AudioContext
AudioContext используется для управления всеми звуками и их воспроизведения. Чтобы воспроизвести звук с помощью Web Audio API, создайте один или несколько источников звука и подключите их к месту назначения, предоставленному экземпляром AudioContext. Это подключение не обязательно должно быть прямым и может проходить через любое количество промежуточных узлов AudioNodes, которые действуют как модули обработки аудиосигнала. Такая маршрутизация подробно описана в спецификации Web Audio.
Один экземпляр AudioContext может поддерживать несколько входов звука и сложные аудиографы, поэтому для каждого создаваемого аудиоприложения нам понадобится только один из них.
В следующем фрагменте кода создается объект 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');
}
}
Для старых браузеров на основе WebKit используйте префикс webkit, как в webkitAudioContext.
Многие интересные функции Web Audio API, такие как создание AudioNodes и декодирование данных аудиофайлов, являются методами AudioContext.
Загрузка звуков
Web Audio API использует AudioBuffer для звуков короткой и средней длины. Основной подход заключается в использовании XMLHttpRequest для получения звуковых файлов.
API поддерживает загрузку аудиофайлов в различных форматах, таких как WAV, MP3, AAC, OGG и других. Поддержка разных аудиоформатов в браузерах может отличаться.
В следующем фрагменте кода показано, как загрузить образец звука:
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();
}
Данные аудиофайла являются двоичными (не текстовыми), поэтому мы устанавливаем для запроса responseType значение 'arraybuffer'. Подробную информацию о ArrayBuffers вы найдете в статье об XHR2.
После получения данных аудиофайла (недекодированных) их можно сохранить для последующего декодирования или декодировать сразу с помощью метода AudioContext decodeAudioData(). Этот метод принимает ArrayBuffer данных аудиофайла, хранящихся в request.response, и декодирует их асинхронно (не блокируя основной поток выполнения JavaScript).
После завершения работы decodeAudioData() вызывает функцию обратного вызова, которая предоставляет декодированные аудиоданные PCM в виде AudioBuffer.
Воспроизведение звуков
После загрузки одного или нескольких AudioBuffers мы готовы воспроизводить звуки. Предположим, мы только что загрузили в AudioBuffer звук лая собаки и загрузка завершилась. Затем мы можем воспроизвести этот буфер с помощью следующего кода.
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
}
Эта функция playSound() может вызываться каждый раз, когда кто-то нажимает клавишу или щелкает мышью.
Функция noteOn(time) позволяет легко планировать точное воспроизведение звука в играх и других приложениях, где важна синхронизация. Однако, чтобы это планирование работало правильно, убедитесь, что ваши звуковые буферы предварительно загружены.
Абстрагирование Web Audio API
Конечно, лучше создать более общую систему загрузки, которая не будет задана в коде для загрузки этого конкретного звука. Существует множество подходов к работе с короткими и средними звуками, которые используются в аудиоприложениях и играх. Один из них – использование BufferLoader (не входит в веб-стандарт).
Ниже приведен пример использования класса BufferLoader.
Давайте создадим два объекта AudioBuffers и, как только они загрузятся, воспроизведем их одновременно.
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);
}
Работа со временем: воспроизведение звуков в ритме
Web Audio API позволяет разработчикам точно планировать воспроизведение. Чтобы продемонстрировать это, давайте настроим простую ритм-дорожку. Вероятно, наиболее широко известным паттерном ударной установки является следующий:
в котором хай-хэт играет каждую восьмую ноту, а бас-барабан и малый барабан чередуются каждую четверть в размере 4/4.
Предположим, что мы загрузили буферы kick, snare и hihat. Код для этого будет простым:
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);
}
}
Здесь мы делаем только один повтор вместо неограниченного цикла, который мы видим в нотах. Функция playSound – это метод, который воспроизводит буфер в указанное время:
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
Как изменить громкость звука
Одна из самых базовых операций, которую вы можете захотеть сделать со звуком, – изменить его громкость. С помощью Web Audio API мы можем направить источник в пункт назначения через AudioGainNode, чтобы управлять громкостью:

Для этого выполните следующие действия:
// 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);
После настройки графа вы можете программно изменить громкость, управляя gainNode.gain.value следующим образом:
// Reduce the volume.
gainNode.gain.value = 0.5;
Перекрестное затухание между двумя звуками
Теперь предположим, что у нас есть немного более сложный сценарий, в котором мы воспроизводим несколько звуков, но хотим перекрестно затухать между ними. Это распространенный случай в приложениях для диджеев, где у нас есть два проигрывателя и мы хотим иметь возможность панорамировать от одного источника звука к другому.
Это можно сделать с помощью следующего аудиографа:
Для этого мы просто создаем два узла AudioGainNodes и подключаем каждый источник через узлы, используя примерно такую функцию:
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
};
}
Перекрестное затухание с равной мощностью
При использовании простого линейного кроссфейда во время панорамирования между семплами наблюдается падение громкости.
Чтобы решить эту проблему, мы используем кривую равной мощности, в которой соответствующие кривые усиления нелинейны и пересекаются при более высокой амплитуде. Это минимизирует провалы громкости между аудиорегионами, в результате чего получается более ровное перекрестное затухание между регионами, которые могут немного отличаться по уровню.
Перекрестное затухание в плейлистах
Ещё один распространенный пример применения кроссфейдера – музыкальный проигрыватель.
При смене трека мы хотим, чтобы текущий трек затухал, а новый появлялся, чтобы избежать резкого перехода. Для этого запланируйте перекрестное затухание на будущее. Для этого можно использовать setTimeout, но это неточно. С помощью Web Audio API мы можем использовать интерфейс AudioParam, чтобы запланировать будущие значения для таких параметров, как значение усиления AudioGainNode.
Таким образом, имея плейлист, мы можем переходить между треками, планируя уменьшение усиления для текущего трека и увеличение усиления для следующего, причем оба действия должны происходить незадолго до окончания воспроизведения текущего трека:
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 предоставляет удобный набор методов RampToValue для постепенного изменения значения параметра, например linearRampToValueAtTime и exponentialRampToValueAtTime.
В качестве функции времени перехода можно выбрать встроенную линейную или экспоненциальную (как выше), а также задать собственную кривую значений с помощью массива значений, используя функцию setValueCurveAtTime.
Применение простого фильтра к звуку

Web Audio API позволяет передавать звук из одного аудиоузла в другой, создавая потенциально сложную цепочку процессоров для добавления сложных эффектов к вашим звуковым формам.
Один из способов сделать это – разместить BiquadFilterNode между источником звука и местом назначения. Этот тип аудиоузла может выполнять различные фильтры низкого порядка, которые можно использовать для создания графических эквалайзеров и даже более сложных эффектов, в основном связанных с выбором того, какие части частотного спектра звука следует подчеркнуть, а какие приглушить.
Поддерживаются следующие типы фильтров:
- Фильтр низких частот
- Фильтр высоких частот
- Полосовой фильтр
- Фильтр низких частот
- Фильтр высоких частот
- Фильтр пиков
- Режекторный фильтр
- Всепропускающий фильтр
Все фильтры включают параметры, позволяющие указать усиление, частоту, к которой применяется фильтр, и добротность. Фильтр низких частот сохраняет диапазон низких частот, но отбрасывает высокие частоты. Точка отсечки определяется значением частоты, а добротность – безразмерная величина, определяющая форму графика. Усиление влияет только на определенные фильтры, такие как фильтры нижних частот и пиковые фильтры, и не влияет на этот фильтр нижних частот.
Давайте настроим простой фильтр низких частот, чтобы извлечь только басы из звукового образца:
// 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);
В целом, элементы управления частотой должны быть настроены для работы в логарифмическом масштабе, поскольку человеческий слух работает по тому же принципу (то есть A4 – это 440 Гц, а A5 – 880 Гц). Подробную информацию можно найти в функции FilterSample.changeFrequency по ссылке на исходный код выше.
Наконец, обратите внимание, что образец кода позволяет подключать и отключать фильтр, динамически изменяя граф AudioContext. Мы можем отключить AudioNodes от графа, вызвав node.disconnect(outputNumber).
Например, чтобы перенаправить граф в обход фильтра на прямое подключение, можно сделать следующее:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Что ещё послушать
Мы рассмотрели основы работы с API, в том числе загрузку и воспроизведение аудиосемплов. Мы создали аудиографы с узлами усиления и фильтрами, а также запланировали звуки и настроили аудиопараметры, чтобы включить некоторые распространенные звуковые эффекты. Теперь вы готовы к созданию собственных аудиоприложений.
Если вам нужно вдохновение, посмотрите, какие замечательные проекты уже созданы с помощью Web Audio API. Вот некоторые из моих любимых:
- AudioJedit – инструмент для склеивания аудиозаписей в браузере, использующий постоянные ссылки SoundCloud.
- ToneCraft – звуковой секвенсор, в котором звуки создаются путем укладки 3D-блоков.
- Plink – игра для совместного создания музыки, в которой используются Web Audio и WebSockets.