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.