From fdbd13e77ce850d9e3252c94cb0c29ac731abb62 Mon Sep 17 00:00:00 2001 From: jjceresa <32781294+jjceresa@users.noreply.github.com> Date: Fri, 1 Nov 2019 16:26:46 +0100 Subject: [PATCH] Make reverb output independent of sample rate (#584) Fixes #581 --- src/rvoice/fluid_rev.c | 257 +++++++++++++++++++++++++++++------------ src/rvoice/fluid_rev.h | 2 +- 2 files changed, 186 insertions(+), 73 deletions(-) diff --git a/src/rvoice/fluid_rev.c b/src/rvoice/fluid_rev.c index 014562e8..8520764d 100644 --- a/src/rvoice/fluid_rev.c +++ b/src/rvoice/fluid_rev.c @@ -75,25 +75,26 @@ * the building of a lot of resonances in the reverberation tail even when * using only 8 delays lines (NBR_DELAYS = 8) (default). * - * Although 8 lines give good result, using 12 delays lines brings the overall - * frequency density quality a bit higher. This quality augmentation is noticeable - * particularly when using long reverb time (roomsize = 1) on solo instrument with - * long release time. Of course the cpu load augmentation is +50% relatively - * to 8 lines. + * The frequency density (often called "modal density" is one property that + * contributes to sound quality. Although 8 lines give good result, using 12 delays + * lines brings the overall frequency density quality a bit higher. + * This quality augmentation is noticeable particularly when using long reverb time + * (roomsize = 1) on solo instrument with long release time. Of course the cpu load + * augmentation is +50% relatively to 8 lines. * * As a general rule the reverberation tail quality is easier to perceive by ear * when using: * - percussive instruments (i.e piano and others). * - long reverb time (roomsize = 1). * - no damping (damp = 0). - * + * - Using headphone. Avoid using loud speaker, you will be quickly misguided by the + * natural reverberation of the room in which you are. * * The cpu load for 8 lines is a bit lower than for freeverb (- 3%), * but higher for 12 lines (+ 41%). * * - * The memory consumption is less than for freeverb. This saves 147480 bytes - * for 8 lines (- 72%) and 110152 bytes for 12 lines (- 54 %). + * The memory consumption is less than for freeverb * (see the results table below). * * Two macros are usable at compiler time: @@ -113,19 +114,23 @@ * Note: the cpu load in % are relative each to other. These values are * given by the fluidsynth profile commands. * -------------------------------------------------------------------------- - * reverb | NBR_DELAYS | Performances | memory size | quality - * | | (cpu_load: %) | (bytes) | + * reverb | NBR_DELAYS | Performances | memory size | quality + * | | (cpu_load: %) | (bytes)(see note) | * ========================================================================== - * freeverb | 2 x 8 comb | 0.670 % | 204616 | ringing - * | 2 x 4 all-pass | | | + * freeverb | 2 x 8 comb | 0.670 % | 204616 | ringing + * | 2 x 4 all-pass | | | * ----------|--------------------------------------------------------------- - * FDN | 8 | 0.650 % | 57136 | far less - * modulated | |(feeverb - 3%) |(freeverb - 72%) | ringing + * FDN | 8 | 0.650 % | 112160 | far less + * modulated | |(feeverb - 3%) | (55% freeverb) | ringing * |--------------------------------------------------------------- - * | 12 | 0.942 % | 94464 | best than - * | |(freeverb + 41%) |(freeverb - 54%) | 8 lines + * | 12 | 0.942 % | 168240 | best than + * | |(freeverb + 41%) | (82 %freeverb) | 8 lines *--------------------------------------------------------------------------- * + * Note: + * Values in this column is the memory consumption for sample rate <= 44100Hz. + * For sample rate > 44100Hz , multiply these values by (sample rate / 44100Hz). + * * *---------------------------------------------------------------------------- * 'Denormalise' method to avoid loss of performance. @@ -173,6 +178,8 @@ roomsize parameter. - DENORMALISING enable denormalising handling. -----------------------------------------------------------------------------*/ +//#define INFOS_PRINT /* allows message to be printed on the console. */ + /* Number of delay lines (must be only 8 or 12) 8 is the default. 12 produces a better quality but is +50% cpu expensive @@ -612,6 +619,16 @@ static int set_mod_delay_line(mod_delay_line *mdl, return FLUID_OK; } +/*----------------------------------------------------------------------------- + Return norminal delay length + + @param mdl, pointer on modulated delay line. +-----------------------------------------------------------------------------*/ +static int get_mod_delay_line_length(mod_delay_line *mdl) +{ + return (mdl->dl.size - mdl->mod_depth - INTERP_SAMPLES_NBR); +} + /*----------------------------------------------------------------------------- Reads the sample value out of the modulated delay line. @param mdl, pointer on modulated delay line. @@ -738,6 +755,7 @@ static void update_rev_time_damping(fluid_late *late, { int i; fluid_real_t sample_period = 1 / late->samplerate; /* Sampling period */ + int delay_length; /* delay length */ fluid_real_t dc_rev_time; /* Reverb time at 0 Hz (in seconds) */ fluid_real_t alpha, alpha2; @@ -756,8 +774,9 @@ static void update_rev_time_damping(fluid_late *late, Computes dc_rev_time ------------------------------------------*/ dc_rev_time = GET_DC_REV_TIME(roomsize); + delay_length = get_mod_delay_line_length(&late->mod_delay_lines[NBR_DELAYS - 1]); /* computes gi_tmp from dc_rev_time using relation E2 */ - gi_tmp = FLUID_POW(10, -3 * delay_length[NBR_DELAYS - 1] * + gi_tmp = FLUID_POW(10, -3 * delay_length * sample_period / dc_rev_time); /* E2 */ #else /* roomsize parameters have the same response that Freeverb, that is: @@ -768,16 +787,18 @@ static void update_rev_time_damping(fluid_late *late, Computes dc_rev_time ------------------------------------------*/ fluid_real_t gi_min, gi_max; + /* values gi_min et gi_max are computed using E2 for the line with maximum delay */ - gi_max = FLUID_POW(10, (-3 * delay_length[NBR_DELAYS - 1] / MAX_DC_REV_TIME) * - sample_period); /* E2 */ - gi_min = FLUID_POW(10, (-3 * delay_length[NBR_DELAYS - 1] / MIN_DC_REV_TIME) * - sample_period); /* E2 */ + delay_length = get_mod_delay_line_length(&late->mod_delay_lines[NBR_DELAYS - 1]); + gi_max = FLUID_POW(10, (-3 * delay_length / MAX_DC_REV_TIME) * + sample_period); /* E2 */ + gi_min = FLUID_POW(10, (-3 * delay_length / MIN_DC_REV_TIME) * + sample_period); /* E2 */ /* gi = f(roomsize, gi_max, gi_min) */ gi_tmp = gi_min + roomsize * (gi_max - gi_min); /* Computes T60DC from gi using inverse of relation E2.*/ - dc_rev_time = -3 * FLUID_M_LN10 * delay_length[NBR_DELAYS - 1] * sample_period / FLUID_LOGF(gi_tmp); + dc_rev_time = -3 * FLUID_M_LN10 * delay_length * sample_period / FLUID_LOGF(gi_tmp); } #endif /* ROOMSIZE_RESPONSE_LINEAR */ /*-------------------------------------------- @@ -809,12 +830,16 @@ static void update_rev_time_damping(fluid_late *late, /* updates damping coefficients of all lines (gi , ai) from dc_rev_time, alpha */ for(i = 0; i < NBR_DELAYS; i++) { + fluid_real_t gi, ai; + + /* delay length */ + delay_length = get_mod_delay_line_length(&late->mod_delay_lines[i]); + /* iir low pass filter gain */ - fluid_real_t gi = FLUID_POW(10, -3 * delay_length[i] * - sample_period / dc_rev_time); + gi = FLUID_POW(10, -3 * delay_length * sample_period / dc_rev_time); /* iir low pass filter feedback gain */ - fluid_real_t ai = (20.f / 80.f) * FLUID_LOGF(gi) * (1.f - 1.f / alpha2); + ai = (20.f / 80.f) * FLUID_LOGF(gi) * (1.f - 1.f / alpha2); /* b0 = gi * (1 - ai), a1 = - ai */ set_fdn_delay_lpf(&late->mod_delay_lines[i].dl.damping, @@ -830,6 +855,7 @@ static void update_rev_time_damping(fluid_late *late, static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) { int i; + fluid_real_t wet; for(i = 0; i < NBR_DELAYS; i++) { @@ -852,26 +878,23 @@ static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) 11|-1 -1| */ - late->out_left_gain[i] = wet1; + /* for left line: 00, ,02, ,04, ,06, ,08, ,10, ,12,... left_gain = +1 */ + /* for left line: ,01, ,03, ,05, ,07, ,09, ,11,... left_gain = -1 */ + wet = wet1; + if(i & 1) + { + wet = -wet1; + } + late->out_left_gain[i] = wet; - /* Sets Left coefficients first */ - if(i % 2) /* Left is 1,-1, 1,-1, 1,-1,.... */ + /* for right line: 00,01, ,04,05, ,08,09, ,12,13 right_gain = +1 */ + /* for right line: ,02 ,03, ,06,07, ,10,11,... right_gain = -1 */ + wet = wet1; + if(i & 2) { - late->out_left_gain[i] *= -1; - } - - /* Now sets right gain as function of Left */ - /* for right line 1,2,5,6,9,10,13,14, right = - left */ - if((i == 1) || (i == 2) || (i == 5) || (i == 6) || (i == 9) || (i == 10) || (i == 13) || (i == 14)) - { - /* Right is reverse of Left */ - late->out_right_gain[i] = -1 * late->out_left_gain[i]; - } - else /* for Right : line 0,3,4,7,8,11,12,15 */ - { - /* Right is same as Left */ - late->out_right_gain[i] = late->out_left_gain[i]; + wet = -wet1; } + late->out_right_gain[i] = wet; } } @@ -892,29 +915,68 @@ static void delete_fluid_rev_late(fluid_late *late) } /*----------------------------------------------------------------------------- - Creates the fdn reverb. + Creates all modulated lines. @param late, pointer on the fnd late reverb to initialize. - @param sample_rate the sample rate. + @param sample_rate, the audio sample rate. @return FLUID_OK if success, FLUID_FAILED otherwise. -----------------------------------------------------------------------------*/ -static int create_fluid_rev_late(fluid_late *late, fluid_real_t sample_rate) +static int create_mod_delay_lines(fluid_late *late, fluid_real_t sample_rate) { int result; /* return value */ int i; - FLUID_MEMSET(late, 0, sizeof(fluid_late)); + /* + 1)"modal density" is one property that contributes to the quality of the reverb tail. + The more is the modal density, the less are unwanted resonant frequencies + build during the decay time: modal density = total delay / sample rate. - late->samplerate = sample_rate; + Delay line's length given by static table delay_length[] is nominal + to get minimum modal density of 0.15 at sample rate 44100Hz. + Here we set length_factor to 2 to mutiply this nominal modal + density by 2. This leads to a default modal density of 0.15 * 2 = 0.3 for + sample rate <= 44100. - /*-------------------------------------------------------------------------- - First initialize the modulated delay lines + For sample rate > 44100, length_factor is multiplied by + sample_rate / 44100. This ensures that the default modal density keeps inchanged. + (Without this compensation, the default modal density would be diminished for + new sample rate change above 44100Hz). + + 2)Modulated delay line contributes to diminish resonnant frequencies (often called "ringing"). + Modulation depth (mod_depth) is set to nominal value of MOD_DEPTH at sample rate 44100Hz. + For sample rate > 44100, mod_depth is multiplied by sample_rate / 44100. This ensures + that the effect of modulated delay line keeps inchanged. */ - - for(i = 0; i < NBR_DELAYS; i++) + fluid_real_t length_factor = 2.0f; + fluid_real_t mod_depth = MOD_DEPTH; + if(sample_rate > 44100.0f) { - /* sets local delay lines's parameters */ + fluid_real_t sample_rate_factor = sample_rate/44100.0f; + length_factor *= sample_rate_factor; + mod_depth *= sample_rate_factor; + } +#ifdef INFOS_PRINT // allows message to be printed on the console. + printf("length_factor:%f, mod_depth:%f\n", length_factor, mod_depth); + /* Print: modal density and total memory bytes */ + { + int i; + int total_delay; /* total delay in samples */ + for (i = 0, total_delay = 0; i < NBR_DELAYS; i++) + { + total_delay += length_factor * delay_length[i]; + } + + /* modal density and total memory bytes */ + printf("modal density:%f, total memory:%d bytes\n", + total_delay / sample_rate , total_delay * sizeof(fluid_real_t)); + } +#endif + + for(i = 0; i < NBR_DELAYS; i++) /* for each delay line */ + { + /* allocate delay line and set local delay lines's parameters */ result = set_mod_delay_line(&late->mod_delay_lines[i], - delay_length[i], MOD_DEPTH, MOD_RATE); + delay_length[i] * length_factor, + mod_depth, MOD_RATE); if(result == FLUID_FAILED) { @@ -926,12 +988,33 @@ static int create_fluid_rev_late(fluid_late *late, fluid_real_t sample_rate) */ set_mod_frequency(&late->mod_delay_lines[i].mod, MOD_FREQ * MOD_RATE, - sample_rate, + late->samplerate, (float)(MOD_PHASE * i)); } + return FLUID_OK; +} + +/*----------------------------------------------------------------------------- + Creates the fdn reverb. + @param late, pointer on the fnd late reverb to initialize. + @param sample_rate the sample rate. + @return FLUID_OK if success, FLUID_FAILED otherwise. +-----------------------------------------------------------------------------*/ +static int create_fluid_rev_late(fluid_late *late, fluid_real_t sample_rate) +{ + FLUID_MEMSET(late, 0, sizeof(fluid_late)); + + late->samplerate = sample_rate; + + /*-------------------------------------------------------------------------- + First initialize the modulated delay lines + */ + + if(create_mod_delay_lines(late, sample_rate) == FLUID_FAILED) + { + return FLUID_FAILED; + } - /*-----------------------------------------------------------------------*/ - update_stereo_coefficient(late, 1.0f); return FLUID_OK; } @@ -982,7 +1065,7 @@ fluid_revmodel_update(fluid_revmodel_t *rev) /* integrates wet1 in stereo coefficient (this will save one multiply) */ update_stereo_coefficient(&rev->late, rev->wet1); - if(rev->wet1 > 0.0) + if(rev->wet1 > 0.0f) { rev->wet2 /= rev->wet1; } @@ -996,7 +1079,10 @@ fluid_revmodel_update(fluid_revmodel_t *rev) -----------------------------------------------------------------------------*/ /* -* Creates a reverb. +* Creates a reverb. One created the reverb have no parameters set, so +* fluid_revmodel_set() must be called at least one time after calling +* new_fluid_revmodel(). +* * @param sample_rate sample rate in Hz. * @return pointer on the new reverb or NULL if memory error. * Reverb API. @@ -1024,7 +1110,15 @@ new_fluid_revmodel(fluid_real_t sample_rate) /* * free the reverb. -* @param pointer on rev to free. +* Note that while the reverb is used by calling any fluid_revmodel_processXXX() +* function, calling delete_fluid_revmodel() isn't multi task safe because +* delay line are freed. To deal properly with this issue follow the steps: +* +* 1) Stop reverb processing (i.e disable calling of any fluid_revmodel_processXXX(). +* reverb functions. +* 2) Delete the reverb by calling delete_fluid_revmodel(). +* +* @param rev pointer on reverb to free. * Reverb API. */ void @@ -1036,7 +1130,14 @@ delete_fluid_revmodel(fluid_revmodel_t *rev) } /* -* Sets one or more reverb parameters. +* Sets one or more reverb parameters. Note this must be called at least one +* time after calling new_fluid_revmodel(). +* +* Note that while the reverb is used by calling any fluid_revmodel_processXXX() +* function, calling fluid_revmodel_set() could produce audible clics. +* If this is a problem, optionnaly call fluid_revmodel_reset() before calling +* fluid_revmodel_set(). +* * @param rev Reverb instance. * @param set One or more flags from #fluid_revmodel_set_t indicating what * parameters to set (#FLUID_REVMODEL_SET_ALL to set all parameters). @@ -1084,31 +1185,43 @@ fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, /* * Applies a sample rate change on the reverb. +* Note that while the reverb is used by calling any fluid_revmodel_processXXX() +* function, calling fluid_revmodel_samplerate_change() isn't multi task safe because +* delay line are memory reallocated. To deal properly with this issue follow +* the steps: +* 1) Stop reverb processing (i.e disable calling of any fluid_revmodel_processXXX(). +* reverb functions. +* 2) Change sample rate by calling fluid_revmodel_samplerate_change(). +* 3) Restart reverb processing (i.e enabling calling of any fluid_revmodel_processXXX() +* reverb functions. +* +* Another solution is to substitute step (2): +* 2.1) delete the reverb by calling delete_fluid_revmodel(). +* 2.2) create the reverb by calling new_fluid_revmodel(). +* * @param rev the reverb. * @param sample_rate new sample rate value. -* +* @return FLUID_OK if success, FLUID_FAILED otherwise (memory error). * Reverb API. */ -void +int fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate) { - int i; + rev->late.samplerate = sample_rate; /* new sample rate value */ - /* updates modulator frequency according to sample rate change */ - for(i = 0; i < NBR_DELAYS; i++) + /* free all delay lines */ + delete_fluid_rev_late(&rev->late); + + /* create all delay lines */ + if(create_mod_delay_lines(&rev->late, sample_rate) == FLUID_FAILED) { - set_mod_frequency(&rev->late.mod_delay_lines[i].mod, - MOD_FREQ * MOD_RATE, - sample_rate, - (float)(MOD_PHASE * i)); + return FLUID_FAILED; /* memory error */ } /* updates damping filter coefficients according to sample rate change */ - rev->late.samplerate = sample_rate; update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); - /* clears all delay lines */ - fluid_revmodel_init(rev); + return FLUID_OK; } /* diff --git a/src/rvoice/fluid_rev.h b/src/rvoice/fluid_rev.h index 75ab5d23..b24fd5fd 100644 --- a/src/rvoice/fluid_rev.h +++ b/src/rvoice/fluid_rev.h @@ -72,6 +72,6 @@ void fluid_revmodel_reset(fluid_revmodel_t *rev); void fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, fluid_real_t damping, fluid_real_t width, fluid_real_t level); -void fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate); +int fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate); #endif /* _FLUID_REV_H */