Основы

В этой главе мы рассмотрим, как начать работу с Web Audio API: какие браузеры его поддерживают, как определить доступность API, что такое аудиограф, что такое аудиоузлы, как соединять узлы между собой, познакомимся с основными типами узлов, а также узнаем, как загружать и воспроизводить звуковые файлы.

Краткая история аудио в вебе

Первым способом воспроизведения звуков в вебе был тег <bgsound>, который позволял авторам сайтов автоматически запускать фоновую музыку при открытии страницы пользователем. Эта функция была доступна только в Internet Explorer, не была стандартизирована и не получила поддержки в других браузерах. Netscape реализовал похожую функцию с помощью тега <embed>, обеспечивая по сути тот же самый функционал.

Первым кроссбраузерным способом воспроизведения звука в интернете стал Flash, однако у него был серьёзный недостаток — для работы требовался плагин. Позже производители браузеров объединились вокруг элемента HTML5 <audio>, который теперь нативно поддерживает воспроизведение аудио во всех современных браузерах.

Хотя воспроизведение аудио на веб-страницах больше не требует плагинов, тег <audio> имеет существенные ограничения при создании сложных игр и интерактивных приложений. Вот лишь некоторые из этих ограничений:

Предпринималось несколько попыток создать мощный аудио API для веба, чтобы устранить некоторые из упомянутых ранее ограничений. Один из заметных примеров — это Audio Data API, разработанный в Mozilla Firefox. Подход Mozilla предусматривал расширение возможностей JavaScript API элемента <audio>. Этот API предоставлял ограниченный аудиограф (подробнее об этом позже в разделе Аудиоконтекст) и не вышел за рамки первой реализации. Сейчас он считается устаревшим в Firefox и заменён на Web Audio API.

В сравнении с Audio Data API, Web Audio API предложил совершенно новую систему обработки и синтеза звука, никак не завязанную на элемент <audio>, однако предоставил возможности для интеграции с другими Web API (подробнее в разделе Интеграция с другими технологиями). Web Audio API — это высокоуровневый JavaScript API, использующийся для обработки и синтеза аудио в веб-приложениях. Цель этого API — предоставить возможности, которые есть у игровых движков, и у современных десктопных приложений для работы со звуком и решать задачи, связанные с микшированием, обработкой аудио и применением аудиофильтров. В результате получился универсальный API, который может использоваться во множестве задач, связанных с аудио: от использования в играх и интерактивных приложениях, до создания продвинутых приложений для синтеза звука и визуализаций.

Игры и интерактивность

Звук — это огромная часть того, что делает интерактивные впечатления настолько увлекательными. Если вы не верите — попробуйте посмотреть фильм с выключенным звуком.

Игры тоже не исключение! Мои самые тёплые воспоминания о видеоиграх связаны именно с музыкой и звуковыми эффектами. Спустя почти два десятилетия после выхода некоторых моих любимых игр я до сих пор не могу выбросить из головы саундтреки Кодзи Кондо к Zelda и Мэтта Юльмена к Diablo. Даже звуковые эффекты этих мастерски созданных игр мгновенно узнаваемы — от звуковых откликов юнитов при клике в сериях Warcraft и Starcraft от Blizzard до семплов из классики Nintendo.

Звуковые эффекты играют огромную роль не только в играх. Они появились в пользовательских интерфейсах (UI) ещё во времена командной строки, когда некоторые ошибки сопровождались звуковым сигналом. Эта идея продолжает жить и в современных интерфейсах, где грамотно подобранные звуки критически важны для уведомлений, сигналов, а также для приложений аудио‑ и видеосвязи, таких как Skype. Ассистенты вроде Google Now и Siri обеспечивают богатую звуковую обратную связь. По мере того как мы всё глубже погружаемся в мир повсеместных вычислений, интерфейсы на основе речи и жестов, которые не требуют экрана, всё сильнее зависят от звукового наполнения. Наконец, для пользователей с нарушением зрения звуковые подсказки, синтез и распознавание речи критически важны для создания удобного пользовательского опыта.

