mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-04-23 06:47:02 -04:00
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:
parent
2b96f694f7
commit
9cae4ce7e7
4 changed files with 580 additions and 255 deletions
|
|
@ -14,28 +14,31 @@
|
||||||
|
|
||||||
#include <spa/utils/defs.h>
|
#include <spa/utils/defs.h>
|
||||||
|
|
||||||
|
struct ir {
|
||||||
|
float **segments;
|
||||||
|
float *time_buffer[2];
|
||||||
|
float *precalc[2];
|
||||||
|
};
|
||||||
|
|
||||||
struct partition {
|
struct partition {
|
||||||
struct convolver *conv;
|
struct convolver *conv;
|
||||||
|
|
||||||
int block_size;
|
int block_size;
|
||||||
int time_size;
|
int time_size;
|
||||||
int freq_size;
|
int freq_size;
|
||||||
|
|
||||||
int n_segments;
|
|
||||||
float **segments;
|
|
||||||
float **segments_ir;
|
|
||||||
int current;
|
|
||||||
|
|
||||||
float *time_buffer[2];
|
|
||||||
|
|
||||||
void *fft;
|
void *fft;
|
||||||
void *ifft;
|
void *ifft;
|
||||||
|
|
||||||
float *pre_mult;
|
|
||||||
float *freq;
|
float *freq;
|
||||||
float *precalc[2];
|
|
||||||
|
int n_segments;
|
||||||
|
int current;
|
||||||
|
float **segments;
|
||||||
|
|
||||||
int block_fill;
|
int block_fill;
|
||||||
|
int n_ir;
|
||||||
|
struct ir *ir;
|
||||||
|
int time_idx;
|
||||||
|
int precalc_idx;
|
||||||
|
|
||||||
float scale;
|
float scale;
|
||||||
};
|
};
|
||||||
|
|
@ -74,68 +77,74 @@ static void partition_reset(struct spa_fga_dsp *dsp, struct partition *part)
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < part->n_segments; 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->segments[i], part->freq_size, false);
|
||||||
spa_fga_dsp_fft_memclear(dsp, part->time_buffer[0], part->time_size, true);
|
for (i = 0; i < part->n_ir; i++) {
|
||||||
spa_fga_dsp_fft_memclear(dsp, part->time_buffer[1], part->time_size, true);
|
struct ir *r = &part->ir[i];
|
||||||
spa_fga_dsp_fft_memclear(dsp, part->pre_mult, part->freq_size, false);
|
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->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->block_fill = 0;
|
||||||
|
part->time_idx = 0;
|
||||||
|
part->precalc_idx = 0;
|
||||||
part->current = 0;
|
part->current = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void partition_free(struct spa_fga_dsp *dsp, struct partition *part)
|
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++) {
|
for (i = 0; i < part->n_segments; i++) {
|
||||||
if (part->segments)
|
if (part->segments)
|
||||||
spa_fga_dsp_fft_memfree(dsp, part->segments[i]);
|
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)
|
if (part->fft)
|
||||||
spa_fga_dsp_fft_free(dsp, part->fft);
|
spa_fga_dsp_fft_free(dsp, part->fft);
|
||||||
if (part->ifft)
|
if (part->ifft)
|
||||||
spa_fga_dsp_fft_free(dsp, 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);
|
||||||
free(part->segments_ir);
|
free(part->ir);
|
||||||
spa_fga_dsp_fft_memfree(dsp, part->pre_mult);
|
|
||||||
spa_fga_dsp_fft_memfree(dsp, part->freq);
|
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);
|
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 partition *part;
|
||||||
struct spa_fga_dsp *dsp = conv->dsp;
|
struct spa_fga_dsp *dsp = conv->dsp;
|
||||||
int i;
|
int i, j;
|
||||||
|
|
||||||
if (block == 0)
|
if (block == 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
|
|
||||||
irlen--;
|
|
||||||
|
|
||||||
part = calloc(1, sizeof(*part));
|
part = calloc(1, sizeof(*part));
|
||||||
if (part == NULL)
|
if (part == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
part->conv = conv;
|
part->conv = conv;
|
||||||
|
|
||||||
if (irlen == 0)
|
|
||||||
return part;
|
|
||||||
|
|
||||||
|
|
||||||
part->block_size = next_power_of_two(block);
|
part->block_size = next_power_of_two(block);
|
||||||
part->time_size = 2 * part->block_size;
|
part->time_size = 2 * part->block_size;
|
||||||
part->n_segments = (irlen + part->block_size-1) / part->block_size;
|
part->n_segments = (irlen + part->block_size-1) / part->block_size;
|
||||||
part->freq_size = (part->time_size / 2) + 1;
|
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);
|
part->fft = spa_fga_dsp_fft_new(dsp, part->time_size, true);
|
||||||
if (part->fft == NULL)
|
if (part->fft == NULL)
|
||||||
|
|
@ -144,38 +153,44 @@ static struct partition *partition_new(struct convolver *conv, int block, const
|
||||||
if (part->ifft == NULL)
|
if (part->ifft == NULL)
|
||||||
goto error;
|
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 = calloc(part->n_segments, sizeof(float*));
|
||||||
part->segments_ir = calloc(part->n_segments, sizeof(float*));
|
part->freq = spa_fga_dsp_fft_memalloc(dsp, part->freq_size, false);
|
||||||
if (part->segments == NULL || part->segments_ir == NULL)
|
part->ir = calloc(part->n_ir, sizeof(struct ir));
|
||||||
|
if (part->segments == NULL || part->freq == NULL || part->ir == NULL)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
for (i = 0; i < part->n_segments; i++) {
|
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[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)
|
||||||
if (part->segments[i] == NULL || part->segments_ir[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;
|
goto error;
|
||||||
|
|
||||||
spa_fga_dsp_copy(dsp, part->time_buffer[0], &ir[i * part->block_size], copy);
|
for (j = 0; j < part->n_segments; j++) {
|
||||||
if (copy < part->time_size)
|
int left = ir[i].len - iroffset - (j * part->block_size);
|
||||||
spa_fga_dsp_fft_memclear(dsp, part->time_buffer[0] + copy, part->time_size - copy, true);
|
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;
|
part->scale = 1.0f / part->time_size;
|
||||||
partition_reset(dsp, part);
|
partition_reset(dsp, part);
|
||||||
|
|
||||||
|
|
@ -185,41 +200,50 @@ error:
|
||||||
return NULL;
|
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 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,
|
for (i = 0; i < part->n_ir; i++) {
|
||||||
part->freq,
|
struct ir *r = &part->ir[i];
|
||||||
part->segments[current],
|
int current = part->current;
|
||||||
part->segments_ir[0],
|
|
||||||
part->freq_size, part->scale);
|
|
||||||
|
|
||||||
for (i = 1; i < part->n_segments; i++) {
|
spa_fga_dsp_fft_cmul(dsp, part->fft,
|
||||||
if (++current == part->n_segments)
|
|
||||||
current = 0;
|
|
||||||
|
|
||||||
spa_fga_dsp_fft_cmuladd(dsp, part->fft,
|
|
||||||
part->freq,
|
|
||||||
part->freq,
|
part->freq,
|
||||||
part->segments[current],
|
part->segments[current],
|
||||||
part->segments_ir[i],
|
r->segments[0],
|
||||||
part->freq_size, part->scale);
|
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,
|
for (j = 1; j < part->n_segments; j++) {
|
||||||
part->time_buffer[1] + part->block_size + block_fill, len);
|
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;
|
block_fill += len;
|
||||||
if (block_fill == part->block_size) {
|
if (block_fill == part->block_size) {
|
||||||
block_fill = 0;
|
block_fill = 0;
|
||||||
|
|
||||||
SPA_SWAP(part->time_buffer[0], part->time_buffer[1]);
|
part->time_idx = idx ^ 1;
|
||||||
|
|
||||||
if (part->current == 0)
|
if (part->current == 0)
|
||||||
part->current = part->n_segments;
|
part->current = part->n_segments;
|
||||||
|
|
@ -240,7 +264,7 @@ static void *do_background_process(void *data)
|
||||||
if (!conv->running)
|
if (!conv->running)
|
||||||
break;
|
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);
|
sem_post(&conv->sem_finish);
|
||||||
}
|
}
|
||||||
|
|
@ -260,10 +284,12 @@ void convolver_reset(struct convolver *conv)
|
||||||
conv->delay_fill = 0;
|
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;
|
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)
|
if (head_block == 0 || tail_block == 0)
|
||||||
return NULL;
|
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)
|
if (head_block > tail_block)
|
||||||
SPA_SWAP(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));
|
conv = calloc(1, sizeof(*conv));
|
||||||
if (conv == NULL)
|
if (conv == NULL)
|
||||||
return 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->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)
|
if (irlen == 0)
|
||||||
return conv;
|
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;
|
min_size = conv->min_size;
|
||||||
max_size = conv->max_size;
|
max_size = conv->max_size;
|
||||||
|
|
||||||
while (ir_consumed < irlen) {
|
while (ir_consumed < irlen) {
|
||||||
head_ir_len = SPA_MIN(irlen - ir_consumed, 2 * max_size);
|
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)
|
if (conv->partition[conv->n_partition] == NULL)
|
||||||
goto error;
|
goto error;
|
||||||
conv->n_partition++;
|
conv->n_partition++;
|
||||||
|
|
@ -304,11 +343,6 @@ struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tai
|
||||||
max_size = irlen;
|
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);
|
convolver_reset(conv);
|
||||||
|
|
||||||
conv->threaded = false;
|
conv->threaded = false;
|
||||||
|
|
@ -334,6 +368,12 @@ error:
|
||||||
return NULL;
|
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)
|
void convolver_free(struct convolver *conv)
|
||||||
{
|
{
|
||||||
struct spa_fga_dsp *dsp = conv->dsp;
|
struct spa_fga_dsp *dsp = conv->dsp;
|
||||||
|
|
@ -354,13 +394,16 @@ void convolver_free(struct convolver *conv)
|
||||||
free(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;
|
struct spa_fga_dsp *dsp = conv->dsp;
|
||||||
|
|
||||||
|
if (conv->n_partition == 0)
|
||||||
|
return length;
|
||||||
|
|
||||||
while (processed < length) {
|
while (processed < length) {
|
||||||
int remaining = length - processed;
|
int remaining = length - processed, pc_idx;
|
||||||
float *delay = conv->delay[0];
|
float *delay = conv->delay[0];
|
||||||
int delay_fill = conv->delay_fill;
|
int delay_fill = conv->delay_fill;
|
||||||
struct partition *part = conv->partition[0];
|
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));
|
spa_memcpy(delay + delay_fill, input + processed, processing * sizeof(float));
|
||||||
conv->delay_fill += processing;
|
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++) {
|
for (i = 1; i < conv->n_partition; i++) {
|
||||||
part = conv->partition[i];
|
part = conv->partition[i];
|
||||||
|
pc_idx = part->precalc_idx;
|
||||||
block_size = part->block_size;
|
block_size = part->block_size;
|
||||||
block_fill = delay_fill % block_size;
|
block_fill = delay_fill % block_size;
|
||||||
|
|
||||||
spa_fga_dsp_sum(dsp, &output[processed], &output[processed],
|
for (j = 0; j < part->n_ir; j++) {
|
||||||
&part->precalc[0][block_fill], processing);
|
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) {
|
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,
|
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) {
|
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;
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,18 @@
|
||||||
|
|
||||||
#include "audio-dsp.h"
|
#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);
|
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_free(struct convolver *conv);
|
||||||
|
|
||||||
void convolver_reset(struct convolver *conv);
|
void convolver_reset(struct convolver *conv);
|
||||||
int convolver_run(struct convolver *conv, const float *input, float *output, int length);
|
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);
|
||||||
|
|
|
||||||
|
|
@ -706,14 +706,14 @@ static const struct spa_fga_descriptor bq_raw_desc = {
|
||||||
.cleanup = builtin_cleanup,
|
.cleanup = builtin_cleanup,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** convolve */
|
/** convolver */
|
||||||
struct convolver_impl {
|
struct convolver_impl {
|
||||||
struct plugin *plugin;
|
struct plugin *plugin;
|
||||||
|
|
||||||
struct spa_log *log;
|
struct spa_log *log;
|
||||||
struct spa_fga_dsp *dsp;
|
struct spa_fga_dsp *dsp;
|
||||||
unsigned long rate;
|
unsigned long rate;
|
||||||
float *port[3];
|
float *port[10];
|
||||||
float latency;
|
float latency;
|
||||||
|
|
||||||
struct convolver *conv;
|
struct convolver *conv;
|
||||||
|
|
@ -796,51 +796,76 @@ static int finfo_open(const char *filename, struct finfo *info, int rate)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay,
|
struct impulse {
|
||||||
int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency)
|
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;
|
float *samples, v;
|
||||||
int i, n, h;
|
int i, n, h, delay = (int)(ir->delay * info->rate);
|
||||||
|
|
||||||
if (length <= 0)
|
if (ir->length <= 0)
|
||||||
length = info->def_frames;
|
ir->length = info->def_frames;
|
||||||
else
|
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)
|
if (n == 0)
|
||||||
return NULL;
|
return -EINVAL;
|
||||||
|
|
||||||
samples = calloc(n * info->channels, sizeof(float));
|
samples = calloc(n * info->channels, sizeof(float));
|
||||||
if (samples == NULL)
|
if (samples == NULL)
|
||||||
return NULL;
|
return -errno;
|
||||||
|
|
||||||
channel = channel % info->channels;
|
ir->channel = ir->channel % info->channels;
|
||||||
|
|
||||||
switch (info->type) {
|
switch (info->type) {
|
||||||
case TYPE_SNDFILE:
|
case TYPE_SNDFILE:
|
||||||
#ifdef HAVE_SNDFILE
|
#ifdef HAVE_SNDFILE
|
||||||
if (offset > 0)
|
if (ir->offset > 0)
|
||||||
sf_seek(info->fs, offset, SEEK_SET);
|
sf_seek(info->fs, ir->offset, SEEK_SET);
|
||||||
sf_readf_float(info->fs, samples + (delay * info->channels), length);
|
sf_readf_float(info->fs, samples + (delay * info->channels), ir->length);
|
||||||
for (i = 0; i < n; i++)
|
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
|
#endif
|
||||||
break;
|
break;
|
||||||
case TYPE_HILBERT:
|
case TYPE_HILBERT:
|
||||||
gain *= 2 / (float)M_PI;
|
ir->gain *= 2 / (float)M_PI;
|
||||||
h = length / 2;
|
h = ir->length / 2;
|
||||||
for (i = 1; i < h; i += 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;
|
||||||
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;
|
break;
|
||||||
case TYPE_DIRAC:
|
case TYPE_DIRAC:
|
||||||
samples[delay] = gain;
|
samples[delay] = ir->gain;
|
||||||
spa_log_info(pl->log, "created dirac function");
|
spa_log_info(pl->log, "created dirac function");
|
||||||
break;
|
break;
|
||||||
case TYPE_IR:
|
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];
|
struct spa_json it[1];
|
||||||
float v;
|
float v;
|
||||||
if (spa_json_begin_array_relax(&it[0], info->filename+4, strlen(info->filename+4)) <= 0)
|
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)
|
if (spa_json_get_int(&it[0], &h) <= 0)
|
||||||
return NULL;
|
return -EINVAL;
|
||||||
info->rate = h;
|
info->rate = h;
|
||||||
i = 0;
|
i = 0;
|
||||||
while (spa_json_get_float(&it[0], &v) > 0) {
|
while (spa_json_get_float(&it[0], &v) > 0) {
|
||||||
samples[delay + i] = v * gain;
|
samples[delay + i] = v * ir->gain;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*n_samples = n;
|
ir->samples = samples;
|
||||||
*rate = info->rate;
|
ir->n_samples = n;
|
||||||
*latency = (int) (n * info->latency);
|
ir->latency = (int) (n * info->latency);
|
||||||
return samples;
|
ir->rate = info->rate;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -875,49 +901,44 @@ static void finfo_close(struct finfo *info)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
|
static int read_closest(struct plugin *pl, struct impulse *ir, int rate)
|
||||||
int length, int channel, long unsigned *rate, int *n_samples, int *latency)
|
|
||||||
{
|
{
|
||||||
struct finfo finfo[MAX_RATES];
|
struct finfo finfo[MAX_RATES];
|
||||||
int res, diff = INT_MAX;
|
int res = -ENOENT, diff = INT_MAX;
|
||||||
uint32_t best = SPA_ID_INVALID, i;
|
uint32_t best = SPA_ID_INVALID, i;
|
||||||
float *samples = NULL;
|
|
||||||
|
|
||||||
spa_zero(finfo);
|
spa_zero(finfo);
|
||||||
|
|
||||||
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
|
for (i = 0; i < MAX_RATES && ir->filenames[i] && ir->filenames[i][0]; i++) {
|
||||||
res = finfo_open(filenames[i], &finfo[i], *rate);
|
if ((res = finfo_open(ir->filenames[i], &finfo[i], rate)) < 0)
|
||||||
if (res < 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (labs((long)finfo[i].rate - (long)*rate) < diff) {
|
if (labs((long)finfo[i].rate - (long)rate) < diff) {
|
||||||
best = i;
|
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);
|
spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (best != SPA_ID_INVALID) {
|
if (best != SPA_ID_INVALID) {
|
||||||
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]);
|
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, ir->filenames[best]);
|
||||||
samples = finfo_read_samples(pl, &finfo[best], gain,
|
res = finfo_read_samples(pl, &finfo[best], ir);
|
||||||
(int) (delay_sec * finfo[best].rate), offset, length,
|
|
||||||
channel, rate, n_samples, latency);
|
|
||||||
} else {
|
} else {
|
||||||
char buf[PATH_MAX];
|
char buf[PATH_MAX];
|
||||||
spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
|
spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
|
||||||
getcwd(buf, sizeof(buf)));
|
getcwd(buf, sizeof(buf)));
|
||||||
|
|
||||||
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
|
for (i = 0; i < MAX_RATES && ir->filenames[i] && ir->filenames[i][0]; i++) {
|
||||||
res = finfo_open(filenames[i], &finfo[i], *rate);
|
res = finfo_open(ir->filenames[i], &finfo[i], rate);
|
||||||
if (res < 0)
|
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
|
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++)
|
for (i = 0; i < MAX_RATES; i++)
|
||||||
finfo_close(&finfo[i]);
|
finfo_close(&finfo[i]);
|
||||||
|
|
||||||
return samples;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples,
|
static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples,
|
||||||
|
|
@ -997,22 +1018,119 @@ error:
|
||||||
#endif
|
#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,
|
static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor,
|
||||||
unsigned long SampleRate, int index, const char *config)
|
unsigned long SampleRate, int index, const char *config)
|
||||||
{
|
{
|
||||||
struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin);
|
struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin);
|
||||||
struct convolver_impl *impl;
|
struct convolver_impl *impl = NULL;
|
||||||
float *samples;
|
int res, len;
|
||||||
int offset = 0, length = 0, channel = index, n_samples = 0, len;
|
|
||||||
uint32_t i = 0;
|
|
||||||
struct spa_json it[2];
|
struct spa_json it[2];
|
||||||
const char *val;
|
const char *val;
|
||||||
char key[256];
|
char key[256];
|
||||||
char *filenames[MAX_RATES] = { 0 };
|
|
||||||
int blocksize = 0, tailsize = 0;
|
int blocksize = 0, tailsize = 0;
|
||||||
int resample_quality = RESAMPLE_DEFAULT_QUALITY, def_latency;
|
float latency = -1.0f;
|
||||||
float gain = 1.0f, delay = 0.0f, latency = -1.0f;
|
struct impulse ir = IMPULSE_INIT(index);
|
||||||
unsigned long rate;
|
|
||||||
|
|
||||||
errno = EINVAL;
|
errno = EINVAL;
|
||||||
if (config == NULL) {
|
if (config == NULL) {
|
||||||
|
|
@ -1038,107 +1156,26 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
||||||
return NULL;
|
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")) {
|
else if (spa_streq(key, "latency")) {
|
||||||
if (spa_json_parse_float(val, len, &latency) <= 0) {
|
if (spa_json_parse_float(val, len, &latency) <= 0) {
|
||||||
spa_log_error(pl->log, "convolver:latency requires a number");
|
spa_log_error(pl->log, "convolver:latency requires a number");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (filenames[0] == NULL) {
|
spa_json_begin_object(&it[0], config, strlen(config));
|
||||||
spa_log_error(pl->log, "convolver:filename was not given");
|
if ((res = convolver_read_impulse(pl, &it[0], SampleRate, &ir)) < 0) {
|
||||||
return NULL;
|
errno = -res;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blocksize <= 0)
|
if (blocksize <= 0)
|
||||||
blocksize = SPA_CLAMP(n_samples, 64, 256);
|
blocksize = SPA_CLAMP(ir.n_samples, 64, 256);
|
||||||
if (tailsize <= 0)
|
if (tailsize <= 0)
|
||||||
tailsize = SPA_CLAMP(4096, blocksize, 32768);
|
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,
|
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", ir.n_samples,
|
||||||
blocksize, tailsize, delay, def_latency);
|
blocksize, tailsize, ir.delay, ir.latency);
|
||||||
|
|
||||||
impl = calloc(1, sizeof(*impl));
|
impl = calloc(1, sizeof(*impl));
|
||||||
if (impl == NULL)
|
if (impl == NULL)
|
||||||
|
|
@ -1148,21 +1185,20 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
||||||
impl->log = pl->log;
|
impl->log = pl->log;
|
||||||
impl->dsp = pl->dsp;
|
impl->dsp = pl->dsp;
|
||||||
impl->rate = SampleRate;
|
impl->rate = SampleRate;
|
||||||
|
|
||||||
impl->conv = convolver_new(impl->dsp, blocksize, tailsize, samples, n_samples);
|
|
||||||
if (impl->conv == NULL)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (latency < 0.0f)
|
if (latency < 0.0f)
|
||||||
impl->latency = def_latency;
|
impl->latency = ir.latency;
|
||||||
else
|
else
|
||||||
impl->latency = latency * impl->rate;
|
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;
|
return impl;
|
||||||
error:
|
error:
|
||||||
free(samples);
|
impulse_clear(&ir);
|
||||||
free(impl);
|
free(impl);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -1235,6 +1271,188 @@ static const struct spa_fga_descriptor convolve_desc = {
|
||||||
.cleanup = convolver_cleanup,
|
.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 */
|
/** delay */
|
||||||
struct delay_impl {
|
struct delay_impl {
|
||||||
struct plugin *plugin;
|
struct plugin *plugin;
|
||||||
|
|
@ -3339,6 +3557,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index)
|
||||||
return &busy_desc;
|
return &busy_desc;
|
||||||
case 32:
|
case 32:
|
||||||
return &null_desc;
|
return &null_desc;
|
||||||
|
case 33:
|
||||||
|
return &convolver2_desc;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
* The convolver has an input port "In" and an output port "Out". It requires a config
|
||||||
* section in the node declaration in this format:
|
* 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}
|
*\code{.unparsed}
|
||||||
* filter.graph = {
|
* filter.graph = {
|
||||||
* nodes = [
|
* 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
|
*\endcode
|
||||||
*
|
*
|
||||||
* - `blocksize` specifies the size of the blocks to use in the FFT. It is a value
|
* - `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
|
* between 64 and 256. When not specified, this value is
|
||||||
* computed automatically from the number of samples in the file.
|
* computed automatically from the number of samples in the file.
|
||||||
* - `tailsize` specifies the size of the tail blocks to use in the FFT.
|
* - `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
|
* - `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,
|
* - `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
|
* 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
|
* - `resample_quality` The resample quality in case the IR does not match the graph
|
||||||
* samplerate.
|
* samplerate.
|
||||||
* - `latency` The extra latency in seconds to report. When left unspecified (or < 0.0)
|
* - `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
|
* ### Delay
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue