Использование фильтров IIR
Интерфейс IIRFilterNode из API веб-аудио — это AudioNode процессор, который реализует общий фильтр с бесконечной импульсной характеристикой (IIR); этот тип фильтра может использоваться для реализации устройств управления тоном и графических эквалайзеров, а параметры отклика фильтра можно задать, чтобы настроить его по мере необходимости. В этой статье рассматривается, как реализовать фильтр и использовать его в простом примере.
Демо
Наш простой пример для этого руководства предоставляет кнопку воспроизведения/паузы, которая запускает и приостанавливает воспроизведение аудио, и переключатель, который включает и выключает фильтр IIR, изменяя тембр звука. Он также предоставляет холст, на котором отображается частотная характеристика аудио, чтобы вы могли видеть, какое влияние оказывает фильтр IIR.
Вы можете ознакомиться с полным демо на CodePen. Также обратите внимание на исходный код на GitHub. Он включает несколько значений коэффициентов для различных низкочастотных фильтров — вы можете изменить значение константы filterNumber на значение от 0 до 3, чтобы проверить различные доступные эффекты.
Поддержка браузеров
Фильтры IIR хорошо поддерживаются современными браузерами, хотя они были реализованы позднее, чем некоторые более устоявшиеся функции API веб-аудио, такие как фильтры Биквадра.
Фильтр IIRFilterNode
API веб-аудио теперь содержит интерфейс IIRFilterNode. Но что это такое и чем он отличается от BiquadFilterNode, который у нас уже есть?
Фильтр IIR — это фильтр с бесконечной импульсной характеристикой. Он является одним из двух основных типов фильтров, используемых в аудио и цифровой обработке сигналов. Другой тип — FIR — фильтр с конечной импульсной характеристикой. Очень хорошее описание фильтров IIR и FIR можно найти по ссылке.
Фильтр биквадра на самом деле является конкретным типом фильтра с бесконечной импульсной характеристикой. Это распространенный тип, и мы уже имеем его в виде узла в API веб-аудио. Если вы выберете этот узел, сложная работа выполняется за вас. Например, если вы хотите отфильтровать низкие частоты из вашего звука, вы можете установить тип на highpass и затем установить частоту, которую нужно отфильтровать (или отсечь).
Когда вы используете IIRFilterNode вместо BiquadFilterNode, вы создаёте фильтр самостоятельно, а не просто выбираете предварительно запрограммированный тип. Таким образом, вы можете создать фильтр верхних частот, фильтр нижних частот или более индивидуальный. И именно здесь полезен узел фильтра IIR — вы можете создать свой собственный, если ни один из уже доступных параметров не подходит для ваших потребностей. Кроме того, если вашей аудиограмме нужны фильтр верхних частот и полосовой фильтр, вы можете использовать один узел фильтра IIR вместо двух узлов фильтра биквадра, которые в противном случае вам бы потребовались для этого.
С помощью узла фильтра IIR вам нужно самостоятельно задать значения feedforward и feedback, определяющие характеристики фильтра. Недостаток заключается в том, что это требует некоторых сложных математических расчётов.
Если вы хотите узнать больше, вы можете найти информацию о математике, лежащей в основе фильтров IIR, по ссылке. Это уже область теории обработки сигналов — не беспокойтесь, если она кажется вам сложной.
Если вы хотите поиграть с узлом фильтра IIR и вам нужны значения для помощи, по ссылке представлена таблица уже рассчитанных значений; на страницах 4 и 5 связанного PDF-файла значения an относятся к значениям feedForward, а значения bn относятся к значениям feedback. musicdsp.org также является отличным ресурсом, если вы хотите узнать больше о различных фильтрах и о том, как они реализуются в цифровом виде.
С учётом всего этого, давайте посмотрим на код для создания фильтра IIR с помощью API веб-аудио.
Установка коэффициентов IIRFilter
При создании фильтра IIR мы передаём коэффициенты feedforward и feedback в качестве параметров (коэффициенты — так мы называем значения). Оба этих параметра являются массивами, ни один из которых не может содержать более 20 элементов.
При установке коэффициентов значения feedforward не могут быть все нулевыми, иначе ничего не будет передано фильтру. Пример приемлемого значения:
const feedForward = [0.00020298, 0.0004059599, 0.00020298];
Значения feedback не могут начинаться с нуля, иначе при первом проходе ничего не будет возвращено:
const feedBackward = [1.0126964558, -1.9991880801, 0.9873035442];
Примечание: Эти значения рассчитываются на основе фильтра нижних частот, указанного в спецификации API веб-аудио. По мере повышения популярности этого узла фильтров мы должны иметь возможность собрать больше значений коэффициентов.
Использование IIRFilter в аудиографе
Давайте создадим контекст и узел фильтра:
const audioCtx = new AudioContext(); const iirFilter = audioCtx.createIIRFilter(feedForward, feedBack);
Нам нужен источник звука для воспроизведения. Мы настраиваем его с помощью пользовательской функции playSoundNode(), которая создаёт источник буфера из существующего AudioBuffer, подключает его к целевому устройству по умолчанию, начинает его воспроизведение и возвращает его:
function playSourceNode(audioContext, audioBuffer) {
const soundSource = audioContext.createBufferSource();
soundSource.buffer = audioBuffer;
soundSource.connect(audioContext.destination);
soundSource.start();
return soundSource;
}
Эта функция вызывается при нажатии кнопки воспроизведения. HTML кнопки воспроизведения выглядит так:
<button class="button-play" role="switch" data-playing="false" aria-pressed="false"> Play </button>
И обработчик события click начинается следующим образом:
playButton.addEventListener(
"click",
() => {
if (playButton.dataset.playing === "false") {
srcNode = playSourceNode(audioCtx, sample);
// …
}
},
false,
);
Переключатель, включающий и выключающий фильтр IIR, настраивается аналогичным образом. Сначала HTML:
<button class="button-filter" role="switch" data-filteron="false" aria-pressed="false" aria-describedby="label" disabled></button>
Обработчик кнопки фильтра click затем подключает IIRFilter к графу между источником и конечной точкой:
filterButton.addEventListener(
"click",
() => {
if (filterButton.dataset.filteron === "false") {
srcNode.disconnect(audioCtx.destination);
srcNode.connect(iirFilter).connect(audioCtx.destination);
// …
}
},
false,
);
Частотная характеристика
У экземпляров IIRFilterNode доступен только один метод getFrequencyResponse(), который позволяет нам увидеть, что происходит с частотами аудио, проходящего через фильтр.
Давайте построим график частотной характеристики созданного нами фильтра с данными, полученными из этого метода.
Нам нужно создать три массива. Один массив значений частот, для которых мы хотим получить амплитудно-частотную и фазо-частотную характеристики, и два пустых массива для получения данных. Все три должны быть типа float32array и должны иметь одинаковый размер.
// arrays for our frequency response const totalArrayItems = 30; let myFrequencyArray = new Float32Array(totalArrayItems); const magResponseOutput = new Float32Array(totalArrayItems); const phaseResponseOutput = new Float32Array(totalArrayItems);
Давайте заполним первый массив значениями частот, для которых мы хотим получить данные:
myFrequencyArray = myFrequencyArray.map((item, index) => 1.4 ** index);
Мы могли бы использовать линейный подход, но при работе с частотами гораздо лучше использовать логарифмический подход, поэтому давайте заполним массив значениями частот, которые увеличиваются далее в элементах массива.
Теперь давайте получим данные отклика:
iirFilter.getFrequencyResponse( myFrequencyArray, magResponseOutput, phaseResponseOutput, );
Мы можем использовать эти данные для построения графика частотной характеристики фильтра. Мы сделаем это на контексте 2D-холста.
// Create a canvas element and append it to our DOM
const canvasContainer = document.querySelector(".filter-graph");
const canvasEl = document.createElement("canvas");
canvasContainer.appendChild(canvasEl);
// Set 2d context and set dimensions
const canvasCtx = canvasEl.getContext("2d");
const width = canvasContainer.offsetWidth;
const height = canvasContainer.offsetHeight;
canvasEl.width = width;
canvasEl.height = height;
// Set background fill
canvasCtx.fillStyle = "white";
canvasCtx.fillRect(0, 0, width, height);
// Set up some spacing based on size
const spacing = width / 16;
const fontSize = Math.floor(spacing / 1.5);
// Draw our axis
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "grey";
canvasCtx.beginPath();
canvasCtx.moveTo(spacing, spacing);
canvasCtx.lineTo(spacing, height - spacing);
canvasCtx.lineTo(width - spacing, height - spacing);
canvasCtx.stroke();
// Axis is gain by frequency -> make labels
canvasCtx.font = `${fontSize}px sans-serif`;
canvasCtx.fillStyle = "grey";
canvasCtx.fillText("1", spacing - fontSize, spacing + fontSize);
canvasCtx.fillText("g", spacing - fontSize, (height - spacing + fontSize) / 2);
canvasCtx.fillText("0", spacing - fontSize, height - spacing + fontSize);
canvasCtx.fillText("Hz", width / 2, height - spacing + fontSize);
canvasCtx.fillText("20k", width - spacing, height - spacing + fontSize);
// Loop over our magnitude response data and plot our filter
canvasCtx.beginPath();
magResponseOutput.forEach((magResponseData, i) => {
if (i === 0) {
canvasCtx.moveTo(spacing, height - magResponseData * 100 - spacing);
} else {
canvasCtx.lineTo(
(width / totalArrayItems) * i,
height - magResponseData * 100 - spacing,
);
}
});
canvasCtx.stroke();
Заключение
Это всё для нашей демонстрации IIRFilter. Это должно было показать вам основы использования, помочь вам понять, для чего он нужен и как он работает.
© 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/Using_IIR_filters