diff --git a/spa/plugins/audioconvert/channelmix-ops-c.c b/spa/plugins/audioconvert/channelmix-ops-c.c index 63947aa5f..ca1c986a1 100644 --- a/spa/plugins/audioconvert/channelmix-ops-c.c +++ b/spa/plugins/audioconvert/channelmix-ops-c.c @@ -287,15 +287,20 @@ channelmix_f32_2_5p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], for (i = 0; i < n_dst; i++) memset(d[i], 0, n_samples * sizeof(float)); } - else if (v0 == 1.0f && v1 == 1.0f && v4 == 1.0f && v5 == 1.0f) { + else if (v0 == 1.0f && v1 == 1.0f) { for (n = 0; n < n_samples; n++) { float c = s[0][n] + s[1][n]; - d[0][n] = d[4][n] = s[0][n]; - d[1][n] = d[5][n] = s[1][n]; + d[0][n] = s[0][n]; + d[1][n] = s[1][n]; d[2][n] = c; } lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + + delay_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + d[4], s[0], v4, n_samples); + delay_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + d[5], s[1], v5, n_samples); } else { for (n = 0; n < n_samples; n++) { @@ -303,11 +308,14 @@ channelmix_f32_2_5p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], d[0][n] = s[0][n] * v0; d[1][n] = s[1][n] * v1; d[2][n] = c; - d[4][n] = s[0][n] * v4; - d[5][n] = s[1][n] * v5; } lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + + delay_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + d[4], s[0], v4, n_samples); + delay_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + d[5], s[1], v5, n_samples); } } @@ -331,16 +339,20 @@ channelmix_f32_2_7p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], for (i = 0; i < n_dst; i++) memset(d[i], 0, n_samples * sizeof(float)); } - else if (v0 == 1.0f && v1 == 1.0f && v4 == 1.0f && v5 == 1.0f && - v6 == 1.0f && v7 == 1.0f) { + else if (v0 == 1.0f && v1 == 1.0f && v4 == 1.0f && v5 == 1.0f) { for (n = 0; n < n_samples; n++) { float c = s[0][n] + s[1][n]; - d[0][n] = d[4][n] = d[6][n] = s[0][n]; - d[1][n] = d[5][n] = d[7][n] = s[1][n]; + d[0][n] = d[4][n] = s[0][n]; + d[1][n] = d[5][n] = s[1][n]; d[2][n] = c; } lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + + delay_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + d[6], s[0], v6, n_samples); + delay_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + d[7], s[1], v7, n_samples); } else { for (n = 0; n < n_samples; n++) { @@ -350,11 +362,14 @@ channelmix_f32_2_7p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], d[2][n] = c; d[4][n] = s[0][n] * v4; d[5][n] = s[1][n] * v5; - d[6][n] = s[0][n] * v6; - d[7][n] = s[1][n] * v7; } lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + + delay_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + d[6], s[0], v6, n_samples); + delay_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + d[7], s[1], v7, n_samples); } } diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 199d14361..aff444de3 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -544,11 +544,13 @@ int channelmix_init(struct channelmix *mix) if (info == NULL) return -ENOTSUP; - spa_log_debug(mix->log, "selected %s", info->name); - mix->free = impl_channelmix_free; mix->process = info->process; mix->set_volume = impl_channelmix_set_volume; mix->cpu_flags = info->cpu_flags; + mix->delay = mix->rear_delay * mix->freq / 1000.0f; + + spa_log_debug(mix->log, "selected %s delay:%d", info->name, mix->delay); + return make_matrix(mix); } diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h index 29a7fec81..8500f401c 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -33,6 +33,7 @@ extern struct spa_log_topic *log_topic; #include "crossover.h" +#include "delay.h" #define VOLUME_MIN 0.0f #define VOLUME_NORM 1.0f @@ -45,6 +46,7 @@ extern struct spa_log_topic *log_topic; #define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) #define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) +#define BUFFER_SIZE 4096 struct channelmix { uint32_t src_chan; @@ -69,8 +71,13 @@ struct channelmix { float freq; /* sample frequency */ float lfe_cutoff; /* in Hz, 0 is disabled */ + float rear_delay; /* in ms, 0 is disabled */ struct lr4 lr4[SPA_AUDIO_MAX_CHANNELS]; + float buffer[2][BUFFER_SIZE]; + uint32_t pos[2]; + uint32_t delay; + void (*process) (struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); void (*set_volume) (struct channelmix *mix, float volume, bool mute, diff --git a/spa/plugins/audioconvert/channelmix.c b/spa/plugins/audioconvert/channelmix.c index 848871752..2857e7cf2 100644 --- a/spa/plugins/audioconvert/channelmix.c +++ b/spa/plugins/audioconvert/channelmix.c @@ -466,6 +466,14 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 8: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 9: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), @@ -474,7 +482,7 @@ static int impl_node_enum_params(void *object, int seq, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 9: + case 10: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), @@ -483,7 +491,7 @@ static int impl_node_enum_params(void *object, int seq, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 10: + case 11: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), @@ -492,21 +500,22 @@ static int impl_node_enum_params(void *object, int seq, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 11: + case 12: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), - SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency"), + SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency (Hz)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( this->mix.lfe_cutoff, 0.0, 1000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 12: + case 13: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), - SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->disabled), + SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), + SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.rear_delay, 0.0, 1000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: @@ -547,6 +556,8 @@ static int impl_node_enum_params(void *object, int seq, 0); spa_pod_builder_prop(&b, SPA_PROP_params, 0); spa_pod_builder_push_struct(&b, &f[1]); + spa_pod_builder_string(&b, "channelmix.disable"); + spa_pod_builder_bool(&b, this->props.disabled); spa_pod_builder_string(&b, "channelmix.normalize"); spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)); @@ -558,8 +569,8 @@ static int impl_node_enum_params(void *object, int seq, CHANNELMIX_OPTION_UPMIX)); spa_pod_builder_string(&b, "channelmix.lfe-cutoff"); spa_pod_builder_float(&b, this->mix.lfe_cutoff); - spa_pod_builder_string(&b, "channelmix.disable"); - spa_pod_builder_bool(&b, this->props.disabled); + spa_pod_builder_string(&b, "channelmix.rear-delay"); + spa_pod_builder_float(&b, this->mix.rear_delay); spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); break; @@ -585,7 +596,9 @@ static int impl_node_enum_params(void *object, int seq, static int channelmix_set_param(struct impl *this, const char *k, const char *s) { - if (spa_streq(k, "channelmix.normalize")) + if (spa_streq(k, "channelmix.disable")) + this->props.disabled = spa_atob(s); + else if (spa_streq(k, "channelmix.normalize")) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_NORMALIZE, spa_atob(s)); else if (spa_streq(k, "channelmix.mix-lfe")) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_MIX_LFE, spa_atob(s)); @@ -593,8 +606,8 @@ static int channelmix_set_param(struct impl *this, const char *k, const char *s) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_UPMIX, spa_atob(s)); else if (spa_streq(k, "channelmix.lfe-cutoff")) spa_atof(s, &this->mix.lfe_cutoff); - else if (spa_streq(k, "channelmix.disable")) - this->props.disabled = spa_atob(s); + else if (spa_streq(k, "channelmix.rear-delay")) + spa_atof(s, &this->mix.rear_delay); else return 0; return 1; @@ -1577,6 +1590,7 @@ impl_init(const struct spa_handle_factory *factory, this->mix.options = CHANNELMIX_OPTION_NORMALIZE; this->mix.log = this->log; + this->mix.rear_delay = 12.0f; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; diff --git a/spa/plugins/audioconvert/delay.h b/spa/plugins/audioconvert/delay.h new file mode 100644 index 000000000..16e189fec --- /dev/null +++ b/spa/plugins/audioconvert/delay.h @@ -0,0 +1,72 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DELAY_H +#define DELAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +static inline void delay_run(float *buffer, uint32_t *pos, + uint32_t n_buffer, uint32_t delay, + float *dst, const float *src, const float vol, uint32_t n_samples) +{ + uint32_t i; + uint32_t p = *pos; + + for (i = 0; i < n_samples; i++) { + buffer[p] = src[i]; + dst[i] = buffer[(p - delay) & (n_buffer-1)] * vol; + p = (p + 1) & (n_buffer-1); + } + *pos = p; +} + +static inline void delay_convolve_run(float *buffer, uint32_t *pos, + uint32_t n_buffer, uint32_t delay, + const float *taps, uint32_t n_taps, + float *dst, const float *src, const float vol, uint32_t n_samples) +{ + uint32_t i, j; + uint32_t p = *pos; + + for (i = 0; i < n_samples; i++) { + float sum = 0.0f; + + buffer[p] = src[i]; + for (j = 0; j < n_taps; j++) + sum += (taps[j] * buffer[((p - delay) - j) & (n_buffer-1)]); + dst[i] = sum * vol; + + p = (p + 1) & (n_buffer-1); + } + *pos = p; +} + +#ifdef __cplusplus +} +#endif + +#endif /* DELAY_H */ diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in index d996a5f6a..24caae996 100644 --- a/src/daemon/client-rt.conf.in +++ b/src/daemon/client-rt.conf.in @@ -85,4 +85,5 @@ stream.properties = { #channelmix.mix-lfe = true #channelmix.upmix = false #channelmix.lfe-cutoff = 0 + #channelmix.rear-delay = 12.0 } diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in index ee0c000fd..69b6cb3f6 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -75,4 +75,5 @@ stream.properties = { #channelmix.mix-lfe = false #channelmix.upmix = false #channelmix.lfe-cutoff = 0 + #channelmix.rear-delay = 12.0 } diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 7f1da6669..c43596894 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -206,6 +206,7 @@ context.objects = [ #channelmix.mix-lfe = false #channelmix.upmix = false #channelmix.lfe-cutoff = 0 + #channelmix.rear-delay = 12.0 channelmix.disable = true #node.param.Props = { # params = [ @@ -261,6 +262,7 @@ context.objects = [ #channelmix.mix-lfe = false #channelmix.upmix = false #channelmix.lfe-cutoff = 0 + #channelmix.rear-delay = 12.0 channelmix.disable = true #node.param.Props = { # params = [ diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 239e3d7e2..89501ff0f 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -85,6 +85,7 @@ stream.properties = { #channelmix.mix-lfe = false #channelmix.upmix = false #channelmix.lfe-cutoff = 0 + #channelmix.rear-delay = 12.0 } # client/stream specific properties