Additional general-purpose IIR filter (#331)

* Additional high-pass filter
* ability to use a linear Q
* add custom sinus modulator mapping function
* add GEN_CUSTOM_FILTERFC and GEN_CUSTOM_FILTERQ generators
* introduce fluid_synth_custom_filter()
* make custom filter user adjustable by custom flags
This commit is contained in:
Tom M 2018-02-11 15:18:28 +01:00 committed by GitHub
parent a807ea22b3
commit 5d26d2dee8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 288 additions and 87 deletions

View file

@ -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?

View file

@ -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

View file

@ -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 */
};
/**

View file

@ -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);

View file

@ -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

View file

@ -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 */

View file

@ -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.

View file

@ -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;
};

View file

@ -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*);

View file

@ -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 }
};

View file

@ -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;

View file

@ -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.
*

View file

@ -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,

View file

@ -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);
}

View file

@ -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))