diff --git a/src/rvoice/fluid_iir_filter.c b/src/rvoice/fluid_iir_filter.c index b7e21a725..374c1f5d4 100644 --- a/src/rvoice/fluid_iir_filter.c +++ b/src/rvoice/fluid_iir_filter.c @@ -22,6 +22,13 @@ #include "fluid_sys.h" #include "fluid_conv.h" + +static FLUID_INLINE void +fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, fluid_real_t output_rate, + fluid_real_t *a1_out, fluid_real_t *a2_out, + fluid_real_t *b02_out, fluid_real_t *b1_out); + + /** * Applies a low- or high-pass filter with variable cutoff frequency and quality factor * for a given biquad transfer function: @@ -48,9 +55,9 @@ */ void fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, - fluid_real_t *dsp_buf, int count) + fluid_real_t *dsp_buf, int count, fluid_real_t output_rate) { - if(iir_filter->type == FLUID_IIR_DISABLED || iir_filter->q_lin == 0) + if(iir_filter->type == FLUID_IIR_DISABLED || FLUID_FABS(iir_filter->last_q) <= 0.001) { return; } @@ -65,6 +72,9 @@ fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, fluid_real_t dsp_a2 = iir_filter->a2; fluid_real_t dsp_b02 = iir_filter->b02; fluid_real_t dsp_b1 = iir_filter->b1; + + int fres_incr_count = iir_filter->fres_incr_count; + int q_incr_count = iir_filter->q_incr_count; fluid_real_t dsp_centernode; int dsp_i; @@ -94,6 +104,24 @@ fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, // dsp_buf[dsp_i] = dsp_b02 * dsp_input + dsp_hist1; // dsp_hist1 = dsp_b1 * dsp_input - dsp_a1 * dsp_buf[dsp_i] + dsp_hist2; // dsp_hist2 = dsp_b02 * dsp_input - dsp_a2 * dsp_buf[dsp_i]; + + if(fres_incr_count > 0 || q_incr_count > 0) + { + if(fres_incr_count > 0) + { + --fres_incr_count; + iir_filter->last_fres += iir_filter->fres_incr; + } + if(q_incr_count > 0) + { + --q_incr_count; + iir_filter->last_q += iir_filter->q_incr; + } + + LOG_FILTER("last_fres: %.2f Hz | target_fres: %.2f Hz |---| last_q: %.4f | target_q: %.4f", iir_filter->last_fres, iir_filter->target_fres, iir_filter->last_q, iir_filter->target_q); + + fluid_iir_filter_calculate_coefficients(iir_filter, output_rate, &dsp_a1, &dsp_a2, &dsp_b02, &dsp_b1); + } } iir_filter->hist1 = dsp_hist1; @@ -102,12 +130,14 @@ fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, iir_filter->a2 = dsp_a2; iir_filter->b02 = dsp_b02; iir_filter->b1 = dsp_b1; + + iir_filter->fres_incr_count = fres_incr_count; + iir_filter->q_incr_count = q_incr_count; fluid_check_fpe("voice_filter"); } } - DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_init) { fluid_iir_filter_t *iir_filter = obj; @@ -129,7 +159,7 @@ 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->last_q = 0; iir_filter->filter_startup = 1; } @@ -139,7 +169,8 @@ DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_fres) fluid_real_t fres = param[0].real; iir_filter->fres = fres; - iir_filter->last_fres = -1.; + + LOG_FILTER("fluid_iir_filter_set_fres: fres= %f [acents]",fres); } static fluid_real_t fluid_iir_filter_q_from_dB(fluid_real_t q_dB) @@ -178,6 +209,8 @@ DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q) fluid_iir_filter_t *iir_filter = obj; fluid_real_t q = param[0].real; int flags = iir_filter->flags; + + LOG_FILTER("fluid_iir_filter_set_q: Q= %f [dB]",q); if(flags & FLUID_IIR_Q_ZERO_OFF && q <= 0.0) { @@ -185,52 +218,47 @@ DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q) } 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++; + /* q is linear (only for user-defined filter) */ } 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)) + LOG_FILTER("fluid_iir_filter_set_q: Q= %f [linear]",q); + + if(iir_filter->filter_startup) { - /* 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_SQRT(q); + iir_filter->last_q = q; } - - /* The synthesis loop will have to recalculate the filter coefficients. */ - iir_filter->last_fres = -1.; + else + { + static const fluid_real_t q_incr_count = FLUID_BUFSIZE; + iir_filter->q_incr = (q - iir_filter->last_q) / (q_incr_count); + iir_filter->q_incr_count = q_incr_count; + } +#ifdef DBG_FILTER + iir_filter->target_q = q; +#endif } static FLUID_INLINE void fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, - int transition_samples, - fluid_real_t output_rate) + fluid_real_t output_rate, + fluid_real_t *a1_out, fluid_real_t *a2_out, + fluid_real_t *b02_out, fluid_real_t *b1_out) { - /* FLUID_IIR_Q_LINEAR may switch the filter off by setting Q==0 */ - if(iir_filter->q_lin == 0) + // FLUID_IIR_Q_LINEAR may switch the filter off by setting Q==0 + // Due to the linear smoothing, last_q may not exactly become zero. + if(FLUID_FABS(iir_filter->last_q) <= 0.001) { return; } else { + int flags = iir_filter->flags; + fluid_real_t filter_gain = 1.0f; + /* * Those equations from Robert Bristow-Johnson's `Cookbook * formulae for audio EQ biquad filter coefficients', obtained @@ -244,7 +272,7 @@ fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, (iir_filter->last_fres / output_rate); fluid_real_t sin_coeff = FLUID_SIN(omega); fluid_real_t cos_coeff = FLUID_COS(omega); - fluid_real_t alpha_coeff = sin_coeff / (2.0f * iir_filter->q_lin); + fluid_real_t alpha_coeff = sin_coeff / (2.0f * iir_filter->last_q); fluid_real_t a0_inv = 1.0f / (1.0f + alpha_coeff); /* Calculate the filter coefficients. All coefficients are @@ -252,20 +280,35 @@ fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, * * Here a couple of multiplications are saved by reusing common expressions. * The original equations should be: - * iir_filter->b0=(1.-cos_coeff)*a0_inv*0.5*iir_filter->filter_gain; - * 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; */ + * iir_filter->b0=(1.-cos_coeff)*a0_inv*0.5*filter_gain; + * iir_filter->b1=(1.-cos_coeff)*a0_inv*filter_gain; + * iir_filter->b2=(1.-cos_coeff)*a0_inv*0.5*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 b02_temp, b1_temp; + 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. + */ + filter_gain /= FLUID_SQRT(iir_filter->last_q); + } + switch(iir_filter->type) { case FLUID_IIR_HIGHPASS: - b1_temp = (1.0f + cos_coeff) * a0_inv * iir_filter->filter_gain; + b1_temp = (1.0f + cos_coeff) * a0_inv * filter_gain; /* both b0 -and- b2 */ b02_temp = b1_temp * 0.5f; @@ -274,7 +317,7 @@ fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, break; case FLUID_IIR_LOWPASS: - b1_temp = (1.0f - cos_coeff) * a0_inv * iir_filter->filter_gain; + b1_temp = (1.0f - cos_coeff) * a0_inv * filter_gain; /* both b0 -and- b2 */ b02_temp = b1_temp * 0.5f; @@ -285,11 +328,10 @@ fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, return; } - iir_filter->a1 = a1_temp; - iir_filter->a2 = a2_temp; - iir_filter->b02 = b02_temp; - iir_filter->b1 = b1_temp; - iir_filter->filter_startup = 0; + *a1_out = a1_temp; + *a2_out = a2_temp; + *b02_out = b02_temp; + *b1_out = b1_temp; fluid_check_fpe("voice_write filter calculation"); } @@ -300,7 +342,13 @@ 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; + unsigned int calc_coeff_flag = FALSE; + fluid_real_t fres, fres_diff; + + if(iir_filter->type == FLUID_IIR_DISABLED) + { + return; + } /* calculate the frequency of the resonant filter in Hz */ fres = fluid_ct2hz(iir_filter->fres + fres_mod); @@ -324,23 +372,48 @@ void fluid_iir_filter_calc(fluid_iir_filter_t *iir_filter, fres = 5.f; } - // FLUID_LOG(FLUID_INFO, "%f + %f cents = %f cents = %f Hz | Q: %f", iir_filter->fres, fres_mod, iir_filter->fres + fres_mod, fres, iir_filter->q_lin); + LOG_FILTER("%f + %f = %f cents = %f Hz | Q: %f", iir_filter->fres, fres_mod, iir_filter->fres + fres_mod, fres, iir_filter->last_q); + /* if filter enabled and there is a significant frequency change.. */ - if(iir_filter->type != FLUID_IIR_DISABLED && FLUID_FABS(fres - iir_filter->last_fres) > 0.01f) + fres_diff = fres - iir_filter->last_fres; + if(iir_filter->filter_startup) { - /* The filter coefficients have to be recalculated (filter - * parameters have changed). Recalculation for various reasons is - * forced by setting last_fres to -1. The flag filter_startup - * indicates, that the DSP loop runs for the first time, in this - * case, the filter is set directly, instead of smoothly fading - * between old and new settings. */ + // The filer was just starting up, make sure to calculate initial coefficients for the initial Q value, even though the fres may not have changed + calc_coeff_flag = TRUE; + + iir_filter->fres_incr_count = 0; iir_filter->last_fres = fres; - fluid_iir_filter_calculate_coefficients(iir_filter, FLUID_BUFSIZE, - output_rate); + iir_filter->filter_startup = 0; + } + else if(FLUID_FABS(fres_diff) > 0.01f) + { + fluid_real_t fres_incr_count = FLUID_BUFSIZE; + fluid_real_t num_buffers = iir_filter->last_q; + fluid_clip(num_buffers, 1, 5); + // For high values of Q, the phase gets really steep. To prevent clicks when quickly modulating fres in this case, we need to smooth out "slower". + // This is done by simply using Q times FLUID_BUFSIZE samples for the interpolation to complete, capped at 5. + // 5 was chosen because the phase doesn't really get any steeper when continuing to increase Q. + fres_incr_count *= num_buffers; + iir_filter->fres_incr = fres_diff / (fres_incr_count); + iir_filter->fres_incr_count = fres_incr_count; +#ifdef DBG_FILTER + iir_filter->target_fres = fres; +#endif + + // The filter coefficients have to be recalculated (filter cutoff has changed). + calc_coeff_flag = TRUE; + } + else + { + // We do not account for any change of Q here - if it was changed q_incro_count will be non-zero and recalculating the coeffs + // will be taken care of in fluid_iir_filter_apply(). } + if(calc_coeff_flag) + { + fluid_iir_filter_calculate_coefficients(iir_filter, output_rate, &iir_filter->a1, &iir_filter->a2, &iir_filter->b02, &iir_filter->b1); + } fluid_check_fpe("voice_write DSP coefficients"); } - diff --git a/src/rvoice/fluid_iir_filter.h b/src/rvoice/fluid_iir_filter.h index 09e11f0b3..73e87754c 100644 --- a/src/rvoice/fluid_iir_filter.h +++ b/src/rvoice/fluid_iir_filter.h @@ -23,6 +23,14 @@ #include "fluidsynth_priv.h" +// Uncomment to get debug logging for filter parameters +// #define DBG_FILTER +#ifdef DBG_FILTER +#define LOG_FILTER(...) FLUID_LOG(FLUID_DBG, __VA_ARGS__) +#else +#define LOG_FILTER(...) +#endif + typedef struct _fluid_iir_filter_t fluid_iir_filter_t; DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_init); @@ -30,7 +38,7 @@ DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_fres); DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q); 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, fluid_real_t output_rate); void fluid_iir_filter_reset(fluid_iir_filter_t *iir_filter); @@ -54,15 +62,20 @@ struct _fluid_iir_filter_t fluid_real_t a2; /* a1 / a0 */ fluid_real_t hist1, hist2; /* Sample history for the IIR filter */ - int filter_startup; /* Flag: If set, the filter will be set directly. - Else it changes smoothly. */ + int filter_startup; /* Flag: If set, the filter parameters will be set directly. Else it changes smoothly. */ - fluid_real_t fres; /* the resonance frequency, in cents (not absolute cents) */ - fluid_real_t last_fres; /* Current resonance frequency of the IIR filter */ - /* Serves as a flag: A deviation between fres and last_fres */ - /* indicates, that the filter has to be recalculated. */ - fluid_real_t q_lin; /* the q-factor on a linear scale */ - fluid_real_t filter_gain; /* Gain correction factor, depends on q */ + fluid_real_t fres; /* The desired resonance frequency, in absolute cents, this filter is currently set to */ + fluid_real_t last_fres; /* The filter's current (smoothed out) resonance frequency in Hz, which will converge towards its target fres once fres_incr_count has become zero */ + fluid_real_t fres_incr; /* The linear increment of fres each sample */ + int fres_incr_count; /* The number of samples left for the smoothed last_fres adjustment to complete */ + + fluid_real_t last_q; /* The filter's current (smoothed) Q-factor (or "bandwidth", or "resonance-friendlyness") on a linear scale. Just like fres, this will converge towards its target Q once q_incr_count has become zero. */ + fluid_real_t q_incr; /* The linear increment of q each sample */ + int q_incr_count; /* The number of samples left for the smoothed Q adjustment to complete */ +#ifdef DBG_FILTER + fluid_real_t target_fres; /* The filter's target fres, that last_fres should converge towards - for debugging only */ + fluid_real_t target_q; /* The filter's target Q - for debugging only */ +#endif }; #endif diff --git a/src/rvoice/fluid_rvoice.c b/src/rvoice/fluid_rvoice.c index 9c0ea28a5..5b96fb774 100644 --- a/src/rvoice/fluid_rvoice.c +++ b/src/rvoice/fluid_rvoice.c @@ -488,11 +488,11 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + modenv_val * voice->envlfo.modenv_to_fc); - fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); + fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count, voice->dsp.output_rate); /* 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); + fluid_iir_filter_apply(&voice->resonant_custom_filter, dsp_buf, count, voice->dsp.output_rate); return count; } diff --git a/src/synth/fluid_voice.c b/src/synth/fluid_voice.c index f975ef50c..418a7e2dc 100644 --- a/src/synth/fluid_voice.c +++ b/src/synth/fluid_voice.c @@ -859,11 +859,6 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) break; case GEN_FILTERFC: - /* The resonance frequency is converted from absolute cents to - * midicents .val and .mod are both used, this permits real-time - * modulation. The allowed range is tested in the 'fluid_ct2hz' - * function [PH,20021214] - */ UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_filter, x); break;