diff --git a/src/daemon/filter-chain/meson.build b/src/daemon/filter-chain/meson.build index a33e6a296..4b9df2fa8 100644 --- a/src/daemon/filter-chain/meson.build +++ b/src/daemon/filter-chain/meson.build @@ -1,5 +1,6 @@ conf_files = [ [ 'demonic.conf', 'demonic.conf' ], + [ 'sink-eq6.conf', 'sink-eq6.conf' ], [ 'source-rnnoise.conf', 'source-rnnoise.conf' ], ] diff --git a/src/daemon/filter-chain/sink-eq6.conf b/src/daemon/filter-chain/sink-eq6.conf new file mode 100644 index 000000000..253cd6644 --- /dev/null +++ b/src/daemon/filter-chain/sink-eq6.conf @@ -0,0 +1,88 @@ +# 6 band sink equalizer +# +# start with pipewire -c filter-chain/sink-eq6.conf +# +context.properties = { + log.level = 0 +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + { name = libpipewire-module-rtkit + args = { + #nice.level = -11 + #rt.prio = 88 + #rt.time.soft = 200000 + #rt.time.hard = 200000 + } + flags = [ ifexists nofail ] + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-client-node } + { name = libpipewire-module-adapter } + + { name = libpipewire-module-filter-chain + args = { + node.name = "eq_sink" + node.description = "Equalizer Sink" + media.name = "Equalizer Sink" + filter.graph = { + nodes = [ + { + type = builtin + name = eq_band_1 + label = bq_lowshelf + control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_2 + label = bq_peaking + control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_3 + label = bq_peaking + control = { "Freq" = 500.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_4 + label = bq_peaking + control = { "Freq" = 2000.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_5 + label = bq_peaking + control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_6 + label = bq_highshelf + control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 } + } + ] + links = [ + { output = "eq_band_1:Out" input = "eq_band_2:In" } + { output = "eq_band_2:Out" input = "eq_band_3:In" } + { output = "eq_band_3:Out" input = "eq_band_4:In" } + { output = "eq_band_4:Out" input = "eq_band_5:In" } + { output = "eq_band_5:Out" input = "eq_band_6:In" } + ] + } + capture.props = { + media.class = Audio/Sink + } + playback.props = { + node.passive = true + } + } + } +] diff --git a/src/modules/meson.build b/src/modules/meson.build index eb63ef35f..905a3e965 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -25,7 +25,8 @@ pipewire_module_loopback = shared_library('pipewire-module-loopback', ) pipewire_module_filter_chain = shared_library('pipewire-module-filter-chain', - [ 'module-filter-chain.c' ], + [ 'module-filter-chain.c', + 'module-filter-chain/biquad.c' ], c_args : pipewire_module_c_args, include_directories : [configinc, spa_inc], install : true, diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 53bf0132d..c4b58d1a3 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -534,7 +534,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_NODE_LATENCY); - if ((str = pw_properties_get(props, "aec,method")) == NULL) + if ((str = pw_properties_get(props, "aec.method")) == NULL) str = "null"; if (strcmp(str, "webrtc") == 0) impl->aec_info = echo_cancel_webrtc; diff --git a/src/modules/module-filter-chain/biquad.c b/src/modules/module-filter-chain/biquad.c new file mode 100644 index 000000000..337a43938 --- /dev/null +++ b/src/modules/module-filter-chain/biquad.c @@ -0,0 +1,381 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Copyright (C) 2010 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.WEBKIT file. + */ + +#include +#include "biquad.h" + +#ifndef max +#define max(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#endif + +#ifndef min +#define min(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, + double a0, double a1, double a2) +{ + double a0_inv = 1 / a0; + bq->b0 = b0 * a0_inv; + bq->b1 = b1 * a0_inv; + bq->b2 = b2 * a0_inv; + bq->a1 = a1 * a0_inv; + bq->a2 = a2 * a0_inv; +} + +static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = max(0.0, min(cutoff, 1.0)); + + if (cutoff == 1 || cutoff == 0) { + /* When cutoff is 1, the z-transform is 1. + * When cutoff is zero, nothing gets through the filter, so set + * coefficients up correctly. + */ + set_coefficient(bq, cutoff, 0, 0, 1, 0, 0); + return; + } + + /* Compute biquad coefficients for lowpass filter */ + resonance = max(0.0, resonance); /* can't go negative */ + double g = pow(10.0, 0.05 * resonance); + double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + double theta = M_PI * cutoff; + double sn = 0.5 * d * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta - gamma); + + double b0 = 2 * alpha; + double b1 = 2 * 2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); +} + +static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = max(0.0, min(cutoff, 1.0)); + + if (cutoff == 1 || cutoff == 0) { + /* When cutoff is one, the z-transform is 0. */ + /* When cutoff is zero, we need to be careful because the above + * gives a quadratic divided by the same quadratic, with poles + * and zeros on the unit circle in the same place. When cutoff + * is zero, the z-transform is 1. + */ + set_coefficient(bq, 1 - cutoff, 0, 0, 1, 0, 0); + return; + } + + /* Compute biquad coefficients for highpass filter */ + resonance = max(0.0, resonance); /* can't go negative */ + double g = pow(10.0, 0.05 * resonance); + double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + double theta = M_PI * cutoff; + double sn = 0.5 * d * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta + gamma); + + double b0 = 2 * alpha; + double b1 = 2 * -2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); +} + +static void biquad_bandpass(struct biquad *bq, double frequency, double Q) +{ + /* No negative frequencies allowed. */ + frequency = max(0.0, frequency); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + if (frequency <= 0 || frequency >= 1) { + /* When the cutoff is zero, the z-transform approaches 0, if Q + * > 0. When both Q and cutoff are zero, the z-transform is + * pretty much undefined. What should we do in this case? + * For now, just make the filter 0. When the cutoff is 1, the + * z-transform also approaches 0. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + return; + } + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is 1, so set the filter that way. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = alpha; + double b1 = 0; + double b2 = -alpha; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_lowshelf(struct biquad *bq, double frequency, double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + double A = pow(10.0, db_gain / 40); + + if (frequency == 1) { + /* The z-transform is a constant gain. */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + return; + } + if (frequency <= 0) { + /* When frequency is 0, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double S = 1; /* filter slope (1 is max value) */ + double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2); + double k = cos(w0); + double k2 = 2 * sqrt(A) * alpha; + double a_plus_one = A + 1; + double a_minus_one = A - 1; + + double b0 = A * (a_plus_one - a_minus_one * k + k2); + double b1 = 2 * A * (a_minus_one - a_plus_one * k); + double b2 = A * (a_plus_one - a_minus_one * k - k2); + double a0 = a_plus_one + a_minus_one * k + k2; + double a1 = -2 * (a_minus_one + a_plus_one * k); + double a2 = a_plus_one + a_minus_one * k - k2; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_highshelf(struct biquad *bq, double frequency, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + double A = pow(10.0, db_gain / 40); + + if (frequency == 1) { + /* The z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + if (frequency <= 0) { + /* When frequency = 0, the filter is just a gain, A^2. */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double S = 1; /* filter slope (1 is max value) */ + double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2); + double k = cos(w0); + double k2 = 2 * sqrt(A) * alpha; + double a_plus_one = A + 1; + double a_minus_one = A - 1; + + double b0 = A * (a_plus_one + a_minus_one * k + k2); + double b1 = -2 * A * (a_minus_one + a_plus_one * k); + double b2 = A * (a_plus_one + a_minus_one * k - k2); + double a0 = a_plus_one - a_minus_one * k + k2; + double a1 = 2 * (a_minus_one - a_plus_one * k); + double a2 = a_plus_one - a_minus_one * k - k2; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_peaking(struct biquad *bq, double frequency, double Q, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + double A = pow(10.0, db_gain / 40); + + if (frequency <= 0 || frequency >= 1) { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is A^2, so set the filter that way. + */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1 + alpha * A; + double b1 = -2 * k; + double b2 = 1 - alpha * A; + double a0 = 1 + alpha / A; + double a1 = -2 * k; + double a2 = 1 - alpha / A; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_notch(struct biquad *bq, double frequency, double Q) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + if (frequency <= 0 || frequency >= 1) { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is 0, so set the filter that way. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1; + double b1 = -2 * k; + double b2 = 1; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_allpass(struct biquad *bq, double frequency, double Q) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = max(0.0, min(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = max(0.0, Q); + + if (frequency <= 0 || frequency >= 1) { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is -1, so set the filter that way. + */ + set_coefficient(bq, -1, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1 - alpha; + double b1 = -2 * k; + double b2 = 1 + alpha; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain) +{ + /* Default is an identity filter. Also clear history values. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + bq->x1 = 0; + bq->x2 = 0; + bq->y1 = 0; + bq->y2 = 0; + + switch (type) { + case BQ_LOWPASS: + biquad_lowpass(bq, freq, Q); + break; + case BQ_HIGHPASS: + biquad_highpass(bq, freq, Q); + break; + case BQ_BANDPASS: + biquad_bandpass(bq, freq, Q); + break; + case BQ_LOWSHELF: + biquad_lowshelf(bq, freq, gain); + break; + case BQ_HIGHSHELF: + biquad_highshelf(bq, freq, gain); + break; + case BQ_PEAKING: + biquad_peaking(bq, freq, Q, gain); + break; + case BQ_NOTCH: + biquad_notch(bq, freq, Q); + break; + case BQ_ALLPASS: + biquad_allpass(bq, freq, Q); + break; + case BQ_NONE: + break; + } +} diff --git a/src/modules/module-filter-chain/biquad.h b/src/modules/module-filter-chain/biquad.h new file mode 100644 index 000000000..c584aa96e --- /dev/null +++ b/src/modules/module-filter-chain/biquad.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef BIQUAD_H_ +#define BIQUAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) + * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs + * are stored in x1 and x2, and the previous two outputs are stored in y1 and + * y2. + * + * We use double during the coefficients calculation for better accurary, but + * float is used during the actual filtering for faster computation. + */ +struct biquad { + float b0, b1, b2; + float a1, a2; + float x1, x2; + float y1, y2; +}; + +/* The type of the biquad filters */ +enum biquad_type { + BQ_NONE, + BQ_LOWPASS, + BQ_HIGHPASS, + BQ_BANDPASS, + BQ_LOWSHELF, + BQ_HIGHSHELF, + BQ_PEAKING, + BQ_NOTCH, + BQ_ALLPASS +}; + +/* Initialize a biquad filter parameters from its type and parameters. + * Args: + * bq - The biquad filter we want to set. + * type - The type of the biquad filter. + * frequency - The value should be in the range [0, 1]. It is relative to + * half of the sampling rate. + * Q - Quality factor. See Web Audio API for details. + * gain - The value is in dB. See Web Audio API for details. + */ +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* BIQUAD_H_ */ diff --git a/src/modules/module-filter-chain/builtin.h b/src/modules/module-filter-chain/builtin.h index fdab38563..93b29e217 100644 --- a/src/modules/module-filter-chain/builtin.h +++ b/src/modules/module-filter-chain/builtin.h @@ -22,8 +22,16 @@ * DEALINGS IN THE SOFTWARE. */ +#include "biquad.h" + struct builtin { + unsigned long rate; LADSPA_Data *port[64]; + + struct biquad bq; + float freq; + float Q; + float gain; }; static LADSPA_Handle builtin_instantiate(const struct _LADSPA_Descriptor * Descriptor, @@ -35,6 +43,8 @@ static LADSPA_Handle builtin_instantiate(const struct _LADSPA_Descriptor * Descr if (impl == NULL) return NULL; + impl->rate = SampleRate; + return impl; } @@ -104,11 +114,269 @@ static const LADSPA_Descriptor mixer_desc = { .cleanup = builtin_cleanup, }; + +static const LADSPA_PortDescriptor bq_port_desc[] = { + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, +}; + +static const char * const bq_port_names[] = { + "Out", "In", "Freq", "Q", "Gain" +}; + +static const LADSPA_PortRangeHint bq_range_hints[] = { + { 0, }, { 0, }, + { LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_SAMPLE_RATE | LADSPA_HINT_DEFAULT_LOW, 0.0, 1.0 }, + { LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_DEFAULT_0, 0.0, 10.0 }, + { LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_DEFAULT_0, -120.0, 5.0 }, +}; + +static void bq_run(struct builtin *impl, unsigned long samples, int type) +{ + struct biquad *bq = &impl->bq; + unsigned long i; + float *out = impl->port[0]; + float *in = impl->port[1]; + float freq = impl->port[2][0]; + float Q = impl->port[3][0]; + float gain = impl->port[4][0]; + float x1, x2, y1, y2; + float b0, b1, b2, a1, a2; + + if (impl->freq != freq || impl->Q != Q || impl->gain != gain) { + impl->freq = freq; + impl->Q = Q; + impl->gain = gain; + biquad_set(bq, type, freq / impl->rate, Q, gain); + } + x1 = bq->x1; + x2 = bq->x2; + y1 = bq->y1; + y2 = bq->y2; + b0 = bq->b0; + b1 = bq->b1; + b2 = bq->b2; + a1 = bq->a1; + a2 = bq->a2; + for (i = 0; i < samples; i++) { + float x = in[i]; + float y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + out[i] = y; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + bq->x1 = x1; + bq->x2 = x2; + bq->y1 = y1; + bq->y2 = y2; +} + +/** bq_lowpass */ +static void bq_lowpass_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_LOWPASS); +} + +static const LADSPA_Descriptor bq_lowpass_desc = { + .Label = "bq_lowpass", + .Name = "Biquad lowpass filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_lowpass_run, + .cleanup = builtin_cleanup, +}; + +/** bq_highpass */ +static void bq_highpass_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_HIGHPASS); +} + +static const LADSPA_Descriptor bq_highpass_desc = { + .Label = "bq_highpass", + .Name = "Biquad highpass filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_highpass_run, + .cleanup = builtin_cleanup, +}; + +/** bq_bandpass */ +static void bq_bandpass_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_BANDPASS); +} + +static const LADSPA_Descriptor bq_bandpass_desc = { + .Label = "bq_bandpass", + .Name = "Biquad bandpass filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_bandpass_run, + .cleanup = builtin_cleanup, +}; + +/** bq_lowshelf */ +static void bq_lowshelf_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_LOWSHELF); +} + +static const LADSPA_Descriptor bq_lowshelf_desc = { + .Label = "bq_lowshelf", + .Name = "Biquad lowshelf filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_lowshelf_run, + .cleanup = builtin_cleanup, +}; + +/** bq_highshelf */ +static void bq_highshelf_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_HIGHSHELF); +} + +static const LADSPA_Descriptor bq_highshelf_desc = { + .Label = "bq_highshelf", + .Name = "Biquad highshelf filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_highshelf_run, + .cleanup = builtin_cleanup, +}; + +/** bq_peaking */ +static void bq_peaking_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_PEAKING); +} + +static const LADSPA_Descriptor bq_peaking_desc = { + .Label = "bq_peaking", + .Name = "Biquad peaking filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_peaking_run, + .cleanup = builtin_cleanup, +}; + +/** bq_notch */ +static void bq_notch_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_NOTCH); +} + +static const LADSPA_Descriptor bq_notch_desc = { + .Label = "bq_notch", + .Name = "Biquad notch filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_notch_run, + .cleanup = builtin_cleanup, +}; + + +/** bq_allpass */ +static void bq_allpass_run(LADSPA_Handle Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + bq_run(impl, SampleCount, BQ_ALLPASS); +} + +static const LADSPA_Descriptor bq_allpass_desc = { + .Label = "bq_allpass", + .Name = "Biquad allpass filter", + .Maker = "PipeWire", + .Copyright = "MIT", + .PortCount = 5, + .PortDescriptors = bq_port_desc, + .PortNames = bq_port_names, + .PortRangeHints = bq_range_hints, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = bq_allpass_run, + .cleanup = builtin_cleanup, +}; + static const LADSPA_Descriptor * builtin_ladspa_descriptor(unsigned long Index) { switch(Index) { case 0: return &mixer_desc; + case 1: + return &bq_lowpass_desc; + case 2: + return &bq_highpass_desc; + case 3: + return &bq_bandpass_desc; + case 4: + return &bq_lowshelf_desc; + case 5: + return &bq_highshelf_desc; + case 6: + return &bq_peaking_desc; + case 7: + return &bq_notch_desc; + case 8: + return &bq_allpass_desc; } return NULL; }