diff --git a/spa/plugins/filter-graph/audio-dsp-c.c b/spa/plugins/filter-graph/audio-dsp-c.c index 6ea559908..0aab9decc 100644 --- a/spa/plugins/filter-graph/audio-dsp-c.c +++ b/spa/plugins/filter-graph/audio-dsp-c.c @@ -205,9 +205,10 @@ void dsp_linear_c(void *obj, float * dst, void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, - uint32_t delay, float *dst, const float *src, uint32_t n_samples) + uint32_t delay, float *dst, const float *src, uint32_t n_samples, + float fb, float ff) { - if (delay == 0) { + if (delay == 0 && fb == 0.0f && ff == 0.0f) { dsp_copy_c(obj, dst, src, n_samples); } else { uint32_t w, o, i; @@ -215,10 +216,20 @@ void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, w = *pos; o = n_buffer - delay; - for (i = 0; i < n_samples; i++) { - buffer[w] = buffer[w + n_buffer] = src[i]; - dst[i] = buffer[w + o]; - w = w + 1 >= n_buffer ? 0 : w + 1; + if (fb == 0.0f && ff == 0.0f) { + for (i = 0; i < n_samples; i++) { + buffer[w] = buffer[w + n_buffer] = src[i]; + dst[i] = buffer[w + o]; + w = w + 1 >= n_buffer ? 0 : w + 1; + } + } else { + for (i = 0; i < n_samples; i++) { + float d = buffer[w + o]; + float s = src[i]; + buffer[w] = buffer[w + n_buffer] = s + d * fb; + dst[i] = ff * s + d; + w = w + 1 >= n_buffer ? 0 : w + 1; + } } *pos = w; } diff --git a/spa/plugins/filter-graph/audio-dsp-impl.h b/spa/plugins/filter-graph/audio-dsp-impl.h index 388a7453c..d60f346a3 100644 --- a/spa/plugins/filter-graph/audio-dsp-impl.h +++ b/spa/plugins/filter-graph/audio-dsp-impl.h @@ -32,7 +32,7 @@ void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_ float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples) #define MAKE_DELAY_FUNC(arch) \ void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \ - uint32_t delay, float *dst, const float *src, uint32_t n_samples) + uint32_t delay, float *dst, const float *src, uint32_t n_samples, float fb, float ff) #define MAKE_FFT_NEW_FUNC(arch) \ void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real) diff --git a/spa/plugins/filter-graph/audio-dsp-sse.c b/spa/plugins/filter-graph/audio-dsp-sse.c index 59aee8271..e3a877b71 100644 --- a/spa/plugins/filter-graph/audio-dsp-sse.c +++ b/spa/plugins/filter-graph/audio-dsp-sse.c @@ -614,34 +614,70 @@ void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq } void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, - float *dst, const float *src, uint32_t n_samples) + float *dst, const float *src, uint32_t n_samples, float fb, float ff) { - __m128 t[1]; + __m128 t[4]; uint32_t w = *pos; uint32_t o = n_buffer - delay; uint32_t n, unrolled; - if (SPA_IS_ALIGNED(src, 16) && - SPA_IS_ALIGNED(dst, 16)) - unrolled = n_samples & ~3; - else - unrolled = 0; + if (fb == 0.0f && ff == 0.0f) { + if (SPA_IS_ALIGNED(src, 16) && + SPA_IS_ALIGNED(dst, 16) && delay >= 4) + unrolled = n_samples & ~3; + else + unrolled = 0; - for(n = 0; n < unrolled; n += 4) { - t[0] = _mm_load_ps(&src[n]); - _mm_storeu_ps(&buffer[w], t[0]); - _mm_storeu_ps(&buffer[w+n_buffer], t[0]); - t[0] = _mm_loadu_ps(&buffer[w+o]); - _mm_store_ps(&dst[n], t[0]); - w = w + 4 >= n_buffer ? 0 : w + 4; - } - for(; n < n_samples; n++) { - t[0] = _mm_load_ss(&src[n]); - _mm_store_ss(&buffer[w], t[0]); - _mm_store_ss(&buffer[w+n_buffer], t[0]); - t[0] = _mm_load_ss(&buffer[w+o]); - _mm_store_ss(&dst[n], t[0]); - w = w + 1 >= n_buffer ? 0 : w + 1; + for(n = 0; n < unrolled; n += 4) { + t[0] = _mm_load_ps(&src[n]); + _mm_storeu_ps(&buffer[w], t[0]); + _mm_storeu_ps(&buffer[w+n_buffer], t[0]); + t[0] = _mm_loadu_ps(&buffer[w+o]); + _mm_store_ps(&dst[n], t[0]); + w = w + 4 >= n_buffer ? 0 : w + 4; + } + for(; n < n_samples; n++) { + t[0] = _mm_load_ss(&src[n]); + _mm_store_ss(&buffer[w], t[0]); + _mm_store_ss(&buffer[w+n_buffer], t[0]); + t[0] = _mm_load_ss(&buffer[w+o]); + _mm_store_ss(&dst[n], t[0]); + w = w + 1 >= n_buffer ? 0 : w + 1; + } + } else { + __m128 fb0 = _mm_set1_ps(fb); + __m128 ff0 = _mm_set1_ps(ff); + + if (SPA_IS_ALIGNED(src, 16) && + SPA_IS_ALIGNED(dst, 16) && delay >= 4) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + t[0] = _mm_loadu_ps(&buffer[w+o]); + t[1] = _mm_load_ps(&src[n]); + t[2] = _mm_mul_ps(t[0], fb0); + t[2] = _mm_add_ps(t[2], t[1]); + _mm_storeu_ps(&buffer[w], t[2]); + _mm_storeu_ps(&buffer[w+n_buffer], t[2]); + t[2] = _mm_mul_ps(t[1], ff0); + t[2] = _mm_add_ps(t[2], t[0]); + _mm_store_ps(&dst[n], t[2]); + w = w + 4 >= n_buffer ? 0 : w + 4; + } + for(; n < n_samples; n++) { + t[0] = _mm_load_ss(&buffer[w+o]); + t[1] = _mm_load_ss(&src[n]); + t[2] = _mm_mul_ss(t[0], fb0); + t[2] = _mm_add_ss(t[2], t[1]); + _mm_store_ss(&buffer[w], t[2]); + _mm_store_ss(&buffer[w+n_buffer], t[2]); + t[2] = _mm_mul_ps(t[1], ff0); + t[2] = _mm_add_ps(t[2], t[0]); + _mm_store_ss(&dst[n], t[2]); + w = w + 1 >= n_buffer ? 0 : w + 1; + } } *pos = w; } diff --git a/spa/plugins/filter-graph/audio-dsp.h b/spa/plugins/filter-graph/audio-dsp.h index 4fc06eb78..81cbdaf36 100644 --- a/spa/plugins/filter-graph/audio-dsp.h +++ b/spa/plugins/filter-graph/audio-dsp.h @@ -58,7 +58,8 @@ struct spa_fga_dsp_methods { float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples); void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, - float *dst, const float *src, uint32_t n_samples); + float *dst, const float *src, uint32_t n_samples, + float fb, float ff); }; static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples) @@ -159,10 +160,11 @@ static inline void spa_fga_dsp_biquad_run(struct spa_fga_dsp *obj, } static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, - float *dst, const float *src, uint32_t n_samples) + float *dst, const float *src, uint32_t n_samples, + float fb, float ff) { spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0, - buffer, pos, n_buffer, delay, dst, src, n_samples); + buffer, pos, n_buffer, delay, dst, src, n_samples, fb, ff); } #endif /* SPA_FGA_DSP_H */ diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index 29ded6739..8a964ccd7 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -1228,7 +1228,7 @@ struct delay_impl { struct spa_log *log; unsigned long rate; - float *port[4]; + float *port[6]; float delay; uint32_t delay_samples; @@ -1334,7 +1334,8 @@ static void delay_run(void * Instance, unsigned long SampleCount) } if (in != NULL && out != NULL) { spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, - impl->delay_samples, out, in, SampleCount); + impl->delay_samples, out, in, SampleCount, + impl->port[4][0], impl->port[5][0]); } if (impl->port[3] != NULL) impl->port[3][0] = impl->latency; @@ -1359,6 +1360,16 @@ static struct spa_fga_port delay_ports[] = { .hint = SPA_FGA_HINT_LATENCY, .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, + { .index = 4, + .name = "Feedback", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f + }, + { .index = 5, + .name = "Feedforward", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f + }, }; static const struct spa_fga_descriptor delay_desc = { diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 832c4b433..eb6a6c8ef 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -420,11 +420,12 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * ### Delay * - * The delay can be used to delay a signal in time. + * The delay can be used to delay a signal in time. With the Feedback and Feedforward + * controls it can also be used as a comb and an allpass filter. * * The delay has an input port "In" and an output port "Out". It also has - * a "Delay (s)" control port. It requires a config section in the node declaration - * in this format: + * a "Delay (s)" control port and a "Feedback" and "Feedforward" port. It requires a + * config section in the node declaration in this format: * *\code{.unparsed} * filter.graph = { @@ -439,6 +440,8 @@ extern struct spa_handle_factory spa_filter_graph_factory; * } * control = { * "Delay (s)" = ... + * "Feedback" = ... + * "Feedforward" = ... * } * ... * } @@ -452,6 +455,10 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - `latency` the latency in seconds. This is 0 by default but in some cases * the delay can be used to introduce latency with this option. * + * With the "Feedback" port one can create a comb filter. With the "Feedback" + * port and "Feedforward" port set to A and -A respectively, one can create + * an allpass filter. These settings can be used to create custom reverb units. + * * ### Invert * * The invert plugin can be used to invert the phase of the signal.