[Перевод] Концепции, лежащие в основе Web Audio API

_rj3qdi2rvk5vwbvfskmddp8yic.png

Доброго времени суток, друзья!

В этой статье объясняются некоторые концепции из теории музыки, на основе которых работает Web Audio API (WAA). Зная эти концепции, вы сможете принимать взвешенные решения при проектировании аудио в приложении. Статья не сделает вас опытным инженером по звуку, но поможет понять, почему WAA работает так, как работает.

Аудио схема


Суть WAA заключается в выполнении некоторых операций со звуком внутри аудио контекста (audio context). Этот API был специально разработан для модульной маршрутизации (modular routing). Основные операции со звуком представляют собой узлы (audio nodes), связанные между собой и формирующие схему маршрутизации (audio routing graph). Несколько источников — с разными типами каналов — обрабатываются внутри единого контекста. Такая модульная схема обеспечивает необходимую гибкость для создания сложных функций с динамическими эффектами.

Аудио узлы связаны между собой через входы и выходы, формируют цепь, которая начинается от одного или нескольких источников, проходит через один или несколько узлов, и заканчивается в пункте назначения (destination). В принципе, можно обойтись и без пункта назначения, например, если мы хотим просто визуализировать некоторые аудио данные. Типичный рабочий процесс для веб аудио выглядит примерно так:

  1. Создаем аудио контекст
  2. Внутри контекста создаем источники — такие как 
  3. Создаем узлы эффектов, такие как реверберация, биквадратный фильтр, паннер или компрессор
  4. Выбираем пункт назначения для аудио, такой как колонки компьютера пользователя
  5. Устанавливаем соединение между источниками через эффекты к пункту назначения


76wqvvwgifik6zojizkg_dxxb9q.png

Обозначение канала


Количество доступных аудио каналов часто обозначается в числовом формате, например, 2.0 или 5.1. Это называется обозначением канала. Первая цифра означает полный диапазон частот, которые включает сигнал. Вторая цифра означает количество каналов, зарезервированных для выходов низкочастотного эффекта — сабвуферов.

Каждый вход или выход состоит из одного или более каналов, построенных по определенной аудио схеме. Существуют различные дискретные структуры каналов, такие как моно, стерео, квадро, 5.1 и т.д.

7zrnxy_vaeyntkb3o5hgvbvs_k4.png