Интерактивный звук ставит перед разработчиками ряд интересных задач. Чтобы создать убедительное музыкальное сопровождение внутри игр, дизайнерам приходится учитывать все потенциально непредсказуемые игровые состояния, в которых может оказаться игрок. На практике это означает, что отдельные участки игры могут длиться неопределённое время, а звуки — взаимодействовать с окружением и смешиваться между собой сложным образом, требуя эффектов, которые зависят от окружающей среды, и относительного позиционирования источников звука. Наконец, в игре может одновременно воспроизводиться очень много звуков, и все они должны хорошо сочетаться друг с другом и звучать без потери качества и производительности.

Аудиоконтекст

Web Audio API выстроен вокруг концепции аудиоконтекста. Аудиоконтекст — это ориентированный граф аудиоузлов, который определяет, как аудиопоток проходит от источника (чаще всего это аудиофайл) до финального аудиовыхода (например, к вашим колонкам). По мере прохождения аудио через каждый узел, его свойства могут меняться или подвергаться анализу. Самый простой аудиоконтекст — это прямая связь от узла источника к аудиовыходу (см. Рисунок 1-1).

Рисунок 1-1. Простейший аудиоконтекст
Рисунок 1-1. Простейший аудиоконтекст

Аудиоконтекст может быть сложным, состоящим из множества узлов, работающих между источником и выходом звука (см. Рисунок 1-2), выполняющих продвинутый синтез или анализ аудио.

На Рисунке 1-1 и Рисунке 1-2 показаны аудиоузлы в виде блоков. Стрелки представляют собой связи между узлами. Узлы часто могут иметь несколько входящих и исходящих связей. По умолчанию, если в узел приходит несколько входящих связей, Web Audio API смешивает входящие звуковые сигналы в один общий.

Концепт аудиографа не был изобретён в Web Audio API — до этого он уже был реализован в таких популярных аудиофреймворках, как CoreAudio от Apple, который имеет свой собственный Audio Processing Graph API. Сама идея аудиографа зародилась ещё раньше и берёт своё начало из таких аудиосред как модульный синтезатор Moog, разработанный в 1960-ых годах.

Рисунок 1-2. Более сложный аудиоконтекст
Рисунок 1-2. Более сложный аудиоконтекст

Инициализация аудиоконтекста

В настоящее время Web Audio API реализован в браузерах Chrome и Safari (включая MobileSafari, начиная с iOS 6) и доступен веб-разработчикам через JavaScript. В этих браузерах конструктор аудиоконтекста использует префикс webkit, то есть вместо создания new AudioContext необходимо использовать new webkitAudioContext. Наверняка это изменится в будущем, по мере стабилизации API и появления реализации без префиксов, а также по мере появления реализации у других производителей браузеров. Mozilla публично заявила о намерении реализовать Web Audio API в Firefox, а Opera уже начала участвовать в рабочей группе.

С учётом этого, ниже представлен самый универсальный способ инициализации аудиоконтекста, который учитывает возможные реализации в разных браузерах (в том числе и будущие):

var сontextClass = (window.AudioContext ||
  window.webkitAudioContext ||
  window.mozAudioContext ||
  window.oAudioContext ||
  window.msAudioContext
);

if (contextClass) {
  // Web Audio API доступен
  var context = new сontextClass();
} else {
  // Web Audio API недоступен
  // Попросите пользователя использовать современные браузеры
}

Один аудиоконтекст поддерживает несколько источников аудио и сложные аудиографы, так что, как правило, в рамках одного приложения достаточно одного экземпляра аудиоконтекста. Экземпляр аудиоконтекста содержит множество методов для создания аудиоузлов и управления глобальными настройками аудио. К счастью, эти методы работают относительно стабильно и не требуют использования префикса webkit. Однако стоит помнить, что API всё ещё развивается, поэтому могут появляться изменения, которые не будут иметь обратной совместимости (см. Устаревшие возможности API).

Типы аудиоузлов

Аудиоузлы — это основные компоненты Web Audio API. Они представляют собой объекты, которые могут принимать и генерировать звуковые сигналы.

Узлы источников звука

Источниками звука могут быть аудиобуферы, живые аудиовходы, элементы <audio>, осцилляторы и JS-обработчики.

Модифицирующие узлы

Фильтры, узлы реверберации, узлы панорамирования и т.д.

Узлы для анализа звука

Анализаторы и JS-обработчики.

Узлы аудиовыходов

Аудиовыходы и оффлайн-буферы для отложенной обработки звука.

