diff --git a/include/bw_gain.h b/include/bw_gain.h
index 8fc8707..ed156e5 100644
--- a/include/bw_gain.h
+++ b/include/bw_gain.h
@@ -23,12 +23,13 @@
* version {{{ 1.2.0 }}}
* requires {{{ bw_common bw_math bw_one_pole }}}
* description {{{
- * Gain.
+ * Smoothed gain module with optional sticky gain-reach threshold.
* }}}
* changelog {{{
*
* - Version 1.2.0:
*
+ * - Added optional sticky gain-reach threshold and related API.
* - Added support for
BW_INCLUDE_WITH_QUOTES
,
* BW_NO_CXX
, and
* BW_CXX_NO_EXTERN_C
.
@@ -118,6 +119,20 @@ typedef struct bw_gain_coeffs bw_gain_coeffs;
/*! <<<```
* Coefficients and related.
*
+ * #### bw_gain_sticky_mode
+ * ```>>> */
+typedef enum {
+ bw_gain_sticky_mode_abs,
+ bw_gain_sticky_mode_rel
+} bw_gain_sticky_mode;
+/*! <<<```
+ * Distance metrics for sticky behavior:
+ * * `bw_gain_sticky_mode_abs`: absolute gain difference
+ * (|`current` - `target`|, with `current` and `target` linear);
+ * * `bw_gain_sticky_mode_rel`: relative gain difference with respect to
+ * target gain (|`current` - `target`| / |`target`|, with `current` and
+ * `target` linear).
+ *
* #### bw_gain_init()
* ```>>> */
static inline void bw_gain_init(
@@ -151,8 +166,26 @@ static inline void bw_gain_update_coeffs_ctrl(
* ```>>> */
static inline void bw_gain_update_coeffs_audio(
bw_gain_coeffs * BW_RESTRICT coeffs);
+
+static inline void bw_gain_update_coeffs_audio_sticky_abs(
+ bw_gain_coeffs * BW_RESTRICT coeffs);
+
+static inline void bw_gain_update_coeffs_audio_sticky_rel(
+ bw_gain_coeffs * BW_RESTRICT coeffs);
/*! <<<```
- * Triggers audio-rate update of coefficients in `coeffs`.
+ * These functions trigger audio-rate update of coefficients in `coeffs`.
+ *
+ * In particular:
+ * * `bw_gain_update_coeffs_audio` assumes that the gain-reach threshold is
+ * `0.f`;
+ * * `bw_gain_update_coeffs_audio_sticky_abs` assumes that the gain-reach
+ * threshold is not `0.f` and the distance metric for sticky behavior is
+ * set to `bw_gain_sticky_mode_abs`;
+ * * `bw_gain_update_coeffs_audio_sticky_rel` assumes that the gain-reach
+ * threshold is not `0.f` and the distance metric for sticky behavior is
+ * set to `bw_gain_sticky_mode_rel`.
+ *
+ * Such assumptions are unchecked even for debugging purposes.
*
* #### bw_gain_process1()
* ```>>> */
@@ -224,6 +257,34 @@ static inline void bw_gain_set_smooth_tau(
*
* Default value: `0.05f`.
*
+ * #### bw_gain_set_sticky_thresh()
+ * ```>>> */
+static inline void bw_gain_set_sticky_thresh(
+ bw_gain_coeffs * BW_RESTRICT coeffs,
+ float value);
+/*! <<<```
+ * Sets the gain-reach threshold specified by `value` in `coeffs`.
+ *
+ * When the difference between the current and the target gain would fall
+ * under such threshold according to the current distance metric (see
+ * `bw_gain_set_sticky_mode()`), the current gain is forcefully set to be
+ * equal to the target gain value.
+ *
+ * Valid range: [`0.f`, `1e18f`].
+ *
+ * Default value: `0.f`.
+ *
+ * #### bw_gain_set_sticky_mode()
+ * ```>>> */
+static inline void bw_gain_set_sticky_mode(
+ bw_gain_coeffs * BW_RESTRICT coeffs,
+ bw_gain_sticky_mode value);
+/*! <<<```
+ * Sets the current distance metric for sticky behavior to `value` in
+ * `coeffs`.
+ *
+ * Default value: `bw_gain_sticky_mode_abs`.
+ *
* #### bw_gain_get_gain_lin()
* ```>>> */
static inline float bw_gain_get_gain_lin(
@@ -371,6 +432,32 @@ static inline void bw_gain_update_coeffs_audio(
BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
}
+static inline void bw_gain_update_coeffs_audio_sticky_abs(
+ bw_gain_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != BW_NULL);
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
+
+ bw_one_pole_update_coeffs_audio(&coeffs->smooth_coeffs);
+ bw_one_pole_process1_sticky_abs(&coeffs->smooth_coeffs, &coeffs->smooth_state, coeffs->gain);
+
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
+}
+
+static inline void bw_gain_update_coeffs_audio_sticky_rel(
+ bw_gain_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != BW_NULL);
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
+
+ bw_one_pole_update_coeffs_audio(&coeffs->smooth_coeffs);
+ bw_one_pole_process1_sticky_rel(&coeffs->smooth_coeffs, &coeffs->smooth_state, coeffs->gain);
+
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
+}
+
static inline float bw_gain_process1(
const bw_gain_coeffs * BW_RESTRICT coeffs,
float x) {
@@ -401,10 +488,21 @@ static inline void bw_gain_process(
BW_ASSERT(y != BW_NULL);
bw_gain_update_coeffs_ctrl(coeffs);
- for (size_t i = 0; i < n_samples; i++) {
- bw_gain_update_coeffs_audio(coeffs);
- y[i] = bw_gain_process1(coeffs, x[i]);
- }
+ if (bw_one_pole_get_sticky_thresh(&coeffs->smooth_coeffs) == 0.f)
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_gain_update_coeffs_audio(coeffs);
+ y[i] = bw_gain_process1(coeffs, x[i]);
+ }
+ else if (bw_one_pole_get_sticky_mode(&coeffs->smooth_coeffs) == bw_one_pole_sticky_mode_abs)
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_gain_update_coeffs_audio_sticky_abs(coeffs);
+ y[i] = bw_gain_process1(coeffs, x[i]);
+ }
+ else
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_gain_update_coeffs_audio_sticky_rel(coeffs);
+ y[i] = bw_gain_process1(coeffs, x[i]);
+ }
BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
@@ -437,11 +535,24 @@ static inline void bw_gain_process_multi(
#endif
bw_gain_update_coeffs_ctrl(coeffs);
- for (size_t i = 0; i < n_samples; i++) {
- bw_gain_update_coeffs_audio(coeffs);
- for (size_t j = 0; j < n_channels; j++)
- y[j][i] = bw_gain_process1(coeffs, x[j][i]);
- }
+ if (bw_one_pole_get_sticky_thresh(&coeffs->smooth_coeffs) == 0.f)
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_gain_update_coeffs_audio(coeffs);
+ for (size_t j = 0; j < n_channels; j++)
+ y[j][i] = bw_gain_process1(coeffs, x[j][i]);
+ }
+ else if (bw_one_pole_get_sticky_mode(&coeffs->smooth_coeffs) == bw_one_pole_sticky_mode_abs)
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_gain_update_coeffs_audio_sticky_abs(coeffs);
+ for (size_t j = 0; j < n_channels; j++)
+ y[j][i] = bw_gain_process1(coeffs, x[j][i]);
+ }
+ else
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_gain_update_coeffs_audio_sticky_rel(coeffs);
+ for (size_t j = 0; j < n_channels; j++)
+ y[j][i] = bw_gain_process1(coeffs, x[j][i]);
+ }
BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_reset_coeffs);
@@ -495,6 +606,35 @@ static inline void bw_gain_set_smooth_tau(
BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_init);
}
+static inline void bw_gain_set_sticky_thresh(
+ bw_gain_coeffs * BW_RESTRICT coeffs,
+ float value) {
+ BW_ASSERT(coeffs != BW_NULL);
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_init);
+ BW_ASSERT(!bw_is_nan(value));
+ BW_ASSERT(value >= 0.f && value <= 1e18f);
+
+ bw_one_pole_set_sticky_thresh(&coeffs->smooth_coeffs, value);
+
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_init);
+}
+
+static inline void bw_gain_set_sticky_mode(
+ bw_gain_coeffs * BW_RESTRICT coeffs,
+ bw_gain_sticky_mode value) {
+ BW_ASSERT(coeffs != BW_NULL);
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_init);
+ BW_ASSERT(value == bw_gain_sticky_mode_abs || value == bw_gain_sticky_mode_rel);
+
+ bw_one_pole_set_sticky_mode(&coeffs->smooth_coeffs, value == bw_gain_sticky_mode_abs ? bw_one_pole_sticky_mode_abs : bw_one_pole_sticky_mode_rel);
+
+ BW_ASSERT_DEEP(bw_gain_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_gain_coeffs_state_init);
+}
+
static inline float bw_gain_get_gain_lin(
const bw_gain_coeffs * BW_RESTRICT coeffs) {
BW_ASSERT(coeffs != BW_NULL);
@@ -585,6 +725,12 @@ public:
void setSmoothTau(
float value);
+ void setStickyThresh(
+ float value);
+
+ void setStickyMode(
+ bw_gain_sticky_mode value);
+
float getGainLin();
float getGainCur();
@@ -654,6 +800,18 @@ inline void Gain::setSmoothTau(
bw_gain_set_smooth_tau(&coeffs, value);
}
+template
+inline void Gain::setStickyThresh(
+ float value) {
+ bw_gain_set_sticky_thresh(&coeffs, value);
+}
+
+template
+inline void Gain::setStickyMode(
+ bw_gain_sticky_mode value) {
+ bw_gain_set_sticky_mode(&coeffs, value);
+}
+
template
inline float Gain::getGainLin() {
return bw_gain_get_gain_lin(&coeffs);
diff --git a/include/bw_one_pole.h b/include/bw_one_pole.h
index afcb612..30c764e 100644
--- a/include/bw_one_pole.h
+++ b/include/bw_one_pole.h
@@ -32,6 +32,9 @@
*
* - Version 1.2.0:
*
+ * - Added
bw_one_pole_get_sticky_thresh()
and
+ * bw_one_pole_get_sticky_mode()
and related C++
+ * API.
* - Added support for
BW_INCLUDE_WITH_QUOTES
,
* BW_NO_CXX
, and
* BW_CXX_NO_EXTERN_C
.
@@ -434,6 +437,20 @@ static inline void bw_one_pole_set_sticky_mode(
*
* Default value: `bw_one_pole_sticky_mode_abs`.
*
+ * #### bw_one_pole_get_sticky_thresh()
+ * ```>>> */
+static inline float bw_one_pole_get_sticky_thresh(
+ const bw_one_pole_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Returns the current target-reach threshold in `coeffs`.
+ *
+ * #### bw_one_pole_get_sticky_thresh()
+ * ```>>> */
+static inline bw_one_pole_sticky_mode bw_one_pole_get_sticky_mode(
+ const bw_one_pole_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Returns the current distance metric for sticky behavior in `coeffs`.
+ *
* #### bw_one_pole_get_y_z1()
* ```>>> */
static inline float bw_one_pole_get_y_z1(
@@ -1180,6 +1197,22 @@ static inline void bw_one_pole_set_sticky_mode(
BW_ASSERT_DEEP(coeffs->state >= bw_one_pole_coeffs_state_init);
}
+static inline float bw_one_pole_get_sticky_thresh(
+ const bw_one_pole_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != BW_NULL);
+ BW_ASSERT_DEEP(bw_one_pole_coeffs_is_valid(coeffs));
+
+ return coeffs->sticky_thresh;
+}
+
+static inline bw_one_pole_sticky_mode bw_one_pole_get_sticky_mode(
+ const bw_one_pole_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != BW_NULL);
+ BW_ASSERT_DEEP(bw_one_pole_coeffs_is_valid(coeffs));
+
+ return coeffs->sticky_mode;
+}
+
static inline float bw_one_pole_get_y_z1(
const bw_one_pole_state * BW_RESTRICT state) {
BW_ASSERT(state != BW_NULL);
@@ -1330,6 +1363,10 @@ public:
void setStickyMode(
bw_one_pole_sticky_mode value);
+ float getStickyThresh();
+
+ bw_one_pole_sticky_mode getStickyMode();
+
float getYZ1(
size_t channel);
/*! <<<...
@@ -1466,6 +1503,16 @@ inline void OnePole::setStickyMode(
bw_one_pole_set_sticky_mode(&coeffs, value);
}
+template
+inline float OnePole::getStickyThresh() {
+ return bw_one_pole_get_sticky_thresh(&coeffs);
+}
+
+template
+inline bw_one_pole_sticky_mode OnePole::getStickyMode() {
+ return bw_one_pole_get_sticky_mode(&coeffs);
+}
+
template
inline float OnePole::getYZ1(
size_t channel) {