До появления элемента HTML5 <audio>
для нарушения тишины в сети требовался Flash или другой плагин. Хотя аудио в Интернете больше не требует плагина, тег audio накладывает существенные ограничения на реализацию сложных игр и интерактивных приложений.
API веб-аудио — это API JavaScript высокого уровня для обработки и синтеза звука в веб-приложениях. Цель этого API — включить возможности, имеющиеся в современных игровых звуковых движках, а также некоторые задачи микширования, обработки и фильтрации, которые имеются в современных настольных приложениях для создания звука. Далее следует краткое введение в использование этого мощного API.
Начало работы с AudioContext
AudioContext предназначен для управления и воспроизведения всех звуков. Чтобы создать звук с помощью 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 веб-аудио использует 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)
позволяет легко запланировать точное воспроизведение звука для игр и других приложений, критичных по времени. Однако, чтобы это планирование работало правильно, убедитесь, что ваши звуковые буферы предварительно загружены.
Абстрагирование 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 веб-аудио позволяет разработчикам точно планировать воспроизведение. Чтобы продемонстрировать это, давайте настроим простой ритм-трек. Вероятно, наиболее широко известным паттерном ударной установки является следующий:
в котором хэт исполняется каждую восьмую ноту, а кик и малый барабан исполняются попеременно каждую четверть, в размере 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);
}
Изменение громкости звука
Одна из самых основных операций, которые вы, возможно, захотите выполнить со звуком, — это изменение его громкости. Используя 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
для такого планирования, это неточно . С помощью 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 веб-аудио предоставляет удобный набор методов RampToValue
для постепенного изменения значения параметра, например linearRampToValueAtTime
и exponentialRampToValueAtTime
.
Хотя функцию времени перехода можно выбрать из встроенных линейных и экспоненциальных функций (как указано выше), вы также можете указать свою собственную кривую значений через массив значений с помощью функции setValueCurveAtTime
.
Применение простого эффекта фильтра к звуку
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, включая загрузку и воспроизведение аудиосэмплов. Мы построили аудиографики с узлами усиления и фильтрами, а также запланировали настройки звуков и параметров звука, чтобы включить некоторые распространенные звуковые эффекты. На этом этапе вы готовы приступить к созданию замечательных веб-аудиоприложений!
Если вы ищете вдохновение, многие разработчики уже создали отличные работы, используя API веб-аудио. Некоторые из моих любимых включают в себя:
- AudioJedit — инструмент для склейки звука в браузере, использующий постоянные ссылки SoundCloud.
- ToneCraft — звуковой секвенсор, в котором звуки создаются путем наложения 3D-блоков.
- Plink — игра для совместного создания музыки с использованием веб-аудио и веб-сокетов.