filter-chain: add convolver2

Add support for multiple convolver outputs. This makes things more
efficient because we only need to do the input FFT once to produce the N
outputs.

Add convolver2 that can have multiple outputs.
This commit is contained in:
Wim Taymans 2026-04-21 16:24:38 +02:00
parent 2b96f694f7
commit 9cae4ce7e7
4 changed files with 580 additions and 255 deletions

View file

@ -14,28 +14,31 @@
#include <spa/utils/defs.h>
struct ir {
float **segments;
float *time_buffer[2];
float *precalc[2];
};
struct partition {
struct convolver *conv;
int block_size;
int time_size;
int freq_size;
int n_segments;
float **segments;
float **segments_ir;
int current;
float *time_buffer[2];
void *fft;
void *ifft;
float *pre_mult;
float *freq;
float *precalc[2];
int n_segments;
int current;
float **segments;
int block_fill;
int n_ir;
struct ir *ir;
int time_idx;
int precalc_idx;
float scale;
};
@ -74,68 +77,74 @@ static void partition_reset(struct spa_fga_dsp *dsp, struct partition *part)
int i;
for (i = 0; i < part->n_segments; i++)
spa_fga_dsp_fft_memclear(dsp, part->segments[i], part->freq_size, false);
spa_fga_dsp_fft_memclear(dsp, part->time_buffer[0], part->time_size, true);
spa_fga_dsp_fft_memclear(dsp, part->time_buffer[1], part->time_size, true);
spa_fga_dsp_fft_memclear(dsp, part->pre_mult, part->freq_size, false);
for (i = 0; i < part->n_ir; i++) {
struct ir *r = &part->ir[i];
spa_fga_dsp_fft_memclear(dsp, r->time_buffer[0], part->time_size, true);
spa_fga_dsp_fft_memclear(dsp, r->time_buffer[1], part->time_size, true);
spa_fga_dsp_fft_memclear(dsp, r->precalc[0], part->block_size, true);
spa_fga_dsp_fft_memclear(dsp, r->precalc[1], part->block_size, true);
}
spa_fga_dsp_fft_memclear(dsp, part->freq, part->freq_size, false);
spa_fga_dsp_fft_memclear(dsp, part->precalc[0], part->block_size, true);
spa_fga_dsp_fft_memclear(dsp, part->precalc[1], part->block_size, true);
part->block_fill = 0;
part->time_idx = 0;
part->precalc_idx = 0;
part->current = 0;
}
static void partition_free(struct spa_fga_dsp *dsp, struct partition *part)
{
int i;
int i, j;
for (i = 0; i < part->n_segments; i++) {
if (part->segments)
spa_fga_dsp_fft_memfree(dsp, part->segments[i]);
if (part->segments_ir)
spa_fga_dsp_fft_memfree(dsp, part->segments_ir[i]);
}
for (i = 0; i < part->n_ir; i++) {
struct ir *r = &part->ir[i];
for (j = 0; j < part->n_segments; j++) {
if (r->segments)
spa_fga_dsp_fft_memfree(dsp, r->segments[j]);
}
if (r->time_buffer[0])
spa_fga_dsp_fft_memfree(dsp, r->time_buffer[0]);
if (r->time_buffer[1])
spa_fga_dsp_fft_memfree(dsp, r->time_buffer[1]);
if (r->precalc[0])
spa_fga_dsp_fft_memfree(dsp, r->precalc[0]);
if (r->precalc[1])
spa_fga_dsp_fft_memfree(dsp, r->precalc[1]);
free(r->segments);
}
if (part->fft)
spa_fga_dsp_fft_free(dsp, part->fft);
if (part->ifft)
spa_fga_dsp_fft_free(dsp, part->ifft);
if (part->time_buffer[0])
spa_fga_dsp_fft_memfree(dsp, part->time_buffer[0]);
if (part->time_buffer[1])
spa_fga_dsp_fft_memfree(dsp, part->time_buffer[1]);
free(part->segments);
free(part->segments_ir);
spa_fga_dsp_fft_memfree(dsp, part->pre_mult);
free(part->ir);
spa_fga_dsp_fft_memfree(dsp, part->freq);
spa_fga_dsp_fft_memfree(dsp, part->precalc[0]);
spa_fga_dsp_fft_memfree(dsp, part->precalc[1]);
free(part);
}
static struct partition *partition_new(struct convolver *conv, int block, const float *ir, int irlen)
static struct partition *partition_new(struct convolver *conv, int block,
const struct convolver_ir ir[], int n_ir, int iroffset, int irlen)
{
struct partition *part;
struct spa_fga_dsp *dsp = conv->dsp;
int i;
int i, j;
if (block == 0)
return NULL;
while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
irlen--;
part = calloc(1, sizeof(*part));
if (part == NULL)
return NULL;
part->conv = conv;
if (irlen == 0)
return part;
part->block_size = next_power_of_two(block);
part->time_size = 2 * part->block_size;
part->n_segments = (irlen + part->block_size-1) / part->block_size;
part->freq_size = (part->time_size / 2) + 1;
part->n_ir = n_ir;
part->fft = spa_fga_dsp_fft_new(dsp, part->time_size, true);
if (part->fft == NULL)
@ -144,38 +153,44 @@ static struct partition *partition_new(struct convolver *conv, int block, const
if (part->ifft == NULL)
goto error;
part->time_buffer[0] = spa_fga_dsp_fft_memalloc(dsp, part->time_size, true);
part->time_buffer[1] = spa_fga_dsp_fft_memalloc(dsp, part->time_size, true);
if (part->time_buffer[0] == NULL || part->time_buffer[1] == NULL)
goto error;
part->segments = calloc(part->n_segments, sizeof(float*));
part->segments_ir = calloc(part->n_segments, sizeof(float*));
if (part->segments == NULL || part->segments_ir == NULL)
part->freq = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
part->ir = calloc(part->n_ir, sizeof(struct ir));
if (part->segments == NULL || part->freq == NULL || part->ir == NULL)
goto error;
for (i = 0; i < part->n_segments; i++) {
int left = irlen - (i * part->block_size);
int copy = SPA_MIN(part->block_size, left);
part->segments[i] = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
part->segments_ir[i] = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
if (part->segments[i] == NULL || part->segments_ir[i] == NULL)
if (part->segments[i] == NULL)
goto error;
}
for (i = 0; i < part->n_ir; i++) {
struct ir *r = &part->ir[i];
r->segments = calloc(part->n_segments, sizeof(float*));
r->time_buffer[0] = spa_fga_dsp_fft_memalloc(dsp, part->time_size, true);
r->time_buffer[1] = spa_fga_dsp_fft_memalloc(dsp, part->time_size, true);
r->precalc[0] = spa_fga_dsp_fft_memalloc(dsp, part->block_size, true);
r->precalc[1] = spa_fga_dsp_fft_memalloc(dsp, part->block_size, true);
if (r->segments == NULL || r->time_buffer[0] == NULL || r->time_buffer[1] == NULL ||
r->precalc[0] == NULL || r->precalc[1] == NULL)
goto error;
spa_fga_dsp_copy(dsp, part->time_buffer[0], &ir[i * part->block_size], copy);
if (copy < part->time_size)
spa_fga_dsp_fft_memclear(dsp, part->time_buffer[0] + copy, part->time_size - copy, true);
for (j = 0; j < part->n_segments; j++) {
int left = ir[i].len - iroffset - (j * part->block_size);
int copy = SPA_CLAMP(left, 0, part->block_size);
spa_fga_dsp_fft_run(dsp, part->fft, 1, part->time_buffer[0], part->segments_ir[i]);
r->segments[j] = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
if (r->segments[j] == NULL)
goto error;
spa_fga_dsp_copy(dsp, r->time_buffer[0], &ir[i].ir[iroffset + (j * part->block_size)], copy);
spa_fga_dsp_fft_memclear(dsp, r->time_buffer[0] + copy, part->time_size - copy, true);
spa_fga_dsp_fft_run(dsp, part->fft, 1, r->time_buffer[0], r->segments[j]);
}
}
part->pre_mult = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
part->freq = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
part->precalc[0] = spa_fga_dsp_fft_memalloc(dsp, part->block_size, true);
part->precalc[1] = spa_fga_dsp_fft_memalloc(dsp, part->block_size, true);
if (part->pre_mult == NULL || part->freq == NULL ||
part->precalc[0] == NULL || part->precalc[1] == NULL)
goto error;
part->scale = 1.0f / part->time_size;
partition_reset(dsp, part);
@ -185,41 +200,50 @@ error:
return NULL;
}
static int partition_run(struct spa_fga_dsp *dsp, struct partition *part, const float *input, float *output, int len)
static int partition_run(struct spa_fga_dsp *dsp, struct partition *part, const float *input,
float *output[], int offset, int len)
{
int i;
int i, j;
int block_fill = part->block_fill;
int current = part->current;
int idx = part->time_idx, pc_idx = part->precalc_idx;
float *dst;
spa_fga_dsp_fft_run(dsp, part->fft, 1, input, part->segments[current]);
spa_fga_dsp_fft_run(dsp, part->fft, 1, input, part->segments[part->current]);
spa_fga_dsp_fft_cmul(dsp, part->fft,
part->freq,
part->segments[current],
part->segments_ir[0],
part->freq_size, part->scale);
for (i = 0; i < part->n_ir; i++) {
struct ir *r = &part->ir[i];
int current = part->current;
for (i = 1; i < part->n_segments; i++) {
if (++current == part->n_segments)
current = 0;
spa_fga_dsp_fft_cmuladd(dsp, part->fft,
part->freq,
spa_fga_dsp_fft_cmul(dsp, part->fft,
part->freq,
part->segments[current],
part->segments_ir[i],
r->segments[0],
part->freq_size, part->scale);
}
spa_fga_dsp_fft_run(dsp, part->ifft, -1, part->freq, part->time_buffer[0]);
spa_fga_dsp_sum(dsp, output, part->time_buffer[0] + block_fill,
part->time_buffer[1] + part->block_size + block_fill, len);
for (j = 1; j < part->n_segments; j++) {
if (++current == part->n_segments)
current = 0;
spa_fga_dsp_fft_cmuladd(dsp, part->fft,
part->freq,
part->freq,
part->segments[current],
r->segments[j],
part->freq_size, part->scale);
}
spa_fga_dsp_fft_run(dsp, part->ifft, -1, part->freq, r->time_buffer[idx]);
dst = output ? output[i]: r->precalc[pc_idx];
if (dst)
spa_fga_dsp_sum(dsp, dst + offset, r->time_buffer[idx] + block_fill,
r->time_buffer[idx^1] + part->block_size + block_fill, len);
}
block_fill += len;
if (block_fill == part->block_size) {
block_fill = 0;
SPA_SWAP(part->time_buffer[0], part->time_buffer[1]);
part->time_idx = idx ^ 1;
if (part->current == 0)
part->current = part->n_segments;
@ -240,7 +264,7 @@ static void *do_background_process(void *data)
if (!conv->running)
break;
partition_run(conv->dsp, part, conv->delay[1], part->time_buffer[1], part->block_size);
partition_run(conv->dsp, part, conv->delay[1], NULL, 0, part->block_size);
sem_post(&conv->sem_finish);
}
@ -260,10 +284,12 @@ void convolver_reset(struct convolver *conv)
conv->delay_fill = 0;
}
struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tail_block, const float *ir, int irlen)
struct convolver *convolver_new_many(struct spa_fga_dsp *dsp, int head_block, int tail_block,
const struct convolver_ir ir[], int n_ir)
{
struct convolver *conv;
int head_ir_len, min_size, max_size, ir_consumed = 0;
int i, irlen, head_ir_len, min_size, max_size, ir_consumed = 0;
struct convolver_ir tmp[n_ir];
if (head_block == 0 || tail_block == 0)
return NULL;
@ -272,27 +298,40 @@ struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tai
if (head_block > tail_block)
SPA_SWAP(head_block, tail_block);
while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
irlen--;
conv = calloc(1, sizeof(*conv));
if (conv == NULL)
return NULL;
irlen = 0;
for (i = 0; i < n_ir; i++) {
int l = ir[i].len;
while (l > 0 && fabs(ir[i].ir[l-1]) < 0.000001f)
l--;
tmp[i].ir = ir[i].ir;
tmp[i].len = l;
irlen = SPA_MAX(irlen, l);
}
conv->dsp = dsp;
conv->min_size = next_power_of_two(head_block);
conv->max_size = next_power_of_two(tail_block);
conv->delay[0] = spa_fga_dsp_fft_memalloc(dsp, 2 * conv->max_size, true);
conv->delay[1] = spa_fga_dsp_fft_memalloc(dsp, 2 * conv->max_size, true);
if (conv->delay[0] == NULL || conv->delay[1] == NULL)
goto error;
if (irlen == 0)
return conv;
conv->min_size = next_power_of_two(head_block);
conv->max_size = next_power_of_two(tail_block);
min_size = conv->min_size;
max_size = conv->max_size;
while (ir_consumed < irlen) {
head_ir_len = SPA_MIN(irlen - ir_consumed, 2 * max_size);
conv->partition[conv->n_partition] = partition_new(conv, min_size, ir + ir_consumed, head_ir_len);
conv->partition[conv->n_partition] = partition_new(conv, min_size,
tmp, n_ir, ir_consumed, head_ir_len);
if (conv->partition[conv->n_partition] == NULL)
goto error;
conv->n_partition++;
@ -304,11 +343,6 @@ struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tai
max_size = irlen;
}
conv->delay[0] = spa_fga_dsp_fft_memalloc(dsp, 2 * conv->max_size, true);
conv->delay[1] = spa_fga_dsp_fft_memalloc(dsp, 2 * conv->max_size, true);
if (conv->delay[0] == NULL || conv->delay[1] == NULL)
goto error;
convolver_reset(conv);
conv->threaded = false;
@ -334,6 +368,12 @@ error:
return NULL;
}
struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tail_block, const float *ir, int irlen)
{
const struct convolver_ir tmp = { ir, irlen };
return convolver_new_many(dsp, head_block, tail_block, &tmp, 1);
}
void convolver_free(struct convolver *conv)
{
struct spa_fga_dsp *dsp = conv->dsp;
@ -354,13 +394,16 @@ void convolver_free(struct convolver *conv)
free(conv);
}
int convolver_run(struct convolver *conv, const float *input, float *output, int length)
int convolver_run_many(struct convolver *conv, const float *input, float *output[], int length)
{
int processed = 0, i;
int processed = 0, i, j;
struct spa_fga_dsp *dsp = conv->dsp;
if (conv->n_partition == 0)
return length;
while (processed < length) {
int remaining = length - processed;
int remaining = length - processed, pc_idx;
float *delay = conv->delay[0];
int delay_fill = conv->delay_fill;
struct partition *part = conv->partition[0];
@ -371,20 +414,23 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int
spa_memcpy(delay + delay_fill, input + processed, processing * sizeof(float));
conv->delay_fill += processing;
partition_run(dsp, part, delay + delay_fill - block_fill, &output[processed], processing);
partition_run(dsp, part, delay + delay_fill - block_fill, output, processed, processing);
for (i = 1; i < conv->n_partition; i++) {
part = conv->partition[i];
pc_idx = part->precalc_idx;
block_size = part->block_size;
block_fill = delay_fill % block_size;
spa_fga_dsp_sum(dsp, &output[processed], &output[processed],
&part->precalc[0][block_fill], processing);
for (j = 0; j < part->n_ir; j++) {
struct ir *r = &part->ir[j];
spa_fga_dsp_sum(dsp, &output[j][processed], &output[j][processed],
&r->precalc[pc_idx][block_fill], processing);
}
if (block_fill + processing == block_size) {
SPA_SWAP(part->precalc[0], part->precalc[1]);
part->precalc_idx = pc_idx ^ 1;
partition_run(dsp, part, delay + conv->delay_fill - block_size,
part->precalc[0], block_size);
NULL, 0, block_size);
}
}
if (conv->delay_fill == conv->max_size) {
@ -396,3 +442,9 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int
}
return 0;
}
int convolver_run(struct convolver *conv, const float *input, float *output, int length)
{
float *tmp[1] = { output };
return convolver_run_many(conv, input, tmp, length);
}

