Skip to content

Commit

Permalink
LibMedia+LibWeb: Add computation of biquad filter coefficients
Browse files Browse the repository at this point in the history
In LibMedia, do the computations, and call into that from
LibWeb/BiquadFilter
  • Loading branch information
noahmbright committed Oct 24, 2024
1 parent 802480d commit c05562d
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 8 deletions.
117 changes: 109 additions & 8 deletions Userland/Libraries/LibMedia/Audio/SignalProcessing.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <AK/Array.h>
#include <AK/Complex.h>
#include <AK/Math.h>
#include <AK/Vector.h>

namespace Audio {
Expand Down Expand Up @@ -37,7 +38,7 @@ Vector<Complex<T>> biquad_filter_frequency_response(Vector<T> const& frequencies
T A2 = coefficients[5] / coefficients[3];

auto transfer_function = [&](Complex<T> 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;
Expand All @@ -47,23 +48,123 @@ Vector<Complex<T>> biquad_filter_frequency_response(Vector<T> const& frequencies
frequency_response.ensure_capacity(frequencies.size());

for (size_t k = 0; k < frequency_response.size(); k++) {
auto i = Complex<T>(0, 1);
auto arg = cexp(2 * AK::Pi<T> * i * frequencies[k]);
auto i = Complex<T>(0.0, 1.0);
auto arg = cexp(2.0 * AK::Pi<T> * i * frequencies[k]);
frequency_response[k] = transfer_function(arg);
}

return frequency_response;
}

using AK::cos;

template<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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<AK::Concepts::Arithmetic T>
Array<T, 6> 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 };
}

Expand Down
43 changes: 43 additions & 0 deletions Userland/Libraries/LibWeb/WebAudio/BiquadFilterNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <AK/Math.h>
#include <LibMedia/Audio/SignalProcessing.h>
#include <LibWeb/Bindings/AudioParamPrototype.h>
#include <LibWeb/Bindings/BiquadFilterNodePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAudio/AudioNode.h>
#include <LibWeb/WebAudio/AudioParam.h>
#include <LibWeb/WebAudio/BaseAudioContext.h>
#include <LibWeb/WebAudio/BiquadFilterNode.h>

namespace Web::WebAudio {
Expand Down Expand Up @@ -70,6 +73,46 @@ WebIDL::ExceptionOr<void> BiquadFilterNode::get_frequency_response(JS::Handle<We
(void)mag_response;
(void)phase_response;
dbgln("FIXME: Implement BiquadFilterNode::get_frequency_response(Float32Array, Float32Array, Float32Array)");

auto F_s = context()->sample_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<f64> * 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<f64, 6> coefficients = get_coefficients();
(void)coefficients;

return {};
}

Expand Down

0 comments on commit c05562d

Please sign in to comment.