Источниками звука могут быть не только аудиофайлы, но также аудиопоток в реальном времени от музыкального инструмента или микрофона, перенаправление аудиовыхода из HTML-элемента <audio> (см. Настраиваем фоновую музыку с помощью HTML-элемента <audio>) или вообще полностью синтезированные звуки (см. Обработка аудио с помощью JavaScript). Обычно в качестве аудиовыхода используются колонки, но вы можете обрабатывать звук без его воспроизведения (например, для чистой визуализации) или выполнять его обработку в оффлайн-режиме, при которой аудиопоток записывается в буфер назначения для отложенного использования.

Подключение узлов в аудиографе

Любой аудиоузел может быть подключен к другому аудиоузлу через метод connect(). Ниже мы подключим выход узла аудиоисточника к узлу усиления, а узел усиления к глобальному аудиовыходу:

// Создадим источник
var source = context.createBufferSource();

// Создаём узел усиления звука
var gain = context.createGain();

// Подключаем источник к узлу усиления, а фильтр к выходу
var destination = context.destination;
source.connect(gain);
gain.connect(destination);

Обратите внимание, что context.destination — это специальный узел, который связан с дефолтным аудиовыходом вашей системы. Конечный аудиограф будет выглядеть как на Рисунке 1-3.

Рисунок 1-3. Наш первый аудиограф
Рисунок 1-3. Наш первый аудиограф

Теперь мы можем динамически изменять этот граф. Мы можем отключать аудиоузлы, вызывая node.disconnect(outputNumber). Для примера, направим подключение от источника к аудиовыходу, минуя промежуточный узел:

source.disconnect(0);
gain.disconnect(0);
source.connect(context.destination);

Сила модульной маршрутизации

Во многих играх различные источники звука могут быть объединены в один для создания финального микса. Такими источниками могут быть: фоновая музыка, звуковые эффекты, звуки интерфейса, и даже голосовой чат в мультиплеер-играх. Web Audio API позволяет разделить все эти каналы и даёт полный контроль над каждым каналом отдельно и над всеми вместе. Граф обработки аудио для такой системы может выглядеть как на Рисунке 1-4.

Рисунок 1-4. Несколько источников с индивидуальным управлением усилением и мастер-регулятором усиления
Рисунок 1-4. Несколько источников с индивидуальным управлением усилением и мастер-регулятором усиления

Здесь каждый узел усиления связан со своим отдельным источником звука, а контроль над усилением звука со всех каналов одновременно осуществляется через отдельный мастер-узел. Такая система позволяет легко контролировать уровень каждого канала отдельно.

Что такое звук?


С точки зрения физики, звук — это продольная волна (иногда называемая волной давления), которая распространяется в воздухе или другой среде. Источник звука заставляет молекулы воздуха вибрировать и сталкиваться друг с другом. В результате возникают области повышенного и пониженного давления, которые чередуются, сходятся и расходятся в виде полос. Если бы можно было изобразить узор звуковой волны, остановленной во времени, он мог бы выглядеть как на Рисунке 1-5.

Рисунок 1-5. Звуковая волна, распространяющаяся через воздушные частицы
Рисунок 1-5. Звуковая волна, распространяющаяся через воздушные частицы

Математически, звук может быть представлен функцией, отображающей изменение давления в зависимости от времени. На Рисунке 1-6 показан график такой функции. Области с большими значениями на графике соответствуют областям с частицами, расположенными более плотно на Рисунке 1-5 (области высокого давления), а с меньшими значениями — областям с редко расположенными частицами (области низкого давления).

Рисунок 1-6. Математическое представление звуковой волны из Рисунка 1-5
Рисунок 1-6. Математическое представление звуковой волны из Рисунка 1-5

Ещё с начала двадцатого века электроника позволяла воспринимать и воссоздавать звуки. Микрофоны могут воспринять волну давления звука и превратить её в электрический сигнал, в котором (для примера) +5 вольт будут соответствовать самому высокому уровню давления, а -5 — самому низкому. И наоборот, аудиоколонка может воспринимать эти значения напряжения сигнала в вольтах и превращать их в волну давления, которую мы можем слышать.

