module-filter-chain: add raw biquad

Add bq_raw that allows access to the raw biquad filter values.

Fixes #3139
This commit is contained in:
Wim Taymans 2023-04-06 18:28:49 +02:00
parent 950c4487e9
commit 880c1b0bd6
2 changed files with 336 additions and 90 deletions

View file

@ -162,7 +162,11 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
*
* All biquad filters have an input port "In" and an output port "Out". They have
* a "Freq", "Q" and "Gain" control. Their meaning depends on the particular biquad that
* is used. The following labels can be used:
* is used. The biquads also have "b0", "b1", "b2", "a0", "a1" and "a2" ports that
* are read-only except for the bq_raw biquad, which can configure default values
* depending on the graph rate and change those at runtime.
*
* The following labels can be used:
*
* - `bq_lowpass` a lowpass filter.
* - `bq_highpass` a highpass filter.
@ -172,6 +176,30 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
* - `bq_peaking` a peaking filter.
* - `bq_notch` a notch filter.
* - `bq_allpass` an allpass filter.
* - `bq_raw` a raw biquad filter. You need a config section to specify coefficients
* per sample rate. The coefficients of the sample rate closest to the
* graph rate are selected:
*
*\code{.unparsed}
* filter.graph = {
* nodes = [
* {
* type = builtin
* name = ...
* label = bq_raw
* config = {
* coefficients = [
* { rate = 44100, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. },
* { rate = 48000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. },
* { rate = 192000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }
* ]
* }
* ...
* }
* }
* ...
* }
*\endcode
*
* ### Convolver
*
@ -1006,6 +1034,20 @@ static void node_control_changed(struct node *node)
node->control_changed = false;
}
static void update_props_param(struct impl *impl)
{
struct graph *graph = &impl->graph;
uint8_t buffer[1024];
struct spa_pod_dynamic_builder b;
const struct spa_pod *params[1];
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
params[0] = get_props_param(graph, &b.b);
pw_stream_update_params(impl->capture, params, 1);
spa_pod_dynamic_builder_clean(&b);
}
static void param_props_changed(struct impl *impl, const struct spa_pod *param)
{
struct spa_pod_object *obj = (struct spa_pod_object *) param;
@ -1018,19 +1060,12 @@ static void param_props_changed(struct impl *impl, const struct spa_pod *param)
changed += parse_params(graph, &prop->value);
}
if (changed > 0) {
uint8_t buffer[1024];
struct spa_pod_dynamic_builder b;
const struct spa_pod *params[1];
struct node *node;
spa_list_for_each(node, &graph->node_list, link)
node_control_changed(node);
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
params[0] = get_props_param(graph, &b.b);
pw_stream_update_params(impl->capture, params, 1);
spa_pod_dynamic_builder_clean(&b);
update_props_param(impl);
}
}
@ -1847,6 +1882,7 @@ static int graph_instantiate(struct graph *graph)
d->control_changed(node->hndl[i]);
}
}
update_props_param(impl);
return 0;
error:
graph_cleanup(graph);

View file

