From 1eeba2252a04a42ce77da9f91030cf3ecdfe8672 Mon Sep 17 00:00:00 2001 From: Stefano D'Angelo Date: Fri, 30 Dec 2022 11:30:21 +0100 Subject: [PATCH] add prewarp ctrl in bw_{lp1,mm1}, used in bw_{l,h}s1, fixed bw_{l,h}s2 --- TODO | 2 +- include/bw_hs1.h | 32 +++++++++++++++++++++++--- include/bw_hs2.h | 26 ++++++++++++--------- include/bw_lp1.h | 59 ++++++++++++++++++++++++++++++++++++++++-------- include/bw_ls1.h | 30 ++++++++++++++++++++++-- include/bw_ls2.h | 26 ++++++++++++--------- include/bw_mm1.h | 25 ++++++++++++++++++++ 7 files changed, 163 insertions(+), 37 deletions(-) diff --git a/TODO b/TODO index e0cf0c3..b9fcc4a 100644 --- a/TODO +++ b/TODO @@ -19,7 +19,7 @@ code: * common smoothing policy (as control rate as possible?) - smoothing control? * avoid "force" in coeffs update by using inline functions? * should rather use backward Euler in bw_onepole? -* Q to slope and viceversa functions in 2nd order shelf filters? +* Q to slope and viceversa functions in 2nd order shelf filters? keep updated values (seamless switch, syncrhonicity)? build system: * make makefiles handle paths with spaces etc diff --git a/include/bw_hs1.h b/include/bw_hs1.h index ab60e74..97c10aa 100644 --- a/include/bw_hs1.h +++ b/include/bw_hs1.h @@ -150,6 +150,11 @@ static inline void bw_hs1_set_high_gain_dB(bw_hs1_coeffs *BW_RESTRICT coeffs, fl struct _bw_hs1_coeffs { // Sub-components bw_mm1_coeffs mm1_coeffs; + + // Parameters + float cutoff; + float high_gain; + char update; }; struct _bw_hs1_state { @@ -158,15 +163,30 @@ struct _bw_hs1_state { static inline void bw_hs1_init(bw_hs1_coeffs *BW_RESTRICT coeffs) { bw_mm1_init(&coeffs->mm1_coeffs); + bw_mm1_set_prewarp_at_cutoff(&coeffs->mm1_coeffs, 0); bw_mm1_set_coeffs_x(&coeffs->mm1_coeffs, 0.f); bw_mm1_set_coeffs_lp(&coeffs->mm1_coeffs, 1.f); + coeffs->cutoff = 1e3f; + coeffs->dc_gain = 1.f; } static inline void bw_hs1_set_sample_rate(bw_hs1_coeffs *BW_RESTRICT coeffs, float sample_rate) { bw_mm1_set_sample_rate(&coeffs->mm1_coeffs, sample_rate); } +static inline void _bw_hs1_update_mm1_params(bw_ls1_coeffs *BW_RESTRICT coeffs) { + if (coeffs->update) { + bw_mm1_set_cutoff(&coeffs->mm1_coeffs, coeffs->cutoff * bw_sqrtf_2(coeffs->dc_gain)); + bw_mm1_set_coeff_x(&coeffs->mm1_coeffs, coeffs->dc_gain); + bw_mm1_set_coeff_lp(&coeffs->mm1_coeffs, 1.f - coeffs->dc_gain); + bw_mm1_set_prewarp_freq(&coeffs->mm1_coeffs, coeffs->cutoff); + coeffs->update = 0; + } +} + static inline void bw_hs1_reset_coeffs(bw_hs1_coeffs *BW_RESTRICT coeffs) { + coeffs->update = 1; + _bw_hs1_update_mm1_params(coeffs); bw_mm1_reset_coeffs(&coeffs->mm1_coeffs); } @@ -175,6 +195,7 @@ static inline void bw_hs1_reset_state(const bw_hs1_coeffs *BW_RESTRICT coeffs, b } static inline void bw_hs1_update_coeffs_ctrl(bw_hs1_coeffs *BW_RESTRICT coeffs) { + _bw_hs1_update_mm1_params(coeffs); bw_mm1_update_coeffs_ctrl(&coeffs->mm1_coeffs); } @@ -195,12 +216,17 @@ static inline void bw_hs1_process(bw_hs1_coeffs *BW_RESTRICT coeffs, bw_hs1_stat } static inline void bw_hs1_set_cutoff(bw_hs1_coeffs *BW_RESTRICT coeffs, float value) { - bw_mm1_set_cutoff(&coeffs->mm1_coeffs, value); + if (value != coeffs->cutoff) { + coeffs->cutoff = value; + coeffs->update = 1; + } } static inline void bw_hs1_set_high_gain_lin(bw_hs1_coeffs *BW_RESTRICT coeffs, float value) { - bw_mm1_set_coeff_x(&coeffs->mm1_coeffs, value); - bw_mm1_set_coeff_lp(&coeffs->mm1_coeffs, 1.f - value); + if (value != coeffs->high_gain) { + coeffs->high_gain = value; + coeffs->update = 1; + } } static inline void bw_hs1_set_high_gain_dB(bw_hs1_coeffs *BW_RESTRICT coeffs, float value) { diff --git a/include/bw_hs2.h b/include/bw_hs2.h index 699cfe0..7a6343e 100644 --- a/include/bw_hs2.h +++ b/include/bw_hs2.h @@ -213,17 +213,7 @@ static inline void bw_hs2_set_sample_rate(bw_hs2_coeffs *BW_RESTRICT coeffs, flo bw_mm2_set_sample_rate(&coeffs->mm2_coeffs, sample_rate); } -static inline void bw_hs2_reset_coeffs(bw_hs2_coeffs *BW_RESTRICT coeffs) { - bw_mm2_reset_coeffs(&coeffs->mm2_coeffs); - coeffs->param_changed = ~0; - bw_hs2_update_coeffs_ctrl(coeffs); -} - -static inline void bw_hs2_reset_state(const bw_hs2_coeffs *BW_RESTRICT coeffs, bw_hs2_state *BW_RESTRICT state) { - bw_mm2_reset_state(&coeffs->mm2_coeffs, &state->mm2_state); -} - -static inline void bw_hs2_update_coeffs_ctrl(bw_hs2_coeffs *BW_RESTRICT coeffs) { +static inline void _bw_ls2_update_mm2_params(bw_ls1_coeffs *BW_RESTRICT coeffs) { if (coeffs->param_changed) { if (coeffs->param_changed & _BW_HS2_PARAM_GAIN) { coeffs->sg = bw_math_sqrtf_2(coeffs->gain); @@ -244,6 +234,20 @@ static inline void bw_hs2_update_coeffs_ctrl(bw_hs2_coeffs *BW_RESTRICT coeffs) } coeffs->param_changed = 0; } +} + +static inline void bw_hs2_reset_coeffs(bw_hs2_coeffs *BW_RESTRICT coeffs) { + coeffs->param_changed = ~0; + _bw_hs2_update_mm2_params(coeffs); + bw_mm2_reset_coeffs(&coeffs->mm2_coeffs); +} + +static inline void bw_hs2_reset_state(const bw_hs2_coeffs *BW_RESTRICT coeffs, bw_hs2_state *BW_RESTRICT state) { + bw_mm2_reset_state(&coeffs->mm2_coeffs, &state->mm2_state); +} + +static inline void bw_hs2_update_coeffs_ctrl(bw_hs2_coeffs *BW_RESTRICT coeffs) { + _bw_hs2_update_mm2_params(coeffs); bw_mm2_update_coeffs_ctrl(&coeffs->mm2_coeffs); } diff --git a/include/bw_lp1.h b/include/bw_lp1.h index d5f43e1..0ad778a 100644 --- a/include/bw_lp1.h +++ b/include/bw_lp1.h @@ -119,6 +119,25 @@ static inline void bw_lp1_set_cutoff(bw_lp1_coeffs *BW_RESTRICT coeffs, float va * Sets the cutoff frequency `value` (Hz) in `coeffs`. * * Default value: `1e3f`. + * + * #### bw_lp1_set_prewarp_at_cutoff() + * ```>>> */ +static inline void bw_lp1_set_prewarp_at_cutoff(bw_lp1_coeffs *BW_RESTRICT coeffs, char value); +/*! <<<``` + * Sets whether bilinear transform prewarping frequency should match the + * cutoff frequency (non-`0`) or not (`0`). + * + * Default value: non-`0` (on). + * + * #### bw_lp1_set_prewarp_freq() + * ```>>> */ +static inline void bw_lp1_set_prewarp_freq(bw_lp1_coeffs *BW_RESTRICT coeffs, float value); +/*! <<<``` + * Sets the prewarping frequency `value` (Hz) in `coeffs`. + * + * Only used when the prewarp\_at\_cutoff parameter is off. + * + * Default value: `1e3f`. * }}} */ /*** Implementation ***/ @@ -132,11 +151,13 @@ static inline void bw_lp1_set_cutoff(bw_lp1_coeffs *BW_RESTRICT coeffs, float va struct _bw_lp1_coeffs { // Sub-components bw_one_pole_coeffs smooth_coeffs; - bw_one_pole_state smooth_state; + bw_one_pole_state smooth_cutoff_state; + bw_one_pole_state smooth_prewarp_freq_state; // Coefficients float t_k; + float prewarp_k; float t; float X_x; float X_X_z1; @@ -144,6 +165,7 @@ struct _bw_lp1_coeffs { // Parameters float cutoff; + float prewarp_freq; }; struct _bw_lp1_state { @@ -156,6 +178,8 @@ static inline void bw_lp1_init(bw_lp1_coeffs *BW_RESTRICT coeffs) { bw_one_pole_set_tau(&coeffs->smooth_coeffs, 0.005f); bw_one_pole_set_sticky_thresh(&coeffs->smooth_coeffs, 1e-3f); coeffs->cutoff = 1e3f; + coeffs->prewarp_freq = 1e3f; + coeffs->prewarp_k = 1.f; } static inline void bw_lp1_set_sample_rate(bw_lp1_coeffs *BW_RESTRICT coeffs, float sample_rate) { @@ -165,20 +189,29 @@ static inline void bw_lp1_set_sample_rate(bw_lp1_coeffs *BW_RESTRICT coeffs, flo } static inline void _bw_lp1_do_update_coeffs(bw_lp1_coeffs *BW_RESTRICT coeffs, char force) { - float cutoff_cur = bw_one_pole_get_y_z1(&coeffs->smooth_state); + const float prewarp_freq = coeffs->prewarp_freq + coeffs->prewarp_k * (coeffs->cutoff - coeffs->prewarp_freq); + float prewarp_freq_cur = bw_one_pole_get_y_z1(&coeffs->smooth_prewarp_freq_state); + float cutoff_cur = bw_one_pole_get_y_z1(&coeffs->smooth_cutoff_state); + const char prewarp_freq_changed = force || prewarp_freq != prewarp_freq_cur; const char cutoff_changed = force || coeffs->cutoff != cutoff_cur; - if (cutoff_changed) { - cutoff_cur = bw_one_pole_process1_sticky_rel(&coeffs->smooth_coeffs, &coeffs->smooth_state, coeffs->cutoff); - coeffs->t = bw_tanf_3(coeffs->t_k * cutoff_cur); - const float k = bw_rcpf_2(1.f + coeffs->t); - coeffs->X_x = k * cutoff_cur; + if (prewarp_freq_changed || cutoff_changed) { + if (prewarp_freq_changed) { + prewarp_freq_cur = bw_one_pole_process1_sticky_rel(&coeffs->smooth_coeffs, &coeffs->smooth_prewarp_freq_state, prewarp_freq); + coeffs->t = bw_tanf_3(coeffs->t_k * prewarp_freq_cur); + } + if (cutoff_changed) { + cutoff_cur = bw_one_pole_process1_sticky_rel(&coeffs->smooth_coeffs, &coeffs->smooth_cutoff_state, coeffs->cutoff); + coeffs->y_X = bw_rcpf_2(cutoff_cur); + } + const float k = cutoff_cur * bw_rcpf_2(cutoff_cur * coeffs->t + prewarp_freq_cur); + coeffs->X_x = k * prewarp_freq_cur; coeffs->X_X_z1 = k * coeffs->t; - coeffs->y_X = bw_rcpf_2(cutoff_cur); } } static inline void bw_lp1_reset_coeffs(bw_lp1_coeffs *BW_RESTRICT coeffs) { - bw_one_pole_reset_state(&coeffs->smooth_coeffs, &coeffs->smooth_state, coeffs->cutoff); + bw_one_pole_reset_state(&coeffs->smooth_coeffs, &coeffs->smooth_cutoff_state, coeffs->cutoff); + bw_one_pole_reset_state(&coeffs->smooth_coeffs, &coeffs->smooth_prewarp_freq_state, coeffs->prewarp_freq + coeffs->prewarp_k * (coeffs->cutoff - coeffs->prewarp_freq)); _bw_lp1_do_update_coeffs(coeffs, 1); } @@ -213,6 +246,14 @@ static inline void bw_lp1_set_cutoff(bw_lp1_coeffs *BW_RESTRICT coeffs, float va coeffs->cutoff = value; } +static inline void bw_lp1_set_prewarp_at_cutoff(bw_lp1_coeffs *BW_RESTRICT coeffs, char value) { + coeffs->prewarp_k = value ? 1.f : 0.f; +} + +static inline void bw_lp1_set_prewarp_freq(bw_lp1_coeffs *BW_RESTRICT coeffs, float value) { + coeffs->prewarp_freq = value; +} + #ifdef __cplusplus } #endif diff --git a/include/bw_ls1.h b/include/bw_ls1.h index 14ace55..1b59c47 100644 --- a/include/bw_ls1.h +++ b/include/bw_ls1.h @@ -148,6 +148,11 @@ static inline void bw_ls1_set_dc_gain_dB(bw_ls1_coeffs *BW_RESTRICT coeffs, floa struct _bw_ls1_coeffs { // Sub-components bw_mm1_coeffs mm1_coeffs; + + // Parameters + float cutoff; + float dc_gain; + char update; }; struct _bw_ls1_state { @@ -156,15 +161,29 @@ struct _bw_ls1_state { static inline void bw_ls1_init(bw_ls1_coeffs *BW_RESTRICT coeffs) { bw_mm1_init(&coeffs->mm1_coeffs); + bw_mm1_set_prewarp_at_cutoff(&coeffs->mm1_coeffs, 0); bw_mm1_set_coeffs_x(&coeffs->mm1_coeffs, 1.f); bw_mm1_set_coeffs_lp(&coeffs->mm1_coeffs, 0.f); + coeffs->cutoff = 1e3f; + coeffs->dc_gain = 1.f; } static inline void bw_ls1_set_sample_rate(bw_ls1_coeffs *BW_RESTRICT coeffs, float sample_rate) { bw_mm1_set_sample_rate(&coeffs->mm1_coeffs, sample_rate); } +static inline void _bw_ls1_update_mm1_params(bw_ls1_coeffs *BW_RESTRICT coeffs) { + if (coeffs->update) { + bw_mm1_set_cutoff(&coeffs->mm1_coeffs, coeffs->cutoff * bw_rcpf_2(bw_sqrtf_2(coeffs->dc_gain))); + bw_mm1_set_coeff_lp(&coeffs->mm1_coeffs, coeffs->dc_gain - 1.f); + bw_mm1_set_prewarp_freq(&coeffs->mm1_coeffs, coeffs->cutoff); + coeffs->update = 0; + } +} + static inline void bw_ls1_reset_coeffs(bw_ls1_coeffs *BW_RESTRICT coeffs) { + coeffs->update = 1; + _bw_ls1_update_mm1_params(coeffs); bw_mm1_reset_coeffs(&coeffs->mm1_coeffs); } @@ -173,6 +192,7 @@ static inline void bw_ls1_reset_state(const bw_ls1_coeffs *BW_RESTRICT coeffs, b } static inline void bw_ls1_update_coeffs_ctrl(bw_ls1_coeffs *BW_RESTRICT coeffs) { + _bw_ls1_update_mm1_params(coeffs); bw_mm1_update_coeffs_ctrl(&coeffs->mm1_coeffs); } @@ -193,11 +213,17 @@ static inline void bw_ls1_process(bw_ls1_coeffs *BW_RESTRICT coeffs, bw_ls1_stat } static inline void bw_ls1_set_cutoff(bw_ls1_coeffs *BW_RESTRICT coeffs, float value) { - bw_mm1_set_cutoff(&coeffs->mm1_coeffs, value); + if (value != coeffs->cutoff) { + coeffs->cutoff = value; + coeffs->update = 1; + } } static inline void bw_ls1_set_dc_gain_lin(bw_ls1_coeffs *BW_RESTRICT coeffs, float value) { - bw_mm1_set_coeff_lp(&coeffs->mm1_coeffs, value - 1.f); + if (value != coeffs->dc_gain) { + coeffs->dc_gain = value; + coeffs->update = 1; + } } static inline void bw_ls1_set_dc_gain_dB(bw_ls1_coeffs *BW_RESTRICT coeffs, float value) { diff --git a/include/bw_ls2.h b/include/bw_ls2.h index f65b081..f1e22ac 100644 --- a/include/bw_ls2.h +++ b/include/bw_ls2.h @@ -214,17 +214,7 @@ static inline void bw_ls2_set_sample_rate(bw_ls2_coeffs *BW_RESTRICT coeffs, flo bw_mm2_set_sample_rate(&coeffs->mm2_coeffs, sample_rate); } -static inline void bw_ls2_reset_coeffs(bw_ls2_coeffs *BW_RESTRICT coeffs) { - bw_mm2_reset_coeffs(&coeffs->mm2_coeffs); - coeffs->param_changed = ~0; - bw_ls2_update_coeffs_ctrl(coeffs); -} - -static inline void bw_ls2_reset_state(const bw_ls2_coeffs *BW_RESTRICT coeffs, bw_ls2_state *BW_RESTRICT state) { - bw_mm2_reset_state(&coeffs->mm2_coeffs, &state->mm2_state); -} - -static inline void bw_ls2_update_coeffs_ctrl(bw_ls2_coeffs *BW_RESTRICT coeffs) { +static inline void _bw_ls2_update_mm2_params(bw_ls1_coeffs *BW_RESTRICT coeffs) { if (coeffs->param_changed) { if (coeffs->param_changed & _BW_LS2_PARAM_GAIN) { coeffs->sg = bw_math_sqrtf_2(coeffs->gain); @@ -245,6 +235,20 @@ static inline void bw_ls2_update_coeffs_ctrl(bw_ls2_coeffs *BW_RESTRICT coeffs) } coeffs->param_changed = 0; } +} + +static inline void bw_ls2_reset_coeffs(bw_ls2_coeffs *BW_RESTRICT coeffs) { + coeffs->param_changed = ~0; + _bw_ls2_update_mm2_params(coeffs); + bw_mm2_reset_coeffs(&coeffs->mm2_coeffs); +} + +static inline void bw_ls2_reset_state(const bw_ls2_coeffs *BW_RESTRICT coeffs, bw_ls2_state *BW_RESTRICT state) { + bw_mm2_reset_state(&coeffs->mm2_coeffs, &state->mm2_state); +} + +static inline void bw_ls2_update_coeffs_ctrl(bw_ls2_coeffs *BW_RESTRICT coeffs) { + _bw_ls2_update_mm2_params(coeffs); bw_mm2_update_coeffs_ctrl(&coeffs->mm2_coeffs); } diff --git a/include/bw_mm1.h b/include/bw_mm1.h index 263add0..a6ebde8 100644 --- a/include/bw_mm1.h +++ b/include/bw_mm1.h @@ -117,6 +117,23 @@ static inline void bw_mm1_set_cutoff(bw_mm1_coeffs *BW_RESTRICT coeffs, float va * * Default value: `1e3f`. * + * #### bw_mm1_set_prewarp_at_cutoff() + * ```>>> */ +static inline void bw_mm1_set_prewarp_at_cutoff(bw_mm1_coeffs *BW_RESTRICT coeffs, char value); +/*! <<<``` + * Sets whether bilinear transform prewarping frequency should match the + * cutoff frequency (non-`0`) or not (`0`). + * + * Default value: non-`0` (on). + * + * #### bw_mm1_set_prewarp_freq() + * ```>>> */ +static inline void bw_mm1_set_prewarp_freq(bw_mm1_coeffs *BW_RESTRICT coeffs, float value); +/*! <<<``` + * Sets the prewarping frequency `value` (Hz) in `coeffs`. + * + * Only used when the prewarp\_at\_cutoff parameter is off. + * * #### bw_mm1_set_coeff_x() * ```>>> */ static inline void bw_mm1_set_coeff_x(bw_mm1_coeffs *BW_RESTRICT coeffs, float value); @@ -210,6 +227,14 @@ static inline void bw_mm1_set_cutoff(bw_mm1_coeffs *BW_RESTRICT coeffs, float va bw_lp1_set_cutoff(&coeffs->lp1_coeffs, value); } +static inline void bw_mm1_set_prewarp_at_cutoff(bw_mm1_coeffs *BW_RESTRICT coeffs, char value) { + bw_lp1_set_prewarp_at_cutoff(&coeffs->lp1_coeffs, value); +} + +static inline void bw_mm1_set_prewarp_freq(bw_mm1_coeffs *BW_RESTRICT coeffs, float value) { + bw_lp1_set_prewarp_freq(&coeffs->lp1_coeffs, value); +} + static inline void bw_mm1_set_coeff_x(bw_mm1_coeffs *BW_RESTRICT coeffs, float value) { bw_gain_set_gain_lin(&coeffs->gain_x_coeffs, value); }