Неважно, анализируем мы звук или синтезируем его, всё самое интересное для программистов аудио происходит внутри «чёрного ящика» (см. Рисунок 1-7), отвечающего за обработку аудиосигнала. Раньше этот процесс осуществлялся с помощью аналоговых фильтров и другого оборудования, которое по современным меркам выглядит устаревшим. Сегодня существуют цифровые реализации большинства этих старых аналоговых устройств. Но прежде чем мы сможем приступить к программной реализации этих интересных задач, нам нужно представить звук в той форме, с которой могут работать компьютеры.

Рисунок 1-7. Запись и воспроизведение звука
Рисунок 1-7. Запись и воспроизведение звука

Что такое цифровой звук?


Мы можем перевести аналоговый звук в цифровой посредством его семплирования — то есть измерения частоты аналогового сигнала через небольшие отрезки времени. Далее мы можем закодировать сигнал в каждом семпле в виде конкретного числа. Частота таких измерений аналогового сигнала называется частотой дискретизации. Самая распространенная частота дискретизации — 44.1 kHz. Это означает, что для каждой секунды звука записывается 44 100 чисел. Сами числа находятся в определённом диапазоне значений. Каждому значению обычно выделяется определённое количество битов — это называется глубиной битности. В большинстве случаев для цифровой аудиозаписи, включая CD-диски, используется глубина 16 бит, которой обычно достаточно для большинства слушателей. Аудиофилы могут предпочитать 24-битную глубину, которая обеспечивает такую точность, выше которой человеческое ухо уже перестаёт улавливать какую-либо разницу.

Процесс конвертации аналогового сигнала называется квантованием (или дискретизацией) (см. Рисунок 1-8).

Рисунок 1-8. Аналоговый звук, преобразуемый в цифровой
Рисунок 1-8. Аналоговый звук, преобразуемый в цифровой

Как видно из Рисунка 1-8, квантованая версия сигнала отличается от исходного аналогового. Эта разница (на графике выделена синим) уменьшается при увеличении частоты дискретизации и битовой глубины. Однако, уменьшение разницы может увеличить размер файла для хранения звука. Поэтому, к примеру, телефонные системы часто использовали частоту дискретизации всего 8 kHz, так как диапазон частот для передачи человеческого голоса намного меньше полного диапазона слышимых человеческим ухом частот (обычно от 20 Hz до 20 kHz).

Для оцифровки звука компьютеры могут оперировать длинными массивами чисел. Этот способ кодирования звука называется импульсно-кодовой модуляцией (pulse-code modulation, PCM). В Web Audio API такие массивы с числами используются в абстракции AudioBuffer. Аудиобуферы могут хранить множество аудиоканалов (чаще всего в стерео — правый и левый каналы), представленных в виде массива чисел с плавающей точкой в диапазоне от –1 до 1. Тот же сигнал может быть представлен в виде массива целых чисел: если глубина битности 16 бит, то они окажутся в диапазоне от –215 до 215 – 1.

Форматы кодирования аудио


Необработанное аудио в формате PCM занимает слишком много места, поэтому чаще всего аудио хранится в сжатых форматах. Существует два типа сжатия: lossy (с потерями) and lossless (без потерь). Сжатие без потерь (например, FLAC) гарантирует идентичность бит у сжатого и восстановленного звука. Сжатие с потерями (например, MP3) использует особенности человеческого слуха, чтобы сохранить дополнительное место, вырезав биты сигнала, которые скорее всего не будут услышаны. Форматы с потерями в целом подходят для большинства слушателей, кроме некоторых аудиофилов.

Метрика степени сжатия звука называется битрейт (bit rate) и обозначает количество бит аудио, необходимых для передачи звука, длительностью в секунду. Чем выше битрейт, тем больше данных может быть выделено на единицу времени, тем меньше степень сжатия и выше качество звука. Такие форматы как MP3 часто описываются используемым битрейтом (128 или 192 Kb/s). Форматы с потерями позволяют кодировать сигнал с произвольным битрейтом. Например, для передачи человеческого голоса по телефону часто используется битрейт 8 Kb/s. Такие форматы как OGG поддерживают вариативный битрейт, который может меняться со временем. Не путайте этот параметр с частотой дискретизации или разрядностью (см. Что такое звук?)!

