Spec-Zone.ru › Web APIs

Визуализация с помощью API Web Audio

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

Примечание: Вы найдете рабочие примеры всех фрагментов кода в нашей демонстрации Voice-change-O-matic.

Основные понятия

Для извлечения данных из вашего аудиоисточника вам нужен узел AnalyserNode, который создается с помощью метода BaseAudioContext.createAnalyser, например:

const audioCtx = new AudioContext();
const analyser = audioCtx.createAnalyser();

Затем этот узел подключается к вашему аудиоисточнику в какой-то момент между источником и точкой назначения, например:

const source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(audioCtx.destination);

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

Узел анализатора затем будет собирать аудиоданные, используя быстрое преобразование Фурье (FFT) в определенной частотной области, в зависимости от значения свойства AnalyserNode.fftSize (если значение не указано, значение по умолчанию равно 2048.)

Примечание: Вы также можете указать минимальное и максимальное значения мощности для диапазона шкалирования данных FFT, используя AnalyserNode.minDecibels и AnalyserNode.maxDecibels, а также различные константы усреднения данных с помощью AnalyserNode.smoothingTimeConstant. Прочитайте эти страницы, чтобы узнать больше о том, как их использовать.

Для сбора данных необходимо использовать методы AnalyserNode.getFloatFrequencyData() и AnalyserNode.getByteFrequencyData() для сбора данных о частоте и AnalyserNode.getByteTimeDomainData() и AnalyserNode.getFloatTimeDomainData() для сбора данных о форме волны.

Эти методы копируют данные в указанный массив, поэтому вам необходимо создать новый массив для получения данных перед вызовом одного из них. Первый метод производит 32-битные числа с плавающей запятой, а второй и третий — 8-битные целые без знака, поэтому стандартный JavaScript-массив не подойдет — вам нужно использовать массив Float32Array или Uint8Array, в зависимости от обрабатываемых данных.

Итак, например, предположим, что размер FFT равен 2048. Мы возвращаем значение AnalyserNode.frequencyBinCount, которое составляет половину FFT, затем вызываем Uint8Array() с аргументом длины frequencyBinCount — это количество точек данных, которые мы будем собирать для этого размера FFT.

analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

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

analyser.getByteTimeDomainData(dataArray);

Теперь мы имеем аудиоданные для этого момента времени, сохраненные в нашем массиве, и можем визуализировать их любым удобным способом, например, нарисовав их на HTML-элементе <canvas>.

Давайте перейдем к рассмотрению конкретных примеров.

Создание визуализации формы волны/оциллографа

Для создания визуализации осциллографа формы волны (благодарность Soledad Penadés за исходный код в Voice-change-O-matic) сначала следуем стандартному шаблону, описанному в предыдущем разделе, для настройки буфера:

analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

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

canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);

Теперь мы определяем функцию draw():

function draw() {

Внутри неё, мы используем requestAnimationFrame() для поддержания циклического вызова функции отрисовки после её запуска:

const drawVisual = requestAnimationFrame(draw);

Затем мы получаем данные временной области и копируем их в наш массив:

analyser.getByteTimeDomainData(dataArray);

Далее, заполняем холст твёрдым цветом для начала:

canvasCtx.fillStyle = "rgb(200 200 200)";
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);

Устанавливаем ширину линии и цвет заливки для волны, которую мы будем рисовать, затем начинаем рисовать путь:

canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "rgb(0 0 0)";
canvasCtx.beginPath();

Определяем ширину каждого сегмента линии, разделив ширину холста на длину массива (равную frequencyBinCount, как определено ранее), а затем определяем переменную x для определения позиции перехода при рисовании каждого сегмента линии.

const sliceWidth = WIDTH / bufferLength;
let x = 0;

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

for (let i = 0; i < bufferLength; i++) {
  const v = dataArray[i] / 128.0;
  const y = v * (HEIGHT / 2);

  if (i === 0) {
    canvasCtx.moveTo(x, y);
  } else {
    canvasCtx.lineTo(x, y);
  }

  x += sliceWidth;
}

