Permalink
Fetching contributors…
Cannot retrieve contributors at this time
580 lines (497 sloc) 15.8 KB
/* convoLV2 -- LV2 convolution plugin
*
* Copyright (C) 2012 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Usage information:
*
* Non-realtime, initialization:
* 1) clv_alloc();
* 2) clv_configure(); // can be called multiple times
* 3) clv_initialize(); // fix settings
*
* Realtime process
* 4) convolve();
*
* Non-rt, cleanup
* 5A) clv_release(); // -> goto (2) or (3)
* OR
* 5B) clv_free(); // -> The End
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>
#include <assert.h>
#include <zita-convolver.h>
#include <sndfile.h>
#include <samplerate.h>
#include "convolution.h"
#if ZITA_CONVOLVER_MAJOR_VERSION != 3
# error "This programs requires zita-convolver 3.x.x"
#endif
#ifndef SRC_QUALITY // alternatives: SRC_SINC_FASTEST, SRC_SINC_MEDIUM_QUALITY, (SRC_ZERO_ORDER_HOLD, SRC_LINEAR)
# define SRC_QUALITY SRC_SINC_BEST_QUALITY
#endif
static pthread_mutex_t fftw_planner_lock = PTHREAD_MUTEX_INITIALIZER;
struct LV2convolv {
Convproc *convproc;
/* IR file */
char *ir_fn; ///< path to IR file
unsigned int chn_inp[MAX_CHANNEL_MAPS]; ///< I/O channel map: ir_map[id] = in-channel ;
unsigned int chn_out[MAX_CHANNEL_MAPS]; ///< I/O channel map: ir_map[id] = out-channel ;
unsigned int ir_chan[MAX_CHANNEL_MAPS]; ///< IR channel map: ir_chan[id] = file-channel;
unsigned int ir_delay[MAX_CHANNEL_MAPS]; ///< pre-delay ; value >=0
float ir_gain[MAX_CHANNEL_MAPS]; ///< IR-gain value: float -inf..+inf
/* convolution settings*/
unsigned int size; ///< max length of convolution computation
float density; ///< density; 0<= dens <= 1.0 ; '0' = auto (1.0 / min(inchn,outchn)
/* process settings */
unsigned int fragment_size; ///< process period-size
};
/** read an audio-file completely into memory
* allocated memory needs to be free()ed by caller
*/
static int audiofile_read (const char *fn, const int sample_rate, float **buf, unsigned int *n_ch, unsigned int *n_sp) {
SF_INFO nfo;
SNDFILE *sndfile;
float resample_ratio = 1.0;
memset(&nfo, 0, sizeof(SF_INFO));
if ((sndfile = sf_open(fn, SFM_READ, &nfo)) == 0) {
return -1;
}
if (n_ch) *n_ch = (unsigned int) nfo.channels;
if (n_sp) *n_sp = (unsigned int) nfo.frames;
if (sample_rate != nfo.samplerate) {
fprintf(stderr, "convoLV2: samplerate mismatch file:%d host:%d\n", nfo.samplerate, sample_rate);
resample_ratio = (float) sample_rate / (float) nfo.samplerate;
}
if (buf) {
const size_t frames_in = nfo.channels * nfo.frames;
const size_t frames_out = nfo.channels * ceil(nfo.frames * resample_ratio);
sf_count_t rd;
float *rdb;
*buf = (float*) malloc(frames_out*sizeof(float));
if (!*buf) {
fprintf (stderr, "convoLV2: memory allocation failed for IR audio-file buffer.\n");
sf_close (sndfile);
return (-2);
}
if (resample_ratio == 1.0) {
rdb = *buf;
} else {
rdb = (float*) malloc(frames_in*sizeof(float));
if (!rdb) {
fprintf (stderr, "convoLV2: memory allocation failed for IR resample buffer.\n");
sf_close (sndfile);
free(*buf);
return -1;
}
}
if(nfo.frames != (rd=sf_readf_float(sndfile, rdb, nfo.frames))) {
fprintf(stderr, "convoLV2: IR short read %ld of %ld\n", (long int) rd, (long int) nfo.frames);
free(*buf);
if (resample_ratio != 1.0) {
free(rdb);
}
sf_close (sndfile);
return -3;
}
if (resample_ratio != 1.0) {
VERBOSE_printf("convoLV2: resampling IR %ld -> %ld [frames * channels].\n",
(long int) frames_in,
(long int) frames_out);
SRC_STATE* src_state = src_new(SRC_QUALITY, nfo.channels, NULL);
SRC_DATA src_data;
src_data.input_frames = nfo.frames;
src_data.output_frames = nfo.frames * resample_ratio;
src_data.end_of_input = 1;
src_data.src_ratio = resample_ratio;
src_data.input_frames_used = 0;
src_data.output_frames_gen = 0;
src_data.data_in = rdb;
src_data.data_out = *buf;
src_process(src_state, &src_data);
VERBOSE_printf("convoLV2: resampled IR %ld -> %ld [frames * channels].\n",
src_data.input_frames_used * nfo.channels,
src_data.output_frames_gen * nfo.channels);
if (n_sp) *n_sp = (unsigned int) src_data.output_frames_gen;
free(rdb);
src_delete (src_state);
}
}
sf_close (sndfile);
return (0);
}
LV2convolv *clv_alloc() {
int i;
LV2convolv *clv = (LV2convolv*) calloc(1, sizeof(LV2convolv));
if (!clv) {
return NULL;
}
clv->convproc = NULL;
for (i = 0; i < MAX_CHANNEL_MAPS; ++i) {
clv->ir_chan[i] = i + 1;
clv->chn_inp[i] = i + 1;
clv->chn_out[i] = i + 1;
clv->ir_delay[i] = 0;
clv->ir_gain[i] = 0.5f;
}
clv->ir_fn = NULL;
clv->density = 0.f;
clv->size = 0x00100000;
return clv;
}
void clv_release (LV2convolv *clv) {
if (!clv) return;
if (clv->convproc) {
clv->convproc->stop_process ();
delete (clv->convproc);
}
clv->convproc = NULL;
}
void clv_clone_settings(LV2convolv *clv_new, LV2convolv *clv) {
if (!clv) return;
memcpy (clv_new, clv, sizeof(LV2convolv));
clv_new->convproc = NULL;
if (clv->ir_fn) {
clv_new->ir_fn = strdup (clv->ir_fn);
}
}
void clv_free (LV2convolv *clv) {
if (!clv) return;
clv_release (clv);
free (clv->ir_fn);
free (clv);
}
int clv_configure (LV2convolv *clv, const char *key, const char *value) {
if (!clv) return 0;
int n;
if (strcasecmp (key, "convolution.ir.file") == 0) {
free(clv->ir_fn);
clv->ir_fn = strdup(value);
} else if (!strncasecmp (key, "convolution.out.source.", 23)) {
if (sscanf (key, "convolution.source.%d", &n) == 1) {
if ((0 < n) && (n <= MAX_CHANNEL_MAPS))
clv->chn_inp[n] = atoi(value);
}
} else if (!strncasecmp (key, "convolution.out.source.", 23)) {
if (sscanf (key, "convolution.output.%d", &n) == 1) {
if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
clv->chn_out[n] = atoi(value);
}
} else if (!strncasecmp (key, "convolution.ir.channel.", 23)) {
if (sscanf (key, "convolution.ir.channel.%d", &n) == 1) {
if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
clv->ir_chan[n] = atoi(value);
}
} else if (!strncasecmp (key, "convolution.ir.gain.", 20)) {
if (sscanf (key, "convolution.ir.gain.%d", &n) == 1) {
if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
clv->ir_gain[n] = atof(value);
}
} else if (!strncasecmp (key, "convolution.ir.delay.", 21)) {
if (sscanf (key, "convolution.ir.delay.%d", &n) == 1) {
if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
clv->ir_delay[n] = atoi(value);
}
} else if (strcasecmp (key, "convolution.maxsize") == 0) {
clv->size = atoi(value);
if (clv->size > 0x00400000) {
clv->size = 0x00400000;
}
if (clv->size < 0x00001000) {
clv->size = 0x00001000;
}
} else {
return 0;
}
return 1; // OK
}
char *clv_dump_settings (LV2convolv *clv) {
if (!clv) return NULL;
#define MAX_CFG_SIZE ( MAX_CHANNEL_MAPS * 160 + 60 + (clv->ir_fn ? strlen(clv->ir_fn) : 0) )
int i;
size_t off = 0;
char *rv = (char*) malloc (MAX_CFG_SIZE * sizeof (char));
#undef MAX_CFG_SIZE
for (i = 0; i < MAX_CHANNEL_MAPS; ++i) {
// f=12 ; d= 3 ; v=10
off+= sprintf (rv + off, "convolution.ir.gain.%d=%e\n", i, clv->ir_gain[i]); // 22 + d + f
off+= sprintf (rv + off, "convolution.ir.delay.%d=%d\n", i, clv->ir_delay[i]);// 23 + d + v
off+= sprintf (rv + off, "convolution.ir.channel.%d=%d\n", i, clv->ir_chan[i]); // 25 + d + d
off+= sprintf (rv + off, "convolution.source.%d=%d\n", i, clv->chn_inp[i]); // 21 + d + d
off+= sprintf (rv + off, "convolution.output.%d=%d\n", i, clv->chn_out[i]); // 21 + d + d
}
off+= sprintf(rv + off, "convolution.maxsize=%u\n", clv->size); // 21 + v
return rv;
}
int clv_query_setting (LV2convolv *clv, const char *key, char *value, size_t val_max_len) {
int rv = 0;
if (!clv || !value || !key) {
return -1;
}
if (strcasecmp (key, "convolution.ir.file") == 0) {
if (clv->ir_fn) {
if (strlen(clv->ir_fn) >= val_max_len) {
rv = -1;
}
else {
rv=snprintf(value, val_max_len, "%s", clv->ir_fn);
}
}
}
// TODO allow querying other settings
return rv;
}
int clv_initialize (
LV2convolv *clv,
const unsigned int sample_rate,
const unsigned int in_channel_cnt,
const unsigned int out_channel_cnt,
const unsigned int buffersize)
{
unsigned int c;
const unsigned int n_elem = in_channel_cnt * out_channel_cnt;
/* zita-conv settings */
const unsigned int options = 0;
/* IR file */
unsigned int n_chan = 0;
unsigned int n_frames = 0;
unsigned int max_size = 0;
float *p = NULL; /* temp. IR file buffer */
float *gb = NULL; /* temp. gain-scaled IR file buffer */
clv->fragment_size = buffersize;
if (zita_convolver_major_version () != ZITA_CONVOLVER_MAJOR_VERSION) {
fprintf (stderr, "convoLV2: Zita-convolver version does not match.\n");
return -1;
}
if (clv->convproc) {
fprintf (stderr, "convoLV2: already initialized.\n");
return (-1);
}
if (!clv->ir_fn) {
fprintf (stderr, "convoLV2: No IR file was configured.\n");
return -1;
}
if (access(clv->ir_fn, R_OK) != 0) {
fprintf(stderr, "convoLV2: cannot stat IR: %s\n", clv->ir_fn);
return -1;
}
pthread_mutex_lock(&fftw_planner_lock);
clv->convproc = new Convproc;
clv->convproc->set_options (options);
clv->convproc->set_density (clv->density);
if (audiofile_read (clv->ir_fn, sample_rate, &p, &n_chan, &n_frames)) {
fprintf(stderr, "convoLV2: failed to read IR.\n");
goto errout;
}
if (n_frames == 0 || n_chan == 0) {
fprintf(stderr, "convoLV2: invalid IR file.\n");
goto errout;
}
for (c = 0; c < MAX_CHANNEL_MAPS; c++) {
// TODO only relevant channels
if (clv->ir_delay[c] > max_size) {
max_size = clv->ir_delay[c];
}
}
max_size += n_frames;
if (max_size > clv->size) {
max_size = clv->size;
}
VERBOSE_printf("convoLV2: max-convolution length %d samples (limit %d), period: %d samples\n", max_size, clv->size, buffersize);
if (clv->convproc->configure (
/*in*/ in_channel_cnt,
/*out*/ out_channel_cnt,
/*max-convolution length */ max_size,
/*quantum*/ buffersize,
/*min-part*/ buffersize /* must be >= fragm */,
/*max-part*/ buffersize /* Convproc::MAXPART -> stich output every period */
)) {
fprintf (stderr, "convoLV2: Cannot initialize convolution engine.\n");
goto errout;
}
gb = (float*) malloc (n_frames * sizeof(float));
if (!gb) {
fprintf (stderr, "convoLV2: memory allocation failed for convolution buffer.\n");
goto errout;
}
VERBOSE_printf("convoLV2: Proc: in: %d, out: %d || IR-file: %d chn, %d samples\n",
in_channel_cnt, out_channel_cnt, n_chan, n_frames);
// TODO use pre-configured channel-map (from state), IFF set and valid for the current file
// reset channel map
for (c = 0; c < MAX_CHANNEL_MAPS; ++c) {
clv->ir_chan[c] = 0;
clv->chn_inp[c] = 0;
clv->chn_out[c] = 0;
}
// follow channel map conventions
if (n_elem == n_chan) {
// exact match: for every input-channel, iterate over all outputs
// eg. 1: L -> L , 2: L -> R, 3: R -> L, 4: R -> R
for (c = 0; c < n_chan && c < MAX_CHANNEL_MAPS; ++c) {
clv->ir_chan[c] = 1 + c;
clv->chn_inp[c] = 1 + ((c / out_channel_cnt) % in_channel_cnt);
clv->chn_out[c] = 1 + (c % out_channel_cnt);
}
}
else if (n_elem > n_chan) {
VERBOSE_printf("convoLV2: IR file has too few channels for given processor config.\n");
// missing some channels, first assign in -> out, then x-over
// eg. 1: L -> L , 2: R -> R, 3: L -> R, 4: R -> L
// this allows to e.g load a 2-channel (stereo) IR into a
// 2x2 true-stereo effect instance
for (c = 0; c < n_chan && c < MAX_CHANNEL_MAPS; ++c) {
clv->ir_chan[c] = 1 + c;
clv->chn_inp[c] = 1 + (c % in_channel_cnt);
clv->chn_out[c] = 1 + (((c + c / in_channel_cnt) % in_channel_cnt) % out_channel_cnt);
}
// assign mono input to 1: L -> L , 2: R -> R,
for (;n_chan == 1 && c < 2; ++c) {
clv->ir_chan[c] = 1;
clv->chn_inp[c] = 1 + (c % in_channel_cnt);
clv->chn_out[c] = 1 + (c % out_channel_cnt);
}
}
else {
assert (n_elem < n_chan);
VERBOSE_printf("convoLV2: IR file has too many channels for given processor config.\n");
// allow loading a quad file to a mono-in stereo-out
// eg. 1: L -> L , 2: L -> R
for (c = 0; c < n_elem && c < MAX_CHANNEL_MAPS; ++c) {
clv->ir_chan[c] = 1 + c;
clv->chn_inp[c] = 1 + ((c / out_channel_cnt) % in_channel_cnt);
clv->chn_out[c] = 1 + (c % out_channel_cnt);
}
}
// assign channel map to convolution engine
for (c = 0; c < MAX_CHANNEL_MAPS; ++c) {
unsigned int i;
if (clv->chn_inp[c] == 0 || clv->chn_out[c] == 0 || clv->ir_chan[c] == 0) {
continue;
}
assert (clv->ir_chan[c] <= n_chan);
for (i = 0; i < n_frames; ++i) {
// decode interleaved channels, apply gain scaling
gb[i] = p[i * n_chan + clv->ir_chan[c] - 1] * clv->ir_gain[c];
}
VERBOSE_printf ("convoLV2: SET in %d -> out %d [IR chn:%d gain:%+.3f dly:%d]\n",
clv->chn_inp[c],
clv->chn_out[c],
clv->ir_chan[c],
clv->ir_gain[c],
clv->ir_delay[c]
);
clv->convproc->impdata_create (
clv->chn_inp[c] - 1,
clv->chn_out[c] - 1,
1, gb, clv->ir_delay[c], clv->ir_delay[c] + n_frames);
}
free(gb); gb = NULL;
free(p); p = NULL;
#if 1 // INFO
clv->convproc->print (stderr);
#endif
if (clv->convproc->start_process (0, 0)) {
fprintf(stderr, "convoLV2: Cannot start processing.\n");
goto errout;
}
pthread_mutex_unlock(&fftw_planner_lock);
return 0;
errout:
free(gb);
free(p);
delete(clv->convproc);
clv->convproc = NULL;
pthread_mutex_unlock(&fftw_planner_lock);
return -1;
}
int clv_is_active (LV2convolv *clv) {
if (!clv || !clv->convproc || !clv->ir_fn) {
return 0;
}
return 1;
}
static void silent_output(float * const * outbuf, size_t n_channels, size_t n_samples) {
unsigned int c;
for (c = 0; c < n_channels; ++c) {
memset (outbuf[c], 0, n_samples * sizeof(float));
}
}
int clv_convolve (LV2convolv *clv,
const float * const * inbuf,
float * const * outbuf,
const unsigned int in_channel_cnt,
const unsigned int out_channel_cnt,
const unsigned int n_samples,
const float output_gain)
{
unsigned int c;
if (!clv || !clv->convproc) {
silent_output(outbuf, out_channel_cnt, n_samples);
return (0);
}
if (clv->convproc->state () == Convproc::ST_WAIT) {
clv->convproc->check_stop ();
}
if (clv->fragment_size != n_samples) {
silent_output(outbuf, out_channel_cnt, n_samples);
return -1;
}
#if 1
if (clv->convproc->state () != Convproc::ST_PROC) {
/* This cannot happen in sync-mode, but... */
assert (0);
silent_output(outbuf, out_channel_cnt, n_samples);
return (n_samples);
}
#endif
for (c = 0; c < in_channel_cnt; ++c)
#if 0 // no denormal protection
memcpy (clv->convproc->inpdata (c), inbuf[c], n_samples * sizeof (float));
#else // prevent denormals
{
unsigned int i;
float *id = clv->convproc->inpdata(c);
for (i = 0; i < n_samples; ++i) {
id[i] = inbuf[c][i] + 1e-20f;
}
}
#endif
int f = clv->convproc->process (false);
if (f /*&Convproc::FL_LOAD)*/ ) {
/* Note this will actually never happen in sync-mode */
assert (0);
silent_output(outbuf, out_channel_cnt, n_samples);
return (n_samples);
}
for (c = 0; c < out_channel_cnt; ++c) {
if (output_gain == 1.0) {
memcpy (outbuf[c], clv->convproc->outdata (c), n_samples * sizeof (float));
} else {
unsigned int s;
float const * const od = clv->convproc->outdata (c);
for (s = 0; s < n_samples; ++s) {
outbuf[c][s] = od[s] * output_gain;
}
}
}
return (n_samples);
}