diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 7073baa4b..17f891b94 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -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); diff --git a/src/modules/module-filter-chain/builtin_plugin.c b/src/modules/module-filter-chain/builtin_plugin.c index ec74a8137..2147de74e 100644 --- a/src/modules/module-filter-chain/builtin_plugin.c +++ b/src/modules/module-filter-chain/builtin_plugin.c @@ -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; + 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]; - 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); + 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; }