diff --git a/doc/fluidsynth-v11-devdoc.txt b/doc/fluidsynth-v11-devdoc.txt index e54308cb..0dcddb6c 100644 --- a/doc/fluidsynth-v11-devdoc.txt +++ b/doc/fluidsynth-v11-devdoc.txt @@ -117,6 +117,8 @@ Changes in FluidSynth 2.0.0 concerning developers: - expose functions to manipulate the ladspa effects unit (see ladspa.h) - add support for text and lyrics midi events, see fluid_midi_event_set_lyrics() and fluid_midi_event_set_text() - add 24 bit sample support, see _fluid_sample_t::data24 +- add an additional general-purpose IIR filter, see fluid_synth_set_custom_filter() +- add a custom sinusoidal modulator mapping function, see #FLUID_MOD_SIN \section NewIn1_1_9 Whats new in 1.1.9? diff --git a/include/fluidsynth/gen.h b/include/fluidsynth/gen.h index d4700073..b1cadd09 100644 --- a/include/fluidsynth/gen.h +++ b/include/fluidsynth/gen.h @@ -99,6 +99,11 @@ enum fluid_gen_type { * is used, however, as the destination for the default pitch wheel * modulator. */ GEN_PITCH, /**< Pitch @note Not a real SoundFont generator */ + + /* non-standard generator for an additional custom high- or low-pass filter */ + GEN_CUSTOM_FILTERFC, /**< Custom filter cutoff frequency */ + GEN_CUSTOM_FILTERQ, /**< Custom filter Q */ + #ifndef __DOXYGEN__ GEN_LAST /**< @internal Value defines the count of generators (#fluid_gen_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ #endif diff --git a/include/fluidsynth/mod.h b/include/fluidsynth/mod.h index 298b36e2..08dfebe8 100644 --- a/include/fluidsynth/mod.h +++ b/include/fluidsynth/mod.h @@ -49,7 +49,9 @@ enum fluid_mod_flags FLUID_MOD_CONVEX = 8, /**< Convex mapping function */ FLUID_MOD_SWITCH = 12, /**< Switch (on/off) mapping function */ FLUID_MOD_GC = 0, /**< General controller source type (#fluid_mod_src) */ - FLUID_MOD_CC = 16 /**< MIDI CC controller (source will be a MIDI CC number) */ + FLUID_MOD_CC = 16, /**< MIDI CC controller (source will be a MIDI CC number) */ + + FLUID_MOD_SIN = 0x80, /**< Custom non-standard sinus mapping function */ }; /** diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index 37e020df..6088569f 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -310,6 +310,22 @@ FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t* synth, fluid_voice_t* buf[], int bufsize, int ID); FLUIDSYNTH_API int fluid_synth_handle_midi_event(void* data, fluid_midi_event_t* event); +enum fluid_iir_filter_type { + FLUID_IIR_DISABLED = 0, /**< Custom IIR filter is not operating */ + FLUID_IIR_LOWPASS, /**< Custom IIR filter is operating as low-pass filter */ + FLUID_IIR_HIGHPASS, /**< Custom IIR filter is operating as high-pass filter */ + FLUID_IIR_LAST /**< @internal Value defines the count of filter types (#fluid_iir_filter_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ +}; + +enum fluid_iir_filter_flags { + FLUID_IIR_Q_LINEAR = 1 << 0, /**< The Soundfont spec requires the filter Q to be interpreted in dB. If this flag is set the filter Q is instead assumed to be in a linear range */ + FLUID_IIR_Q_ZERO_OFF = 1 << 1, /**< If this flag the filter is switched off if Q == 0 (prior to any transformation) */ + FLUID_IIR_NO_GAIN_AMP = 1 << 2 /**< The Soundfont spec requires to correct the gain of the filter depending on the filter's Q. If this flag is set the filter gain will not be corrected. */ +}; + +FLUIDSYNTH_API int fluid_synth_set_custom_filter(fluid_synth_t*, int type, int flags); + + /* LADSPA */ FLUIDSYNTH_API fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth); diff --git a/src/rvoice/fluid_iir_filter.c b/src/rvoice/fluid_iir_filter.c index 03b8725b..bd488be5 100644 --- a/src/rvoice/fluid_iir_filter.c +++ b/src/rvoice/fluid_iir_filter.c @@ -23,7 +23,12 @@ #include "fluid_conv.h" /** - * Applies a lowpass filter with variable cutoff frequency and quality factor. + * Applies a low- or high-pass filter with variable cutoff frequency and quality factor + * for a given biquad transfer function: + * b0 + b1*z^-1 + b2*z^-2 + * H(z) = ------------------------ + * a0 + a1*z^-1 + a2*z^-2 + * * Also modifies filter state accordingly. * @param iir_filter Filter parameter * @param dsp_buf Pointer to the synthesized audio data @@ -31,14 +36,12 @@ */ /* * Variable description: - * - dsp_a1, dsp_a2, dsp_b0, dsp_b1, dsp_b2: Filter coefficients + * - dsp_a1, dsp_a2: Filter coefficients for the the previously filtered output signal + * - dsp_b0, dsp_b1, dsp_b2: Filter coefficients for input signal + * - coefficients normalized to a0 * * A couple of variables are used internally, their results are discarded: * - dsp_i: Index through the output buffer - * - dsp_phase_fractional: The fractional part of dsp_phase - * - dsp_coeff: A table of four coefficients, depending on the fractional phase. - * Used to interpolate between samples. - * - dsp_process_buffer: Holds the processed signal between stages * - dsp_centernode: delay line for the IIR filter * - dsp_hist1: same * - dsp_hist2: same @@ -47,6 +50,12 @@ void fluid_iir_filter_apply(fluid_iir_filter_t* iir_filter, fluid_real_t *dsp_buf, int count) { + if(iir_filter->type == FLUID_IIR_DISABLED || iir_filter->q_lin == 0) + { + return; + } + else + { /* IIR filter sample history */ fluid_real_t dsp_hist1 = iir_filter->hist1; fluid_real_t dsp_hist2 = iir_filter->hist2; @@ -91,10 +100,10 @@ fluid_iir_filter_apply(fluid_iir_filter_t* iir_filter, if (dsp_filter_coeff_incr_count-- > 0) { fluid_real_t old_b02 = dsp_b02; - dsp_a1 += dsp_a1_incr; - dsp_a2 += dsp_a2_incr; - dsp_b02 += dsp_b02_incr; - dsp_b1 += dsp_b1_incr; + dsp_a1 += dsp_a1_incr; + dsp_a2 += dsp_a2_incr; + dsp_b02 += dsp_b02_incr; + dsp_b1 += dsp_b1_incr; /* Compensate history to avoid the filter going havoc with large frequency changes */ if (iir_filter->compensate_incr && fabs(dsp_b02) > 0.001) { @@ -125,15 +134,27 @@ fluid_iir_filter_apply(fluid_iir_filter_t* iir_filter, iir_filter->filter_coeff_incr_count = dsp_filter_coeff_incr_count; fluid_check_fpe ("voice_filter"); + } } +void fluid_iir_filter_init(fluid_iir_filter_t* iir_filter, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags) +{ + iir_filter->type = type; + iir_filter->flags = flags; + if(type != FLUID_IIR_DISABLED) + { + fluid_iir_filter_reset(iir_filter); + } +} + void fluid_iir_filter_reset(fluid_iir_filter_t* iir_filter) { iir_filter->hist1 = 0; iir_filter->hist2 = 0; iir_filter->last_fres = -1.; + iir_filter->q_lin = 0; iir_filter->filter_startup = 1; } @@ -145,48 +166,102 @@ fluid_iir_filter_set_fres(fluid_iir_filter_t* iir_filter, iir_filter->last_fres = -1.; } - -void -fluid_iir_filter_set_q_dB(fluid_iir_filter_t* iir_filter, - fluid_real_t q_dB) +static fluid_real_t fluid_iir_filter_q_from_dB(fluid_real_t q_dB) { + /* The generator contains 'centibels' (1/10 dB) => divide by 10 to + * obtain dB */ + q_dB /= 10.0f; + + /* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */ + fluid_clip(q_dB, 0.0f, 96.0f); + + /* Short version: Modify the Q definition in a way, that a Q of 0 + * dB leads to no resonance hump in the freq. response. + * + * Long version: From SF2.01, page 39, item 9 (initialFilterQ): + * "The gain at the cutoff frequency may be less than zero when + * zero is specified". Assume q_dB=0 / q_lin=1: If we would leave + * q as it is, then this results in a 3 dB hump slightly below + * fc. At fc, the gain is exactly the DC gain (0 dB). What is + * (probably) meant here is that the filter does not show a + * resonance hump for q_dB=0. In this case, the corresponding + * q_lin is 1/sqrt(2)=0.707. The filter should have 3 dB of + * attenuation at fc now. In this case Q_dB is the height of the + * resonance peak not over the DC gain, but over the frequency + * response of a non-resonant filter. This idea is implemented as + * follows: */ + q_dB -= 3.01f; + /* The 'sound font' Q is defined in dB. The filter needs a linear q. Convert. */ - iir_filter->q_lin = (fluid_real_t) (pow(10.0f, q_dB / 20.0f)); - - /* SF 2.01 page 59: - * - * The SoundFont specs ask for a gain reduction equal to half the - * height of the resonance peak (Q). For example, for a 10 dB - * resonance peak, the gain is reduced by 5 dB. This is done by - * multiplying the total gain with sqrt(1/Q). `Sqrt' divides dB - * by 2 (100 lin = 40 dB, 10 lin = 20 dB, 3.16 lin = 10 dB etc) - * The gain is later factored into the 'b' coefficients - * (numerator of the filter equation). This gain factor depends - * only on Q, so this is the right place to calculate it. - */ - iir_filter->filter_gain = (fluid_real_t) (1.0 / sqrt(iir_filter->q_lin)); - - /* The synthesis loop will have to recalculate the filter coefficients. */ - iir_filter->last_fres = -1.; - + return pow(10.0f, q_dB / 20.0f); } +void +fluid_iir_filter_set_q(fluid_iir_filter_t* iir_filter, fluid_real_t q) +{ + int flags = iir_filter->flags; + + if(flags & FLUID_IIR_Q_ZERO_OFF && q<=0.0) + { + q = 0; + } + else if(flags & FLUID_IIR_Q_LINEAR) + { + /* q is linear (only for user-defined filter) + * increase to avoid Q being somewhere between zero and one, + * which results in some strange amplified lowpass signal + */ + q++; + } + else + { + q = fluid_iir_filter_q_from_dB(q); + } + + iir_filter->q_lin = q; + iir_filter->filter_gain = 1.0; + + if(!(flags & FLUID_IIR_NO_GAIN_AMP)) + { + /* SF 2.01 page 59: + * + * The SoundFont specs ask for a gain reduction equal to half the + * height of the resonance peak (Q). For example, for a 10 dB + * resonance peak, the gain is reduced by 5 dB. This is done by + * multiplying the total gain with sqrt(1/Q). `Sqrt' divides dB + * by 2 (100 lin = 40 dB, 10 lin = 20 dB, 3.16 lin = 10 dB etc) + * The gain is later factored into the 'b' coefficients + * (numerator of the filter equation). This gain factor depends + * only on Q, so this is the right place to calculate it. + */ + iir_filter->filter_gain /= sqrt(q); + } + + /* The synthesis loop will have to recalculate the filter coefficients. */ + iir_filter->last_fres = -1.; +} static FLUID_INLINE void fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t* iir_filter, int transition_samples, fluid_real_t output_rate) { - + /* FLUID_IIR_Q_LINEAR may switch the filter off by setting Q==0 */ + if(iir_filter->q_lin == 0) + { + return; + } + else + { /* - * Those equations from Robert Bristow-Johnson's `Cookbook - * formulae for audio EQ biquad filter coefficients', obtained - * from Harmony-central.com / Computer / Programming. They are - * the result of the bilinear transform on an analogue filter - * prototype. To quote, `BLT frequency warping has been taken - * into account for both significant frequency relocation and for - * bandwidth readjustment'. */ + * Those equations from Robert Bristow-Johnson's `Cookbook + * formulae for audio EQ biquad filter coefficients', obtained + * from Harmony-central.com / Computer / Programming. They are + * the result of the bilinear transform on an analogue filter + * prototype. To quote, `BLT frequency warping has been taken + * into account for both significant frequency relocation and for + * bandwidth readjustment'. */ fluid_real_t omega = (fluid_real_t) (2.0 * M_PI * (iir_filter->last_fres / ((float) output_rate))); @@ -204,11 +279,33 @@ fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t* iir_filter, * iir_filter->b1=(1.-cos_coeff)*a0_inv*iir_filter->filter_gain; * iir_filter->b2=(1.-cos_coeff)*a0_inv*0.5*iir_filter->filter_gain; */ + /* "a" coeffs are same for all 3 available filter types */ fluid_real_t a1_temp = -2.0f * cos_coeff * a0_inv; fluid_real_t a2_temp = (1.0f - alpha_coeff) * a0_inv; - fluid_real_t b1_temp = (1.0f - cos_coeff) * a0_inv * iir_filter->filter_gain; - /* both b0 -and- b2 */ - fluid_real_t b02_temp = b1_temp * 0.5f; + + fluid_real_t b02_temp, b1_temp; + switch(iir_filter->type) + { + case FLUID_IIR_HIGHPASS: + b1_temp = (1.0f + cos_coeff) * a0_inv * iir_filter->filter_gain; + + /* both b0 -and- b2 */ + b02_temp = b1_temp * 0.5f; + + b1_temp *= -1.0f; + break; + + case FLUID_IIR_LOWPASS: + b1_temp = (1.0f - cos_coeff) * a0_inv * iir_filter->filter_gain; + + /* both b0 -and- b2 */ + b02_temp = b1_temp * 0.5f; + break; + + default: + /* filter disabled, should never get here */ + return; + } iir_filter->compensate_incr = 0; @@ -249,6 +346,7 @@ fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t* iir_filter, iir_filter->filter_coeff_incr_count = transition_samples; } fluid_check_fpe ("voice_write filter calculation"); + } } @@ -280,7 +378,7 @@ void fluid_iir_filter_calc(fluid_iir_filter_t* iir_filter, fres = 5; /* if filter enabled and there is a significant frequency change.. */ - if (fabs (fres - iir_filter->last_fres) > 0.01) + if (iir_filter->type != FLUID_IIR_DISABLED && fabs (fres - iir_filter->last_fres) > 0.01) { /* The filter coefficients have to be recalculated (filter * parameters have changed). Recalculation for various reasons is diff --git a/src/rvoice/fluid_iir_filter.h b/src/rvoice/fluid_iir_filter.h index f8a122ff..d1af2a0e 100644 --- a/src/rvoice/fluid_iir_filter.h +++ b/src/rvoice/fluid_iir_filter.h @@ -26,28 +26,32 @@ typedef struct _fluid_iir_filter_t fluid_iir_filter_t; +void fluid_iir_filter_init(fluid_iir_filter_t* iir_filter, enum fluid_iir_filter_type, enum fluid_iir_filter_flags flags); + void fluid_iir_filter_apply(fluid_iir_filter_t* iir_filter, - fluid_real_t *dsp_buf, int dsp_buf_count); + fluid_real_t *dsp_buf, int dsp_buf_count); void fluid_iir_filter_reset(fluid_iir_filter_t* iir_filter); -void fluid_iir_filter_set_q_dB(fluid_iir_filter_t* iir_filter, - fluid_real_t q_dB); +void fluid_iir_filter_set_q(fluid_iir_filter_t* iir_filter, fluid_real_t q); void fluid_iir_filter_set_fres(fluid_iir_filter_t* iir_filter, fluid_real_t fres); void fluid_iir_filter_calc(fluid_iir_filter_t* iir_filter, fluid_real_t output_rate, - fluid_real_t fres_mod); + fluid_real_t fres_mod); /* We can't do information hiding here, as fluid_voice_t includes the struct without a pointer. */ struct _fluid_iir_filter_t { + enum fluid_iir_filter_type type; /* specifies the type of this filter */ + enum fluid_iir_filter_flags flags; /* additional flags to customize this filter */ + /* filter coefficients */ /* The coefficients are normalized to a0. */ - /* b0 and b2 are identical => b02 */ + /* b0 and b2 are identical => b02 */ fluid_real_t b02; /* b0 / a0 */ fluid_real_t b1; /* b1 / a0 */ fluid_real_t a1; /* a0 / a0 */ diff --git a/src/rvoice/fluid_rvoice.c b/src/rvoice/fluid_rvoice.c index 6d448252..daeef5b2 100644 --- a/src/rvoice/fluid_rvoice.c +++ b/src/rvoice/fluid_rvoice.c @@ -361,12 +361,17 @@ fluid_rvoice_write (fluid_rvoice_t* voice, fluid_real_t *dsp_buf) return count; /*************** resonant filter ******************/ + fluid_iir_filter_calc(&voice->resonant_filter, voice->dsp.output_rate, - fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + - fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_fc); + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + + fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_fc); fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); - + + /* additional custom filter - only uses the fixed modulator, no lfos... */ + fluid_iir_filter_calc(&voice->resonant_custom_filter, voice->dsp.output_rate, 0); + fluid_iir_filter_apply(&voice->resonant_custom_filter, dsp_buf, count); + return count; } @@ -487,6 +492,7 @@ fluid_rvoice_reset(fluid_rvoice_t* voice) /* Clear sample history in filter */ fluid_iir_filter_reset(&voice->resonant_filter); + fluid_iir_filter_reset(&voice->resonant_custom_filter); /* Force setting of the phase at the first DSP loop run * This cannot be done earlier, because it depends on modulators. diff --git a/src/rvoice/fluid_rvoice.h b/src/rvoice/fluid_rvoice.h index aeca0168..b0dece68 100644 --- a/src/rvoice/fluid_rvoice.h +++ b/src/rvoice/fluid_rvoice.h @@ -151,6 +151,7 @@ struct _fluid_rvoice_t fluid_rvoice_envlfo_t envlfo; fluid_rvoice_dsp_t dsp; fluid_iir_filter_t resonant_filter; /* IIR resonant dsp filter */ + fluid_iir_filter_t resonant_custom_filter; /* optional custom/general-purpose IIR resonant filter */ fluid_rvoice_buffers_t buffers; }; diff --git a/src/rvoice/fluid_rvoice_event.c b/src/rvoice/fluid_rvoice_event.c index 8e240e33..c73b1b93 100644 --- a/src/rvoice/fluid_rvoice_event.c +++ b/src/rvoice/fluid_rvoice_event.c @@ -109,8 +109,9 @@ fluid_rvoice_event_dispatch(fluid_rvoice_event_t* event) EVENTFUNC_I1(fluid_lfo_set_delay, fluid_lfo_t*); EVENTFUNC_R1(fluid_lfo_set_incr, fluid_lfo_t*); + EVENTFUNC_II(fluid_iir_filter_init, fluid_iir_filter_t*); EVENTFUNC_R1(fluid_iir_filter_set_fres, fluid_iir_filter_t*); - EVENTFUNC_R1(fluid_iir_filter_set_q_dB, fluid_iir_filter_t*); + EVENTFUNC_R1(fluid_iir_filter_set_q, fluid_iir_filter_t*); EVENTFUNC_II(fluid_rvoice_buffers_set_mapping, fluid_rvoice_buffers_t*); EVENTFUNC_IR(fluid_rvoice_buffers_set_amp, fluid_rvoice_buffers_t*); diff --git a/src/synth/fluid_gen.c b/src/synth/fluid_gen.c index a3efcb2a..c4b81807 100644 --- a/src/synth/fluid_gen.c +++ b/src/synth/fluid_gen.c @@ -85,7 +85,9 @@ static const fluid_gen_info_t fluid_gen_info[] = { { GEN_SCALETUNE, 0, 1, 0.0f, 1200.0f, 100.0f }, { GEN_EXCLUSIVECLASS, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_OVERRIDEROOTKEY, 1, 0, 0.0f, 127.0f, -1.0f }, - { GEN_PITCH, 1, 0, 0.0f, 127.0f, 0.0f } + { GEN_PITCH, 1, 0, 0.0f, 127.0f, 0.0f }, + { GEN_CUSTOM_FILTERFC, 1, 2, 0.0f, 22050.0f, 0.0f }, + { GEN_CUSTOM_FILTERQ, 1, 1, 0.0f, 960.0f, 0.0f } }; diff --git a/src/synth/fluid_mod.c b/src/synth/fluid_mod.c index cb276c97..3126d15f 100644 --- a/src/synth/fluid_mod.c +++ b/src/synth/fluid_mod.c @@ -281,6 +281,30 @@ fluid_mod_transform_source_value(fluid_real_t val, unsigned char mod_flags, cons case FLUID_MOD_SWITCH | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =15 */ val = (val_norm >= 0.5f)? -1.0f : 1.0f; break; + + /* + * MIDI CCs only have a resolution of 7 bits. The closer val_norm gets to 1, + * the less will be the resulting change of the sinus. When using this sin() + * for scaling the cutoff frequency, there will be no audible difference between + * MIDI CCs 118 to 127. To avoid this waste of CCs multiply with 0.87 + * (at least for unipolar) which makes sin() never get to 1.0 but to 0.98 which + * is close enough. + */ + case FLUID_MOD_SIN | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* custom sin(x) */ + val = sin(M_PI/2 * val_norm * 0.87); + break; + case FLUID_MOD_SIN | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* custom */ + val = sin(M_PI/2 * (1.0f - val_norm) * 0.87); + break; + case FLUID_MOD_SIN | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* custom */ + val = (val_norm > 0.5f) ? sin(M_PI/2 * 2 * (val_norm - 0.5f)) + : -sin(M_PI/2 * 2 * (0.5f - val_norm)); + break; + case FLUID_MOD_SIN | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* custom */ + val = (val_norm > 0.5f) ? -sin(M_PI/2 * 2 * (val_norm - 0.5f)) + : sin(M_PI/2 * 2 * (0.5f - val_norm)); + break; + default: FLUID_LOG(FLUID_ERR, "Unknown modulator type '%d', disabling modulator.", mod_flags); val = 0.0f; @@ -491,6 +515,8 @@ void fluid_dump_modulator(fluid_mod_t * mod){ switch(dest){ case GEN_FILTERQ: printf("Q"); break; case GEN_FILTERFC: printf("fc"); break; + case GEN_CUSTOM_FILTERQ: printf("custom-Q"); break; + case GEN_CUSTOM_FILTERFC: printf("custom-fc"); break; case GEN_VIBLFOTOPITCH: printf("VibLFO-to-pitch"); break; case GEN_MODENVTOPITCH: printf("ModEnv-to-pitch"); break; case GEN_MODLFOTOPITCH: printf("ModLFO-to-pitch"); break; diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index 2341b709..6c57d0b9 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -139,6 +139,7 @@ static fluid_mod_t default_reverb_mod; /* SF2.01 section 8.4.8 */ static fluid_mod_t default_chorus_mod; /* SF2.01 section 8.4.9 */ static fluid_mod_t default_pitch_bend_mod; /* SF2.01 section 8.4.10 */ + /* reverb presets */ static const fluid_revmodel_presets_t revmodel_preset[] = { /* name */ /* roomsize */ /* damp */ /* width */ /* level */ @@ -554,7 +555,7 @@ new_fluid_synth(fluid_settings_t *settings) fluid_settings_getnum_float(settings, "synth.gain", &synth->gain); fluid_settings_getint(settings, "synth.device-id", &synth->device_id); fluid_settings_getint(settings, "synth.cpu-cores", &synth->cores); - + fluid_settings_getnum_float(settings, "synth.overflow.percussion", &synth->overflow.percussion); fluid_settings_getnum_float(settings, "synth.overflow.released", &synth->overflow.released); fluid_settings_getnum_float(settings, "synth.overflow.sustained", &synth->overflow.sustained); @@ -724,7 +725,7 @@ new_fluid_synth(fluid_settings_t *settings) goto error_recovery; } } - + fluid_synth_set_sample_rate(synth, synth->sample_rate); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_polyphony, synth->polyphony, 0.0f); @@ -2491,6 +2492,8 @@ fluid_synth_update_polyphony_LOCAL(fluid_synth_t* synth, int new_polyphony) synth->voice[i] = new_fluid_voice(synth->sample_rate); if (synth->voice[i] == NULL) return FLUID_FAILED; + + fluid_voice_set_custom_filter(synth->voice[i], synth->custom_filter_type, synth->custom_filter_flags); } synth->nvoice = new_polyphony; } @@ -5157,6 +5160,41 @@ fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth) return synth->ladspa_fx; } +/** + * Configure a general-purpose IIR biquad filter. + * + * This is an optional, additional filter that operates independently from the default low-pass filter required by the Soundfont2 standard. + * By default this filter is off (#FLUID_IIR_DISABLED). + * + * @param synth FluidSynth instance + * @param type Type of the IIR filter to use (see #fluid_iir_filter_type) + * @param flags Additional flags to customize this filter or zero to stay with the default (see #fluid_iir_filter_flags) + * + * @return #FLUID_OK if the settings have been successfully applied, otherwise #FLUID_FAILED + */ +int fluid_synth_set_custom_filter(fluid_synth_t* synth, int type, int flags) +{ + int i; + fluid_voice_t *voice; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(type >= FLUID_IIR_DISABLED && type < FLUID_IIR_LAST, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + synth->custom_filter_type = type; + synth->custom_filter_flags = flags; + + for (i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + fluid_voice_set_custom_filter(voice, type, flags); + } + + FLUID_API_RETURN(FLUID_OK); +} + /** * Set the important channels for voice overflow priority calculation. * diff --git a/src/synth/fluid_synth.h b/src/synth/fluid_synth.h index 96d4dd8c..e88f05d7 100644 --- a/src/synth/fluid_synth.h +++ b/src/synth/fluid_synth.h @@ -166,6 +166,8 @@ struct _fluid_synth_t fluid_mod_t* default_mod; /**< the (dynamic) list of default modulators */ fluid_ladspa_fx_t* ladspa_fx; /**< Effects unit for LADSPA support */ + enum fluid_iir_filter_type custom_filter_type; /**< filter type of the user-defined filter currently used for all voices */ + enum fluid_iir_filter_flags custom_filter_flags; /**< filter type of the user-defined filter currently used for all voices */ }; fluid_preset_t* fluid_synth_find_preset(fluid_synth_t* synth, diff --git a/src/synth/fluid_voice.c b/src/synth/fluid_voice.c index df25a38a..2635f104 100644 --- a/src/synth/fluid_voice.c +++ b/src/synth/fluid_voice.c @@ -101,6 +101,8 @@ fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t* voice); #define UPDATE_RVOICE_R1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, voice->rvoice, arg1) #define UPDATE_RVOICE_I1(proc, arg1) UPDATE_RVOICE_GENERIC_I1(proc, voice->rvoice, arg1) #define UPDATE_RVOICE_FILTER1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, &voice->rvoice->resonant_filter, arg1) +#define UPDATE_RVOICE_CUSTOM_FILTER1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, &voice->rvoice->resonant_custom_filter, arg1) +#define UPDATE_RVOICE_CUSTOM_FILTER_I2(proc, arg1, arg2) UPDATE_RVOICE_GENERIC_IR(proc, &voice->rvoice->resonant_custom_filter, arg1, arg2) #define UPDATE_RVOICE2(proc, iarg, rarg) UPDATE_RVOICE_GENERIC_IR(proc, voice->rvoice, iarg, rarg) #define UPDATE_RVOICE_BUFFERS2(proc, iarg, rarg) UPDATE_RVOICE_GENERIC_IR(proc, &voice->rvoice->buffers, iarg, rarg) @@ -175,6 +177,9 @@ static void fluid_voice_initialize_rvoice(fluid_voice_t* voice) 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); fluid_voice_update_modenv(voice, FLUID_VOICE_ENVFINISHED, 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); + + fluid_iir_filter_init(&voice->rvoice->resonant_filter, FLUID_IIR_LOWPASS, 0); + fluid_iir_filter_init(&voice->rvoice->resonant_custom_filter, FLUID_IIR_DISABLED, 0); } /* @@ -206,8 +211,8 @@ new_fluid_voice(fluid_real_t output_rate) voice->sample = NULL; /* Initialize both the rvoice and overflow_rvoice */ - voice->can_access_rvoice = 1; - voice->can_access_overflow_rvoice = 1; + voice->can_access_rvoice = TRUE; + voice->can_access_overflow_rvoice = TRUE; fluid_voice_initialize_rvoice(voice); fluid_voice_swap_rvoice(voice); fluid_voice_initialize_rvoice(voice); @@ -539,7 +544,9 @@ fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t* voice) /* GEN_COARSETUNE [1] #51 */ /* GEN_FINETUNE [1] #52 */ GEN_OVERRIDEROOTKEY, /* #58 */ - GEN_PITCH /* --- */ + GEN_PITCH, /* --- */ + GEN_CUSTOM_FILTERFC, /* --- */ + GEN_CUSTOM_FILTERQ, /* --- */ }; /* When the voice is made ready for the synthesis process, a lot of @@ -684,10 +691,8 @@ void fluid_voice_update_param(fluid_voice_t* voice, int gen) { unsigned int count, z; - fluid_real_t q_dB; fluid_real_t x = fluid_voice_gen_value(voice, gen); - switch (gen) { case GEN_PAN: @@ -775,33 +780,18 @@ fluid_voice_update_param(fluid_voice_t* voice, int gen) break; case GEN_FILTERQ: - /* The generator contains 'centibels' (1/10 dB) => divide by 10 to - * obtain dB */ - q_dB = x / 10.0f; - - /* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */ - fluid_clip(q_dB, 0.0f, 96.0f); - - /* Short version: Modify the Q definition in a way, that a Q of 0 - * dB leads to no resonance hump in the freq. response. - * - * Long version: From SF2.01, page 39, item 9 (initialFilterQ): - * "The gain at the cutoff frequency may be less than zero when - * zero is specified". Assume q_dB=0 / q_lin=1: If we would leave - * q as it is, then this results in a 3 dB hump slightly below - * fc. At fc, the gain is exactly the DC gain (0 dB). What is - * (probably) meant here is that the filter does not show a - * resonance hump for q_dB=0. In this case, the corresponding - * q_lin is 1/sqrt(2)=0.707. The filter should have 3 dB of - * attenuation at fc now. In this case Q_dB is the height of the - * resonance peak not over the DC gain, but over the frequency - * response of a non-resonant filter. This idea is implemented as - * follows: */ - q_dB -= 3.01f; - UPDATE_RVOICE_FILTER1(fluid_iir_filter_set_q_dB, q_dB); - + UPDATE_RVOICE_FILTER1(fluid_iir_filter_set_q, x); break; + /* same as the two above, only for the custom filter */ + case GEN_CUSTOM_FILTERFC: + UPDATE_RVOICE_CUSTOM_FILTER1(fluid_iir_filter_set_fres, x); + break; + + case GEN_CUSTOM_FILTERQ: + UPDATE_RVOICE_CUSTOM_FILTER1(fluid_iir_filter_set_q, x); + break; + case GEN_MODLFOTOPITCH: fluid_clip(x, -12000.0, 12000.0); UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_pitch, x); @@ -1755,3 +1745,10 @@ fluid_voice_get_overflow_prio(fluid_voice_t* voice, return this_voice_prio; } + + +void fluid_voice_set_custom_filter(fluid_voice_t* voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags) +{ + UPDATE_RVOICE_CUSTOM_FILTER_I2(fluid_iir_filter_init, type, flags); +} + diff --git a/src/synth/fluid_voice.h b/src/synth/fluid_voice.h index e42a8f59..b68ada76 100644 --- a/src/synth/fluid_voice.h +++ b/src/synth/fluid_voice.h @@ -182,6 +182,7 @@ fluid_voice_unlock_rvoice(fluid_voice_t* voice) fluid_real_t fluid_voice_gen_value(const fluid_voice_t* voice, int num); +void fluid_voice_set_custom_filter(fluid_voice_t* voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags); #define fluid_voice_get_loudness(voice) (fluid_adsr_env_get_max_val(&voice->volenv))