diff --git a/examples/synth/src/bw_example_synth.c b/examples/synth/src/bw_example_synth.c index 3d0b99d..2e93b9f 100644 --- a/examples/synth/src/bw_example_synth.c +++ b/examples/synth/src/bw_example_synth.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bw_osc_tri.h b/include/bw_osc_tri.h index e25b0e0..b728f63 100644 --- a/include/bw_osc_tri.h +++ b/include/bw_osc_tri.h @@ -29,7 +29,7 @@ *
    *
  • Version 0.2.0: *
      - *
    • Refactored API to avoid dynamic memory allocation.
    • + *
    • Refactored API.
    • *
    *
  • *
  • Version 0.1.0: @@ -48,42 +48,40 @@ extern "C" { #endif -/*! api {{{ - * #### bw_osc_tri - * ```>>> */ -typedef struct _bw_osc_tri bw_osc_tri; -/*! <<<``` - * Instance object. - * >>> */ +#include -/*! ... +/*! api {{{ + * #### bw_osc_tri_coeffs + * ```>>> */ +typedef struct _bw_osc_tri_coeffs bw_osc_tri_coeffs; +/*! <<<``` + * Coefficients. + * * #### bw_osc_tri_init() * ```>>> */ -void bw_osc_tri_init(bw_osc_tri *instance); +static inline void bw_osc_tri_init(bw_osc_tri_coeffs *BW_RESTRICT coeffs); /*! <<<``` - * Initializes the `instance` object. - * >>> */ - -/*! ... + * Initializes the `coeffs`. + * * #### bw_osc_tri_set_sample_rate() * ```>>> */ -void bw_osc_tri_set_sample_rate(bw_osc_tri *instance, float sample_rate); +static inline void bw_osc_tri_set_sample_rate(bw_osc_tri_coeffs *BW_RESTRICT coeffs, float sample_rate); /*! <<<``` - * Sets the `sample_rate` (Hz) value for the given `instance`. + * Sets the `sample_rate` (Hz) value for the given `coeffs`. * >>> */ -/*! ... - * #### bw_osc_tri_reset() - * ```>>> */ -void bw_osc_tri_reset(bw_osc_tri *instance); -/*! <<<``` - * Resets the given `instance` to its initial state. - * >>> */ +static inline void bw_osc_tri_reset_coeffs(bw_osc_tri_coeffs *BW_RESTRICT coeffs); + +static inline void bw_osc_tri_update_coeffs_ctrl(bw_osc_tri_coeffs *BW_RESTRICT coeffs); +static inline void bw_osc_tri_update_coeffs_audio(bw_osc_tri_coeffs *BW_RESTRICT coeffs); + +static inline float bw_osc_tri_process1(const bw_osc_tri_coeffs *BW_RESTRICT coeffs, float x); +static inline float bw_osc_tri_process1_antialias(const bw_osc_tri_coeffs *BW_RESTRICT coeffs, float x, float x_phase_inc); /*! ... * #### bw_osc_tri_process() * ```>>> */ -void bw_osc_tri_process(bw_osc_tri *instance, const float *x, const float *x_phase_inc, float* y, int n_samples); +static inline void bw_osc_tri_process(bw_osc_tri_coeffs *BW_RESTRICT coeffs, const float *x, const float *x_phase_inc, float *y, int n_samples); /*! <<<``` * Lets the given `instance` process `n_samples` samples from the input * buffer `x` containing the normalized phase signal and fills the @@ -96,7 +94,7 @@ void bw_osc_tri_process(bw_osc_tri *instance, const float *x, const float *x_pha /*! ... * #### bw_osc_tri_set_antialiasing() * ```>>> */ -void bw_osc_tri_set_antialiasing(bw_osc_tri *instance, char value); +static inline void bw_osc_tri_set_antialiasing(bw_osc_tri_coeffs *BW_RESTRICT coeffs, char value); /*! <<<``` * Sets whether the antialiasing is on (`value` non-`0`) or off (`0`) for the * given `instance`. @@ -105,9 +103,9 @@ void bw_osc_tri_set_antialiasing(bw_osc_tri *instance, char value); * >>> */ /*! ... - * #### bw_osc_tri_set_slope() + * #### bw_osc_tri_set_tri_width() * ```>>> */ -void bw_osc_tri_set_slope(bw_osc_tri *instance, float value); +static inline void bw_osc_tri_set_slope(bw_osc_tri_coeffs *BW_RESTRICT coeffs, float value); /*! <<<``` * Sets the slope (increasing time over period) to `value` (range [`0.f`, * `1.f`]) for the given `instance`. @@ -115,22 +113,108 @@ void bw_osc_tri_set_slope(bw_osc_tri *instance, float value); * Default value: `0.5f`. * }}} */ -/* WARNING: the internal definition of this struct is not part of the public - * API. Its content may change at any time in future versions. Please, do not - * access its members directly. */ -struct _bw_osc_tri { - // Coefficients - float smooth_mA1; +/*** Implementation ***/ + +/* WARNING: This part of the file is not part of the public API. Its content may + * change at any time in future versions. Please, do not use it directly. */ + +#include +#include + +struct _bw_osc_tri_coeffs { + // Sub-components + bw_one_pole_coeffs smooth_coeffs; + bw_one_pole_state smooth_state; // Parameters - char first_run; - char antialiasing; - float slope; - - // State - float slope_z1; + char antialiasing; + float slope; }; +static inline void bw_osc_tri_init(bw_osc_tri_coeffs *BW_RESTRICT coeffs) { + bw_one_pole_init(&coeffs->smooth_coeffs); + bw_one_pole_set_tau(&coeffs->smooth_coeffs, 0.005f); + coeffs->antialiasing = 0; + coeffs->slope = 0.5f; +} + +static inline void bw_osc_tri_set_sample_rate(bw_osc_tri_coeffs *BW_RESTRICT coeffs, float sample_rate) { + bw_one_pole_set_sample_rate(&coeffs->smooth_coeffs, sample_rate); + bw_one_pole_reset_coeffs(&coeffs->smooth_coeffs); +} + +static inline void bw_osc_tri_reset_coeffs(bw_osc_tri_coeffs *BW_RESTRICT coeffs) { + bw_one_pole_reset_state(&coeffs->smooth_coeffs, &coeffs->smooth_state, coeffs->slope); +} + +static inline void bw_osc_tri_update_coeffs_ctrl(bw_osc_tri_coeffs *BW_RESTRICT coeffs) { +} + +static inline void bw_osc_tri_update_coeffs_audio(bw_osc_tri_coeffs *BW_RESTRICT coeffs) { + bw_one_pole_process1(&coeffs->smooth_coeffs, &coeffs->smooth_state, coeffs->slope); +} + +static inline float bw_osc_tri_process1(const bw_osc_tri_coeffs *BW_RESTRICT coeffs, float x) { + const float slope = bw_one_pole_get_y_z1(&coeffs->smooth_state); + const float phase_d = x + x; + return x < slope ? (phase_d - slope) * bw_rcpf_2(slope) : (1.f + slope - phase_d) * bw_rcpf_2(1.f - slope); +} + +// PolyBLAMP residual based on Parzen window (4th-order B-spline), one-sided (x in [0, 2]) +static inline float _bw_osc_tri_blamp_diff(float x) { + return x < 1.f + ? x * (x * ((0.05f * x - 0.1666666666666667f) * x * x + 0.6666666666666666f) - 1.0f) + 0.4666666666666667f + : x * (x * (x * ((0.1666666666666667f - 0.01666666666666667f * x) * x - 0.6666666666666666f) + 1.333333333333333f) - 1.333333333333333f) + 0.5333333333333333f; +} + +static inline float bw_osc_tri_process1_antialias(const bw_osc_tri_coeffs *BW_RESTRICT coeffs, float x, float x_phase_inc) { + const float slope = bw_one_pole_get_y_z1(&coeffs->smooth_state); + const float s_1_p_pw = 1.f + slope; + const float s_1_m_pw = 1.f - slope; + const float phase_d = x + x; + float v = x < slope ? (phase_d - slope) * bw_rcpf_2(slope) : (s_1_p_pw - phase_d) * bw_rcpf_2(s_1_m_pw); + if (x_phase_inc != 0.f) { + const float phase_inc_2 = x_phase_inc + x_phase_inc; + const float phase_inc_rcp = bw_rcpf_2(x_phase_inc); + const float pw_m_phase = slope - x; + const float phase_2 = bw_copysignf(0.5f, pw_m_phase) + 0.5f - pw_m_phase; + const float s_1_m_phase = 1.f - x; + const float s_1_m_phase_2 = 1.f - phase_2; + float blamp = 0.f; + if (s_1_m_phase_2 < phase_inc_2) + blamp += _bw_osc_tri_blamp_diff(s_1_m_phase_2 * phase_inc_rcp); + if (s_1_m_phase < phase_inc_2) + blamp -= _bw_osc_tri_blamp_diff(s_1_m_phase * phase_inc_rcp); + if (x < phase_inc_2) + blamp -= _bw_osc_tri_blamp_diff(x * phase_inc_rcp); + if (phase_2 < phase_inc_2) + blamp += _bw_osc_tri_blamp_diff(phase_2 * phase_inc_rcp); + v -= bw_rcpf_2(slope * s_1_m_pw) * bw_absf(x_phase_inc) * blamp; + } + return v; +} + +static inline void bw_osc_tri_process(bw_osc_tri_coeffs *BW_RESTRICT coeffs, const float *x, const float *x_phase_inc, float *y, int n_samples) { + if (coeffs->antialiasing) + for (int i = 0; i < n_samples; i++) { + bw_osc_tri_update_coeffs_audio(coeffs); + y[i] = bw_osc_tri_process1_antialias(coeffs, x[i], x_phase_inc[i]); + } + else + for (int i = 0; i < n_samples; i++) { + bw_osc_tri_update_coeffs_audio(coeffs); + y[i] = bw_osc_tri_process1(coeffs, x[i]); + } +} + +static inline void bw_osc_tri_set_antialiasing(bw_osc_tri_coeffs *BW_RESTRICT coeffs, char value) { + coeffs->antialiasing = value; +} + +static inline void bw_osc_tri_set_slope(bw_osc_tri_coeffs *BW_RESTRICT coeffs, float value) { + coeffs->slope = value; +} + #ifdef __cplusplus } #endif