Наконец, завершаем линию в середине правой стороны холста, затем рисуем заданную линию:

  canvasCtx.lineTo(WIDTH, HEIGHT / 2);
  canvasCtx.stroke();
}

В конце этого фрагмента кода вызываем функцию draw(), чтобы запустить весь процесс:

draw();

Это даёт нам красивую визуализацию формы волны, обновляющуюся несколько раз в секунду:

a black oscilloscope line, showing the waveform of an audio signal

Создание графика частотных полос

Еще одной интересной визуализацией звука является график частотных полос в стиле Winamp. У нас есть пример в Voice-change-O-matic; давайте посмотрим, как это делается.

Сначала мы снова настраиваем наш анализатор и массив данных, а затем очищаем текущий дисплей холста с помощью clearRect(). Единственное отличие от предыдущего примера — мы уменьшили размер FFT, чтобы каждая полоска на графике была достаточно широкой, чтобы выглядеть как полоска, а не тонкая линия.

analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);

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

function draw() {
  drawVisual = requestAnimationFrame(draw);

  analyser.getByteFrequencyData(dataArray);

  canvasCtx.fillStyle = "rgb(0 0 0)";
  canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);

Теперь мы устанавливаем barWidth равным ширине холста, делённой на количество полос (длина буфера). Однако мы также умножаем эту ширину на 2,5, так как большая часть частот будет возвращаться как не имеющие аудио, поскольку большинство звуков, которые мы слышим каждый день, находятся в определённом низкочастотном диапазоне. Мы не хотим отображать множество пустых полос, поэтому мы сдвигаем те, которые будут отображаться на заметной высоте, по горизонтали, чтобы они заполняли экран.

Мы также устанавливаем переменную barHeight и переменную x для записи того, насколько далеко от левого края экрана будет отрисована текущая полоса.

const barWidth = (WIDTH / bufferLength) * 2.5;
let barHeight;
let x = 0;

Как и прежде, мы запускаем цикл for и перебираем каждое значение в dataArray. Для каждого из них мы устанавливаем barHeight равным значению массива, задаём цвет заливки, основываясь на значении barHeight (более высокие полосы — более яркие), и рисуем полоску по горизонтали в x пикселях от левого края холста, имеющую ширину barWidth и высоту barHeight / 2 (в итоге мы решили укоротить полоски пополам, чтобы они лучше помещались на холсте).

Единственное значение, требующее объяснения, — это вертикальное смещение, на котором мы рисуем каждую полоску: HEIGHT - barHeight / 2. Я делаю это, потому что хочу, чтобы каждая полоса поднималась от нижней части холста, а не опускалась от верхней, как если бы мы установили вертикальную позицию в 0. Поэтому вместо этого мы устанавливаем вертикальную позицию каждый раз равной высоте холста минус barHeight / 2, чтобы каждая полоса была нарисована от середины холста вниз.

for (let i = 0; i < bufferLength; i++) {
  barHeight = dataArray[i] / 2;

  canvasCtx.fillStyle = `rgb(${barHeight + 100} 50 50)`;
  canvasCtx.fillRect(x, HEIGHT - barHeight / 2, barWidth, barHeight);

  x += barWidth + 1;
}

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

draw();

Этот код даёт результат, подобный следующему:

a series of red bars in a bar graph, showing intensity of different frequencies in an audio signal

Примечание: В примерах в этой статье использовались AnalyserNode.getByteFrequencyData() и AnalyserNode.getByteTimeDomainData(). Для примеров с AnalyserNode.getFloatFrequencyData() и AnalyserNode.getFloatTimeDomainData(), обратитесь к нашей демонстрации Voice-change-O-matic-float-data — это точно такой же пример, как и оригинальный Voice-change-O-matic, за исключением того, что он использует данные с плавающей запятой, а не данные целых чисел без знака. Подробности см. в разделе этого фрагмента исходного кода.

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API