diff options
author | Robin Gareus <robin@gareus.org> | 2019-12-02 23:58:15 +0100 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2019-12-03 00:01:10 +0100 |
commit | d425f6dcb597a1bcc01267490627a08bfe48bddd (patch) | |
tree | 3d40c38e53a96d8298e0b5eccd4008b47ab577f8 | |
parent | c5066dcf38298e18445f2407bd5f92f2c8b4a16d (diff) |
Update to fluidsynth-2.1
see https://github.com/FluidSynth/fluidsynth/releases/tag/v2.1.0
- new, less "ringing" reverb engine
- new, stereophonic chorus engine
- improved integrity checking of SoundFont modulators
...
23 files changed, 1314 insertions, 604 deletions
diff --git a/libs/fluidsynth/README b/libs/fluidsynth/README index 376a96dfc9..153bab67a4 100644 --- a/libs/fluidsynth/README +++ b/libs/fluidsynth/README @@ -1,7 +1,7 @@ This is a stripped down version of fluidsynth (library only) from git://github.com/FluidSynth/fluidsynth.git -rev. v2.0.7-98-g9ab3e3ab +rev. v2.1.0-1-gb266cf2ab fluidsynth is licensed in terms of the LGPL-2+, see individual source files for (C) holders. diff --git a/libs/fluidsynth/fluidsynth/gen.h b/libs/fluidsynth/fluidsynth/gen.h index 4b625831b0..1f46fe2a99 100644 --- a/libs/fluidsynth/fluidsynth/gen.h +++ b/libs/fluidsynth/fluidsynth/gen.h @@ -95,11 +95,22 @@ enum fluid_gen_type GEN_EXCLUSIVECLASS, /**< Exclusive class number */ GEN_OVERRIDEROOTKEY, /**< Sample root note override */ - /* the initial pitch is not a "standard" generator. It is not - * mentioned in the list of generator in the SF2 specifications. It - * is used, however, as the destination for the default pitch wheel - * modulator. */ - GEN_PITCH, /**< Pitch @note Not a real SoundFont generator */ + /** + * @brief Initial Pitch + * + * @note This is not "standard" SoundFont generator, because it is not + * mentioned in the list of generators in the SF2 specifications. + * It is used by FluidSynth internally to compute the nominal pitch of + * a note on note-on event. By nature it shouldn't be allowed to be modulated, + * however the specification defines a default modulator having "Initial Pitch" + * as destination (cf. SF2.01 page 57 section 8.4.10 MIDI Pitch Wheel to Initial Pitch). + * Thus it is impossible to cancel this default modulator, which would be required + * to let the MIDI Pitch Wheel controller modulate a different generator. + * In order to provide this flexibility, FluidSynth >= 2.1.0 uses a default modulator + * "Pitch Wheel to Fine Tune", rather than Initial Pitch. The same "compromise" can + * be found on the Audigy 2 ZS for instance. + */ + GEN_PITCH, GEN_CUSTOM_BALANCE, /**< Balance @note Not a real SoundFont generator */ /* non-standard generator for an additional custom high- or low-pass filter */ diff --git a/libs/fluidsynth/fluidsynth/log.h b/libs/fluidsynth/fluidsynth/log.h index 00d802f50e..3ea74b2614 100644 --- a/libs/fluidsynth/fluidsynth/log.h +++ b/libs/fluidsynth/fluidsynth/log.h @@ -77,7 +77,11 @@ fluid_log_function_t fluid_set_log_function(int level, fluid_log_function_t fun, FLUIDSYNTH_API void fluid_default_log_function(int level, const char *message, void *data); -FLUIDSYNTH_API int fluid_log(int level, const char *fmt, ...); +FLUIDSYNTH_API int fluid_log(int level, const char *fmt, ...) +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +__attribute__ ((format (printf, 2, 3))) +#endif +; #ifdef __cplusplus diff --git a/libs/fluidsynth/fluidsynth/synth.h b/libs/fluidsynth/fluidsynth/synth.h index 87826809fd..3003972542 100644 --- a/libs/fluidsynth/fluidsynth/synth.h +++ b/libs/fluidsynth/fluidsynth/synth.h @@ -32,13 +32,13 @@ extern "C" { * @brief Embeddable SoundFont synthesizer * * You create a new synthesizer with new_fluid_synth() and you destroy - * if with delete_fluid_synth(). Use the settings structure to specify + * it with delete_fluid_synth(). Use the fluid_settings_t structure to specify * the synthesizer characteristics. * * You have to load a SoundFont in order to hear any sound. For that * you use the fluid_synth_sfload() function. * - * You can use the audio driver functions described below to open + * You can use the audio driver functions to open * the audio device and create a background audio thread. * * The API for sending MIDI events is probably what you expect: @@ -244,7 +244,7 @@ const char *fluid_synth_error(fluid_synth_t *synth); enum fluid_synth_add_mod { FLUID_SYNTH_OVERWRITE, /**< Overwrite any existing matching modulator */ - FLUID_SYNTH_ADD, /**< Add (sum) modulator amounts */ + FLUID_SYNTH_ADD, /**< Sum up modulator amounts */ }; FLUIDSYNTH_API int fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode); @@ -296,7 +296,7 @@ enum fluid_iir_filter_type }; /** - * Specifies optional settings to use for the custom IIR filter + * Specifies optional settings to use for the custom IIR filter. Can be bitwise ORed. */ enum fluid_iir_filter_flags { diff --git a/libs/fluidsynth/src/fluid_chorus.c b/libs/fluidsynth/src/fluid_chorus.c index abcd4bc0c4..7092d5e7d6 100644 --- a/libs/fluidsynth/src/fluid_chorus.c +++ b/libs/fluidsynth/src/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,42 @@ error_recovery: 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; + unsigned int u; - for(i = 0; i < MAX_SAMPLES; i++) + /* reset delay line */ + for(i = 0; i < chorus->size; i++) { - chorus->chorusbuf[i] = 0.0; + chorus->line[i] = 0; } - return FLUID_OK; -} - -void -fluid_chorus_reset(fluid_chorus_t *chorus) -{ - fluid_chorus_init(chorus); + /* reset modulators's allpass filter */ + for(u = 0; u < FLUID_N_ELEMENTS(chorus->mod); u++) + { + /* initializes 1st order All-Pass interpolator members */ + chorus->mod[u].buffer = 0; /* previous delay sample value */ + chorus->mod[u].frac_pos_mod = 0; /* fractional position (between consecutives sample) */ + } } /** @@ -254,7 +633,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 +642,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 +701,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/libs/fluidsynth/src/fluid_chorus.h b/libs/fluidsynth/src/fluid_chorus.h index 94130957a2..f815ac42d4 100644 --- a/libs/fluidsynth/src/fluid_chorus.h +++ b/libs/fluidsynth/src/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/libs/fluidsynth/src/fluid_conv.c b/libs/fluidsynth/src/fluid_conv.c index a2ba770b35..5c055d2be9 100644 --- a/libs/fluidsynth/src/fluid_conv.c +++ b/libs/fluidsynth/src/fluid_conv.c @@ -23,66 +23,56 @@ #include "fluid_conv_tables.c" /* - * fluid_ct2hz + * Converts absolute cents to Hertz + * + * As per sfspec section 9.3: + * + * ABSOLUTE CENTS - An absolute logarithmic measure of frequency based on a + * reference of MIDI key number scaled by 100. + * A cent is 1/1200 of an octave [which is the twelve hundredth root of two], + * and value 6900 is 440 Hz (A-440). + * + * Implemented below basically is the following: + * 440 * 2^((cents-6900)/1200) + * = 440 * 2^((int)((cents-6900)/1200)) * 2^(((int)cents-6900)%1200)) + * = 2^((int)((cents-6900)/1200)) * (440 * 2^(((int)cents-6900)%1200))) + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * This second factor is stored in the lookup table. + * + * The first factor can be implemented with a fast shift when the exponent + * is always an int. This is the case when using 440/2^6 Hz rather than 440Hz + * reference. */ fluid_real_t fluid_ct2hz_real(fluid_real_t cents) { - if(cents < 0) + if(FLUID_UNLIKELY(cents < 0)) { return (fluid_real_t) 1.0; } - else if(cents < 900) - { - return (fluid_real_t) 6.875 * fluid_ct2hz_tab[(int)(cents + 300)]; - } - else if(cents < 2100) - { - return (fluid_real_t) 13.75 * fluid_ct2hz_tab[(int)(cents - 900)]; - } - else if(cents < 3300) - { - return (fluid_real_t) 27.5 * fluid_ct2hz_tab[(int)(cents - 2100)]; - } - else if(cents < 4500) - { - return (fluid_real_t) 55.0 * fluid_ct2hz_tab[(int)(cents - 3300)]; - } - else if(cents < 5700) - { - return (fluid_real_t) 110.0 * fluid_ct2hz_tab[(int)(cents - 4500)]; - } - else if(cents < 6900) - { - return (fluid_real_t) 220.0 * fluid_ct2hz_tab[(int)(cents - 5700)]; - } - else if(cents < 8100) - { - return (fluid_real_t) 440.0 * fluid_ct2hz_tab[(int)(cents - 6900)]; - } - else if(cents < 9300) - { - return (fluid_real_t) 880.0 * fluid_ct2hz_tab[(int)(cents - 8100)]; - } - else if(cents < 10500) - { - return (fluid_real_t) 1760.0 * fluid_ct2hz_tab[(int)(cents - 9300)]; - } - else if(cents < 11700) - { - return (fluid_real_t) 3520.0 * fluid_ct2hz_tab[(int)(cents - 10500)]; - } - else if(cents < 12900) - { - return (fluid_real_t) 7040.0 * fluid_ct2hz_tab[(int)(cents - 11700)]; - } - else if(cents < 14100) - { - return (fluid_real_t) 14080.0 * fluid_ct2hz_tab[(int)(cents - 12900)]; - } else { - return (fluid_real_t) 1.0; /* some loony trying to make you deaf */ + unsigned int mult, fac, rem; + unsigned int icents = (unsigned int)cents; + icents += 300u; + + // don't use stdlib div() here, it turned out have poor performance + fac = icents / 1200u; + rem = icents % 1200u; + + // Think of "mult" as the factor that we multiply (440/2^6)Hz with, + // or in other words mult is the "first factor" of the above + // functions comment. + // + // Assuming sizeof(uint)==4 this will give us a maximum range of + // 32 * 1200cents - 300cents == 38100 cents == 29,527,900,160 Hz + // which is much more than ever needed. For bigger values, just + // safely wrap around (the & is just a replacement for the quick + // modulo operation % 32). + mult = 1u << (fac & (sizeof(mult)*8u - 1u)); + + // don't use ldexp() either (poor performance) + return mult * fluid_ct2hz_tab[rem]; } } diff --git a/libs/fluidsynth/src/fluid_gen.c b/libs/fluidsynth/src/fluid_gen.c index cf3cc48b46..6472dd3258 100644 --- a/libs/fluidsynth/src/fluid_gen.c +++ b/libs/fluidsynth/src/fluid_gen.c @@ -26,7 +26,7 @@ /* See SFSpec21 $8.1.3 */ static const fluid_gen_info_t fluid_gen_info[] = { - /* number/name init scale min max def */ + /* number/name init nrpn-scale min max def */ { GEN_STARTADDROFS, 1, 1, 0.0f, 1e10f, 0.0f }, { GEN_ENDADDROFS, 1, 1, -1e10f, 0.0f, 0.0f }, { GEN_STARTLOOPADDROFS, 1, 1, -1e10f, 1e10f, 0.0f }, diff --git a/libs/fluidsynth/src/fluid_gen.h b/libs/fluidsynth/src/fluid_gen.h index 76d168c25f..3f8f14b1c5 100644 --- a/libs/fluidsynth/src/fluid_gen.h +++ b/libs/fluidsynth/src/fluid_gen.h @@ -27,7 +27,7 @@ typedef struct _fluid_gen_info_t { char num; /* Generator number */ - char init; /* Does the generator need to be initialized (cfr. fluid_voice_init()) */ + char init; /* Does the generator need to be initialized (not used) */ char nrpn_scale; /* The scale to convert from NRPN (cfr. fluid_gen_map_nrpn()) */ float min; /* The minimum value */ float max; /* The maximum value */ diff --git a/libs/fluidsynth/src/fluid_midi.c b/libs/fluidsynth/src/fluid_midi.c index c92fb2fefa..844de01c8f 100644 --- a/libs/fluidsynth/src/fluid_midi.c +++ b/libs/fluidsynth/src/fluid_midi.c @@ -180,7 +180,7 @@ fluid_file_read_full(fluid_file fp, size_t *length) return NULL; } - FLUID_LOG(FLUID_DBG, "File load: Allocating %d bytes", buflen); + FLUID_LOG(FLUID_DBG, "File load: Allocating %lu bytes", buflen); buffer = FLUID_MALLOC(buflen); if(buffer == NULL) @@ -193,7 +193,7 @@ fluid_file_read_full(fluid_file fp, size_t *length) if(n != buflen) { - FLUID_LOG(FLUID_ERR, "Only read %d bytes; expected %d", n, + FLUID_LOG(FLUID_ERR, "Only read %lu bytes; expected %lu", n, buflen); FLUID_FREE(buffer); return NULL; diff --git a/libs/fluidsynth/src/fluid_rev.c b/libs/fluidsynth/src/fluid_rev.c index 014562e807..f3ee0e47f9 100644 --- a/libs/fluidsynth/src/fluid_rev.c +++ b/libs/fluidsynth/src/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 @@ -298,16 +305,6 @@ a flatter response on comb filter. So the input gain is set to 0.1 rather 1.0. * #define DELAY_L11 1187 #endif -/* Delay lines length table (in samples) */ -static const int delay_length[NBR_DELAYS] = -{ - DELAY_L0, DELAY_L1, DELAY_L2, DELAY_L3, - DELAY_L4, DELAY_L5, DELAY_L6, DELAY_L7, -#if (NBR_DELAYS == 12) - DELAY_L8, DELAY_L9, DELAY_L10, DELAY_L11 -#endif -}; - /*---------------------------------------------------------------------------*/ /* The FDN late feed back matrix: A @@ -613,6 +610,16 @@ static int set_mod_delay_line(mod_delay_line *mdl, } /*----------------------------------------------------------------------------- + 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. @return the sample value. @@ -738,6 +745,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 +764,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 +777,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 +820,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 +845,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 +868,23 @@ static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) 11|-1 -1| */ - late->out_left_gain[i] = wet1; - - /* Sets Left coefficients first */ - if(i % 2) /* Left is 1,-1, 1,-1, 1,-1,.... */ + /* 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) { - late->out_left_gain[i] *= -1; + wet = -wet1; } + late->out_left_gain[i] = wet; - /* 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 */ + /* 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) { - /* 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 +905,78 @@ 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) { + /* Delay lines length table (in samples) */ + static const int delay_length[NBR_DELAYS] = + { + DELAY_L0, DELAY_L1, DELAY_L2, DELAY_L3, + DELAY_L4, DELAY_L5, DELAY_L6, DELAY_L7, + #if (NBR_DELAYS == 12) + DELAY_L8, DELAY_L9, DELAY_L10, DELAY_L11 + #endif + }; + int result; /* return value */ int i; - FLUID_MEMSET(late, 0, sizeof(fluid_late)); - - late->samplerate = sample_rate; - - /*-------------------------------------------------------------------------- - First initialize the modulated delay lines + /* + 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. + + 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. + + 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. */ + fluid_real_t length_factor = 2.0f; + fluid_real_t mod_depth = MOD_DEPTH; + if(sample_rate > 44100.0f) + { + 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]; + } - for(i = 0; i < NBR_DELAYS; 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 */ { - /* sets local delay lines's parameters */ + /* 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/libs/fluidsynth/src/fluid_rev.h b/libs/fluidsynth/src/fluid_rev.h index 75ab5d23c4..b24fd5fd78 100644 --- a/libs/fluidsynth/src/fluid_rev.h +++ b/libs/fluidsynth/src/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 */ diff --git a/libs/fluidsynth/src/fluid_rvoice.c b/libs/fluidsynth/src/fluid_rvoice.c index 76e5579257..8837e4415d 100644 --- a/libs/fluidsynth/src/fluid_rvoice.c +++ b/libs/fluidsynth/src/fluid_rvoice.c @@ -26,7 +26,7 @@ static void fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks); /** - * @return -1 if voice has finished, 0 if it's currently quiet, 1 otherwise + * @return -1 if voice is quiet, 0 if voice has finished, 1 otherwise */ static FLUID_INLINE int fluid_rvoice_calc_amp(fluid_rvoice_t *voice) @@ -357,7 +357,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) if(count <= 0) { - return count; + return count; /* return -1 if voice is quiet, 0 if voice has finished */ } /******************* phase **********************/ diff --git a/libs/fluidsynth/src/fluid_rvoice_mixer.c b/libs/fluidsynth/src/fluid_rvoice_mixer.c index 5ea08ff6fa..d7fd2f541f 100644 --- a/libs/fluidsynth/src/fluid_rvoice_mixer.c +++ b/libs/fluidsynth/src/fluid_rvoice_mixer.c @@ -359,14 +359,14 @@ get_dest_buf(fluid_rvoice_buffers_t *buffers, int index, } /** - * Mix data down to buffers + * Mix samples down from internal dsp_buf to output buffers * * @param buffers Destination buffer(s) * @param dsp_buf Mono sample source - * @param start_block Block to start mixing at + * @param start_block starting sample in dsp_buf * @param sample_count number of samples to mix following \c start_block * @param dest_bufs Array of buffers to mixdown to - * @param dest_bufcount Length of dest_bufs + * @param dest_bufcount Length of dest_bufs (i.e count of buffers) */ static void fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, @@ -374,9 +374,11 @@ fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, int start_block, int sample_count, fluid_real_t **dest_bufs, int dest_bufcount) { + /* buffers count to mixdown to */ int bufcount = buffers->count; int i, dsp_i; + /* if there is nothing to mix, return immediatly */ if(sample_count <= 0 || dest_bufcount <= 0) { return; @@ -385,6 +387,7 @@ fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, FLUID_ASSERT((uintptr_t)dsp_buf % FLUID_DEFAULT_ALIGNMENT == 0); FLUID_ASSERT((uintptr_t)(&dsp_buf[start_block * FLUID_BUFSIZE]) % FLUID_DEFAULT_ALIGNMENT == 0); + /* mixdown for each buffer */ for(i = 0; i < bufcount; i++) { fluid_real_t *FLUID_RESTRICT buf = get_dest_buf(buffers, i, dest_bufs, dest_bufcount); @@ -397,11 +400,17 @@ fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, FLUID_ASSERT((uintptr_t)buf % FLUID_DEFAULT_ALIGNMENT == 0); + /* mixdown sample_count samples in the current buffer buf + Note, that this loop could be unrolled by FLUID_BUFSIZE elements */ #pragma omp simd aligned(dsp_buf,buf:FLUID_DEFAULT_ALIGNMENT) - - for(dsp_i = (start_block * FLUID_BUFSIZE); dsp_i < sample_count; dsp_i++) + for(dsp_i = 0; dsp_i < sample_count; dsp_i++) { - buf[dsp_i] += amp * dsp_buf[dsp_i]; + // Index by blocks (not by samples) to let the compiler know that we always start accessing + // buf and dsp_buf at the FLUID_BUFSIZE*sizeof(fluid_real_t) byte boundary and never somewhere + // in between. + // A good compiler should understand: Aha, so I don't need to add a peel loop when vectorizing + // this loop. Great. + buf[start_block * FLUID_BUFSIZE + dsp_i] += amp * dsp_buf[start_block * FLUID_BUFSIZE + dsp_i]; } } } @@ -416,30 +425,42 @@ fluid_mixer_buffers_render_one(fluid_mixer_buffers_t *buffers, fluid_rvoice_t *rvoice, fluid_real_t **dest_bufs, unsigned int dest_bufcount, fluid_real_t *src_buf, int blockcount) { - int i, total_samples = 0, start_block = 0; + int i, total_samples = 0, last_block_mixed = 0; for(i = 0; i < blockcount; i++) { + /* render one block in src_buf */ int s = fluid_rvoice_write(rvoice, &src_buf[FLUID_BUFSIZE * i]); - if(s == -1) { - start_block += s; - s = FLUID_BUFSIZE; - } + /* the voice is silent, mix back all the previously rendered sound */ + fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, + total_samples - (last_block_mixed*FLUID_BUFSIZE), + dest_bufs, dest_bufcount); - total_samples += s; - - if(s < FLUID_BUFSIZE) + last_block_mixed = i+1; /* future block start index to mix from */ + total_samples += FLUID_BUFSIZE; /* accumulate samples count rendered */ + } + else { - break; + /* the voice wasn't quiet. Some samples have been rendered [0..FLUID_BUFSIZE] */ + total_samples += s; + if(s < FLUID_BUFSIZE) + { + /* voice has finished */ + break; + } } } - fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, -start_block, total_samples - ((-start_block)*FLUID_BUFSIZE), dest_bufs, dest_bufcount); + /* Now mix the remaining blocks from last_block_mixed to total_sample */ + fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, + total_samples - (last_block_mixed*FLUID_BUFSIZE), + dest_bufs, dest_bufcount); if(total_samples < blockcount * FLUID_BUFSIZE) { + /* voice has finished */ fluid_finish_rvoice(buffers, rvoice); } } @@ -601,7 +622,7 @@ fluid_mixer_buffers_zero(fluid_mixer_buffers_t *buffers, int current_blockcount) static int fluid_mixer_buffers_init(fluid_mixer_buffers_t *buffers, fluid_rvoice_mixer_t *mixer) { - const int samplecount = FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT; + static const int samplecount = FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT; buffers->mixer = mixer; buffers->buf_count = mixer->buffers.buf_count; diff --git a/libs/fluidsynth/src/fluid_sffile.c b/libs/fluidsynth/src/fluid_sffile.c index 5fa34297aa..131b56b0c2 100644 --- a/libs/fluidsynth/src/fluid_sffile.c +++ b/libs/fluidsynth/src/fluid_sffile.c @@ -30,6 +30,10 @@ #include <sndfile.h> #endif +#if LIBINSTPATCH_SUPPORT +#include <libinstpatch/libinstpatch.h> +#endif + /*=================================sfload.c======================== Borrowed from Smurf SoundFont Editor by Josh Green =================================================================*/ @@ -326,11 +330,13 @@ static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int en /** * Check if a file is a SoundFont file. + * + * If fluidsynth was built with DLS support, this function will also identify DLS files. * @param filename Path to the file to check - * @return TRUE if it could be a SoundFont, FALSE otherwise + * @return TRUE if it could be a SF2, SF3 or DLS file, FALSE otherwise * - * @note The current implementation only checks for the "RIFF" and "sfbk" headers in - * the file. It is useful to distinguish between SoundFont and other (e.g. MIDI) files. + * @note This function only checks whether header(s) in the RIFF chunk are present. + * A call to fluid_synth_sfload() might still fail. */ int fluid_is_soundfont(const char *filename) { @@ -344,7 +350,7 @@ int fluid_is_soundfont(const char *filename) { return retcode; } - + if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) { break; @@ -366,6 +372,21 @@ int fluid_is_soundfont(const char *filename) } retcode = (fcc == SFBK_FCC); + if(retcode) + { + break; // seems to be SF2, stop here + } +#ifdef LIBINSTPATCH_SUPPORT + else + { + IpatchFileHandle *fhandle = ipatch_file_identify_open(filename, NULL); + if(fhandle != NULL) + { + retcode = (ipatch_file_identify(fhandle->file, NULL) == IPATCH_TYPE_DLS_FILE); + ipatch_file_close(fhandle); + } + } +#endif } while(0); @@ -775,7 +796,7 @@ static int process_info(SFData *sf, int size) if((chunk.id != ICMT_FCC && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2)) { FLUID_LOG(FLUID_ERR, "INFO sub chunk %.4s has invalid chunk size of %d bytes", - &chunk.id, chunk.size); + (char*)&chunk.id, chunk.size); return FALSE; } @@ -909,19 +930,19 @@ static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChu if(chunk->id != expid) { - FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", &expid); + FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", (char*)&expid); return FALSE; } if(chunk->size % reclen) /* valid chunk size? */ { - FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", &expid, reclen); + FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", (char*)&expid, reclen); return FALSE; } if((*size -= chunk->size) < 0) { - FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", &expid); + FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", (char*)&expid); return FALSE; } @@ -2580,7 +2601,7 @@ static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigne sfdata.end = end_byte; sfdata.offset = 0; - memset(&sfinfo, 0, sizeof(sfinfo)); + FLUID_MEMSET(&sfinfo, 0, sizeof(sfinfo)); /* Seek to beginning of Ogg Vorbis data in Soundfont */ if(sf->fcbs->fseek(sf->sffd, sf->samplepos + start_byte, SEEK_SET) == FLUID_FAILED) @@ -2594,12 +2615,12 @@ static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigne if(!sndfile) { - FLUID_LOG(FLUID_ERR, sf_strerror(sndfile)); + FLUID_LOG(FLUID_ERR, "%s", sf_strerror(sndfile)); return -1; } // Empty sample - if(!sfinfo.frames || !sfinfo.channels) + if(sfinfo.frames <= 0 || sfinfo.channels <= 0) { FLUID_LOG(FLUID_DBG, "Empty decompressed sample"); *data = NULL; @@ -2607,7 +2628,12 @@ static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigne return 0; } - /* FIXME: ensure that the decompressed WAV data is 16-bit mono? */ + // Mono sample + if(sfinfo.channels != 1) + { + FLUID_LOG(FLUID_DBG, "Unsupported channel count %d in ogg sample", sfinfo.channels); + goto error_exit; + } wav_data = FLUID_ARRAY(short, sfinfo.frames * sfinfo.channels); @@ -2617,11 +2643,11 @@ static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigne goto error_exit; } - /* Automatically decompresses the Ogg Vorbis data to 16-bit WAV */ + /* Automatically decompresses the Ogg Vorbis data to 16-bit PCM */ if(sf_readf_short(sndfile, wav_data, sfinfo.frames) < sfinfo.frames) { FLUID_LOG(FLUID_DBG, "Decompression failed!"); - FLUID_LOG(FLUID_ERR, sf_strerror(sndfile)); + FLUID_LOG(FLUID_ERR, "%s", sf_strerror(sndfile)); goto error_exit; } diff --git a/libs/fluidsynth/src/fluid_sfont.c b/libs/fluidsynth/src/fluid_sfont.c index 4780c7833f..35c0f36356 100644 --- a/libs/fluidsynth/src/fluid_sfont.c +++ b/libs/fluidsynth/src/fluid_sfont.c @@ -609,7 +609,7 @@ fluid_sample_set_sound_data(fluid_sample_t *sample, goto error_rec; } - FLUID_MEMSET(sample->data, 0, storedNbFrames); + FLUID_MEMSET(sample->data, 0, storedNbFrames * sizeof(short)); FLUID_MEMCPY(sample->data + SAMPLE_LOOP_MARGIN, data, nbframes * sizeof(short)); if(data24 != NULL) @@ -628,7 +628,7 @@ fluid_sample_set_sound_data(fluid_sample_t *sample, /* pointers */ /* all from the start of data */ sample->start = SAMPLE_LOOP_MARGIN; - sample->end = SAMPLE_LOOP_MARGIN + storedNbFrames - 1; + sample->end = SAMPLE_LOOP_MARGIN + nbframes - 1; } else { diff --git a/libs/fluidsynth/src/fluid_synth.c b/libs/fluidsynth/src/fluid_synth.c index 570a2535cd..382979f7f5 100644 --- a/libs/fluidsynth/src/fluid_synth.c +++ b/libs/fluidsynth/src/fluid_synth.c @@ -117,7 +117,6 @@ static int fluid_synth_set_important_channels(fluid_synth_t *synth, const char * /* Callback handlers for real-time settings */ -static void fluid_synth_handle_sample_rate(void *data, const char *name, double value); static void fluid_synth_handle_gain(void *data, const char *name, double value); static void fluid_synth_handle_polyphony(void *data, const char *name, int value); static void fluid_synth_handle_device_id(void *data, const char *name, int value); @@ -197,7 +196,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); @@ -431,6 +430,10 @@ fluid_synth_init(void) /* SF2.01 page 57 section 8.4.10 MIDI Pitch Wheel to Initial Pitch ... */ + /* Initial Pitch is not a "standard" generator, because it isn't mentioned in the + list of generators in the SF2 specifications. That's why destination Initial Pitch + is replaced here by fine tune generator. + */ fluid_mod_set_source1(&default_pitch_bend_mod, FLUID_MOD_PITCHWHEEL, /* Index=14 */ FLUID_MOD_GC /* CC =0 */ | FLUID_MOD_LINEAR /* type=0 */ @@ -443,7 +446,8 @@ fluid_synth_init(void) | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_POSITIVE /* D=0 */ ); - fluid_mod_set_dest(&default_pitch_bend_mod, GEN_PITCH); /* Destination: Initial pitch */ + /* Also see the comment in gen.h about GEN_PITCH */ + fluid_mod_set_dest(&default_pitch_bend_mod, GEN_FINETUNE); /* Destination: Fine Tune */ fluid_mod_set_amount(&default_pitch_bend_mod, 12700.0); /* Amount: 12700 cents */ @@ -589,8 +593,10 @@ static FLUID_INLINE unsigned int fluid_synth_get_min_note_length_LOCAL(fluid_syn * @param settings Configuration parameters to use (used directly). * @return New FluidSynth instance or NULL on error * - * @note The settings parameter is used directly and should not be modified - * or freed independently. + * @note The @p settings parameter is used directly and should freed after + * the synth has been deleted. Further note that you may modify FluidSettings of the + * @p settings instance. However, only those FluidSettings marked as 'realtime' will + * affect the synth immediately. */ fluid_synth_t * new_fluid_synth(fluid_settings_t *settings) @@ -647,8 +653,6 @@ new_fluid_synth(fluid_settings_t *settings) fluid_settings_getnum_float(settings, "synth.overflow.important", &synth->overflow.important); /* register the callbacks */ - fluid_settings_callback_num(settings, "synth.sample-rate", - fluid_synth_handle_sample_rate, synth); fluid_settings_callback_num(settings, "synth.gain", fluid_synth_handle_gain, synth); fluid_settings_callback_int(settings, "synth.polyphony", @@ -1126,6 +1130,10 @@ fluid_synth_error(fluid_synth_t *synth) /** * Send a note-on event to a FluidSynth object. + * + * This function will take care of proper legato playing. If a note on channel @p chan is + * already playing at the given key @p key, it will be released (even if it is sustained). + * In other words, overlapping notes are not allowed. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param key MIDI note number (0-127) @@ -3046,20 +3054,31 @@ fluid_synth_update_presets(fluid_synth_t *synth) } } -/* Handler for synth.sample-rate setting. */ -static void -fluid_synth_handle_sample_rate(void *data, const char *name, double value) -{ - fluid_synth_t *synth = (fluid_synth_t *)data; - fluid_synth_set_sample_rate(synth, (float) value); -} - - /** - * Set sample rate of the synth. - * @note This function should only be used when no voices or notes are active. + * Set up an event to change the sample-rate of the synth during the next rendering call. + * @warning This function is broken-by-design! Don't use it! Instead, specify the sample-rate when creating the synth. + * @deprecated As of fluidsynth 2.1.0 this function has been deprecated. + * Changing the sample-rate is generally not considered to be a real-time use-case, as it always produces some audible artifact ("click", "pop") on the dry sound and effects (because LFOs for chorus and reverb need to be reinitialized). + * The sample-rate change may also require memory allocation deep down in the effect units. + * However, this memory allocation may fail and there is no way for the caller to know that, because the actual change of the sample-rate is executed during rendering. + * This function cannot (must not) do the sample-rate change itself, otherwise the synth needs to be locked down, causing rendering to block. + * Esp. do not use this function if this @p synth instance is used by an audio driver, because the audio driver cannot be notified by this sample-rate change. + * Long story short: don't use it. + * @code{.cpp} + fluid_synth_t* synth; // assume initialized + // [...] + // sample-rate change needed? Delete the audio driver, if any. + delete_fluid_audio_driver(adriver); + // then delete the synth + delete_fluid_synth(synth); + // update the sample-rate + fluid_settings_setnum(settings, "synth.sample-rate", 22050.0); + // and re-create objects + synth = new_fluid_synth(settings); + adriver = new_fluid_audio_driver(settings, synth); + * @endcode * @param synth FluidSynth instance - * @param sample_rate New sample rate (Hz) + * @param sample_rate New sample-rate (Hz) * @since 1.1.2 */ void @@ -3382,6 +3401,8 @@ fluid_synth_nwrite_float(fluid_synth_t *synth, int len, fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(left != NULL, FLUID_FAILED); fluid_return_val_if_fail(right != NULL, FLUID_FAILED); + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below /* First, take what's still available in the buffer */ count = 0; @@ -3666,6 +3687,8 @@ fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[], fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(nfx % 2 == 0, FLUID_FAILED); fluid_return_val_if_fail(nout % 2 == 0, FLUID_FAILED); + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below nfxchan = synth->effects_channels; nfxunits = synth->effects_groups; @@ -3798,9 +3821,19 @@ fluid_synth_write_float(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr) { - int i, j, k, l; - float *left_out = (float *) lout; - float *right_out = (float *) rout; + return fluid_synth_write_float_LOCAL(synth, len, lout, loff, lincr, rout, roff, rincr, fluid_synth_render_blocks); +} + +int +fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr, + int (*block_render_func)(fluid_synth_t *, int) + ) +{ + int n, cur, size; + float *left_out = (float *) lout + loff; + float *right_out = (float *) rout + roff; fluid_real_t *left_in; fluid_real_t *right_in; double time = fluid_utime(); @@ -3811,28 +3844,60 @@ fluid_synth_write_float(fluid_synth_t *synth, int len, fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(lout != NULL, FLUID_FAILED); fluid_return_val_if_fail(rout != NULL, FLUID_FAILED); + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 1); - l = synth->cur; fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); - for(i = 0, j = loff, k = roff; i < len; i++, l++, j += lincr, k += rincr) + size = len; + cur = synth->cur; + + do { /* fill up the buffers as needed */ - if(l >= synth->curmax) + if(cur >= synth->curmax) { - int blocksleft = (len - i + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; - synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft); + int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; + synth->curmax = FLUID_BUFSIZE * block_render_func(synth, blocksleft); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + cur = 0; + } + + /* calculate amount of available samples */ + n = synth->curmax - cur; - l = 0; + /* keep track of emitted samples */ + if(n > size) + { + n = size; } - left_out[j] = (float) left_in[0 * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + l]; - right_out[k] = (float) right_in[0 * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + l]; + size -= n; + + /* update pointers to current position */ + left_in += cur + n; + right_in += cur + n; + + /* set final cursor position */ + cur += n; + + /* reverse index */ + n = 0 - n; + + do + { + *left_out = (float) left_in[n]; + *right_out = (float) right_in[n]; + + left_out += lincr; + right_out += rincr; + } + while(++n < 0); } + while(size); - synth->cur = l; + synth->cur = cur; time = fluid_utime() - time; cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); @@ -3924,43 +3989,77 @@ fluid_synth_write_s16(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr) { - int i, j, k, cur; - int16_t *left_out = lout; - int16_t *right_out = rout; + int di, n, cur, size; + int16_t *left_out = (int16_t *)lout + loff; + int16_t *right_out = (int16_t *)rout + roff; fluid_real_t *left_in; fluid_real_t *right_in; double time = fluid_utime(); - int di; float cpu_load; fluid_profile_ref_var(prof_ref); + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(lout != NULL, FLUID_FAILED); + fluid_return_val_if_fail(rout != NULL, FLUID_FAILED); + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 1); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + size = len; cur = synth->cur; di = synth->dither_index; - for(i = 0, j = loff, k = roff; i < len; i++, cur++, j += lincr, k += rincr) + do { - /* fill up the buffers as needed */ if(cur >= synth->curmax) { - int blocksleft = (len - i + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; + int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); cur = 0; } - left_out[j] = round_clip_to_i16(left_in[0 * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + cur] * 32766.0f + rand_table[0][di]); - right_out[k] = round_clip_to_i16(right_in[0 * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + cur] * 32766.0f + rand_table[1][di]); + /* calculate amount of available samples */ + n = synth->curmax - cur; - if(++di >= DITHER_SIZE) + /* keep track of emitted samples */ + if(n > size) { - di = 0; + n = size; } + + size -= n; + + /* update pointers to current position */ + left_in += cur + n; + right_in += cur + n; + + /* set final cursor position */ + cur += n; + + /* reverse index */ + n = 0 - n; + + do + { + *left_out = round_clip_to_i16(left_in[n] * 32766.0f + rand_table[0][di]); + *right_out = round_clip_to_i16(right_in[n] * 32766.0f + rand_table[1][di]); + + left_out += lincr; + right_out += rincr; + + if(++di >= DITHER_SIZE) + { + di = 0; + } + } + while(++n < 0); } + while(size); synth->cur = cur; synth->dither_index = di; /* keep dither buffer continous */ @@ -5185,9 +5284,9 @@ 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 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 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) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ @@ -5243,19 +5342,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) @@ -5312,7 +5398,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) @@ -5328,7 +5414,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) @@ -5344,7 +5430,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) diff --git a/libs/fluidsynth/src/fluid_synth.h b/libs/fluidsynth/src/fluid_synth.h index fe222f7b6f..955b3fa12e 100644 --- a/libs/fluidsynth/src/fluid_synth.h +++ b/libs/fluidsynth/src/fluid_synth.h @@ -216,6 +216,11 @@ int fluid_synth_set_gen2(fluid_synth_t *synth, int chan, int fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int)); +int +fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr, + int (*block_render_func)(fluid_synth_t *, int)); /* * misc */ diff --git a/libs/fluidsynth/src/fluid_sys.c b/libs/fluidsynth/src/fluid_sys.c index f6c56d3995..c6300b90c2 100644 --- a/libs/fluidsynth/src/fluid_sys.c +++ b/libs/fluidsynth/src/fluid_sys.c @@ -192,6 +192,29 @@ fluid_log(int level, const char *fmt, ...) return FLUID_FAILED; } +void* fluid_alloc(size_t len) +{ + void* ptr = malloc(len); + +#if defined(DEBUG) && !defined(_MSC_VER) + // garbage initialize allocated memory for debug builds to ease reproducing + // bugs like 44453ff23281b3318abbe432fda90888c373022b . + // + // MSVC++ already garbage initializes allocated memory by itself (debug-heap). + // + // 0xCC because + // * it makes pointers reliably crash when dereferencing them, + // * floating points are still some valid but insanely huge negative number, and + // * if for whatever reason this allocated memory is executed, it'll trigger + // INT3 (...at least on x86) + if(ptr != NULL) + { + memset(ptr, 0xCC, len); + } +#endif + return ptr; +} + /** * Convenience wrapper for free() that satisfies at least C90 requirements. * Especially useful when using fluidsynth with programming languages that do not provide malloc() and free(). @@ -291,22 +314,21 @@ void fluid_msleep(unsigned int msecs) /** * Get time in milliseconds to be used in relative timing operations. - * @return Unix time in milliseconds. + * @return Monotonic time in milliseconds. */ unsigned int fluid_curtime(void) { - static glong initial_seconds = 0; - GTimeVal timeval; + float now; + static float initial_time = 0; - if(initial_seconds == 0) + if(initial_time == 0) { - g_get_current_time(&timeval); - initial_seconds = timeval.tv_sec; + initial_time = (float)fluid_utime(); } - g_get_current_time(&timeval); + now = (float)fluid_utime(); - return (unsigned int)((timeval.tv_sec - initial_seconds) * 1000.0 + timeval.tv_usec / 1000.0); + return (unsigned int)((now - initial_time) / 1000.0f); } /** @@ -1460,7 +1482,7 @@ static fluid_thread_return_t fluid_server_socket_run(void *data) { if(server_socket->cont) { - FLUID_LOG(FLUID_ERR, "Failed to accept connection: %ld", fluid_socket_get_error()); + FLUID_LOG(FLUID_ERR, "Failed to accept connection: %d", fluid_socket_get_error()); } server_socket->cont = 0; @@ -1519,7 +1541,7 @@ new_fluid_server_socket(int port, fluid_server_func_t func, void *data) if(sock == INVALID_SOCKET) { - FLUID_LOG(FLUID_ERR, "Failed to create server socket: %ld", fluid_socket_get_error()); + FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error()); fluid_socket_cleanup(); return NULL; } @@ -1534,7 +1556,7 @@ new_fluid_server_socket(int port, fluid_server_func_t func, void *data) if(sock == INVALID_SOCKET) { - FLUID_LOG(FLUID_ERR, "Failed to create server socket: %ld", fluid_socket_get_error()); + FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error()); fluid_socket_cleanup(); return NULL; } @@ -1547,7 +1569,7 @@ new_fluid_server_socket(int port, fluid_server_func_t func, void *data) if(bind(sock, (const struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) { - FLUID_LOG(FLUID_ERR, "Failed to bind server socket: %ld", fluid_socket_get_error()); + FLUID_LOG(FLUID_ERR, "Failed to bind server socket: %d", fluid_socket_get_error()); fluid_socket_close(sock); fluid_socket_cleanup(); return NULL; @@ -1555,7 +1577,7 @@ new_fluid_server_socket(int port, fluid_server_func_t func, void *data) if(listen(sock, SOMAXCONN) == SOCKET_ERROR) { - FLUID_LOG(FLUID_ERR, "Failed to listen on server socket: %ld", fluid_socket_get_error()); + FLUID_LOG(FLUID_ERR, "Failed to listen on server socket: %d", fluid_socket_get_error()); fluid_socket_close(sock); fluid_socket_cleanup(); return NULL; diff --git a/libs/fluidsynth/src/fluid_sys.h b/libs/fluidsynth/src/fluid_sys.h index 3bc0c5a40f..b747e5676a 100644 --- a/libs/fluidsynth/src/fluid_sys.h +++ b/libs/fluidsynth/src/fluid_sys.h @@ -124,6 +124,8 @@ typedef gint32 int32_t; typedef guint32 uint32_t; typedef gint64 int64_t; typedef guint64 uint64_t; +typedef guintptr uintptr_t; +typedef gintptr intptr_t; #endif @@ -168,10 +170,14 @@ typedef guint64 uint64_t; #define FLUID_INLINE inline -#define FLUID_POINTER_TO_UINT GPOINTER_TO_UINT -#define FLUID_UINT_TO_POINTER GUINT_TO_POINTER -#define FLUID_POINTER_TO_INT GPOINTER_TO_INT -#define FLUID_INT_TO_POINTER GINT_TO_POINTER + +/* Integer<->pointer conversion */ +#define FLUID_POINTER_TO_UINT(x) ((unsigned int)(uintptr_t)(x)) +#define FLUID_UINT_TO_POINTER(x) ((void *)(uintptr_t)(x)) +#define FLUID_POINTER_TO_INT(x) ((signed int)(intptr_t)(x)) +#define FLUID_INT_TO_POINTER(x) ((void *)(intptr_t)(x)) + +/* Endian detection */ #define FLUID_IS_BIG_ENDIAN (G_BYTE_ORDER == G_BIG_ENDIAN) #define FLUID_LE32TOH(x) GINT32_FROM_LE(x) @@ -198,6 +204,11 @@ char *fluid_strtok(char **str, const char *delim); typedef int socklen_t; #endif +/** + Time functions + + */ + unsigned int fluid_curtime(void); double fluid_utime(void); diff --git a/libs/fluidsynth/src/fluid_voice.c b/libs/fluidsynth/src/fluid_voice.c index 85a2d43e69..d961ae4a7c 100644 --- a/libs/fluidsynth/src/fluid_voice.c +++ b/libs/fluidsynth/src/fluid_voice.c @@ -825,7 +825,7 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) voice->root_pitch = voice->sample->origpitch * 100.0f - voice->sample->pitchadj; } - x = (fluid_ct2hz(voice->root_pitch) * ((fluid_real_t) voice->output_rate / voice->sample->samplerate)); + x = (fluid_ct2hz_real(voice->root_pitch) * ((fluid_real_t) voice->output_rate / voice->sample->samplerate)); } else { @@ -838,7 +838,7 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) voice->root_pitch = 0; } - x = fluid_ct2hz(voice->root_pitch); + x = fluid_ct2hz_real(voice->root_pitch); } /* voice->pitch depends on voice->root_pitch, so calculate voice->pitch now */ diff --git a/libs/fluidsynth/src/fluidsynth_priv.h b/libs/fluidsynth/src/fluidsynth_priv.h index 1567a10a8c..71569bd045 100644 --- a/libs/fluidsynth/src/fluidsynth_priv.h +++ b/libs/fluidsynth/src/fluidsynth_priv.h @@ -181,12 +181,14 @@ typedef void (*fluid_rvoice_function_t)(void *obj, const fluid_rvoice_param_t pa #endif /* Memory allocation */ -#define FLUID_MALLOC(_n) malloc(_n) +#define FLUID_MALLOC(_n) fluid_alloc(_n) #define FLUID_REALLOC(_p,_n) realloc(_p,_n) -#define FLUID_NEW(_t) (_t*)malloc(sizeof(_t)) -#define FLUID_ARRAY_ALIGNED(_t,_n,_a) (_t*)malloc((_n)*sizeof(_t) + ((unsigned int)_a - 1u)) -#define FLUID_ARRAY(_t,_n) FLUID_ARRAY_ALIGNED(_t,_n,1u) #define FLUID_FREE(_p) fluid_free(_p) +#define FLUID_NEW(_t) (_t*)FLUID_MALLOC(sizeof(_t)) +#define FLUID_ARRAY_ALIGNED(_t,_n,_a) (_t*)FLUID_MALLOC((_n)*sizeof(_t) + ((unsigned int)_a - 1u)) +#define FLUID_ARRAY(_t,_n) FLUID_ARRAY_ALIGNED(_t,_n,1u) + +void* fluid_alloc(size_t len); /* File access */ #define FLUID_FOPEN(_f,_m) fopen(_f,_m) @@ -273,7 +275,7 @@ do { strncpy(_dst,_src,_n); \ #define FLUID_LOG fluid_log #endif -#ifdef DEBUG +#if defined(DEBUG) && !defined(NDEBUG) #define FLUID_ASSERT(a) g_assert(a) #else #define FLUID_ASSERT(a) diff --git a/tools/fluid-patches/ardour_fluidsynth.diff b/tools/fluid-patches/ardour_fluidsynth.diff index 68251eb522..8aaf597687 100644 --- a/tools/fluid-patches/ardour_fluidsynth.diff +++ b/tools/fluid-patches/ardour_fluidsynth.diff @@ -1,7 +1,16 @@ diff --git b/libs/fluidsynth/fluidsynth/synth.h a/libs/fluidsynth/fluidsynth/synth.h -index 369a2c2615..87826809fd 100644 +index f4802ee5b9..3003972542 100644 --- b/libs/fluidsynth/fluidsynth/synth.h +++ a/libs/fluidsynth/fluidsynth/synth.h +@@ -176,7 +176,7 @@ FLUIDSYNTH_API int fluid_synth_count_effects_groups(fluid_synth_t *synth); + + /* Synthesis parameters */ + +-FLUID_DEPRECATED FLUIDSYNTH_API void fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate); ++FLUIDSYNTH_API void fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate); + FLUIDSYNTH_API void fluid_synth_set_gain(fluid_synth_t *synth, float gain); + FLUIDSYNTH_API float fluid_synth_get_gain(fluid_synth_t *synth); + FLUIDSYNTH_API int fluid_synth_set_polyphony(fluid_synth_t *synth, int polyphony); @@ -233,7 +233,7 @@ FLUIDSYNTH_API int fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int p /* Misc */ @@ -78,7 +87,7 @@ index 946a873bbf..79f83a4583 100644 /* * fluid_hashtable_foreach_remove_or_steal: diff --git b/libs/fluidsynth/src/fluid_midi.c a/libs/fluidsynth/src/fluid_midi.c -index cd0d23ced3..c92fb2fefa 100644 +index ea1aff5202..844de01c8f 100644 --- b/libs/fluidsynth/src/fluid_midi.c +++ a/libs/fluidsynth/src/fluid_midi.c @@ -77,7 +77,7 @@ static int fluid_midi_file_read_tracklen(fluid_midi_file *mf); @@ -139,7 +148,7 @@ index 3e7661741f..ec8e967a35 100644 #ifdef DEBUG void fluid_dump_modulator(fluid_mod_t *mod); diff --git b/libs/fluidsynth/src/fluid_rvoice_mixer.c a/libs/fluidsynth/src/fluid_rvoice_mixer.c -index 85c3fb9142..5ea08ff6fa 100644 +index 257f0fbdec..d7fd2f541f 100644 --- b/libs/fluidsynth/src/fluid_rvoice_mixer.c +++ a/libs/fluidsynth/src/fluid_rvoice_mixer.c @@ -23,7 +23,6 @@ @@ -191,7 +200,7 @@ index 78532ad2a3..a825603a44 100644 static int diff --git b/libs/fluidsynth/src/fluid_synth.c a/libs/fluidsynth/src/fluid_synth.c -index 3f602b88d1..570a2535cd 100644 +index e03c64089f..382979f7f5 100644 --- b/libs/fluidsynth/src/fluid_synth.c +++ a/libs/fluidsynth/src/fluid_synth.c @@ -25,7 +25,6 @@ @@ -202,7 +211,7 @@ index 3f602b88d1..570a2535cd 100644 #ifdef TRAP_ON_FPE #define _GNU_SOURCE -@@ -263,7 +262,7 @@ void fluid_version(int *major, int *minor, int *micro) +@@ -262,7 +261,7 @@ void fluid_version(int *major, int *minor, int *micro) * @return FluidSynth version string, which is internal and should not be * modified or freed. */ @@ -211,7 +220,7 @@ index 3f602b88d1..570a2535cd 100644 fluid_version_str(void) { return FLUIDSYNTH_VERSION; -@@ -5472,7 +5471,7 @@ fluid_synth_set_interp_method(fluid_synth_t *synth, int chan, int interp_method) +@@ -5558,7 +5557,7 @@ fluid_synth_set_interp_method(fluid_synth_t *synth, int chan, int interp_method) } FLUID_API_RETURN(FLUID_OK); @@ -220,7 +229,7 @@ index 3f602b88d1..570a2535cd 100644 /** * Get the total count of MIDI channels. -@@ -6410,6 +6409,7 @@ int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type) +@@ -6496,6 +6495,7 @@ int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type) FLUID_API_RETURN(FLUID_OK); } @@ -228,7 +237,7 @@ index 3f602b88d1..570a2535cd 100644 /** * Return the LADSPA effects instance used by FluidSynth * -@@ -6422,6 +6422,7 @@ fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth) +@@ -6508,6 +6508,7 @@ fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth) return synth->ladspa_fx; } @@ -237,7 +246,7 @@ index 3f602b88d1..570a2535cd 100644 /** * Configure a general-purpose IIR biquad filter. diff --git b/libs/fluidsynth/src/fluid_synth.h a/libs/fluidsynth/src/fluid_synth.h -index 0b9758bad5..fe222f7b6f 100644 +index b649bcf340..955b3fa12e 100644 --- b/libs/fluidsynth/src/fluid_synth.h +++ a/libs/fluidsynth/src/fluid_synth.h @@ -33,8 +33,6 @@ @@ -260,10 +269,10 @@ index 0b9758bad5..fe222f7b6f 100644 enum fluid_iir_filter_flags custom_filter_flags; /**< filter type of the user-defined filter currently used for all voices */ }; diff --git b/libs/fluidsynth/src/fluid_sys.c a/libs/fluidsynth/src/fluid_sys.c -index 8128fbba91..f6c56d3995 100644 +index 7454217dd2..c6300b90c2 100644 --- b/libs/fluidsynth/src/fluid_sys.c +++ a/libs/fluidsynth/src/fluid_sys.c -@@ -216,9 +216,10 @@ void fluid_free(void* ptr) +@@ -239,9 +239,10 @@ void fluid_free(void* ptr) * @param delim String of delimiter chars. * @return Pointer to the next token or NULL if no more tokens. */ @@ -277,10 +286,10 @@ index 8128fbba91..f6c56d3995 100644 if(str == NULL || delim == NULL || !*delim) diff --git b/libs/fluidsynth/src/fluid_sys.h a/libs/fluidsynth/src/fluid_sys.h -index 6490359027..3bc0c5a40f 100644 +index 24df6edb5b..b747e5676a 100644 --- b/libs/fluidsynth/src/fluid_sys.h +++ a/libs/fluidsynth/src/fluid_sys.h -@@ -128,8 +128,9 @@ typedef guint64 uint64_t; +@@ -130,8 +130,9 @@ typedef gintptr intptr_t; #endif #if defined(WIN32) && HAVE_WINDOWS_H @@ -292,7 +301,7 @@ index 6490359027..3bc0c5a40f 100644 /* WIN32 special defines */ #define STDIN_FILENO 0 -@@ -187,7 +188,7 @@ typedef guint64 uint64_t; +@@ -193,7 +194,7 @@ typedef gintptr intptr_t; /* * Utility functions */ |