Аудио источники могут быть получены разными способами. Звук может быть:

  • Сгенерирован JavaScript-кодом посредством аудио узла (такого как осциллятор)
  • Создан из необработанных данных с помощью ИКМ (импульсно-кодовой модуляции)
  • Получен из медиа элементов HTML (таких как 
  • Получен из медиа потока WebRTC (такого как вебкамера или микрофон)


Аудио данные: что находится в семпле


Семплирование означает преобразование непрерывного сигнала в дискретный (разделенный) (аналогового сигнала в цифровой). Другими словами, непрерывная звуковая волна, такая как живой концерт, преобразуется в последовательность семплов, что позволяет компьютеру обрабатывать аудио отдельными блоками.

Аудио буфер: кадры, семплы и каналы


AudioBuffer в качестве параметров принимает количество каналов (1 для моно, 2 для стерео и т.д.), длину — количество кадров семпла внутри буфера, и частоту дискретизации — количество кадров в секунду.

Семпл — это простое 32-битное значение с плавающей точкой (float32), представляющее собой значение аудио потока в конкретный момент времени и в конкретном канале (левый или правый и др.). Кадр или кадр семпла — это набор значений всех каналов, воспроизводимых в определенный момент времени: все семплы всех каналов, воспроизводимые в одно и тоже время (два для стерео, шесть для 5.1 и т.д.).

Частота дискретизации — это количество семплов (или кадров, поскольку все семплы кадра проигрываются в одно время), воспроизводимые за одну секунду, измеряется в герцах (Гц). Чем выше частота, тем лучше качество звука.

Давайте посмотрим на моно и стерео буферы, каждый длиной в одну секунду, воспроизводимые с частотой 44100 Гц:

  • Моно буфер будет иметь 44100 семплов и 44100 кадров. Значением свойства «length» будет 44100
  • Стерео буфер будет иметь 88200 семплов, но также 44100 кадров. Значением свойства «length» будет 44100 — длина равняется количеству кадров


uqhppqfp_yz_k_hk_axza9j7qgm.png

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

Заметка: чтобы получить время в секундах из количества кадров необходимо разделить количество кадров на частоту дискретизации. Чтобы получить количество кадров из количества семплов, делим последние на количество каналов.

Пример:

let context = new AudioContext()
let buffer = context.createBuffer(2, 22050, 44100)


Заметка: в цифровом аудио 44100 Гц или 44.1 кГц — это стандартная частота семплирования. Но почему 44.1 кГц?

Во-первых, потому что диапазон слышимых частот (частот, различимых человеческим ухом) варьируется от 20 до 20000 Гц. Согласно теореме Котельникова частота дискретизации должна более чем в два раза превышать наибольшую частоту в спектре сигнала. Поэтому частота семплирования должна быть больше 40 кГц.

Во-вторых, сигналы должны быть отфильтрованы с помощью фильтра нижних частот перед семплированием, в противном случае будет иметь место наложение спектральных «хвостов» (подмена частот, маскировка частот, алиасинг) и форма восстановленного сигнала будет искажена. В идеале, фильтр нижних частот должен пропускать частоты ниже 20 кГц (без ослабления) и отбрасывать частоты выше 20 кГЦ. На практике требуется некоторая переходная полоса (между полосой пропускания и полосой подавления), где частоты частично ослабляются. Более легким и экономичным способом это сделать является применение противоподменного фильтра. Для частоты дискретизации равной 44.1 кГц переходная полоса составляет 2.05 кГц.

В приведенном примере мы получим стерео буфер с двумя каналами, воспроизводимый в аудио контексте с частотой 44100 Гц (стандарт), длиной 0.5 секунды (22050 кадров / 44100 Гц = 0.5 с).

let context = new AudioContext()
let buffer = context.createBuffer(1, 22050, 22050)


В данном случае мы получим моно буфер с одним каналом, воспроизводимый в аудио контексте с частотой 44100 Гц, произойдет его передискретизация до 44100 Гц (и увеличение кадров до 44100), длиной 1 секунда (44100 кадров / 44100 Гц = 1 с).

Заметка: аудио передискретизация («ресемплирование») очень похоже на изменение размеров («ресайзинг») изображений. Допустим, у нас есть изображение размером 16×16, однако мы хотим заполнить этим изображением область размером 32×32. Мы делаем ресайзинг. Результат будет менее качественным (может быть размытым или рваным в зависимости от алгоритма увеличения), но это работает. Ресемплированное аудио — это тоже самое: мы сохраняем пространство, но на практике едва ли получится добиться высокого качества звучания.

Планарные и чередующиеся буферы


В WAA используется планарный формат буфера. Левый и правый каналы взаимодействуют следующим образом:

LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (для буфера, состоящего из 16 кадров)


В данном случае каждый канал работает независимо от других.

Альтернативой является использование чередующегося формата:

LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (для буфера, состоящего из 16 кадров)


Такой формат часто используется для декодирования MP3.

В WAA используется только планарный формат, поскольку он лучше подходит для обработки звука. Планарный формат преобразуется в чередующийся, когда данные отправляются на звуковую карту для воспроизведения. При декодировании MP3 происходит обратное преобразование.

Аудио каналы


Разные буферы содержат разное количество каналов: от простых моно (один канал) и стерео (левый и правый каналы) до более сложных наборов, таких как квадро и 5.1 с разным количеством семплов в каждом канале, что обеспечивает более насыщенное (богатое) звучание. Каналы обычно представлены аббревиатурами:

Смешивание вверх (up-mixing) и вниз (down-mixing)


Когда количество каналов на входе и выходе не совпадает, применяют смешивание вверх или вниз. Смешивание контролируется свойством AudioNode.channelInterpretation:

Визуализация


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

w303tkjj1p7imhxsaegp0vtabyu.png

Для извлечения данных могут быть использованы следующие методы:

  • AnalyzerNode.getFloatByteFrequencyData () — копирует текущие данные о частоте в массив Float32Array
  • AnalyzerNode.getByteFrequencyData () — копирует текущие данные о частоте в массив Uint8Array (байтовый массив без знака)
  • AnalyserNode.getFloatTimeDomainData () — копирует текущие данные о форме волны или шаге дискретизации в массив Float32Array
  • AnalyserNode.getByteTimeDomainData () — копирует текущие данные о форме волны или шаге дискретизации в массив Uint8Array


Спатиализация


Аудио спатиализация (обрабатываемая PannerNode и AudioListener) позволяет моделировать позицию и направление сигнала в конкретной точке пространства, а также позицию слушателя.

Позиция паннера описывается с помощью правосторонних картезианских координат; для движения используется вектор скорости, необходимый для создания эффекта Доплера, для направления используется конус направленности. Этот конус может быть очень большим в случае разнонаправленных источников звука.

rlmjtz9l-3elsukt6gdnd81xbe4.png

Позиция слушателя описывается так: движение — с использованием вектора скорости, направление, где находится голова слушателя — с использованием двух векторов направленности, спереди и сверху. Привязка осуществляется к верхней части головы и носу слушателя под прямым углом.

7q-whktcaasufh9jfy8z6cigrbc.png

Соединение и разветвление


Соединение описывает процесс, в ходе которого ChannelMergerNode принимает несколько входных моно источников и соединяет их в один многоканальный сигнал на выходе.

vkbvqhpvhnghgf55bx9umic_yhc.png

Разветвление представляет собой обратный процесс (осуществляется посредством ChannelSplitterNode).

6d9cy7_hiinrgqscxxdsbe3x-zy.png

Пример работы с WAA можно посмотреть здесь. Исходный код примера находится здесь. Вот статья про то, как все это работает.

Благодарю за внимание.

© Habrahabr.ru