diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index 75b783b71..06448056e 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -73,7 +73,7 @@ Developers: Settings can be deprecated by adding: SOME TEXTchorus.speed num 0.3 - 0.29 + 0.1 5 Sets the modulation speed in Hz. diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index c5f7ed59f..14c8658dc 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -21,6 +21,7 @@ All the source code examples in this document are in the public domain; you can - \ref Disclaimer - \ref Introduction +- \ref NewIn2_1_0 - \ref NewIn2_0_7 - \ref NewIn2_0_6 - \ref NewIn2_0_5 @@ -65,6 +66,9 @@ What is FluidSynth? - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org +\section NewIn2_1_0 Whats new in 2.1.0? + +- smallest allowed chorus speed is now 0.1 Hz (previously 0.29 Hz) \section NewIn2_0_7 Whats new in 2.0.7? diff --git a/src/rvoice/fluid_chorus.c b/src/rvoice/fluid_chorus.c index abcd4bc0c..191979b62 100644 --- a/src/rvoice/fluid_chorus.c +++ b/src/rvoice/fluid_chorus.c @@ -19,7 +19,7 @@ */ /* - based on a chrous implementation made by Juergen Mueller And Sundry Contributors in 1998 + based on a chorus implementation made by Juergen Mueller And Sundry Contributors in 1998 CHANGES @@ -28,6 +28,8 @@ - Variable delay line implementation using bandlimited interpolation, code reorganization: Markus Nentwig May 2002 + - Complete rewrite using lfo computed on the fly, first order all-pass + interpolator and adding stereo unit: Jean-Jacques Ceresa, Jul 2019 */ @@ -36,83 +38,169 @@ * * Flow diagram scheme for n delays ( 1 <= n <= MAX_CHORUS ): * - * * gain-in ___ - * ibuff -----+--------------------------------------------->| | - * | _________ | | - * | | | * level 1 | | - * +---->| delay 1 |----------------------------->| | - * | |_________| | | - * | /|\ | | - * : | | | - * : +-----------------+ +--------------+ | + | - * : | Delay control 1 |<--| mod. speed 1 | | | - * : +-----------------+ +--------------+ | | - * | _________ | | - * | | | * level n | | - * +---->| delay n |----------------------------->| | - * |_________| | | - * /|\ |___| - * | | - * +-----------------+ +--------------+ | * gain-out - * | Delay control n |<--| mod. speed n | | - * +-----------------+ +--------------+ +----->obuff + * ________ + * direct signal (not implemented) >-->| | + * _________ | | + * mono | | | | + * input ---+---->| delay 1 |-------------------------->| Stereo |---> right + * | |_________| | | output + * | /|\ | Unit | + * : | | | + * : +-----------------+ |(width) | + * : | Delay control 1 |<-+ | | + * : +-----------------+ | | |---> left + * | _________ | | | output + * | | | | | | + * +---->| delay n |-------------------------->| | + * |_________| | | | + * /|\ | |________| + * | | +--------------+ /|\ + * +-----------------+ | |mod depth (ms)| | + * | Delay control n |<-*--|lfo speed (Hz)| gain-out + * +-----------------+ +--------------+ * * * The delay i is controlled by a sine or triangle modulation i ( 1 <= i <= n). * - * The delay of each block is modulated between 0..depth ms + * The chorus unit process a monophonic input signal and produces stereo output + * controled by WIDTH macro. + * Actually WIDTH is fixed to maximum value. But in the future, we could add a + * setting (e.g "synth.chorus.width") allowing the user to get a gradually stereo + * effect from minimum (monophonic) to maximum stereo effect. * - */ - - -/* Variable delay line implementation - * ================================== - * - * The modulated delay needs the value of the delayed signal between - * samples. A lowpass filter is used to obtain intermediate values - * between samples (bandlimited interpolation). The sample pulse - * train is convoluted with the impulse response of the low pass - * filter (sinc function). To make it work with a small number of - * samples, the sinc function is windowed (Hamming window). + * Delays lines are implemented using only one line for all chorus blocks. + * Each chorus block has it own lfo (sinus/triangle). Each lfo are out of phase + * to produce uncorrelated signal at the output of the delay line (this simulates + * the presence of individual line for each block). Each lfo modulates the length + * of the line using a depth modulation value and lfo frequency value common to + * all lfos. * + * LFO modulators are computed on the fly, instead of using lfo lookup table. + * The advantages are: + * - Avoiding a lost of 608272 memory bytes when lfo speed is low (0.3Hz). + * - Allows to diminish the lfo speed lower limit to 0.1Hz instead of 0.3Hz. + * A speed of 0.1 is interresting for chorus. Using a lookuptable for 0.1Hz + * would require too much memory (1824816 bytes). + * - Interpolation make use of first order all-pass interpolator instead of + * bandlimited interpolation. + * - Although lfo modulator is computed on the fly, cpu load is lower than + * using lfo lookup table with bandlimited interpolator. */ #include "fluid_chorus.h" #include "fluid_sys.h" -#define MAX_CHORUS 99 -#define MAX_DELAY 100 -#define MAX_DEPTH 10 -#define MIN_SPEED_HZ 0.29 -#define MAX_SPEED_HZ 5 - -/* Length of one delay line in samples: - * Set through MAX_SAMPLES_LN2. - * For example: - * MAX_SAMPLES_LN2=12 - * => MAX_SAMPLES=pow(2,12-1)=2048 - * => MAX_SAMPLES_ANDMASK=2047 - */ -#define MAX_SAMPLES_LN2 12 -#define MAX_SAMPLES (1 << (MAX_SAMPLES_LN2-1)) -#define MAX_SAMPLES_ANDMASK (MAX_SAMPLES-1) +/*------------------------------------------------------------------------------------- + Private +--------------------------------------------------------------------------------------*/ +// #define DEBUG_PRINT // allows message to be printed on the console. +#define MAX_CHORUS 99 /* number maximum of block */ +#define MAX_LEVEL 10 /* max output level */ +#define MIN_SPEED_HZ 0.1 /* min lfo frequency (Hz) */ +#define MAX_SPEED_HZ 5 /* max lfo frequency (Hz) */ -/* Interpolate how many steps between samples? Must be power of two - For example: 8 => use a resolution of 256 steps between any two - samples +/* WIDTH [0..10] value define a stereo separation between left and right. + When 0, the output is monophonic. When > 0 , the output is stereophonic. + Actually WIDTH is fixed to maximum value. But in the future we could add a setting to + allow a gradually stereo effect from minimum (monophonic) to maximum stereo effect. +*/ +#define WIDTH 10 + +/* SCALE_WET_WIDTH is a compensation weight factor to get an output + amplitude (wet) rather independent of the width setting. + 0: the output amplitude is fully dependant on the width setting. + >0: the output amplitude is less dependant on the width setting. + With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather + independent of width setting (see fluid_chorus_set()). + */ +#define SCALE_WET_WIDTH 0.2f +#define SCALE_WET 1.0f + +#define MAX_SAMPLES 2048 /* delay length in sample (46.4 ms at sample rate: 44100Hz).*/ +#define LOW_MOD_DEPTH 176 /* low mod_depth/2 in samples */ +#define HIGH_MOD_DEPTH MAX_SAMPLES/2 /* high mod_depth in sample */ +#define RANGE_MOD_DEPTH (HIGH_MOD_DEPTH - LOW_MOD_DEPTH) + +/* Important min max values for MOD_RATE */ +/* mod rate define the rate at which the modulator is updated. Examples + 50: the modulator is updated every 50 samples (less cpu cycles expensive). + 1: the modulator is updated every sample (more cpu cycles expensive). */ -#define INTERPOLATION_SUBSAMPLES_LN2 8 -#define INTERPOLATION_SUBSAMPLES (1 << (INTERPOLATION_SUBSAMPLES_LN2-1)) -#define INTERPOLATION_SUBSAMPLES_ANDMASK (INTERPOLATION_SUBSAMPLES-1) - -/* Use how many samples for interpolation? Must be odd. '7' sounds - relatively clean, when listening to the modulated delay signal - alone. For a demo on aliasing try '1' With '3', the aliasing is - still quite pronounced for some input frequencies +/* MOD_RATE acceptable for max lfo speed (5Hz) and max modulation depth (46.6 ms) */ +#define LOW_MOD_RATE 5 /* MOD_RATE acceptable for low modulation depth (8 ms) */ +#define HIGH_MOD_RATE 4 /* MOD_RATE acceptable for max modulation depth (46.6 ms) */ + /* and max lfo speed (5 Hz) */ +#define RANGE_MOD_RATE (HIGH_MOD_RATE - LOW_MOD_RATE) + +/* some chorus cpu_load measurement dependant of modulation rate: mod_rate + (number of chorus blocks: 2) + + No stero unit: + mod_rate | chorus cpu load(%) | one voice cpu load (%) + ---------------------------------------------------- + 50 | 0.204 | + 5 | 0.256 | 0.169 + 1 | 0.417 | + + With stero unit: + mod_rate | chorus cpu load(%) | one voice cpu load (%) + ---------------------------------------------------- + 50 | 0.220 | + 5 | 0.274 | 0.169 + 1 | 0.465 | + */ -#define INTERPOLATION_SAMPLES 5 + +/* + Number of samples to add to the desired length of the delay line. This + allows to take account of rounding error interpolation when using large + modulation depth. + 1 is sufficient for max modulation depth (46.6 ms) and max lfo speed (5 Hz). +*/ +//#define INTERP_SAMPLES_NBR 0 +#define INTERP_SAMPLES_NBR 1 + + +/*----------------------------------------------------------------------------- + Sinusoidal modulator +-----------------------------------------------------------------------------*/ +/* modulator */ +typedef struct +{ + fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ + fluid_real_t buffer1; /* buffer1 */ + fluid_real_t buffer2; /* buffer2 */ + fluid_real_t reset_buffer2;/* reset value of buffer2 */ +} sinus_modulator; + +/*----------------------------------------------------------------------------- + Triangle modulator +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t freq; /* Osc. Frequency (in Hertz) */ + fluid_real_t val; /* internal current value */ + fluid_real_t inc; /* increment value */ +} triang_modulator; + +/*----------------------------------------------------------------------------- + modulator +-----------------------------------------------------------------------------*/ +typedef struct +{ + /*-------------*/ + int line_out; /* current line out position for this modulator */ + /*-------------*/ + sinus_modulator sinus; /* sinus lfo */ + triang_modulator triang; /* triangle lfo */ + /*-------------------------*/ + /* first order All-Pass interpolator members */ + fluid_real_t frac_pos_mod; /* fractional position part between samples */ + /* previous value used when interpolating using fractional */ + fluid_real_t buffer; +} modulator; /* Private data for SKEL file */ struct _fluid_chorus_t @@ -122,93 +210,375 @@ struct _fluid_chorus_t fluid_real_t level; fluid_real_t speed_Hz; int number_blocks; - - fluid_real_t *chorusbuf; - int counter; - long phase[MAX_CHORUS]; - long modulation_period_samples; - int *lookup_tab; fluid_real_t sample_rate; - /* sinc lookup table */ - fluid_real_t sinc_table[INTERPOLATION_SAMPLES][INTERPOLATION_SUBSAMPLES]; + /* width control: 0 monophonic, > 0 more stereophonic */ + fluid_real_t width; + fluid_real_t wet1, wet2; + + fluid_real_t *line; /* buffer line */ + int size; /* effective internal size (in samples) */ + + int line_in; /* line in position */ + + /* center output position members */ + fluid_real_t center_pos_mod; /* center output position modulated by modulator */ + int mod_depth; /* modulation depth (in samples) */ + + /* variable rate control of center output position */ + int index_rate; /* index rate to know when to update center_pos_mod */ + int mod_rate; /* rate at which center_pos_mod is updated */ + + /* modulator member */ + modulator mod[MAX_CHORUS]; /* sinus/triangle modulator */ }; -static void fluid_chorus_triangle(int *buf, int len, int depth); -static void fluid_chorus_sine(int *buf, int len, int depth); +/*----------------------------------------------------------------------------- + Sets the frequency of sinus oscillator. + @param mod pointer on modulator structure. + @param freq frequency of the oscillator in Hz. + @param sample_rate sample rate on audio output in Hz. + @param phase initial phase of the oscillator in degree (0 to 360). +-----------------------------------------------------------------------------*/ +static void set_sinus_frequency(sinus_modulator *mod, + float freq, float sample_rate, float phase) +{ + fluid_real_t w = 2 * FLUID_M_PI * freq / sample_rate; /* intial angle */ + fluid_real_t a; -fluid_chorus_t * -new_fluid_chorus(fluid_real_t sample_rate) + mod->a1 = 2 * FLUID_COS(w); + + a = (2 * FLUID_M_PI / 360) * phase; + + mod->buffer2 = FLUID_SIN(a - w); /* y(n-1) = sin(-intial angle) */ + mod->buffer1 = FLUID_SIN(a); /* y(n) = sin(initial phase) */ + mod->reset_buffer2 = FLUID_SIN(FLUID_M_PI / 2 - w); /* reset value for PI/2 */ +} + +/*----------------------------------------------------------------------------- + Gets current value of sinus modulator: + y(n) = a1 . y(n-1) - y(n-2) + out = a1 . buffer1 - buffer2 + + @param pointer on modulator structure. + @return current value of the modulator sine wave. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_sinus(sinus_modulator *mod) { - int i; - int ii; - fluid_chorus_t *chorus; + fluid_real_t out; + out = mod->a1 * mod->buffer1 - mod->buffer2; + mod->buffer2 = mod->buffer1; - chorus = FLUID_NEW(fluid_chorus_t); + if(out >= 1.0f) /* reset in case of instability near PI/2 */ + { + out = 1.0f; /* forces output to the right value */ + mod->buffer2 = mod->reset_buffer2; + } - if(chorus == NULL) + if(out <= -1.0f) /* reset in case of instability near -PI/2 */ { - FLUID_LOG(FLUID_PANIC, "chorus: Out of memory"); - return NULL; + out = -1.0f; /* forces output to the right value */ + mod->buffer2 = - mod->reset_buffer2; } - FLUID_MEMSET(chorus, 0, sizeof(fluid_chorus_t)); + mod->buffer1 = out; + return out; +} - chorus->sample_rate = sample_rate; +/*----------------------------------------------------------------------------- + Set the frequency of triangular oscillator + The frequency is converted in a slope value. + The initial value is set according to frac_phase which is a position + in the period relative to the beginning of the period. + For example: 0 is the beginning of the period, 1/4 is at 1/4 of the period + relative to the beginning. +-----------------------------------------------------------------------------*/ +static void set_triangle_frequency(triang_modulator *mod, float freq, + float sample_rate, float frac_phase) +{ + fluid_real_t ns_period; /* period in numbers of sample */ + + if(freq <= 0.0) + { + freq = 0.5f; + } + + mod->freq = freq; + + ns_period = sample_rate / freq; + + /* the slope of a triangular osc (0 up to +1 down to -1 up to 0....) is equivalent + to the slope of a saw osc (0 -> +4) */ + mod->inc = 4 / ns_period; /* positive slope */ + + /* The initial value and the sign of the slope depend of initial phase: + intial value = = (ns_period * frac_phase) * slope + */ + mod->val = ns_period * frac_phase * mod->inc; + + if(1.0 <= mod->val && mod->val < 3.0) + { + mod->val = 2.0 - mod->val; /* 1.0 down to -1.0 */ + mod->inc = -mod->inc; /* negative slope */ + } + else if(3.0 <= mod->val) + { + mod->val = mod->val - 4.0; /* -1.0 up to +1.0. */ + } + + /* else val < 1.0 */ +} + +/*----------------------------------------------------------------------------- + Get current value of triangular oscillator + y(n) = y(n-1) + dy +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_triang(triang_modulator *mod) +{ + mod->val = mod->val + mod->inc ; - /* Lookup table for the SI function (impulse response of an ideal low pass) */ + if(mod->val >= 1.0) + { + mod->inc = -mod->inc; + return 1.0; + } - /* i: Offset in terms of whole samples */ - for(i = 0; i < INTERPOLATION_SAMPLES; i++) + if(mod->val <= -1.0) { + mod->inc = -mod->inc; + return -1.0; + } + + return mod->val; +} +/*----------------------------------------------------------------------------- + Reads the sample value out of the modulated delay line. + @param mdl, pointer on modulated delay line. + @return the sample value. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_delay(fluid_chorus_t *chorus, + modulator *mod) +{ + fluid_real_t out_index; /* new modulated index position */ + int int_out_index; /* integer part of out_index */ + fluid_real_t out; /* value to return */ - /* ii: Offset in terms of fractional samples ('subsamples') */ - for(ii = 0; ii < INTERPOLATION_SUBSAMPLES; ii++) + /* Checks if the modulator must be updated (every mod_rate samples). */ + /* Important: center_pos_mod must be used immediatly for the + first sample. So, mdl->index_rate must be initialized + to mdl->mod_rate (new_mod_delay_line()) */ + + if(chorus->index_rate >= chorus->mod_rate) + { + /* out_index = center position (center_pos_mod) + sinus waweform */ + if(chorus->type == FLUID_CHORUS_MOD_SINE) { - /* Move the origin into the center of the table */ - double i_shifted = ((double) i - ((double) INTERPOLATION_SAMPLES) / 2. - + (double) ii / (double) INTERPOLATION_SUBSAMPLES); + out_index = chorus->center_pos_mod + + get_mod_sinus(&mod->sinus) * chorus->mod_depth; + } + else + { + out_index = chorus->center_pos_mod + + get_mod_triang(&mod->triang) * chorus->mod_depth; + } - if(fabs(i_shifted) < 0.000001) - { - /* sinc(0) cannot be calculated straightforward (limit needed - for 0/0) */ - chorus->sinc_table[i][ii] = (fluid_real_t)1.; + /* extracts integer part in int_out_index */ + if(out_index >= 0.0f) + { + int_out_index = (int)out_index; /* current integer part */ - } - else + /* forces read index (line_out) with integer modulation value */ + /* Boundary check and circular motion as needed */ + if((mod->line_out = int_out_index) >= chorus->size) { - chorus->sinc_table[i][ii] = (fluid_real_t)sin(i_shifted * M_PI) / (M_PI * i_shifted); - /* Hamming window */ - chorus->sinc_table[i][ii] *= (fluid_real_t)0.5 * (1.0 + cos(2.0 * M_PI * i_shifted / (fluid_real_t)INTERPOLATION_SAMPLES)); - }; - }; - }; + mod->line_out -= chorus->size; + } + } + else /* negative */ + { + int_out_index = (int)(out_index - 1); /* previous integer part */ + /* forces read index (line_out) with integer modulation value */ + /* circular motion as needed */ + mod->line_out = int_out_index + chorus->size; + } + + /* extracts fractionnal part. (it will be used when interpolating + between line_out and line_out +1) and memorize it. + Memorizing is necessary for modulation rate above 1 */ + mod->frac_pos_mod = out_index - int_out_index; + } - /* allocate lookup tables */ - chorus->lookup_tab = FLUID_ARRAY(int, (int)(chorus->sample_rate / MIN_SPEED_HZ)); + /* First order all-pass interpolation ----------------------------------*/ + /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ + /* begins interpolation: read current sample */ + out = chorus->line[mod->line_out]; - if(chorus->lookup_tab == NULL) + /* updates line_out to the next sample. + Boundary check and circular motion as needed */ + if(++mod->line_out >= chorus->size) { - FLUID_LOG(FLUID_PANIC, "chorus: Out of memory"); - goto error_recovery; + mod->line_out -= chorus->size; + } + + /* Fractional interpolation beetween next sample (at next position) and + previous output added to current sample. + */ + out += mod->frac_pos_mod * (chorus->line[mod->line_out] - mod->buffer); + mod->buffer = out; /* memorizes current output */ + return out; +} + +/*----------------------------------------------------------------------------- + Push a sample val into the delay line +-----------------------------------------------------------------------------*/ +#define push_in_delay_line(dl, val) \ +{\ + dl->line[dl->line_in] = val;\ + /* Incrementation and circular motion if necessary */\ + if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ +}\ + +/*----------------------------------------------------------------------------- + Initialize : mod_rate, center_pos_mod, and index rate + + center_pos_mod is initialized so that the delay between center_pos_mod and + line_in is: mod_depth + INTERP_SAMPLES_NBR. +-----------------------------------------------------------------------------*/ +static void set_center_position(fluid_chorus_t *chorus) +{ + int center; + + /* Sets the modulation rate. This rate defines how often + the center position (center_pos_mod ) is modulated . + The value is expressed in samples. The default value is 1 that means that + center_pos_mod is updated at every sample. + For example with a value of 2, the center position position will be + updated only one time every 2 samples only. + */ + chorus->mod_rate = LOW_MOD_RATE; /* default modulation rate */ + + /* compensate mod rate for high modulation depth */ + if(chorus->mod_depth > LOW_MOD_DEPTH) + { + int delta_mod_depth = (chorus->mod_depth - LOW_MOD_DEPTH); + chorus->mod_rate += (delta_mod_depth * RANGE_MOD_RATE) / RANGE_MOD_DEPTH; + } + + /* Initializes the modulated center position (center_pos_mod) so that: + - the delay between center_pos_mod and line_in is: + mod_depth + INTERP_SAMPLES_NBR. + */ + center = chorus->line_in - (INTERP_SAMPLES_NBR + chorus->mod_depth); + + if(center < 0) + { + center += chorus->size; + } + + chorus->center_pos_mod = (fluid_real_t)center; + + /* index rate to control when to update center_pos_mod */ + /* Important: must be set to get center_pos_mod immediatly used for the + reading of first sample (see get_mod_delay()) */ + chorus->index_rate = chorus->mod_rate; +} + +/*----------------------------------------------------------------------------- + Modulated delay line initialization. + + Sets the length line ( alloc delay samples). + Remark: the function sets the internal size accordling to the length delay_length. + The size is augmented by INTERP_SAMPLES_NBR to take account of interpolation. + + @param chorus, pointer chorus unit. + @param delay_length the length of the delay line in samples. + @return FLUID_OK if success , FLUID_FAILED if memory error. + + Return FLUID_OK if success, FLUID_FAILED if memory error. +-----------------------------------------------------------------------------*/ +static int new_mod_delay_line(fluid_chorus_t *chorus, int delay_length) +{ + /*-----------------------------------------------------------------------*/ + /* checks parameter */ + if(delay_length < 1) + { + return FLUID_FAILED; } - /* allocate sample buffer */ + chorus->mod_depth = 0; + /*----------------------------------------------------------------------- + allocates delay_line and initialize members: - line, size, line_in... + */ + /* total size of the line: size = INTERP_SAMPLES_NBR + delay_length */ + chorus->size = delay_length + INTERP_SAMPLES_NBR; + chorus->line = FLUID_ARRAY(fluid_real_t, chorus->size); + + if(! chorus->line) + { + return FLUID_FAILED; + } - chorus->chorusbuf = FLUID_ARRAY(fluid_real_t, MAX_SAMPLES); + /* clears the buffer: + - delay line + - interpolator member: buffer, frac_pos_mod + */ + fluid_chorus_reset(chorus); + + /* Initializes line_in to the start of the buffer */ + chorus->line_in = 0; + /*------------------------------------------------------------------------ + Initializes modulation members: + - modulation rate (the speed at which center_pos_mod is modulated: mod_rate + - modulated center position: center_pos_mod + - index rate to know when to update center_pos_mod:index_rate + -------------------------------------------------------------------------*/ + /* Initializes the modulated center position: + mod_rate, center_pos_mod, and index rate + */ + set_center_position(chorus); + + return FLUID_OK; +} - if(chorus->chorusbuf == NULL) +/*----------------------------------------------------------------------------- + API +------------------------------------------------------------------------------*/ +/** + * Create the chorus unit. + * @sample_rate audio sample rate in Hz. + * @return pointer on chorus unit. + */ +fluid_chorus_t * +new_fluid_chorus(fluid_real_t sample_rate) +{ + fluid_chorus_t *chorus; + + chorus = FLUID_NEW(fluid_chorus_t); + + if(chorus == NULL) { FLUID_LOG(FLUID_PANIC, "chorus: Out of memory"); - goto error_recovery; + return NULL; } - if(fluid_chorus_init(chorus) != FLUID_OK) + FLUID_MEMSET(chorus, 0, sizeof(fluid_chorus_t)); + + chorus->sample_rate = sample_rate; + +#ifdef DEBUG_PRINT + printf("fluid_chorus_t:%d bytes\n", sizeof(fluid_chorus_t)); + printf("fluid_real_t:%d bytes\n", sizeof(fluid_real_t)); +#endif + +#ifdef DEBUG_PRINT + printf("NEW_MOD\n"); +#endif + + if(new_mod_delay_line(chorus, MAX_SAMPLES) == FLUID_FAILED) { goto error_recovery; - }; + } return chorus; @@ -218,33 +588,41 @@ new_fluid_chorus(fluid_real_t sample_rate) return NULL; } +/** + * Delete the chorus unit. + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + */ void delete_fluid_chorus(fluid_chorus_t *chorus) { fluid_return_if_fail(chorus != NULL); - FLUID_FREE(chorus->chorusbuf); - FLUID_FREE(chorus->lookup_tab); + FLUID_FREE(chorus->line); FLUID_FREE(chorus); } -int -fluid_chorus_init(fluid_chorus_t *chorus) +/** + * Clear the internal delay line and associate filter. + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + */ +void +fluid_chorus_reset(fluid_chorus_t *chorus) { int i; + /* reset delay line */ for(i = 0; i < MAX_SAMPLES; i++) { - chorus->chorusbuf[i] = 0.0; + chorus->line[i] = 0.0; } - return FLUID_OK; -} - -void -fluid_chorus_reset(fluid_chorus_t *chorus) -{ - fluid_chorus_init(chorus); + /* reset modulators's allpass filter */ + for(i = 0; i < MAX_CHORUS; i++) + { + /* initializes 1st order All-Pass interpolator members */ + chorus->mod[i].buffer = 0; /* previous delay sample value */ + chorus->mod[i].frac_pos_mod = 0; /* fractional position (between consecutives sample) */ + } } /** @@ -254,7 +632,7 @@ fluid_chorus_reset(fluid_chorus_t *chorus) * @param nr Chorus voice count (0-99, CPU time consumption proportional to * this value) * @param level Chorus level (0.0-10.0) - * @param speed Chorus speed in Hz (0.29-5.0) + * @param speed Chorus speed in Hz (0.1-5.0) * @param depth_ms Chorus depth (max value depends on synth sample rate, * 0.0-21.0 is safe for sample rate values up to 96KHz) * @param type Chorus waveform type (#fluid_chorus_mod) @@ -263,34 +641,34 @@ void fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, fluid_real_t speed, fluid_real_t depth_ms, int type) { - int modulation_depth_samples; int i; - if(set & FLUID_CHORUS_SET_NR) + if(set & FLUID_CHORUS_SET_NR) /* number of block */ { chorus->number_blocks = nr; } - if(set & FLUID_CHORUS_SET_LEVEL) + if(set & FLUID_CHORUS_SET_LEVEL) /* output level */ { chorus->level = level; } - if(set & FLUID_CHORUS_SET_SPEED) + if(set & FLUID_CHORUS_SET_SPEED) /* lfo frequency (in Hz) */ { chorus->speed_Hz = speed; } - if(set & FLUID_CHORUS_SET_DEPTH) + if(set & FLUID_CHORUS_SET_DEPTH) /* modulation depth (in ms) */ { chorus->depth_ms = depth_ms; } - if(set & FLUID_CHORUS_SET_TYPE) + if(set & FLUID_CHORUS_SET_TYPE) /* lfo shape (sinus, triangle) */ { chorus->type = type; } + /* check min , max parameters */ if(chorus->number_blocks < 0) { FLUID_LOG(FLUID_WARN, "chorus: number blocks must be >=0! Setting value to 0."); @@ -322,272 +700,304 @@ fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, chorus->depth_ms = 0.0; } - /* Depth: Check for too high value through modulation_depth_samples. */ - if(chorus->level < 0.0) { FLUID_LOG(FLUID_WARN, "chorus: level must be positive! Setting value to 0."); chorus->level = 0.0; } - else if(chorus->level > 10) + else if(chorus->level > MAX_LEVEL) { FLUID_LOG(FLUID_WARN, "chorus: level must be < 10. A reasonable level is << 1! " "Setting it to 0.1."); chorus->level = 0.1; } - /* The modulating LFO goes through a full period every x samples: */ - chorus->modulation_period_samples = chorus->sample_rate / chorus->speed_Hz; + /* initialize modulation depth (peak to peak) (in samples)*/ + chorus->mod_depth = (int)(chorus->depth_ms / 1000.0 /* convert modulation depth in ms to s*/ + * chorus->sample_rate); - /* The variation in delay time is x: */ - modulation_depth_samples = (int) - (chorus->depth_ms / 1000.0 /* convert modulation depth in ms to s*/ - * chorus->sample_rate); - - if(modulation_depth_samples > MAX_SAMPLES) + if(chorus->mod_depth > MAX_SAMPLES) { FLUID_LOG(FLUID_WARN, "chorus: Too high depth. Setting it to max (%d).", MAX_SAMPLES); - modulation_depth_samples = MAX_SAMPLES; + chorus->mod_depth = MAX_SAMPLES; // set depth to maximum to avoid spamming console with above warning - chorus->depth_ms = (modulation_depth_samples * 1000) / chorus->sample_rate; + chorus->depth_ms = (chorus->mod_depth * 1000) / chorus->sample_rate; + } + + chorus->mod_depth /= 2; /* amplitude is peak to peek / 2 */ +#ifdef DEBUG_PRINT + printf("depth_ms:%f, depth_samples/2:%d\n", chorus->depth_ms, chorus->mod_depth); +#endif + /* Initializes the modulated center position: + mod_rate, center_pos_mod, and index rate. + */ + set_center_position(chorus); /* must be called before set_xxxx_frequency() */ +#ifdef DEBUG_PRINT + printf("mod_rate:%d\n", chorus->mod_rate); +#endif + + /* initialize modulator frequency */ + for(i = 0; i < chorus->number_blocks; i++) + { + set_sinus_frequency(&chorus->mod[i].sinus, + chorus->speed_Hz * chorus->mod_rate, + chorus->sample_rate, + /* phase offset between modulators waveform */ + (float)((360.0f / (float) chorus->number_blocks) * i)); + + set_triangle_frequency(&chorus->mod[i].triang, + chorus->speed_Hz * chorus->mod_rate, + chorus->sample_rate, + /* phase offset between modulators waveform */ + (float)i / chorus->number_blocks); } - /* initialize LFO table */ - switch(chorus->type) +#ifdef DEBUG_PRINT + printf("lfo type:%d\n", chorus->type); + printf("speed_Hz:%f\n", chorus->speed_Hz); +#endif + + /* Initialize the lfo waveform */ + if((chorus->type != FLUID_CHORUS_MOD_SINE) && + (chorus->type != FLUID_CHORUS_MOD_TRIANGLE)) { - default: FLUID_LOG(FLUID_WARN, "chorus: Unknown modulation type. Using sinewave."); chorus->type = FLUID_CHORUS_MOD_SINE; - /* fall-through */ - - case FLUID_CHORUS_MOD_SINE: - fluid_chorus_sine(chorus->lookup_tab, chorus->modulation_period_samples, - modulation_depth_samples); - break; - - case FLUID_CHORUS_MOD_TRIANGLE: - fluid_chorus_triangle(chorus->lookup_tab, chorus->modulation_period_samples, - modulation_depth_samples); - break; } - for(i = 0; i < chorus->number_blocks; i++) +#ifdef DEBUG_PRINT + + if(chorus->type == FLUID_CHORUS_MOD_SINE) + { + printf("lfo: sinus\n"); + } + else { - /* Set the phase of the chorus blocks equally spaced */ - chorus->phase[i] = (int)((double) chorus->modulation_period_samples - * (double) i / (double) chorus->number_blocks); + printf("lfo: triangle\n"); } - /* Start of the circular buffer */ - chorus->counter = 0; + printf("nr:%d\n", chorus->number_blocks); +#endif + + /* Recalculate internal values after parameters change */ + + /* + Note: + Actually WIDTH is fixed to maximum value. But in the future we could add a setting + "synth.chorus.width" to allow a gradually stereo effect from minimum (monophonic) to + maximum stereo effect. + If this setting will be added, remove the following instruction. + */ + chorus->width = WIDTH; + { + /* The stereo amplitude equation (wet1 and wet2 below) have a + tendency to produce high amplitude with high width values ( 1 < width < 10). + This results in an unwanted noisy output clipped by the audio card. + To avoid this dependency, we divide by (1 + chorus->width * SCALE_WET_WIDTH) + Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), + the output amplitude (wet) seems rather independent of width setting */ + + fluid_real_t wet = chorus->level * SCALE_WET ; + + /* wet1 and wet2 are used by the stereo effect controled by the width setting + for producing a stereo ouptput from a monophonic chorus signal. + Please see the note above about a side effect tendency */ + + if(chorus->number_blocks > 1) + { + wet = wet / (1.0f + chorus->width * SCALE_WET_WIDTH); + chorus->wet1 = wet * (chorus->width / 2.0f + 0.5f); + chorus->wet2 = wet * ((1.0f - chorus->width) / 2.0f); +#ifdef DEBUG_PRINT + printf("width:%f\n", chorus->width); + + if(chorus->width > 0) + { + printf("nr > 1, width > 0 => out stereo\n"); + } + else + { + printf("nr > 1, width:0 =>out mono\n"); + } + +#endif + } + else + { + /* only one chorus block */ + if(chorus->width == 0.0) + { + /* wet1 and wet2 should make stereo output monomophic */ + chorus->wet1 = chorus->wet2 = wet; + } + else + { + /* for width > 0, wet1 and wet2 should make stereo output stereo + with only one block. This will only possible by inverting + the unique signal on each left and right output. + Note however that with only one block, it isn't possible to + have a graduate width effect */ + chorus->wet1 = wet; + chorus->wet2 = -wet; /* inversion */ + } + +#ifdef DEBUG_PRINT + printf("width:%f\n", chorus->width); + + if(chorus->width != 0) + { + printf("one block, width > 0 => out stereo\n"); + } + else + { + printf("one block, width:0 => out mono\n"); + } + +#endif + } + } } +/** + * Process chorus by mixing the result in output buffer. + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + * @param in, pointer on monophonic input buffer of FLUID_BUFSIZE samples. + * @param left_out, right_out, pointers on stereo output buffers of + * FLUID_BUFSIZE samples. + */ void fluid_chorus_processmix(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out) { int sample_index; int i; - fluid_real_t d_in, d_out; + fluid_real_t d_out[2]; /* output stereo Left and Right */ + /* foreach sample, process output sample then input sample */ for(sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) { + fluid_real_t out; /* block output */ - d_in = in[sample_index]; - d_out = 0.0f; + d_out[0] = d_out[1] = 0.0f; /* clear stereo unit input */ -# if 0 +#if 0 /* Debug: Listen to the chorus signal only */ left_out[sample_index] = 0; right_out[sample_index] = 0; #endif - /* Write the current sample into the circular buffer */ - chorus->chorusbuf[chorus->counter] = d_in; + ++chorus->index_rate; /* modulator rate */ + /* foreach chorus block, process output sample */ for(i = 0; i < chorus->number_blocks; i++) { - int ii; - /* Calculate the delay in subsamples for the delay line of chorus block nr. */ - - /* The value in the lookup table is so, that this expression - * will always be positive. It will always include a number of - * full periods of MAX_SAMPLES*INTERPOLATION_SUBSAMPLES to - * remain positive at all times. */ - int pos_subsamples = (INTERPOLATION_SUBSAMPLES * chorus->counter - - chorus->lookup_tab[chorus->phase[i]]); + /* get sample from the output of modulated delay line */ + out = get_mod_delay(chorus, &chorus->mod[i]); - int pos_samples = pos_subsamples / INTERPOLATION_SUBSAMPLES; + /* accumulate out into stereo unit input */ + d_out[i & 1] += out; + } - /* modulo divide by INTERPOLATION_SUBSAMPLES */ - pos_subsamples &= INTERPOLATION_SUBSAMPLES_ANDMASK; + /* update modulator index rate and output center position */ + if(chorus->index_rate >= chorus->mod_rate) + { + chorus->index_rate = 0; /* clear modulator index rate */ - for(ii = 0; ii < INTERPOLATION_SAMPLES; ii++) + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((chorus->center_pos_mod += chorus->mod_rate) >= chorus->size) { - /* Add the delayed signal to the chorus sum d_out Note: The - * delay in the delay line moves backwards for increasing - * delay!*/ - - /* The & in chorusbuf[...] is equivalent to a division modulo - MAX_SAMPLES, only faster. */ - d_out += chorus->chorusbuf[pos_samples & MAX_SAMPLES_ANDMASK] - * chorus->sinc_table[ii][pos_subsamples]; - - pos_samples--; - }; - - /* Cycle the phase of the modulating LFO */ - chorus->phase[i]++; - - chorus->phase[i] %= (chorus->modulation_period_samples); - } /* foreach chorus block */ - - d_out *= chorus->level; + chorus->center_pos_mod -= chorus->size; + } + } - /* Add the chorus sum d_out to output */ - left_out[sample_index] += d_out; - right_out[sample_index] += d_out; + /* Adjust stereo input level in case of number_blocks odd: + In those case, d_out[1] level is lower than d_out[0], so we need to + add out value to d_out[1] to have d_out[0] and d_out[1] balanced. + */ + if((i & 1) && i > 2) // i = 3,5,7... + { + d_out[1] += out ; + } - /* Move forward in circular buffer */ - chorus->counter++; - chorus->counter %= MAX_SAMPLES; + /* process stereo unit */ + /* Add the chorus stereo unit d_out to left and right output */ + left_out[sample_index] += d_out[0] * chorus->wet1 + d_out[1] * chorus->wet2; + right_out[sample_index] += d_out[1] * chorus->wet1 + d_out[0] * chorus->wet2; - } /* foreach sample */ + /* Write the current input sample into the circular buffer */ + push_in_delay_line(chorus, in[sample_index]); + } } +/** + * Process chorus by putting the result in output buffer (no mixing). + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + * @param in, pointer on monophonic input buffer of FLUID_BUFSIZE samples. + * @param left_out, right_out, pointers on stereo output buffers of + * FLUID_BUFSIZE samples. + */ /* Duplication of code ... (replaces sample data instead of mixing) */ void fluid_chorus_processreplace(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out) { int sample_index; int i; - fluid_real_t d_in, d_out; + fluid_real_t d_out[2]; /* output stereo Left and Right */ + /* foreach sample, process output sample then input sample */ for(sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) { + fluid_real_t out; /* block output */ - d_in = in[sample_index]; - d_out = 0.0f; + d_out[0] = d_out[1] = 0.0f; /* clear stereo unit input */ -# if 0 +#if 0 /* Debug: Listen to the chorus signal only */ left_out[sample_index] = 0; right_out[sample_index] = 0; #endif - /* Write the current sample into the circular buffer */ - chorus->chorusbuf[chorus->counter] = d_in; + ++chorus->index_rate; /* modulator rate */ + /* foreach chorus block, process output sample */ for(i = 0; i < chorus->number_blocks; i++) { - int ii; - /* Calculate the delay in subsamples for the delay line of chorus block nr. */ - - /* The value in the lookup table is so, that this expression - * will always be positive. It will always include a number of - * full periods of MAX_SAMPLES*INTERPOLATION_SUBSAMPLES to - * remain positive at all times. */ - int pos_subsamples = (INTERPOLATION_SUBSAMPLES * chorus->counter - - chorus->lookup_tab[chorus->phase[i]]); + /* get sample from the output of modulated delay line */ + out = get_mod_delay(chorus, &chorus->mod[i]); - int pos_samples = pos_subsamples / INTERPOLATION_SUBSAMPLES; + /* accumulate out into stereo unit input */ + d_out[i & 1] += out; + } - /* modulo divide by INTERPOLATION_SUBSAMPLES */ - pos_subsamples &= INTERPOLATION_SUBSAMPLES_ANDMASK; + /* update modulator index rate and output center position */ + if(chorus->index_rate >= chorus->mod_rate) + { + chorus->index_rate = 0; /* clear modulator index rate */ - for(ii = 0; ii < INTERPOLATION_SAMPLES; ii++) + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((chorus->center_pos_mod += chorus->mod_rate) >= chorus->size) { - /* Add the delayed signal to the chorus sum d_out Note: The - * delay in the delay line moves backwards for increasing - * delay!*/ - - /* The & in chorusbuf[...] is equivalent to a division modulo - MAX_SAMPLES, only faster. */ - d_out += chorus->chorusbuf[pos_samples & MAX_SAMPLES_ANDMASK] - * chorus->sinc_table[ii][pos_subsamples]; - - pos_samples--; - }; - - /* Cycle the phase of the modulating LFO */ - chorus->phase[i]++; - - chorus->phase[i] %= (chorus->modulation_period_samples); - } /* foreach chorus block */ - - d_out *= chorus->level; - - /* Store the chorus sum d_out to output */ - left_out[sample_index] = d_out; - right_out[sample_index] = d_out; - - /* Move forward in circular buffer */ - chorus->counter++; - chorus->counter %= MAX_SAMPLES; - - } /* foreach sample */ -} - -/* Purpose: - * - * Calculates a modulation waveform (sine) Its value ( modulo - * MAXSAMPLES) varies between 0 and depth*INTERPOLATION_SUBSAMPLES. - * Its period length is len. The waveform data will be used modulo - * MAXSAMPLES only. Since MAXSAMPLES is substracted from the waveform - * a couple of times here, the resulting (current position in - * buffer)-(waveform sample) will always be positive. - */ -static void -fluid_chorus_sine(int *buf, int len, int depth) -{ - int i; - double angle, incr, mult; - - /* Pre-calculate increment between angles. */ - incr = (2. * M_PI) / (double)len; - - /* Pre-calculate 'depth' multiplier. */ - mult = (double) depth / 2.0 * (double) INTERPOLATION_SUBSAMPLES; - - /* Initialize to zero degrees. */ - angle = 0.; - - /* Build sine modulation waveform */ - for(i = 0; i < len; i++) - { - buf[i] = (int)((1. + sin(angle)) * mult) - 3 * MAX_SAMPLES * INTERPOLATION_SUBSAMPLES; - - angle += incr; - } -} - -/* Purpose: - * Calculates a modulation waveform (triangle) - * See fluid_chorus_sine for comments. - */ -static void -fluid_chorus_triangle(int *buf, int len, int depth) -{ - int *il = buf; - int *ir = buf + len - 1; - int ival; - double val, incr; - - /* Pre-calculate increment for the ramp. */ - incr = 2.0 / len * (double)depth * (double) INTERPOLATION_SUBSAMPLES; - - /* Initialize first value */ - val = 0. - 3. * MAX_SAMPLES * INTERPOLATION_SUBSAMPLES; + chorus->center_pos_mod -= chorus->size; + } + } - /* Build triangular modulation waveform */ - while(il <= ir) - { - /* Assume 'val' to be always negative for rounding mode */ - ival = (int)(val - 0.5); + /* Adjust stereo input level in case of number_blocks odd: + In those case, d_out[1] level is lower than d_out[0], so we need to + add out value to d_out[1] to have d_out[0] and d_out[1] balanced. + */ + if((i & 1) && i > 2) // i = 3,5,7... + { + d_out[1] += out ; + } - *il++ = ival; - *ir-- = ival; + /* process stereo unit */ + /* store the chorus stereo unit d_out to left and right output */ + left_out[sample_index] = d_out[0] * chorus->wet1 + d_out[1] * chorus->wet2; + right_out[sample_index] = d_out[1] * chorus->wet1 + d_out[0] * chorus->wet2; - val += incr; + /* Write the current input sample into the circular buffer */ + push_in_delay_line(chorus, in[sample_index]); } } diff --git a/src/rvoice/fluid_chorus.h b/src/rvoice/fluid_chorus.h index 94130957a..f815ac42d 100644 --- a/src/rvoice/fluid_chorus.h +++ b/src/rvoice/fluid_chorus.h @@ -49,7 +49,6 @@ typedef enum */ fluid_chorus_t *new_fluid_chorus(fluid_real_t sample_rate); void delete_fluid_chorus(fluid_chorus_t *chorus); -int fluid_chorus_init(fluid_chorus_t *chorus); void fluid_chorus_reset(fluid_chorus_t *chorus); void fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index 45e2a72f7..b6a09e7ce 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -198,7 +198,7 @@ void fluid_synth_settings(fluid_settings_t *settings) fluid_settings_register_int(settings, "synth.chorus.active", 1, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_int(settings, "synth.chorus.nr", FLUID_CHORUS_DEFAULT_N, 0, 99, 0); fluid_settings_register_num(settings, "synth.chorus.level", FLUID_CHORUS_DEFAULT_LEVEL, 0.0f, 10.0f, 0); - fluid_settings_register_num(settings, "synth.chorus.speed", FLUID_CHORUS_DEFAULT_SPEED, 0.29f, 5.0f, 0); + fluid_settings_register_num(settings, "synth.chorus.speed", FLUID_CHORUS_DEFAULT_SPEED, 0.1f, 5.0f, 0); fluid_settings_register_num(settings, "synth.chorus.depth", FLUID_CHORUS_DEFAULT_DEPTH, 0.0f, 256.0f, 0); fluid_settings_register_int(settings, "synth.ladspa.active", 0, 0, 1, FLUID_HINT_TOGGLED); @@ -5198,7 +5198,7 @@ fluid_synth_set_chorus_on(fluid_synth_t *synth, int on) * @param nr Chorus voice count (0-99, CPU time consumption proportional to * this value) * @param level Chorus level (0.0-10.0) - * @param speed Chorus speed in Hz (0.29-5.0) + * @param speed Chorus speed in Hz (0.1-5.0) * @param depth_ms Chorus depth (max value depends on synth sample rate, * 0.0-21.0 is safe for sample rate values up to 96KHz) * @param type Chorus waveform type (#fluid_chorus_mod) @@ -5256,19 +5256,6 @@ int fluid_synth_set_chorus_type(fluid_synth_t *synth, int type) return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_TYPE, 0, 0, 0, 0, type); } -/** - * Set one or more chorus parameters. - * @param synth FluidSynth instance - * @param set Flags indicating which chorus parameters to set (#fluid_chorus_set_t) - * @param nr Chorus voice count (0-99, CPU time consumption proportional to - * this value) - * @param level Chorus level (0.0-10.0) - * @param speed Chorus speed in Hz (0.29-5.0) - * @param depth_ms Chorus depth (max value depends on synth sample rate, - * 0.0-21.0 is safe for sample rate values up to 96KHz) - * @param type Chorus waveform type (#fluid_chorus_mod) - * @return #FLUID_OK on success, #FLUID_FAILED otherwise - */ int fluid_synth_set_chorus_full(fluid_synth_t *synth, int set, int nr, double level, double speed, double depth_ms, int type) @@ -5325,7 +5312,7 @@ fluid_synth_set_chorus_full(fluid_synth_t *synth, int set, int nr, double level, /** * Get chorus voice number (delay line count) value. * @param synth FluidSynth instance - * @return Chorus voice count (0-99) + * @return Chorus voice count */ int fluid_synth_get_chorus_nr(fluid_synth_t *synth) @@ -5341,7 +5328,7 @@ fluid_synth_get_chorus_nr(fluid_synth_t *synth) /** * Get chorus level. * @param synth FluidSynth instance - * @return Chorus level value (0.0-10.0) + * @return Chorus level value */ double fluid_synth_get_chorus_level(fluid_synth_t *synth) @@ -5357,7 +5344,7 @@ fluid_synth_get_chorus_level(fluid_synth_t *synth) /** * Get chorus speed in Hz. * @param synth FluidSynth instance - * @return Chorus speed in Hz (0.29-5.0) + * @return Chorus speed in Hz */ double fluid_synth_get_chorus_speed(fluid_synth_t *synth)