From ef5d9ff0287f21dd7ac5386248d56cfb517efd1c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 12:38:05 +0200 Subject: [PATCH] filter-graph: add a simple noise gate --- spa/plugins/filter-graph/builtin_plugin.c | 135 ++++++++++++++++++++++ src/modules/module-filter-chain.c | 13 +++ 2 files changed, 148 insertions(+) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index fad14f879..0d39bbe70 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -60,6 +60,9 @@ struct builtin { int mode; uint32_t count; float last; + + float gate; + float hold; }; static void *builtin_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, @@ -2876,6 +2879,136 @@ static const struct spa_fga_descriptor zeroramp_desc = { .cleanup = builtin_cleanup, }; + +/* noisegate */ +static struct spa_fga_port noisegate_ports[] = { + { .index = 0, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Open Threshold", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.004f, .min = 0.0f, .max = 1.0f + }, + { .index = 3, + .name = "Close Threshold", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.003f, .min = 0.0f, .max = 1.0f + }, + { .index = 4, + .name = "Attack (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.005f, .min = 0.0f, .max = 1.0f + }, + { .index = 5, + .name = "Hold (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.050f, .min = 0.0f, .max = 1.0f + }, + { .index = 6, + .name = "Release (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.010f, .min = 0.0f, .max = 1.0f + }, +}; + +static void noisegate_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[0]; + float *out = impl->port[1]; + unsigned long n; + float o_thres = impl->port[2][0]; + float c_thres = impl->port[3][0]; + float gate, hold, o_rate, c_rate, level; + int mode; + + if (out == NULL) + return; + + if (in == NULL) { + memset(out, 0, SampleCount * sizeof(float)); + return; + } + + o_rate = 1.0f / (impl->port[4][0] * impl->rate); + c_rate = 1.0f / (impl->port[6][0] * impl->rate); + gate = impl->gate; + hold = impl->hold; + mode = impl->mode; + level = impl->last; + + for (n = 0; n < SampleCount; n++) { + float lev = fabsf(in[n]); + + if (lev > level) + level = lev; + else + level = lev * 0.05f + level * 0.95f; + + switch (mode) { + case 0: + /* closed */ + if (level >= o_thres) + mode = 1; + break; + case 1: + /* opening */ + gate += o_rate; + if (gate >= 1.0f) { + gate = 1.0f; + mode = 2; + hold = impl->port[5][0] * impl->rate; + } + break; + case 2: + /* hold */ + hold -= 1.0f; + if (hold <= 0.0f) + mode = 3; + break; + case 3: + /* open */ + if (level < c_thres) + mode = 4; + break; + case 4: + /* closing */ + gate -= c_rate; + if (level >= o_thres) + mode = 1; + else if (gate <= 0.0f) { + gate = 0.0f; + mode = 0; + } + break; + } + out[n] = in[n] * gate; + } + impl->gate = gate; + impl->hold = hold; + impl->mode = mode; + impl->last = level; +} + +static const struct spa_fga_descriptor noisegate_desc = { + .name = "noisegate", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(noisegate_ports), + .ports = noisegate_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = noisegate_run, + .cleanup = builtin_cleanup, +}; + static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -2939,6 +3072,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &pipe_desc; case 29: return &zeroramp_desc; + case 30: + return &noisegate_desc; } return NULL; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 1f64dc174..b00130964 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -623,6 +623,19 @@ extern struct spa_handle_factory spa_filter_graph_factory; * "Duration (s)" determines how long the fade-in and fade-out should last * (default 0.000666). * + * ### Noisegate + * + * The `noisegate` plugin can be used to remove low volume noise. + * + * It has an "In" input port and an "Out" output data ports. Normally the input + * data is passed directly to the output. + * + * If the volume drops below "Close threshold", the noisegate will ramp down the + * volume to zero for a duration of "Release (s)" seconds. When the volume is above + * "Open threshold", the noisegate will ramp up the volume to 1 for a duration + * of "Attack (s)" seconds. The noise gate stays open for at least "Hold (s)" + * seconds before it can close again. + * * * ## SOFA filters *