Generic nonlinear model of 4-pole OTA-based ladder filters with separated outputs.
This module can approximate, e.g., SSM2040- and CEM3320-based VCF circuits.
The feedforward branch consists of 4 nonlinear 1-pole lowpass filters with unitary dc gain in series, where the input to the first one is the difference between the module input and the feedback signal, followed by a linear 1-pole highpass filter. The nonlinearity in each lowpass stage effectively limits the slew rate of its output signal.
The feedback branch contains an feedforward/input signal mixer, a linear gain stage, a saturation stage, and finally another linear gain stage.
The cutoff is controlled by various parameters/signals, so that its actual instantaneous value is (approximately) computed as
cutoff * 2 ^ (cutoff_mod + x_mod)
limited to the [1e-6f
, 1e12f
] range, where:
Version: 1.0.0
License: proprietary license
Requires:
We can privately hand you one or more example plugins if you are interested.
Module type: DSP
typedef struct bwp_ladder4_ota_coeffs bwp_ladder4_ota_coeffs;
Coefficients and related.
typedef struct bwp_ladder4_ota_state bwp_ladder4_ota_state;
Internal state and related.
static inline void bwp_ladder4_ota_init(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs);
Initializes input parameter values in coeffs
.
static inline void bwp_ladder4_ota_set_sample_rate(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float sample_rate);
Sets the sample_rate
(Hz) value in coeffs
.
static inline void bwp_ladder4_ota_reset_coeffs(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs);
Resets coefficients in coeffs
to assume their target values.
static inline void bwp_ladder4_ota_reset_state(
const bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
bwp_ladder4_ota_state * BW_RESTRICT state,
float x_0,
float x_mod_0,
float * BW_RESTRICT y_lp1_0,
float * BW_RESTRICT y_lp2_0,
float * BW_RESTRICT y_lp3_0,
float * BW_RESTRICT y_lp4_0,
float * BW_RESTRICT y_hp_0);
Resets the given state
to its initial values using the given coeffs
, the initial input value x_0
and the initial cutoff modulation value x_mod_0
.
The corresponding initial 1st-, 2nd-, 3rd-, and 4th-order lowpass output values are put into y_lp1_0
, y_lp2_0
, y_lp3_0
, and y_lp4_0
respectively, while the corresponding initial highpass output value is put into y_hp_0
.
static inline void bwp_ladder4_ota_reset_state_multi(
const bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
bwp_ladder4_ota_state * BW_RESTRICT const * BW_RESTRICT state,
const float * x_0,
const float * x_mod_0,
float * y_lp1_0,
float * y_lp2_0,
float * y_lp3_0,
float * y_lp4_0,
float * y_hp_0,
size_t n_channels);
Resets each of the n_channels
state
s to its initial values using the given coeffs
, the corresponding initial input value in the x_0
array and the corresponding initial cutoff modulation value x_mod_0
.
The corresponding initial 1st-, 2nd-, 3rd-, and 4th-order lowpass output values are put into the y_lp1_0
, y_lp2_0
, y_lp3_0
, and y_lp4_0
arrays, respectively, if they are not BW_NULL
. Similarly, the corresponding initial highpass output values are put into the y_hp_0
array if it is not BW_NULL
.
static inline void bwp_ladder4_ota_update_coeffs_ctrl(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs);
Triggers control-rate update of coefficients in coeffs
.
static inline void bwp_ladder4_ota_update_coeffs_audio(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs);
Triggers audio-rate update of coefficients in coeffs
.
static inline void bwp_ladder4_ota_process1(
const bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
bwp_ladder4_ota_state * BW_RESTRICT state,
float x,
float * BW_RESTRICT y_lp1,
float * BW_RESTRICT y_lp2,
float * BW_RESTRICT y_lp3,
float * BW_RESTRICT y_lp4,
float * BW_RESTRICT y_hp);
static inline void bwp_ladder4_ota_process1_mod(
const bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
bwp_ladder4_ota_state * BW_RESTRICT state,
float x,
float x_mod,
float * BW_RESTRICT y_lp1,
float * BW_RESTRICT y_lp2,
float * BW_RESTRICT y_lp3,
float * BW_RESTRICT y_lp4,
float * BW_RESTRICT y_hp);
These function process one input sample x
using coeffs
, while using and updating state
. The 1st-, 2nd-, 3rd-, and 4th-order lowpass output samples are put into y_lp1
, y_lp2
, y_lp3
, and y_lp4
respectively. Highpass output samples are put into y_hp
.
In particular:
bwp_ladder4_ota_process1()
does not apply audio-rate cutoff frequency modulation;bwp_ladder4_ota_process1_mod()
applies audio-rate cutoff frequency modulation using x_mod
(scale 1.f
/octave) as modulation input.static inline void bwp_ladder4_ota_process(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
bwp_ladder4_ota_state * BW_RESTRICT state,
const float * x,
const float * x_mod,
float * y_lp1,
float * y_lp2,
float * y_lp3,
float * y_lp4,
float * y_hp,
size_t n_samples);
Processes the first n_samples
of the input buffer x
and fills the first n_samples
of the output buffers y_lp1
, y_lp2
, y_lp3
, y_lp4
, and y_hp
(1st-, 2nd-, 3rd-, 4th-order lowpass, and highpass, respectively), if they are not BW_NULL
, while using and updating both coeffs
and state
(control and audio rate).
If x_mod
is not BW_NULL
, it is used as a source of cutoff frequency modulation (scale 1.f
/octave).
static inline void bwp_ladder4_ota_process_multi(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
bwp_ladder4_ota_state * BW_RESTRICT const * BW_RESTRICT state,
const float * const * x,
const float * const * x_mod,
float * const * y_lp1,
float * const * y_lp2,
float * const * y_lp3,
float * const * y_lp4,
float * const * y_hp,
size_t n_channels,
size_t n_samples);
Processes the first n_samples
of the n_channels
input buffers x
and fills the first n_samples
of the n_channels
output buffers y_lp1
, y_lp2
, y_lp3
, y_lp4
, and y_hp
(1st-, 2nd-, 3rd-, 4th-order lowpass, and highpass, respectively), while using and updating both the common coeffs
and each of the n_channels
state
s (control and audio rate).
If x_mod
and the channel-specific element are not BW_NULL
, this is used as a source of cutoff fequency modulation (scale 1.f
/octave) for that channel.
y_lp1
, y_lp2
, y_lp3
, y_lp4
, and y_hp
or any of their elements may be BW_NULL
.
static inline void bwp_ladder4_ota_set_cutoff(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the cutoff frequency to the given value
(Hz) in coeffs
.
Valid range: [1e-6f
, 1e12f
].
Default value: 1e3f
.
static inline void bwp_ladder4_ota_set_cutoff_mod(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the non-smoothed modulation cutoff frequency component to the given value
in coeffs
.
Valid range: [-100.f
, 100.f
].
Default value: 0.f
.
static inline void bwp_ladder4_ota_set_hp_cutoff(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the cutoff frequency of the 1-pole highpass filter to the given value
(Hz) in coeffs
.
Valid range: [1e-6f
, 1e12f
].
Default value: 1.f
.
static inline void bwp_ladder4_ota_set_feedback_x_gain(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the input gain in the feedback mix to value
(linear gain) in coeffs
.
value
must be finite.
Default value: 0.f
.
static inline void bwp_ladder4_ota_set_feedback_pre_gain(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the pre-saturation feedback gain to the value
in coeffs
.
value
must be finite and non-negative.
Default value: 1.f
.
static inline void bwp_ladder4_ota_set_feedback_post_gain(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the post-saturation feedback gain to the value
in coeffs
.
value
must be finite and non-negative.
Default value: 0.f
.
static inline void bwp_ladder4_ota_set_stage_max_rate(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the maximum slew rate in feedforward lowpass stages to the given value
(1/s) in coeffs
.
Valid range: [1e-12f
, 1e12f
].
Default value: 1e6f
.
static inline void bwp_ladder4_ota_set_feedback_clip(
bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
float value);
Sets the feedback clipping level to the given value
in coeffs
.
Valid range: [1e-12f
, 1e12f
].
Default value: 1.f
.
static inline char bwp_ladder4_ota_coeffs_is_valid(
const bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs);
Tries to determine whether coeffs
is valid and returns non-0
if it seems to be the case and 0
if it is certainly not. False positives are possible, false negatives are not.
coeffs
must at least point to a readable memory block of size greater than or equal to that of bwp_ladder4_ota_coeffs
.
static inline char bwp_ladder4_ota_state_is_valid(
const bwp_ladder4_ota_coeffs * BW_RESTRICT coeffs,
const bwp_ladder4_ota_state * BW_RESTRICT state);
Tries to determine whether state
is valid and returns non-0
if it seems to be the case and 0
if it is certainly not. False positives are possible, false negatives are not.
If coeffs
is not BW_NULL
extra cross-checks might be performed (state
is supposed to be associated to coeffs
).
state
must at least point to a readable memory block of size greater than or equal to that of bwp_ladder4_ota_state
.
template<size_t N_CHANNELS>
class Ladder4OTA {
public:
Ladder4OTA();
void setSampleRate(
float sampleRate);
void reset(
float x0 = 0.f,
float xMod = 0.f,
float * BW_RESTRICT yLp10 = nullptr,
float * BW_RESTRICT yLp20 = nullptr,
float * BW_RESTRICT yLp30 = nullptr,
float * BW_RESTRICT yLp40 = nullptr,
float * BW_RESTRICT yHp0 = nullptr);
# ifndef BW_CXX_NO_ARRAY
void reset(
float x0,
float xMod0,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp10,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp20,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp30,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp40,
std::array<float, N_CHANNELS> * BW_RESTRICT yHp0);
# endif
void reset(
const float * x0,
const float * xMod0 = nullptr,
float * yLp10 = nullptr,
float * yLp20 = nullptr,
float * yLp30 = nullptr,
float * yLp40 = nullptr,
float * yHp0 = nullptr);
# ifndef BW_CXX_NO_ARRAY
void reset(
std::array<float, N_CHANNELS> x0,
std::array<float, N_CHANNELS> xMod0,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp10 = nullptr,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp20 = nullptr,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp30 = nullptr,
std::array<float, N_CHANNELS> * BW_RESTRICT yLp40 = nullptr,
std::array<float, N_CHANNELS> * BW_RESTRICT yHp0 = nullptr);
# endif
void process(
const float * const * x,
const float * const * xMod,
float * const * yLp1,
float * const * yLp2,
float * const * yLp3,
float * const * yLp4,
float * const * yHp,
size_t nSamples);
# ifndef BW_CXX_NO_ARRAY
void process(
std::array<const float *, N_CHANNELS> x,
std::array<const float *, N_CHANNELS> xMod,
std::array<float *, N_CHANNELS> yLp1,
std::array<float *, N_CHANNELS> yLp2,
std::array<float *, N_CHANNELS> yLp3,
std::array<float *, N_CHANNELS> yLp4,
std::array<float *, N_CHANNELS> yHp,
size_t nSamples);
# endif
void setCutoff(
float value);
void setCutoffMod(
float value);
void setHpCutoff(
float value);
void setFeedbackXGain(
float value);
void setFeedbackPreGain(
float value);
void setFeedbackPostGain(
float value);
void setStageMaxRate(
float value);
void setFeedbackClip(
float value);
...
}