From fb20b9602472832bad60b9c97fcef7f57451c1e1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 15 Mar 2024 12:14:37 +0100 Subject: [PATCH] filter-graph: support inline convolver IRs Refactor the IR loading code, Make some generic open/load/close functions and handle the different filenames in the open call. This makes it possible to reuse some of the delay and alloc code. It also makes it easier to add new IRs loading code. Support /IR:,,,... as an inline IR definition. --- spa/plugins/filter-graph/plugin_builtin.c | 256 ++++++++++++---------- src/modules/module-filter-chain.c | 2 +- 2 files changed, 147 insertions(+), 111 deletions(-) diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index 7b343b44b..6e47a9b51 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -704,11 +704,78 @@ struct convolver_impl { struct convolver *conv; }; +struct finfo { +#define TYPE_INVALID 0 +#define TYPE_SNDFILE 1 +#define TYPE_HILBERT 2 +#define TYPE_DIRAC 3 +#define TYPE_IR 4 + uint32_t type; + + const char *filename; #ifdef HAVE_SNDFILE -static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain, int delay, - int offset, int length, int channel, long unsigned *rate, int *n_samples) { - float *samples; - int i, n; + SF_INFO info; + SNDFILE *fs; +#endif + int channels; + int frames; + uint32_t rate; + const char *error; +}; + +static int finfo_open(const char *filename, struct finfo *info, int rate) +{ + info->filename = filename; + if (spa_strstartswith(filename, "/hilbert")) { + info->channels = 1; + info->rate = rate; + info->frames = 64; + info->type = TYPE_HILBERT; + } + else if (spa_strstartswith(filename, "/dirac")) { + info->channels = 1; + info->frames = 1; + info->rate = rate; + info->type = TYPE_DIRAC; + } + else if (spa_strstartswith(filename, "/ir:")) { + struct spa_json it[1]; + float v; + int rate; + info->channels = 1; + info->type = TYPE_IR; + info->frames = 0; + if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0) + return -EINVAL; + if (spa_json_get_int(&it[0], &rate) <= 0) + return -EINVAL; + info->rate = rate; + while (spa_json_get_float(&it[0], &v) > 0) + info->frames++; + } else { +#ifdef HAVE_SNDFILE + info->fs = sf_open(filename, SFM_READ, &info->info); + if (info->fs == NULL) { + info->error = sf_strerror(NULL); + return -ENOENT; + } + info->channels = info->info.channels; + info->frames = info->info.frames; + info->rate = info->info.samplerate; + info->type = TYPE_SNDFILE; +#else + info->error = "compiled without sndfile support, can't load samples"; + return -ENOTSUP; +#endif + } + 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) +{ + float *samples, v; + int i, n, h; if (length <= 0) length = info->frames; @@ -725,128 +792,106 @@ static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain, if (samples == NULL) return NULL; - if (offset > 0) - sf_seek(f, offset, SEEK_SET); - sf_readf_float(f, samples + (delay * info->channels), length); - channel = channel % info->channels; - for (i = 0; i < n; i++) - samples[i] = samples[info->channels * i + channel] * gain; - + 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); + for (i = 0; i < n; i++) + samples[i] = samples[info->channels * i + channel] * gain; +#endif + break; + case TYPE_HILBERT: + gain *= 2 / (float)M_PI; + h = length / 2; + for (i = 1; i < h; i += 2) { + v = (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); + break; + case TYPE_DIRAC: + samples[delay] = gain; + spa_log_info(pl->log, "created dirac function"); + break; + case TYPE_IR: + { + 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; + if (spa_json_get_int(&it[0], &h) <= 0) + return NULL; + info->rate = h; + i = 0; + while (spa_json_get_float(&it[0], &v) > 0) { + samples[delay + i] = v * gain; + i++; + } + break; + } + } *n_samples = n; - *rate = info->samplerate; + *rate = info->rate; return samples; } + + +static void finfo_close(struct finfo *info) +{ +#ifdef HAVE_SNDFILE + if (info->type == TYPE_SNDFILE && info->fs != NULL) + sf_close(info->fs); #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) { -#ifdef HAVE_SNDFILE - SF_INFO infos[MAX_RATES]; - SNDFILE *fs[MAX_RATES]; - - spa_zero(infos); - spa_zero(fs); - - int diff = INT_MAX; - uint32_t best = 0, i; + struct finfo finfo[MAX_RATES]; + int res, 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++) { - fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]); - if (fs[i] == NULL) + res = finfo_open(filenames[i], &finfo[i], *rate); + if (res < 0) continue; - if (labs((long)infos[i].samplerate - (long)*rate) < diff) { + if (labs((long)finfo[i].rate - (long)*rate) < diff) { best = i; - diff = labs((long)infos[i].samplerate - (long)*rate); - spa_log_debug(pl->log, "new closest match: %d", infos[i].samplerate); + diff = labs((long)finfo[i].rate - (long)*rate); + spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate); } } - if (fs[best] != NULL) { - spa_log_info(pl->log, "loading best rate:%u %s", infos[best].samplerate, filenames[best]); - samples = read_samples_from_sf(fs[best], &infos[best], gain, - (int) (delay_sec * infos[best].samplerate), offset, length, + 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); } 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++) { - fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]); - if (fs[i] == NULL) - spa_log_error(pl->log, " failed file %s: %s", filenames[i], sf_strerror(fs[i])); + res = finfo_open(filenames[i], &finfo[i], *rate); + if (res < 0) + spa_log_error(pl->log, " failed file %s: %s", filenames[i], finfo[i].error); else spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]); } } for (i = 0; i < MAX_RATES; i++) - if (fs[i] != NULL) - sf_close(fs[i]); + finfo_close(&finfo[i]); return samples; -#else - spa_log_error(pl->log, "compiled without sndfile support, can't load samples: " - "using dirac impulse"); - float *samples = calloc(1, sizeof(float)); - samples[0] = gain; - *n_samples = 1; - return samples; -#endif -} - -static float *create_hilbert(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset, - int length, int *n_samples) -{ - float *samples, v; - int i, n, h; - int delay = (int) (delay_sec * rate); - - if (length <= 0) - length = 64; - - length -= SPA_MIN(offset, length); - - n = delay + length; - if (n == 0) - return NULL; - - samples = calloc(n, sizeof(float)); - if (samples == NULL) - return NULL; - - gain *= 2 / (float)M_PI; - h = length / 2; - for (i = 1; i < h; i += 2) { - v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h)); - samples[delay + h + i] = -v; - samples[delay + h - i] = v; - } - *n_samples = n; - spa_log_info(pl->log, "created hilbert function"); - return samples; -} - -static float *create_dirac(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset, - int length, int *n_samples) -{ - float *samples; - int delay = (int) (delay_sec * rate); - int n; - - n = delay + 1; - - samples = calloc(n, sizeof(float)); - if (samples == NULL) - return NULL; - - samples[delay] = gain; - - spa_log_info(pl->log, "created dirac function"); - *n_samples = n; - return samples; } static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples, @@ -1045,21 +1090,12 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s if (offset < 0) offset = 0; - if (spa_streq(filenames[0], "/hilbert")) { - samples = create_hilbert(pl, filenames[0], gain, SampleRate, delay, offset, - length, &n_samples); - } else if (spa_streq(filenames[0], "/dirac")) { - samples = create_dirac(pl, filenames[0], gain, SampleRate, delay, offset, - length, &n_samples); - } else { - rate = SampleRate; - samples = read_closest(pl, filenames, gain, delay, offset, - length, channel, &rate, &n_samples); - if (samples != NULL && rate != SampleRate) { - samples = resample_buffer(pl, samples, &n_samples, - rate, SampleRate, resample_quality); - } - } + rate = SampleRate; + samples = read_closest(pl, filenames, gain, delay, offset, + length, channel, &rate, &n_samples); + 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]) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 2cf053ff8..b9ec35f0c 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -405,7 +405,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - `/dirac` creates a [Dirac function](https://en.wikipedia.org/wiki/Dirac_delta_function) that * can be used as gain. * - A filename to load as the IR. This needs to be a file format supported - * by sndfile. + * by sndfile or be an inline IR with "/ir:,,". * - [ filename, ... ] an array of filenames. The file with the closest samplerate match * with the graph samplerate will be used. * - `offset` The sample offset in the file as the start of the IR.