Статьи

Как создать аудио генератор с помощью Web Audio API

Конечный продукт
Что вы будете создавать

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

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

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

1
<canvas id=»oscilloscope»></canvas>

Самая важная часть разметки — это canvas , который будет элементом, отображающим наш осциллограф. Если вы не знакомы с canvas , советую прочитать эту статью под названием « Введение в работу с холстом ».

С установленной сценой для отображения графика нам нужно создать аудио.

Мы начнем с определения пары важных переменных для звукового контекста и усиления. Эти переменные будут использованы для ссылки на более поздний этап кода.

1
2
let audioContext,
   masterGain;

audioContext представляет граф обработки аудио ( полное описание сети обработки аудио сигнала ), построенный из аудио модулей, связанных вместе. Каждый из них представлен AudioNode , и при соединении они создают граф аудио-маршрутизации. Этот аудио контекст управляет как созданием узла (ов), который он содержит, так и выполнением обработки и декодирования аудио.

AudioContext должен быть создан прежде всего, так как все происходит внутри контекста .

Наш masterGain принимает входной сигнал одного или нескольких аудиоисточников и выводит громкость звука, которая была отрегулирована по усилению до уровня, заданного GainNode.gain a-rate узла GainNode.gain . Вы можете думать о мастер-усилении как о громкости. Теперь мы создадим функцию, разрешающую воспроизведение в браузере.

1
2
3
4
function audioSetup() {
    let source = ‘http://ice1.somafm.com/seventies-128-aac’;
    audioContext = new (window.AudioContext || window.webkitAudioContext)();
}

Я начну с определения source переменной, которая будет использоваться для ссылки на аудиофайл. В этом случае я использую URL для службы потоковой передачи, но это также может быть аудиофайл. audioContext определяет аудиообъект и является контекстом, который мы обсуждали ранее. Я также проверяю совместимость с помощью префикса WebKit , но в настоящее время поддержка широко применяется, за исключением IE11 и Opera Mini.

1
2
3
4
function audioSetup() {
    masterGain = audioContext.createGain();
    masterGain.connect(audioContext.destination);
}

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

1
2
3
4
5
6
7
function audioSetup() {
    let song = new Audio(source),
        songSource = audioContext.createMediaElementSource(song);
 
    songSource.connect(masterGain);
    song.play();
}

Переменная song создает новый аудиообъект с помощью конструктора Audio() . Вам понадобится аудиообъект, чтобы у контекста был источник воспроизведения для слушателей.

Переменная songSource — это волшебный соус, который воспроизводит аудио и где мы будем передавать наш аудиоисточник. Используя createMediaElementSource() , аудио можно воспроизводить и манипулировать по желанию. Последняя переменная соединяет наш аудиоисточник с мастер-усилением (громкостью). Последняя строка song.play() — это вызов, который фактически дает разрешение на воспроизведение аудио.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
let audioContext,
    masterGain;
 
function audioSetup() {
    let source = ‘http://ice1.somafm.com/seventies-128-aac’;
 
    audioContext = new (window.AudioContext || window.webkitAudioContext)();
    masterGain = audioContext.createGain();
    masterGain.connect(audioContext.destination);
 
    let song = new Audio(source),
        songSource = audioContext.createMediaElementSource(song);
 
    songSource.connect(masterGain);
    song.play();
}
 
audioSetup();

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

Чтобы отобразить частоту волны для выбранного нами аудиоисточника, нам нужно создать форму волны.

1
2
const analyser = audioContext.createAnalyser();
masterGain.connect(analyser);

Первая ссылка на createAnalyser() предоставляет данные о времени и частоте звука для генерации визуализаций данных. Этот метод создаст AnalyserNode, который передает аудиопоток от входа к выходу, но позволяет получать сгенерированные данные, обрабатывать их и создавать аудиовизуализации, которые имеют ровно один вход и один выход. Узел анализатора будет связан с главным усилением, которое является выходом нашего тракта сигнала и дает возможность анализировать источник.

1
2
const waveform = new Float32Array(analyser.frequencyBinCount);
analyser.getFloatTimeDomainData(waveform);

Этот конструктор Float32Array() представляет массив 32-битных чисел с плавающей точкой. Свойство AnalyserNode интерфейса AnalyserNode представляет собой длинное значение без знака, равное половине размера FFT ( быстрого преобразования Фурье ). Обычно это соответствует количеству значений данных, которые вы будете использовать для визуализации. Мы используем этот подход для многократного сбора частотных данных.

Последний метод getFloatTimeDomainData копирует текущий сигнал или данные во Float32Array передаваемый в него массив Float32Array .

1
2
3
4
function updateWaveform() {
  requestAnimationFrame(updateWaveform);
  analyser.getFloatTimeDomainData(waveform);
}

