From 94e823ddadc78017c59af495e2e13d13b16603c7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Dec 2024 14:59:33 +0100 Subject: [PATCH] filter-graph: add dcblock and ramp plugins --- spa/plugins/filter-graph/builtin_plugin.c | 252 +++++++++++++++++++++- src/daemon/filter-chain/36-dcblock.conf | 59 +++++ src/modules/module-filter-chain.c | 22 ++ 3 files changed, 325 insertions(+), 8 deletions(-) create mode 100644 src/daemon/filter-chain/36-dcblock.conf diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 245e4157a..d64fe23ab 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -2123,16 +2123,248 @@ static struct spa_fga_port max_ports[] = { }; static const struct spa_fga_descriptor max_desc = { - .name = "max", - .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + .name = "max", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, - .n_ports = SPA_N_ELEMENTS(max_ports), - .ports = max_ports, + .n_ports = SPA_N_ELEMENTS(max_ports), + .ports = max_ports, - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = max_run, - .cleanup = builtin_cleanup, + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = max_run, + .cleanup = builtin_cleanup, +}; + +/* DC blocking */ +struct dcblock { + float xm1; + float ym1; +}; + +struct dcblock_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[17]; + + struct dcblock dc[8]; +}; + +static void *dcblock_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct dcblock_impl *impl; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->rate = SampleRate; + return impl; +} + +static void dcblock_run_n(struct dcblock dc[], float *dst[], const float *src[], + uint32_t n_src, float R, uint32_t n_samples) +{ + float x, y; + uint32_t i, n; + + for (i = 0; i < n_src; i++) { + const float *in = src[i]; + float *out = dst[i]; + float xm1 = dc[i].xm1; + float ym1 = dc[i].ym1; + + if (out == NULL || in == NULL) + continue; + + for (n = 0; n < n_samples; n++) { + x = in[n]; + y = x - xm1 + R * ym1; + xm1 = x; + ym1 = y; + out[n] = y; + } + dc[i].xm1 = xm1; + dc[i].ym1 = ym1; + } +} + +static void dcblock_run(void * Instance, unsigned long SampleCount) +{ + struct dcblock_impl *impl = Instance; + float R = impl->port[16][0]; + dcblock_run_n(impl->dc, &impl->port[8], (const float**)&impl->port[0], 8, + R, SampleCount); +} + +static void dcblock_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct dcblock_impl *impl = Instance; + impl->port[Port] = DataLocation; +} + +static struct spa_fga_port dcblock_ports[] = { + { .index = 0, + .name = "In 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "In 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 3, + .name = "In 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 4, + .name = "In 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 5, + .name = "In 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 6, + .name = "In 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 7, + .name = "In 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = 8, + .name = "Out 1", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 9, + .name = "Out 2", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 10, + .name = "Out 3", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 11, + .name = "Out 4", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 12, + .name = "Out 5", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 13, + .name = "Out 6", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 14, + .name = "Out 7", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 15, + .name = "Out 8", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 16, + .name = "R", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.995f, .min = 0.0f, .max = 1.0f + }, +}; + +static const struct spa_fga_descriptor dcblock_desc = { + .name = "dcblock", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(dcblock_ports), + .ports = dcblock_ports, + + .instantiate = dcblock_instantiate, + .connect_port = dcblock_connect_port, + .run = dcblock_run, + .cleanup = free, +}; + +/* ramp */ +static struct spa_fga_port ramp_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Start", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 2, + .name = "Stop", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Current", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 4, + .name = "Duration (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, +}; + +static void ramp_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *out = impl->port[0]; + float start = impl->port[1][0]; + float stop = impl->port[2][0], last; + float *current = impl->port[3]; + float duration = impl->port[4][0]; + float inc = (stop - start) / (duration * impl->rate); + uint32_t n; + + last = stop; + if (inc < 0.f) + SPA_SWAP(start, stop); + + if (out != NULL) { + if (impl->accum == last) { + for (n = 0; n < SampleCount; n++) + out[n] = last; + } else { + for (n = 0; n < SampleCount; n++) { + out[n] = impl->accum; + impl->accum = SPA_CLAMP(impl->accum + inc, start, stop); + } + } + } else { + impl->accum = SPA_CLAMP(impl->accum + SampleCount * inc, start, stop); + } + if (current) + current[0] = impl->accum; +} + +static const struct spa_fga_descriptor ramp_desc = { + .name = "ramp", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(ramp_ports), + .ports = ramp_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = ramp_run, + .cleanup = builtin_cleanup, }; static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) @@ -2184,6 +2416,10 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return ¶m_eq_desc; case 22: return &max_desc; + case 23: + return &dcblock_desc; + case 24: + return &ramp_desc; } return NULL; } diff --git a/src/daemon/filter-chain/36-dcblock.conf b/src/daemon/filter-chain/36-dcblock.conf new file mode 100644 index 000000000..b3b6feb64 --- /dev/null +++ b/src/daemon/filter-chain/36-dcblock.conf @@ -0,0 +1,59 @@ +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "DCBlock Filter" + media.name = "DCBlock Filter" + filter.graph = { + nodes = [ + { + name = dcblock + type = builtin + label = dcblock + control = { + "R" = 0.995 + } + } + { + # add a short 20ms ramp + name = ramp + type = builtin + label = ramp + control = { + "Start" = 0.0 + "Stop" = 1.0 + "Duration (s)" = 0.020 + } + } + { + name = volumeL + type = builtin + label = mult + } + { + name = volumeR + type = builtin + label = mult + } + ] + links = [ + { output = "dcblock:Out 1" input = "volumeL:In 1" } + { output = "dcblock:Out 2" input = "volumeR:In 1" } + { output = "ramp:Out" input = "volumeL:In 2" } + { output = "ramp:Out" input = "volumeR:In 2" } + ] + inputs = [ "dcblock:In 1" "dcblock:In 2" ] + outputs = [ "volumeL:Out" "volumeR:Out" ] + } + capture.props = { + node.name = "effect_input.dcblock" + audio.position = [ FL FR ] + media.class = Audio/Sink + } + playback.props = { + node.name = "effect_output.dcblock" + audio.position = [ FL FR ] + node.passive = true + } + } + } +] diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 642e57e22..e1ec6e6c3 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -472,6 +472,28 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * It has two input ports "In 1" and "In 2" and one output port "Out". * + * ### dcblock + * + * Use the `dcblock` plugin implements a [DC blocker](https://www.dsprelated.com/freebooks/filters/DC_Blocker.html). + * + * It has 8 input ports "In 1" to "In 8" and corresponding output ports "Out 1" + * to "Out 8". Not all ports need to be connected. + * + * It also has 1 control input port "R" that controls the DC block R factor. + * + * ### Ramp + * + * Use the `ramp` plugin creates a linear ramp from `Start` to `Stop`. + * + * It has 3 input control ports "Start", "Stop" and "Duration (s)". It also has one + * output port "Out". A linear ramp will be created from "Start" to "Stop" for a duration + * given by the "Duration (s)" control in (fractional) seconds. The current value will + * be stored in the output notify port "Current". + * + * The ramp output can, for example, be used as input for the `mult` plugin to create + * a volume ramp up or down. For more a more coarse volume ramp, the "Current" value + * can be used in the `linear` plugin. + * * ## SOFA filter * * There is an optional builtin SOFA filter available.