filter-graph: add dcblock and ramp plugins

This commit is contained in:
Wim Taymans 2024-12-09 14:59:33 +01:00
parent df271d13f3
commit 94e823ddad
3 changed files with 325 additions and 8 deletions

View file

@ -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 &param_eq_desc;
case 22:
return &max_desc;
case 23:
return &dcblock_desc;
case 24:
return &ramp_desc;
}
return NULL;
}

View file

@ -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
}
}
}
]

View file

@ -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.