View file

@ -7,8 +7,18 @@
#include "audio-dsp.h"
struct convolver_ir {
const float *ir;
int len;
};
struct convolver *convolver_new(struct spa_fga_dsp *dsp, int block, int tail, const float *ir, int irlen);
void convolver_free(struct convolver *conv);
void convolver_reset(struct convolver *conv);
int convolver_run(struct convolver *conv, const float *input, float *output, int length);
struct convolver *convolver_new_many(struct spa_fga_dsp *dsp, int block, int tail,
const struct convolver_ir *ir, int n_ir);
int convolver_run_many(struct convolver *conv, const float *input, float **output, int length);

View file

@ -706,14 +706,14 @@ static const struct spa_fga_descriptor bq_raw_desc = {
.cleanup = builtin_cleanup,
};
/** convolve */
/** convolver */
struct convolver_impl {
struct plugin *plugin;
struct spa_log *log;
struct spa_fga_dsp *dsp;
unsigned long rate;
float *port[3];
float *port[10];
float latency;
struct convolver *conv;
@ -796,51 +796,76 @@ static int finfo_open(const char *filename, struct finfo *info, int rate)
return 0;
}
static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay,
int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency)
struct impulse {
float gain;
float delay;
char *filenames[MAX_RATES];
int offset;
int length;
int channel;
int resample_quality;
int latency;
unsigned long rate;
float *samples;
int n_samples;
};
#define IMPULSE_INIT(ch) (struct impulse) { 1.0f, 0.0f, { NULL, }, 0, 0, ch, RESAMPLE_DEFAULT_QUALITY, }
static void impulse_clear(struct impulse *ir)
{
uint32_t i;
for (i = 0; i < MAX_RATES; i++)
if (ir->filenames[i])
free(ir->filenames[i]);
free(ir->samples);
spa_zero(*ir);
}
static int finfo_read_samples(struct plugin *pl, struct finfo *info, struct impulse *ir)
{
float *samples, v;
int i, n, h;
int i, n, h, delay = (int)(ir->delay * info->rate);
if (length <= 0)
length = info->def_frames;
if (ir->length <= 0)
ir->length = info->def_frames;
else
length = SPA_MIN(length, info->max_frames);
ir->length = SPA_MIN(ir->length, info->max_frames);
length -= SPA_MIN(offset, length);
ir->length -= SPA_MIN(ir->offset, ir->length);
n = delay + length;
n = delay + ir->length;
if (n == 0)
return NULL;
return -EINVAL;
samples = calloc(n * info->channels, sizeof(float));
if (samples == NULL)
return NULL;
return -errno;
channel = channel % info->channels;
ir->channel = ir->channel % info->channels;
switch (info->type) {
case TYPE_SNDFILE:
#ifdef HAVE_SNDFILE
if (offset > 0)
sf_seek(info->fs, offset, SEEK_SET);
sf_readf_float(info->fs, samples + (delay * info->channels), length);
if (ir->offset > 0)
sf_seek(info->fs, ir->offset, SEEK_SET);
sf_readf_float(info->fs, samples + (delay * info->channels), ir->length);
for (i = 0; i < n; i++)
samples[i] = samples[info->channels * i + channel] * gain;
samples[i] = samples[info->channels * i + ir->channel] * ir->gain;
#endif
break;
case TYPE_HILBERT:
gain *= 2 / (float)M_PI;
h = length / 2;
ir->gain *= 2 / (float)M_PI;
h = ir->length / 2;
for (i = 1; i < h; i += 2) {
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
v = (ir->gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
samples[delay + h + i] = -v;
samples[delay + h - i] = v;
}
spa_log_info(pl->log, "created hilbert function length %d", length);
spa_log_info(pl->log, "created hilbert function length %d", ir->length);
break;
case TYPE_DIRAC:
samples[delay] = gain;
samples[delay] = ir->gain;
spa_log_info(pl->log, "created dirac function");
break;
case TYPE_IR:
@ -848,22 +873,23 @@ static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float ga
struct spa_json it[1];
float v;
if (spa_json_begin_array_relax(&it[0], info->filename+4, strlen(info->filename+4)) <= 0)
return NULL;
return -EINVAL;
if (spa_json_get_int(&it[0], &h) <= 0)
return NULL;
return -EINVAL;
info->rate = h;
i = 0;
while (spa_json_get_float(&it[0], &v) > 0) {
samples[delay + i] = v * gain;
samples[delay + i] = v * ir->gain;
i++;
}
break;
}
}
*n_samples = n;
*rate = info->rate;
*latency = (int) (n * info->latency);
return samples;
ir->samples = samples;
ir->n_samples = n;
ir->latency = (int) (n * info->latency);
ir->rate = info->rate;
return 0;
}
@ -875,49 +901,44 @@ static void finfo_close(struct finfo *info)
#endif
}
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
int length, int channel, long unsigned *rate, int *n_samples, int *latency)
static int read_closest(struct plugin *pl, struct impulse *ir, int rate)
{
struct finfo finfo[MAX_RATES];
int res, diff = INT_MAX;
int res = -ENOENT, diff = INT_MAX;
uint32_t best = SPA_ID_INVALID, i;
float *samples = NULL;
spa_zero(finfo);
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
res = finfo_open(filenames[i], &finfo[i], *rate);
if (res < 0)
for (i = 0; i < MAX_RATES && ir->filenames[i] && ir->filenames[i][0]; i++) {
if ((res = finfo_open(ir->filenames[i], &finfo[i], rate)) < 0)
continue;
if (labs((long)finfo[i].rate - (long)*rate) < diff) {
if (labs((long)finfo[i].rate - (long)rate) < diff) {
best = i;
diff = labs((long)finfo[i].rate - (long)*rate);
diff = labs((long)finfo[i].rate - (long)rate);
spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate);
}
}
if (best != SPA_ID_INVALID) {
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]);
samples = finfo_read_samples(pl, &finfo[best], gain,
(int) (delay_sec * finfo[best].rate), offset, length,
channel, rate, n_samples, latency);
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, ir->filenames[best]);
res = finfo_read_samples(pl, &finfo[best], ir);
} else {
char buf[PATH_MAX];
spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
getcwd(buf, sizeof(buf)));
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
res = finfo_open(filenames[i], &finfo[i], *rate);
for (i = 0; i < MAX_RATES && ir->filenames[i] && ir->filenames[i][0]; i++) {
res = finfo_open(ir->filenames[i], &finfo[i], rate);
if (res < 0)
spa_log_error(pl->log, " failed file %s: %s", filenames[i], finfo[i].error);
spa_log_error(pl->log, " failed file %s: %s", ir->filenames[i], finfo[i].error);
else
spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]);
spa_log_warn(pl->log, " unexpectedly opened file %s", ir->filenames[i]);
}
}
for (i = 0; i < MAX_RATES; i++)
finfo_close(&finfo[i]);
return samples;
return res;
}
static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples,
@ -997,22 +1018,119 @@ error:
#endif
}
static int convolver_read_impulse(struct plugin *pl, struct spa_json *obj,
unsigned long SampleRate, struct impulse *ir)
{
int len, res = -EINVAL;
uint32_t i = 0;
char key[256];
const char *val;
struct spa_json it[2];
unsigned long rate;
while ((len = spa_json_object_next(obj, key, sizeof(key), &val)) > 0) {
if (spa_streq(key, "gain")) {
if (spa_json_parse_float(val, len, &ir->gain) <= 0) {
spa_log_error(pl->log, "convolver:gain requires a number");
goto error;
}
}
else if (spa_streq(key, "delay")) {
int delay_i;
if (spa_json_parse_int(val, len, &delay_i) > 0) {
ir->delay = delay_i / (float)SampleRate;
} else if (spa_json_parse_float(val, len, &ir->delay) <= 0) {
spa_log_error(pl->log, "convolver:delay requires a number");
goto error;
}
}
else if (spa_streq(key, "filename")) {
if (spa_json_is_array(val, len)) {
spa_json_enter(obj, &it[0]);
while ((len = spa_json_next(&it[0], &val)) > 0 &&
i < SPA_N_ELEMENTS(ir->filenames)) {
ir->filenames[i] = malloc(len+1);
if (ir->filenames[i] == NULL)
goto error_errno;
spa_json_parse_stringn(val, len, ir->filenames[i], len+1);
i++;
}
}
else {
ir->filenames[0] = malloc(len+1);
if (ir->filenames[0] == NULL)
goto error_errno;
spa_json_parse_stringn(val, len, ir->filenames[0], len+1);
}
}
else if (spa_streq(key, "offset")) {
if (spa_json_parse_int(val, len, &ir->offset) <= 0) {
spa_log_error(pl->log, "convolver:offset requires a number");
goto error;
}
}
else if (spa_streq(key, "length")) {
if (spa_json_parse_int(val, len, &ir->length) <= 0) {
spa_log_error(pl->log, "convolver:length requires a number");
goto error;
}
}
else if (spa_streq(key, "channel")) {
if (spa_json_parse_int(val, len, &ir->channel) <= 0) {
spa_log_error(pl->log, "convolver:channel requires a number");
goto error;
}
}
else if (spa_streq(key, "resample_quality")) {
if (spa_json_parse_int(val, len, &ir->resample_quality) <= 0) {
spa_log_error(pl->log, "convolver:resample_quality requires a number");
goto error;
}
}
else {
spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key);
}
}
if (ir->filenames[0] == NULL) {
spa_log_error(pl->log, "convolver:filename was not given");
goto error;
}
if (ir->delay < 0.0f)
ir->delay = 0.0f;
if (ir->offset < 0)
ir->offset = 0;
rate = SampleRate;
if ((res = read_closest(pl, ir, rate)) < 0)
goto error;
if (rate != ir->rate)
ir->samples = resample_buffer(pl, ir->samples, &ir->n_samples,
ir->rate, rate, ir->resample_quality);
if (ir->samples == NULL)
goto error_errno;
return res;
error_errno:
res = -errno;
error:
impulse_clear(ir);
return res;
}
static void * convolver_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 convolver_impl *impl;
float *samples;
int offset = 0, length = 0, channel = index, n_samples = 0, len;
uint32_t i = 0;
struct convolver_impl *impl = NULL;
int res, len;
struct spa_json it[2];
const char *val;
char key[256];
char *filenames[MAX_RATES] = { 0 };
int blocksize = 0, tailsize = 0;
int resample_quality = RESAMPLE_DEFAULT_QUALITY, def_latency;
float gain = 1.0f, delay = 0.0f, latency = -1.0f;
unsigned long rate;
float latency = -1.0f;
struct impulse ir = IMPULSE_INIT(index);
errno = EINVAL;
if (config == NULL) {
@ -1038,107 +1156,26 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
return NULL;
}
}
else if (spa_streq(key, "gain")) {
if (spa_json_parse_float(val, len, &gain) <= 0) {
spa_log_error(pl->log, "convolver:gain requires a number");
return NULL;
}
}
else if (spa_streq(key, "delay")) {
int delay_i;
if (spa_json_parse_int(val, len, &delay_i) > 0) {
delay = delay_i / (float)SampleRate;
} else if (spa_json_parse_float(val, len, &delay) <= 0) {
spa_log_error(pl->log, "convolver:delay requires a number");
return NULL;
}
}
else if (spa_streq(key, "filename")) {
if (spa_json_is_array(val, len)) {
spa_json_enter(&it[0], &it[1]);
while ((len = spa_json_next(&it[1], &val)) > 0 &&
i < SPA_N_ELEMENTS(filenames)) {
filenames[i] = malloc(len+1);
if (filenames[i] == NULL)
return NULL;
spa_json_parse_stringn(val, len, filenames[i], len+1);
i++;
}
}
else {
filenames[0] = malloc(len+1);
if (filenames[0] == NULL)
return NULL;
spa_json_parse_stringn(val, len, filenames[0], len+1);
}
}
else if (spa_streq(key, "offset")) {
if (spa_json_parse_int(val, len, &offset) <= 0) {
spa_log_error(pl->log, "convolver:offset requires a number");
return NULL;
}
}
else if (spa_streq(key, "length")) {
if (spa_json_parse_int(val, len, &length) <= 0) {
spa_log_error(pl->log, "convolver:length requires a number");
return NULL;
}
}
else if (spa_streq(key, "channel")) {
if (spa_json_parse_int(val, len, &channel) <= 0) {
spa_log_error(pl->log, "convolver:channel requires a number");
return NULL;
}
}
else if (spa_streq(key, "resample_quality")) {
if (spa_json_parse_int(val, len, &resample_quality) <= 0) {
spa_log_error(pl->log, "convolver:resample_quality requires a number");
return NULL;
}
}
else if (spa_streq(key, "latency")) {
if (spa_json_parse_float(val, len, &latency) <= 0) {
spa_log_error(pl->log, "convolver:latency requires a number");
return NULL;
}
}
else {
spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key);
}
}
if (filenames[0] == NULL) {
spa_log_error(pl->log, "convolver:filename was not given");
return NULL;
}
if (delay < 0.0f)
delay = 0.0f;
if (offset < 0)
offset = 0;
rate = SampleRate;
samples = read_closest(pl, filenames, gain, delay, offset,
length, channel, &rate, &n_samples, &def_latency);
if (samples != NULL && rate != SampleRate)
samples = resample_buffer(pl, samples, &n_samples,
rate, SampleRate, resample_quality);
for (i = 0; i < MAX_RATES; i++)
if (filenames[i])
free(filenames[i]);
if (samples == NULL) {
errno = ENOENT;
spa_json_begin_object(&it[0], config, strlen(config));
if ((res = convolver_read_impulse(pl, &it[0], SampleRate, &ir)) < 0) {
errno = -res;
return NULL;
}
if (blocksize <= 0)
blocksize = SPA_CLAMP(n_samples, 64, 256);
blocksize = SPA_CLAMP(ir.n_samples, 64, 256);
if (tailsize <= 0)
tailsize = SPA_CLAMP(4096, blocksize, 32768);
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", n_samples,
blocksize, tailsize, delay, def_latency);
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", ir.n_samples,
blocksize, tailsize, ir.delay, ir.latency);
impl = calloc(1, sizeof(*impl));
if (impl == NULL)
@ -1148,21 +1185,20 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
impl->log = pl->log;
impl->dsp = pl->dsp;
impl->rate = SampleRate;
impl->conv = convolver_new(impl->dsp, blocksize, tailsize, samples, n_samples);
if (impl->conv == NULL)
goto error;
if (latency < 0.0f)
impl->latency = def_latency;
impl->latency = ir.latency;
else
impl->latency = latency * impl->rate;
free(samples);
impl->conv = convolver_new(impl->dsp, blocksize, tailsize, ir.samples, ir.n_samples);
if (impl->conv == NULL)
goto error;
impulse_clear(&ir);
return impl;
error:
free(samples);
impulse_clear(&ir);
free(impl);
return NULL;
}
@ -1235,6 +1271,188 @@ static const struct spa_fga_descriptor convolve_desc = {
.cleanup = convolver_cleanup,
};
/** convolver2 */
static void * convolver2_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 convolver_impl *impl = NULL;
int i, len, res, max_samples = 0;
struct spa_json it[3];
const char *val;
char key[256];
int blocksize = 0, tailsize = 0;
float latency = -1.0f;
struct impulse ir[8];
struct convolver_ir cir[8];
int n_ir = 0;
errno = EINVAL;
if (config == NULL) {
spa_log_error(pl->log, "convolver: requires a config section");
return NULL;
}
if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) {
spa_log_error(pl->log, "convolver:config must be an object");
return NULL;
}
while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) {
if (spa_streq(key, "blocksize")) {
if (spa_json_parse_int(val, len, &blocksize) <= 0) {
spa_log_error(pl->log, "convolver2:blocksize requires a number");
return NULL;
}
}
else if (spa_streq(key, "tailsize")) {
if (spa_json_parse_int(val, len, &tailsize) <= 0) {
spa_log_error(pl->log, "convolver2:tailsize requires a number");
return NULL;
}
}
else if (spa_streq(key, "latency")) {
if (spa_json_parse_float(val, len, &latency) <= 0) {
spa_log_error(pl->log, "convolver2:latency requires a number");
return NULL;
}
}
else if (spa_streq(key, "impulses")) {
if (!spa_json_is_array(val, len)) {
spa_log_error(pl->log, "convolver2:impulses require an array");
return NULL;
}
spa_json_enter(&it[0], &it[1]);
while (spa_json_enter_object(&it[1], &it[2]) > 0) {
ir[n_ir] = IMPULSE_INIT(n_ir);
if ((res = convolver_read_impulse(pl, &it[2], SampleRate, &ir[n_ir])) < 0)
return NULL;
max_samples = SPA_MAX(max_samples, ir[n_ir].n_samples);
cir[n_ir].ir = ir[n_ir].samples;
cir[n_ir].len = ir[n_ir].n_samples;
n_ir++;
}
}
else {
spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key);
}
}
if (n_ir == 0) {
spa_log_error(pl->log, "convolver2:no impulses given");
goto error;
}
if (blocksize <= 0)
blocksize = SPA_CLAMP(max_samples, 64, 256);
if (tailsize <= 0)
tailsize = SPA_CLAMP(4096, blocksize, 32768);
spa_log_info(pl->log, "using max_samples:%u %d:%d blocksize", max_samples,
blocksize, tailsize);
impl = calloc(1, sizeof(*impl));
if (impl == NULL)
goto error;
impl->plugin = pl;
impl->log = pl->log;
impl->dsp = pl->dsp;
impl->rate = SampleRate;
if (latency < 0.0f)
impl->latency = ir[0].latency;
else
impl->latency = latency * impl->rate;
impl->conv = convolver_new_many(impl->dsp, blocksize, tailsize, cir, n_ir);
if (impl->conv == NULL)
goto error;
return impl;
error:
for (i = 0; i < n_ir; i++)
impulse_clear(&ir[i]);
free(impl);
return NULL;
}
static void convolver2_run(void * Instance, unsigned long SampleCount)
{
struct convolver_impl *impl = Instance;
if (impl->port[0] != NULL)
convolver_run_many(impl->conv, impl->port[0], &impl->port[2], SampleCount);
if (impl->port[1] != NULL)
impl->port[1][0] = impl->latency;
}
static void convolver2_activate(void * Instance)
{
struct convolver_impl *impl = Instance;
if (impl->port[1] != NULL)
impl->port[1][0] = impl->latency;
}
static struct spa_fga_port convolver2_ports[] = {
{ .index = 0,
.name = "In",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 1,
.name = "latency",
.hint = SPA_FGA_HINT_LATENCY,
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
},
{ .index = 2,
.name = "Out 1",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 3,
.name = "Out 2",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 4,
.name = "Out 3",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 5,
.name = "Out 4",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 6,
.name = "Out 5",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 7,
.name = "Out 6",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 8,
.name = "Out 7",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 9,
.name = "Out 8",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
};
static const struct spa_fga_descriptor convolver2_desc = {
.name = "convolver2",
.flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA,
.n_ports = SPA_N_ELEMENTS(convolver2_ports),
.ports = convolver2_ports,
.instantiate = convolver2_instantiate,
.connect_port = convolver_connect_port,
.activate = convolver2_activate,
.deactivate = convolver_deactivate,
.run = convolver2_run,
.cleanup = convolver_cleanup,
};
/** delay */
struct delay_impl {
struct plugin *plugin;
@ -3339,6 +3557,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index)
return &busy_desc;
case 32:
return &null_desc;
case 33:
return &convolver2_desc;
}
return NULL;
}

