До появления элемента <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 .
Звуки загрузки
API Web Audio использует 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 .
После получения (недекодированных) данных аудиофайла их можно сохранить для последующего декодирования или декодировать сразу же с помощью метода decodeAudioData() объекта AudioContext. Этот метод принимает 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) позволяет легко планировать точное воспроизведение звука для игр и других приложений, критичных ко времени. Однако для корректной работы этого планирования убедитесь, что ваши звуковые буферы предварительно загружены.
Абстрагирование 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);
}
Управление временем: воспроизведение звуков в ритме
API Web Audio позволяет разработчикам точно планировать воспроизведение. Чтобы это продемонстрировать, давайте создадим простую ритм-дорожку. Вероятно, наиболее известный паттерн ударных инструментов выглядит следующим образом:

в которой хай-хэт играется через каждые восемь нот, а бас-барабан и малый барабан играют попеременно через каждые четверть, в размере 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;
Плавное затухание между двумя звуками
Теперь предположим, что у нас есть немного более сложная ситуация, когда мы воспроизводим несколько звуков, но хотим плавно переходить между ними. Это распространенный случай в диджейских приложениях, где у нас есть два проигрывателя, и мы хотим иметь возможность панорамировать звук от одного источника к другому.
Это можно сделать с помощью следующего аудиографа:

Для этого достаточно создать два узла AudioGainNode и соединить каждый источник через эти узлы, используя функцию, подобную этой:
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);
}
API Web Audio предоставляет удобный набор методов RampToValue для постепенного изменения значения параметра, таких как linearRampToValueAtTime и exponentialRampToValueAtTime .
Хотя функцию времени перехода можно выбрать из встроенных линейных и экспоненциальных (как указано выше), вы также можете задать собственную кривую значений с помощью массива значений, используя функцию setValueCurveAtTime .
Применение простого эффекта фильтрации к звуку.

API Web Audio позволяет передавать звук из одного аудиоузла в другой, создавая потенциально сложную цепочку процессоров для добавления сложных эффектов к вашим звуковым формам.
Один из способов сделать это — разместить узлы 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 и Web Sockets.