@ -32,10 +32,13 @@ struct builtin {
unsigned long rate;
float *port[64];
int type;
struct biquad bq;
float freq;
float Q;
float gain;
float b0, b1, b2;
float a0, a1, a2;
};
static void *builtin_instantiate(const struct fc_descriptor * Descriptor,
@ -215,6 +218,159 @@ static const struct fc_descriptor mixer_desc = {
.cleanup = builtin_cleanup,
};
/** biquads */
static int bq_type_from_name(const char *name)
{
if (spa_streq(name, "bq_lowpass"))
return BQ_LOWPASS;
if (spa_streq(name, "bq_highpass"))
return BQ_HIGHPASS;
if (spa_streq(name, "bq_bandpass"))
return BQ_BANDPASS;
if (spa_streq(name, "bq_lowshelf"))
return BQ_LOWSHELF;
if (spa_streq(name, "bq_highshelf"))
return BQ_HIGHSHELF;
if (spa_streq(name, "bq_peaking"))
return BQ_PEAKING;
if (spa_streq(name, "bq_notch"))
return BQ_NOTCH;
if (spa_streq(name, "bq_allpass"))
return BQ_ALLPASS;
if (spa_streq(name, "bq_raw"))
return BQ_NONE;
return BQ_NONE;
}
static void bq_raw_update(struct builtin *impl, float b0, float b1, float b2,
float a0, float a1, float a2)
{
struct biquad *bq = &impl->bq;
impl->b0 = b0;
impl->b1 = b1;
impl->b2 = b2;
impl->a0 = a0;
impl->a1 = a1;
impl->a2 = a2;
if (a0 != 0.0f)
a0 = 1.0f / a0;
bq->b0 = impl->b0 * a0;
bq->b1 = impl->b1 * a0;
bq->b2 = impl->b2 * a0;
bq->a1 = impl->a1 * a0;
bq->a2 = impl->a2 * a0;
bq->x1 = bq->x2 = bq->y1 = bq->y2 = 0.0;
}
/*
* config = {
* coefficients = [
* { rate = 44100, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. },
* { rate = 48000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. },
* { rate = 192000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }
* ]
* }
*/
static void *bq_instantiate(const struct fc_descriptor * Descriptor,
unsigned long SampleRate, int index, const char *config)
{
struct builtin *impl;
struct spa_json it[4];
const char *val;
char key[256];
uint32_t best_rate = 0;
impl = calloc(1, sizeof(*impl));
if (impl == NULL)
return NULL;
impl->rate = SampleRate;
impl->b0 = impl->a0 = 1.0f;
impl->type = bq_type_from_name(Descriptor->name);
if (config == NULL)
goto error;
spa_json_init(&it[0], config, strlen(config));
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
goto error;
while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
if (spa_streq(key, "coefficients")) {
if (spa_json_enter_array(&it[1], &it[2]) <= 0) {
pw_log_error("biquads:coefficients require an array");
goto error;
}
while (spa_json_enter_object(&it[2], &it[3]) > 0) {
int32_t rate = 0;
float b0 = 1.0f, b1 = 0.0f, b2 = 0.0f;
float a0 = 1.0f, a1 = 0.0f, a2 = 0.0f;
while (spa_json_get_string(&it[3], key, sizeof(key)) > 0) {
if (spa_streq(key, "rate")) {
if (spa_json_get_int(&it[3], &rate) <= 0) {
pw_log_error("biquads:rate requires a number");
goto error;
}
}
else if (spa_streq(key, "b0")) {
if (spa_json_get_float(&it[3], &b0) <= 0) {
pw_log_error("biquads:b0 requires a float");
goto error;
}
}
else if (spa_streq(key, "b1")) {
if (spa_json_get_float(&it[3], &b1) <= 0) {
pw_log_error("biquads:b1 requires a float");
goto error;
}
}
else if (spa_streq(key, "b2")) {
if (spa_json_get_float(&it[3], &b2) <= 0) {
pw_log_error("biquads:b2 requires a float");
goto error;
}
}
else if (spa_streq(key, "a0")) {
if (spa_json_get_float(&it[3], &a0) <= 0) {
pw_log_error("biquads:a0 requires a float");
goto error;
}
}
else if (spa_streq(key, "a1")) {
if (spa_json_get_float(&it[3], &a1) <= 0) {
pw_log_error("biquads:a1 requires a float");
goto error;
}
}
else if (spa_streq(key, "a2")) {
if (spa_json_get_float(&it[3], &a2) <= 0) {
pw_log_error("biquads:a0 requires a float");
goto error;
}
}
else if (spa_json_next(&it[1], &val) < 0)
break;
}
if (labs((long)rate - (long)SampleRate) <
labs((long)best_rate - (long)SampleRate)) {
best_rate = rate;
bq_raw_update(impl, b0, b1, b2, a0, a1, a2);
}
}
}
else if (spa_json_next(&it[1], &val) < 0)
break;
}
return impl;
error:
free(impl);
errno = EINVAL;
return NULL;
}
#define BQ_NUM_PORTS 11
static struct fc_port bq_ports[] = {
{ .index = 0,
.name = "Out",
@ -240,176 +396,227 @@ static struct fc_port bq_ports[] = {
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 0.0f, .min = -120.0f, .max = 20.0f,
},
{ .index = 5,
.name = "b0",
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 1.0f, .min = -10.0f, .max = 10.0f,
},
{ .index = 6,
.name = "b1",
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 0.0f, .min = -10.0f, .max = 10.0f,
},
{ .index = 7,
.name = "b2",
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 0.0f, .min = -10.0f, .max = 10.0f,
},
{ .index = 8,
.name = "a0",
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 1.0f, .min = -10.0f, .max = 10.0f,
},
{ .index = 9,
.name = "a1",
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 0.0f, .min = -10.0f, .max = 10.0f,
},
{ .index = 10,
.name = "a2",
.flags = FC_PORT_INPUT | FC_PORT_CONTROL,
.def = 0.0f, .min = -10.0f, .max = 10.0f,
},
};
static void bq_run(struct builtin *impl, unsigned long samples, int type)
static void bq_freq_update(struct builtin *impl, int type, float freq, float Q, float gain)
{
struct biquad *bq = &impl->bq;
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];
if (impl->freq != freq || impl->Q != Q || impl->gain != gain) {
impl->freq = freq;
impl->Q = Q;
impl->gain = gain;
biquad_set(bq, type, freq * 2 / impl->rate, Q, gain);
impl->port[5][0] = impl->b0 = bq->b0;
impl->port[6][0] = impl->b1 = bq->b1;
impl->port[7][0] = impl->b2 = bq->b2;
impl->port[8][0] = impl->a0 = 1.0f;
impl->port[9][0] = impl->a1 = bq->a1;
impl->port[10][0] = impl->a2 = bq->a2;
}
static void bq_activate(void * Instance)
{
struct builtin *impl = Instance;
if (impl->type == BQ_NONE) {
float b0, b1, b2, a0, a1, a2;
b0 = impl->port[5][0];
b1 = impl->port[6][0];
b2 = impl->port[7][0];
a0 = impl->port[8][0];
a1 = impl->port[9][0];
a2 = impl->port[10][0];
bq_raw_update(impl, b0, b1, b2, a0, a1, a2);
} else {
float freq = impl->port[2][0];
float Q = impl->port[3][0];
float gain = impl->port[4][0];
bq_freq_update(impl, impl->type, freq, Q, gain);
}
}
static void bq_run(void *Instance, unsigned long samples)
{
struct builtin *impl = Instance;
struct biquad *bq = &impl->bq;
float *out = impl->port[0];
float *in = impl->port[1];
if (impl->type == BQ_NONE) {
float b0, b1, b2, a0, a1, a2;
b0 = impl->port[5][0];
b1 = impl->port[6][0];
b2 = impl->port[7][0];
a0 = impl->port[8][0];
a1 = impl->port[9][0];
a2 = impl->port[10][0];
if (impl->b0 != b0 || impl->b1 != b1 || impl->b2 != b2 ||
impl->a0 != a0 || impl->a1 != a1 || impl->a2 != a2) {
bq_raw_update(impl, b0, b1, b2, a0, a1, a2);
}
} else {
float freq = impl->port[2][0];
float Q = impl->port[3][0];
float gain = impl->port[4][0];
if (impl->freq != freq || impl->Q != Q || impl->gain != gain)
bq_freq_update(impl, impl->type, freq, Q, gain);
}
dsp_ops_biquad_run(dsp_ops, bq, out, in, samples);
}
/** bq_lowpass */
static void bq_lowpass_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_LOWPASS);
}
static const struct fc_descriptor bq_lowpass_desc = {
.name = "bq_lowpass",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_lowpass_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_highpass */
static void bq_highpass_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_HIGHPASS);
}
static const struct fc_descriptor bq_highpass_desc = {
.name = "bq_highpass",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_highpass_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_bandpass */
static void bq_bandpass_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_BANDPASS);
}
static const struct fc_descriptor bq_bandpass_desc = {
.name = "bq_bandpass",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_bandpass_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_lowshelf */
static void bq_lowshelf_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_LOWSHELF);
}
static const struct fc_descriptor bq_lowshelf_desc = {
.name = "bq_lowshelf",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_lowshelf_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_highshelf */
static void bq_highshelf_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_HIGHSHELF);
}
static const struct fc_descriptor bq_highshelf_desc = {
.name = "bq_highshelf",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_highshelf_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_peaking */
static void bq_peaking_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_PEAKING);
}
static const struct fc_descriptor bq_peaking_desc = {
.name = "bq_peaking",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_peaking_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_notch */
static void bq_notch_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_NOTCH);
}
static const struct fc_descriptor bq_notch_desc = {
.name = "bq_notch",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_notch_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/** bq_allpass */
static void bq_allpass_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
bq_run(impl, SampleCount, BQ_ALLPASS);
}
static const struct fc_descriptor bq_allpass_desc = {
.name = "bq_allpass",
.n_ports = 5,
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = builtin_instantiate,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.run = bq_allpass_run,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
/* bq_raw */
static const struct fc_descriptor bq_raw_desc = {
.name = "bq_raw",
.n_ports = BQ_NUM_PORTS,
.ports = bq_ports,
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
};
@ -958,6 +1165,7 @@ static const struct fc_descriptor delay_desc = {
.cleanup = delay_cleanup,
};
/* invert */
static void invert_run(void * Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
@ -1019,6 +1227,8 @@ static const struct fc_descriptor * builtin_descriptor(unsigned long Index)
return &delay_desc;
case 12:
return &invert_desc;
case 13:
return &bq_raw_desc;
}
return NULL;
}