View file

@ -368,6 +368,11 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* The convolver has an input port "In" and an output port "Out". It requires a config
* section in the node declaration in this format:
*
* When multiple impulses are applied to one input, use the convolver2, which is more
* performant.
*
* Check the documentation for Convolver2 for the parameter meanings.
*
*\code{.unparsed}
* filter.graph = {
* nodes = [
@ -392,12 +397,50 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* }
* ...
* }
*
* ### Convolver2
*
* The convolver2 can be used to apply one or more impulse responses to a signal.
* It is usually used for reverbs or virtual surround. The convolver2 is implemented
* with a fast FFT implementation.
*
* The convolver2 has an input port "In" and 8 output ports "Out 1" to "Out 8". It
* requires a config section in the node declaration in this format:
*
*\code{.unparsed}
* filter.graph = {
* nodes = [
* {
* type = builtin
* name = ...
* label = convolver2
* config = {
* blocksize = ...
* tailsize = ...
* impulses = [
* {
* gain = ...
* delay = ...
* filename = ...
* offset = ...
* length = ...
* channel = ...
* resample_quality = ...
* }
* ...
* ]
* latency = ...
* }
* ...
* }
*
*\endcode
*
* - `blocksize` specifies the size of the blocks to use in the FFT. It is a value
* between 64 and 256. When not specified, this value is
* computed automatically from the number of samples in the file.
* - `tailsize` specifies the size of the tail blocks to use in the FFT.
* - `impulses` An array of objects with the IRs for the outputs "Out 1" to "Out 8".
* - `gain` the overall gain to apply to the IR file. Default 1.0
* - `delay` The extra delay to add to the IR. A float number will be interpreted as seconds,
* and integer as samples. Using the delay in seconds is independent of the graph
@ -420,7 +463,7 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* - `resample_quality` The resample quality in case the IR does not match the graph
* samplerate.
* - `latency` The extra latency in seconds to report. When left unspecified (or < 0.0)
* the default IR latency will be used, the the filename argument.
* the default IR latency will be used, depending on the filename argument.
*
* ### Delay
*