Анализ и визуализация

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

Это очень важная тема для разработчиков игр и интерактивных приложений по двум причинам. Во-первых, хороший визуальный анализатор может использоваться как отладочный инструмент (в дополнение к нашим ушам и хорошо настроенной измерительной аппаратуре) для тонкой настройки звука. Во-вторых, визуализация критична для игр и музыкальных приложений, таких как Guitar Hero или музыкального ПО типа GarageBand.

Частотный анализ

Основное средство для анализа звука в Web Audio API — это узлы AnalyserNode. Эти узлы не меняют сам звук и могут быть добавлены в любом месте аудиографа. Будучи подключёнными к аудиографу, они могут использоваться для анализа звуковой волны как во временном, так и в частотном спектре.

Полученные результаты основаны на FFT-анализе буфера определённого размера. У нас есть несколько способов повлиять на выходные данные узла AnalyserNode:

fftSize

Этот параметр задаёт размер буфера, который используется для анализа. Он должен быть равен степени двойки. Более высокие значения этого параметра позволят проводить более детальный анализ, но могут привести к некоторому снижению производительности.

frequencyBinCount

Это значение только для чтения, задаётся автоматически как fftSize/2.

smoothingTimeConstant

Это значение должно находиться в диапазоне от 0 до 1. При значении 1 используется большое окно скользящего среднего, и результаты получаются максимально сглаженными. При значении 0 скользящее среднее не применяется, и результаты изменяются быстро и резко.

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

// Предположим, что узел A соединён с узлом B
var analyser = context.createAnalyser();

A.connect(analyser);
analyser.connect(B);

Далее мы можем получить массив значений частотного или временного спектра:

var freqDomain = new Float32Array(analyser.frequencyBinCount);

analyser.getFloatFrequencyData(freqDomain);

freqDomain в этом примере — это массив 32-битных чисел с плавающей точкой, относящихся к частотному спектру. Эти числа нормализованы и находятся в диапазоне от нуля до единицы. Индексы выходных данных могут быть линейны сопоставлены между нулём и частотой Найквиста, которая определяется как половина частоты дискретизации (значение доступно в Web Audio API через context.sampleRate). Следующий фрагмент кода сопоставляет частоту с подходящим сегментом в массиве частот:

function getFrequencyValue(frequency) {
  var nyquist = context.sampleRate / 2;
  var index = Math.round(frequency / nyquist * freqDomain.length);

  return freqDomain[index];
}

К примеру, если мы попытаемся проанализировать синусоидальную волну частотой 1000 Hz, мы можем ожидать, что вызов getFrequencyValue(1000) вернёт пиковое значение на графике, как показано на Рисунке 5-1.

Частотный спектр также доступен в виде 8-битных беззнаковых целых чисел через вызов getByteFrequencyData(). Значения этих чисел масштабируются таким образом, чтобы соответствовать диапазону между значениями minDecibels и maxDecibelsdBFS) на узле анализатора, поэтому эти параметры можно настраивать для масштабирования выходных данных по желанию.

Рисунок 5-1. Визуализация синусоидальной волны частотой 1000 Hz (полный диапазон от 0 до 22,050 Hz)
Рисунок 5-1. Визуализация синусоидальной волны частотой 1000 Hz (полный диапазон от 0 до 22,050 Hz)

Анимация с requestAnimationFrame

Если мы хотим построить визуализацию формы звуковой волны, мы должны периодически опрашивать анализатор, обрабатывать результаты и отображать их на странице. Мы можем сделать это, установив JavaScript-таймеры через setInterval или setTimeout, но есть более подходящий способ: использовать requestAnimationFrame. Он позволяет браузеру встроить вашу кастомную функцию отрисовки внутрь цикла отрисовки браузера, который отлично оптимизирован для подобного использования. Вместо того, чтобы заставлять эту функцию отрисовки запускаться в конкретные промежутки времени и конкурировать с другими браузерными событиями, мы можем просто запланировать события отрисовки в очереди событий и браузер выполнит их автоматически, как только сможет.

Так как requestAnimationFrame всё ещё экспериментальный API, мы должны использовать версии с префиксами для каждого браузера, и предоставить фолбэк для браузеров, которые его не поддерживают в виде setTimeout:

window.requestAnimationFrame = (function() {
  return window.requestAnimationFrame  ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame    ||
    window.oRequestAnimationFrame      ||
    window.msRequestAnimationFrame     ||
    function(callback){
      window.setTimeout(callback, 1000 / 60);
    };
  })();

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

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

Объединив всё вместе, мы можем настроить цикл рендера, который, как и раньше, будет опрашивать анализатор и отображать его текущий частотный анализ в массив freqDomain:

var freqDomain = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(freqDomain);

for (var i = 0; i < analyser.frequencyBinCount; i++) {
  var value = freqDomain[i];
  var percent = value / 256;
  var height = HEIGHT * percent;
  var offset = HEIGHT - height - 1;
  var barWidth = WIDTH/analyser.frequencyBinCount;
  var hue = i/analyser.frequencyBinCount * 360;

  drawContext.fillStyle = 'hsl(' + hue + ', 100%, 50%)';
  drawContext.fillRect(i * barWidth, offset, barWidth, height);
}

Мы можем сделать похожую вещь и для временного спектра:

var timeDomain = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(timeDomain);

for (var i = 0; i < analyser.frequencyBinCount; i++) {
  var value = timeDomain[i];
  var percent = value / 256;
  var height = HEIGHT * percent;
  var offset = HEIGHT - height - 1;
  var barWidth = WIDTH/analyser.frequencyBinCount;

  drawContext.fillStyle = 'black';
  drawContext.fillRect(i * barWidth, offset, 1, 1);
}

Этот код нарисует график временного спектра поверх цветного графика частотного спектра, используя HTML5 Canvas API. Результат отрисовки канваса изображен на Рисунке 5-2 и будет меняться со временем.

Рисунок 5-2. Изображение визуализатора в действии
Рисунок 5-2. Изображение визуализатора в действии

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