Поддержка различных аудиоформатов в браузерах значительно различается. Как правило, если Web Audio API реализован в браузере, он использует тот же механизм загрузки, что и тег <audio>, поэтому матрица поддержки форматов у <audio> и Web Audio API совпадает. Обычно WAV (простой, без потерь и, как правило, несжатый формат) поддерживается во всех браузерах. Формат MP3 по-прежнему обременён патентами, поэтому он недоступен в некоторых полностью открытых браузерах (например, Firefox и Chromium). К сожалению, менее популярный, но свободный от патентов формат OGG до сих пор не поддерживается в Safari на момент написания этого текста.

С более актуальным списком поддерживаемых аудиоформатов можно ознакомиться здесь: https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats.

Загрузка и воспроизведение звуков

Web Audio API чётко разделяет аудиобуферы и узлы источников. Идея такой архитектуры в том, чтобы разделить аудиоресурс от состояния воспроизведения. Если рассматривать это на примере винилового проигрывателя, буферы можно сравнить с пластинками, а источник звука — со считывающими головками проигрывателя. Разница в том, что в мире Web Audio API вы можете проигрывать одну и ту же пластинку на каком угодно количестве считывающих головок одновременно! Поскольку во многих приложениях требуется одновременное воспроизведение нескольких версий одного и того же буфера, этот подход является ключевым. Например, если вы хотите, чтобы звуки подпрыгивающего мяча срабатывали в быстрой последовательности, достаточно загрузить звуковой буфер один раз и запланировать воспроизведение нескольких источников на его основе (см. Множество одновременных звуков с вариациями).

Для загрузки аудиосемпла в Web Audio API мы можем использовать XMLHttpRequest и обработать результаты с помощью context.decodeAudioData. Всё это будет происходить асинхронно и не будет блокировать основой поток:

var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';

// Декодируем асинхронно
request.onload = function() {
  context.decodeAudioData(request.response, function(theBuffer) {
    buffer = theBuffer;
  }, onError);
}

request.send();

Аудиобуферы — это лишь один из возможных источников воспроизведения. Другие источники включают прямой вход с микрофона или линейного входа, а также тег <audio> и другие (см. Интеграция с другими технологиями).

После загрузки буфера вы можете создать для него соответствующий аудиоузел AudioBufferSourceNode, подключить к вашему аудиографу и вызвать start(0) у узла аудиоисточника. Для остановки проигрывания вызовите stop(0). Оба этих метода принимают параметр, обозначающий время старта/остановки проигрывания в секундах относительно системы координат текущего аудиоконтекста (см. Идеальный тайминг и задержка):

function playSound(buffer) {
  var source = context.createBufferSource();

  source.buffer = buffer;
  source.connect(context.destination);
  source.start(0);
}

В играх часто используется фоновая музыка, играющая по циклу. Однако стоит быть осторожнее с чрезмерной повторяемостью: если игрок застрянет в каком-то уровне или области, и один и тот же трек будет непрерывно звучать на фоне, это может начать вызывать раздражение. В таких случаях имеет смысл постепенно приглушать трек, чтобы избежать переутомления слушателя. Другой стратегией может быть использование различных вариантов трека с разной интенсивностью и плавных переходов между ними в зависимости от ситуации в игре (см. Постепенно меняющиеся параметры аудио).

Собираем всё вместе

Как видно из предыдущих примеров кода, для воспроизведения звуков с помощью Web Audio API требуется произвести некоторую настройку. В реальной игре имеет смысл реализовать абстракцию на JavaScript, которая упростит дальнейшую работу с этим API. Один из примеров такой абстракции — класс BufferLoader. Он объединяет все этапы в простой загрузчик, который принимает набор путей к аудиофайлам и возвращает соответствующие аудиобуферы. Вот пример того, как можно использовать такой класс:

window.onload = init;

var context;
var bufferLoader;

function init() {
  context = new webkitAudioContext();

  bufferLoader = new BufferLoader(
    context,
    [
      '../sounds/hyper-reality/br-jam-loop.wav',
      '../sounds/hyper-reality/laughter.wav',
    ],
    finishedLoading
  );

  bufferLoader.load();
}

function finishedLoading(bufferList) {
  // Создадим два источника аудио и воспроизведём их одновременно
  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.start(0);
  source2.start(0);
}

Простейший пример реализации BufferLoader можно посмотреть тут: http://webaudioapi.com/samples/shared.js.