Весь этот объем данных и обработки использует requestAnimationFrame() для многократного сбора данных во временной области и вывода «стиля осциллографа» текущего аудиовхода. Я также делаю еще один вызов getFloatTimeDomainData() поскольку его необходимо постоянно обновлять, поскольку источник звука является динамическим.

01
02
03
04
05
06
07
08
09
10
const analyser = audioContext.createAnalyser();
masterGain.connect(analyser);
 
const waveform = new Float32Array(analyser.frequencyBinCount);
analyser.getFloatTimeDomainData(waveform);
 
function updateWaveform() {
  requestAnimationFrame(updateWaveform);
  analyser.getFloatTimeDomainData(waveform);
}

Объединение всего кода, обсужденного до настоящего времени, приводит ко всей функции выше. Вызов этой функции будет помещен в нашу функцию audioSetup чуть ниже song.play() . Имея форму волны на месте, нам все еще нужно вывести эту информацию на экран, используя наш элемент canvas , и это следующая часть нашего обсуждения.

Теперь, когда мы создали нашу форму волны и обладаем данными, которые нам необходимы, нам нужно вывести ее на экран; это где элемент canvas вводится.

1
2
3
4
5
function drawOscilloscope() {
    requestAnimationFrame(drawOscilloscope);
    const scopeCanvas = document.getElementById(‘oscilloscope’);
    const scopeContext = scopeCanvas.getContext(‘2d’);
}

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

1
2
3
4
function drawOscilloscope() {
    scopeCanvas.width = waveform.length;
    scopeCanvas.height = 200;
}

Я реализовал основные стили, которые будут рисовать ширину и высоту canvas . Высота установлена ​​на абсолютное значение, а ширина будет длиной формы волны, создаваемой аудиоисточником.

1
2
3
4
function drawOscilloscope() {
  scopeContext.clearRect(0, 0, scopeCanvas.width, scopeCanvas.height);
  scopeContext.beginPath();
}

Метод clearRect(x, y, width, height) любое ранее нарисованное содержимое, чтобы мы могли непрерывно рисовать график частоты. Вы также должны убедиться, что вызываете beginPath() перед началом рисования нового кадра после вызова clearRect() . Этот метод запускает новый путь, очищая список любых и всех подпутей. Последним элементом этой головоломки является цикл для прохождения данных, которые мы получили, чтобы мы могли непрерывно рисовать этот частотный график на экране.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function drawOscilloscope() {
    for(let i = 0; i < waveform.length; i++) {
        const x = i;
        const y = ( 0.5 + (waveform[i] / 2) ) * scopeCanvas.height;
 
        if(i == 0) {
            scopeContext.moveTo(x, y);
        } else {
            scopeContext.lineTo(x, y);
        }
    }
 
    scopeContext.stroke();
}

Этот цикл выше рисует нашу форму волны в элементе canvas . Если мы запишем длину формы волны на консоль (во время воспроизведения аудио), она сообщит 1024 повторно. Обычно это соответствует количеству значений данных, с которыми вам придется поиграть для визуализации. Если вы помните из предыдущего раздела о создании формы волны, мы получаем это значение из Float32Array(analyser.frequencyBinCount) . Вот как мы можем ссылаться на значение 1024, через которое мы будем проходить.

Метод moveTo() будет буквально перемещать начальную точку нового подпути к обновленным (x, y) координатам. Метод lineTo() соединяет последнюю точку в lineTo() с координатами x, y прямой линией (но фактически не рисует ее). Последний фрагмент — это вызов stroke() предоставленный canvas чтобы мы могли на самом деле нарисовать линию частоты. Я оставлю часть, содержащую математику, как задачу для читателя, поэтому обязательно оставьте свой ответ в комментариях ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function drawOscilloscope() {
    requestAnimationFrame(drawOscilloscope);
 
    const scopeCanvas = document.getElementById(‘oscilloscope’);
    const scopeContext = scopeCanvas.getContext(‘2d’);
 
    scopeCanvas.width = waveform.length;
    scopeCanvas.height = 200;
 
    scopeContext.clearRect(0, 0, scopeCanvas.width, scopeCanvas.height);
    scopeContext.beginPath();
 
    for(let i = 0; i < waveform.length; i++) {
        const x = i;
        const y = ( 0.5 + (waveform[i] / 2) ) * scopeCanvas.height;
 
        if(i == 0) {
            scopeContext.moveTo(x, y);
        } else {
            scopeContext.lineTo(x, y);
        }
    }
 
    scopeContext.stroke();
}

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

Я только объяснил важные моменты для демонстрации, но обязательно прочитайте другие части моей демонстрации, чтобы лучше понять, как переключатели и кнопка запуска работают по отношению к коду выше, включая стили CSS ,

API Web Audio действительно интересен всем, кто интересуется аудио, и я призываю вас углубиться в это. Я также собрал несколько действительно интересных примеров из CodePen, которые используют API-интерфейс Web Audio для создания действительно интересных примеров. Наслаждайтесь!