From c05562deefb7251a09b12fedfe3cad9643b92e87 Mon Sep 17 00:00:00 2001 From: Noah Bright Date: Tue, 22 Oct 2024 14:20:41 -0400 Subject: [PATCH] LibMedia+LibWeb: Add computation of biquad filter coefficients In LibMedia, do the computations, and call into that from LibWeb/BiquadFilter --- .../LibMedia/Audio/SignalProcessing.h | 117 ++++++++++++++++-- .../LibWeb/WebAudio/BiquadFilterNode.cpp | 43 +++++++ 2 files changed, 152 insertions(+), 8 deletions(-) diff --git a/Userland/Libraries/LibMedia/Audio/SignalProcessing.h b/Userland/Libraries/LibMedia/Audio/SignalProcessing.h index 6b5e9120f152..bb0f0216278f 100644 --- a/Userland/Libraries/LibMedia/Audio/SignalProcessing.h +++ b/Userland/Libraries/LibMedia/Audio/SignalProcessing.h @@ -8,6 +8,7 @@ #include #include +#include #include namespace Audio { @@ -37,7 +38,7 @@ Vector> biquad_filter_frequency_response(Vector const& frequencies T A2 = coefficients[5] / coefficients[3]; auto transfer_function = [&](Complex z) { - auto z_inv = 1 / z; + auto z_inv = 1.0 / z; auto numerator = B0 + (B1 + B2 * z_inv) * z_inv; auto denominator = 1.0 + (A1 + A2 * z_inv) * z_inv; return numerator / denominator; @@ -47,23 +48,123 @@ Vector> biquad_filter_frequency_response(Vector const& frequencies frequency_response.ensure_capacity(frequencies.size()); for (size_t k = 0; k < frequency_response.size(); k++) { - auto i = Complex(0, 1); - auto arg = cexp(2 * AK::Pi * i * frequencies[k]); + auto i = Complex(0.0, 1.0); + auto arg = cexp(2.0 * AK::Pi * i * frequencies[k]); frequency_response[k] = transfer_function(arg); } return frequency_response; } +using AK::cos; + template Array biquad_filter_lowpass_coefficients(T omega_0, T alpha_Q_dB) { - auto b0 = 0.5 * (1 - cos(omega_0)); - auto b1 = 1 - cos(omega_0); + auto b0 = 0.5 * (1.0 - cos(omega_0)); + auto b1 = 1.0 - cos(omega_0); auto b2 = b0; - auto a0 = 1 + alpha_Q_dB; - auto a1 = -2 * cos(omega_0); - auto a2 = 1 - alpha_Q_dB; + auto a0 = 1.0 + alpha_Q_dB; + auto a1 = -2.0 * cos(omega_0); + auto a2 = 1.0 - alpha_Q_dB; + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_highpass_coefficients(T omega_0, T alpha_Q_dB) +{ + auto b0 = 0.5 * (1.0 + cos(omega_0)); + auto b1 = (1.0 + cos(omega_0)); + auto b2 = b0; + auto a0 = 1.0 + alpha_Q_dB; + auto a1 = -2.0 * cos(omega_0); + auto a2 = 1.0 - alpha_Q_dB; + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_bandpass_coefficients(T omega_0, T alpha_Q) +{ + auto b0 = alpha_Q; + auto b1 = 0.0; + auto b2 = -alpha_Q; + auto a0 = 1.0 + alpha_Q; + auto a1 = -2.0 * cos(omega_0); + auto a2 = 1.0 - alpha_Q; + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_notch_coefficients(T omega_0, T alpha_Q) +{ + auto b0 = 1.0; + auto b1 = -2.0 * cos(omega_0); + auto b2 = 1.0; + auto a0 = 1.0 + alpha_Q; + auto a1 = -2.0 * cos(omega_0); + auto a2 = 1.0 - alpha_Q; + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_allpass_coefficients(T omega_0, T alpha_Q, T A) +{ + auto b0 = 1.0 + alpha_Q * A; + auto b1 = -2.0 * cos(omega_0); + auto b2 = 1.0 - alpha_Q * A; + auto a0 = 1.0 + alpha_Q / A; + auto a1 = b1; + auto a2 = 1.0 - alpha_Q / A; + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_peaking_coefficients(T omega_0, T alpha_Q, T A) +{ + auto b0 = 1.0 + alpha_Q * A; + auto b1 = -2.0 * cos(omega_0); + auto b2 = 1.0 - alpha_Q * A; + auto a0 = 1.0 + alpha_Q / A; + auto a1 = b1; + auto a2 = 1.0 - alpha_Q / A; + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_lowshelf_coefficients(T omega_0, T alpha_S, T A) +{ + auto cosw0 = cos(omega_0); + auto x = 2.0 * alpha_S * AK::sqrt(A); + auto a_plus_1 = A + 1.0; + auto a_minus_1 = A - 1.0; + + auto b0 = A * (a_plus_1 - a_minus_1 * cosw0 + x); + auto b1 = 2.0 * A * (a_minus_1 - a_plus_1 * cosw0); + auto b2 = A * (a_plus_1 - a_minus_1 * cosw0 - x); + + auto a0 = a_plus_1 + a_minus_1 * cosw0 + x; + auto a1 = -2.0 * (a_minus_1 + a_plus_1 * cosw0); + auto a2 = a_plus_1 + a_minus_1 * cosw0 - x; + + return { b0, b1, b2, a0, a1, a2 }; +} + +template +Array biquad_filter_highshelf_coefficients(T omega_0, T alpha_S, T A) +{ + auto cosw0 = cos(omega_0); + auto x = 2.0 * alpha_S * AK::sqrt(A); + auto a_plus_1 = A + 1.0; + auto a_minus_1 = A - 1.0; + + auto b0 = A * (a_plus_1 + a_minus_1 * cosw0 + x); + auto b1 = -2.0 * A * (a_minus_1 + a_plus_1 * cosw0); + auto b2 = A * (a_plus_1 + a_minus_1 * cosw0 - x); + + auto a0 = a_plus_1 - a_minus_1 * cosw0 + x; + auto a1 = -2.0 * (a_minus_1 - a_plus_1 * cosw0); + auto a2 = a_plus_1 - a_minus_1 * cosw0 - x; + return { b0, b1, b2, a0, a1, a2 }; } diff --git a/Userland/Libraries/LibWeb/WebAudio/BiquadFilterNode.cpp b/Userland/Libraries/LibWeb/WebAudio/BiquadFilterNode.cpp index d524f49dc54f..e95b5bcea149 100644 --- a/Userland/Libraries/LibWeb/WebAudio/BiquadFilterNode.cpp +++ b/Userland/Libraries/LibWeb/WebAudio/BiquadFilterNode.cpp @@ -4,11 +4,14 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include #include #include +#include #include namespace Web::WebAudio { @@ -70,6 +73,46 @@ WebIDL::ExceptionOr BiquadFilterNode::get_frequency_response(JS::Handlesample_rate(); + // https://webaudio.github.io/web-audio-api/#computedfrequency + auto f0 = frequency()->value() * pow(2.0, detune()->value() / 1200.0); + auto G = gain()->value(); + auto Q = q()->value(); + + auto A = pow(10, (f64)G / 40.0); + auto omega_0 = 2 * AK::Pi * f0 / F_s; + auto alpha_Q = sin(omega_0) / (2.0 * Q); + auto alpha_Q_dB = 0.5 * sin(omega_0) / (2.0 * pow(10, Q / 20.0)); + auto S = 1.0; + auto alpha_S = 0.5 * sin(omega_0) * sqrt((A + 1.0 / A) * (1.0 / S - 1.0) + 2); + + auto get_coefficients = [&]() { + switch (type()) { + case Bindings::BiquadFilterType::Lowpass: + return Audio::biquad_filter_lowpass_coefficients(omega_0, alpha_Q_dB); + case Bindings::BiquadFilterType::Highpass: + return Audio::biquad_filter_highpass_coefficients(omega_0, alpha_Q_dB); + case Bindings::BiquadFilterType::Bandpass: + return Audio::biquad_filter_bandpass_coefficients(omega_0, alpha_Q_dB); + case Bindings::BiquadFilterType::Notch: + return Audio::biquad_filter_notch_coefficients(omega_0, alpha_Q_dB); + case Bindings::BiquadFilterType::Allpass: + return Audio::biquad_filter_allpass_coefficients(omega_0, alpha_Q, A); + case Bindings::BiquadFilterType::Peaking: + return Audio::biquad_filter_peaking_coefficients(omega_0, alpha_Q, A); + case Bindings::BiquadFilterType::Lowshelf: + return Audio::biquad_filter_lowshelf_coefficients(omega_0, alpha_S, A); + case Bindings::BiquadFilterType::Highshelf: + return Audio::biquad_filter_highshelf_coefficients(omega_0, alpha_S, A); + } + + return Audio::biquad_filter_lowpass_coefficients(omega_0, alpha_Q_dB); + }; + + Array coefficients = get_coefficients(); + (void)coefficients; + return {}; }