From 9c7d9abc18ea3d8a8f4f19dcc3a457f47ad5cf7d Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 31 Jul 2025 16:26:40 +0200 Subject: [PATCH] refined oneknob compressor --- dynamics/oneknob.h | 73 +++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/dynamics/oneknob.h b/dynamics/oneknob.h index 343dcc6..b6058f7 100644 --- a/dynamics/oneknob.h +++ b/dynamics/oneknob.h @@ -24,26 +24,26 @@ inline sample rms_process(rms_detector& det, sample input) } struct hp_filter { - float a0, a1, b1; - float z1; // filter state + float a0, a1, b1; + float z1; // filter state }; inline void hp_filter_init(hp_filter& f, float samplerate) { - float cutoff = 100.0f; - float w0 = 2.0f * 3.14159265359f * cutoff / samplerate; - float alpha = (1.0f - std::tan(w0 / 2.0f)) / (1.0f + std::tan(w0 / 2.0f)); - f.a0 = 0.5f * (1.0f + alpha); - f.a1 = -0.5f * (1.0f + alpha); - f.b1 = alpha; - f.z1 = 0.0f; + float cutoff = 100.0f; + float w0 = 2.0f * 3.14159265359f * cutoff / samplerate; + float alpha = (1.0f - std::tan(w0 / 2.0f)) / (1.0f + std::tan(w0 / 2.0f)); + f.a0 = 0.5f * (1.0f + alpha); + f.a1 = -0.5f * (1.0f + alpha); + f.b1 = alpha; + f.z1 = 0.0f; } inline float hp_filter_process(hp_filter& f, float x) { - float y = f.a0 * x + f.a1 * f.z1 - f.b1 * f.z1; - f.z1 = x; - return y; + float y = f.a0 * x + f.a1 * f.z1 - f.b1 * f.z1; + f.z1 = x; + return y; } struct oneknob_comp { @@ -63,49 +63,48 @@ inline void oneknob_init(oneknob_comp& comp, float samplerate, float window_ms) { rms_init(comp.detector, samplerate, window_ms); hp_filter_init(comp.filter, samplerate); - comp.amount = 0.0f; - const float attack_ms = 10.f; - const float release_ms = 100.f; + const float attack_ms = 0.2f; + const float release_ms = 150.f; - comp.attack_coef = expf(-1.0f / (samplerate * (attack_ms * 0.001f))); - comp.release_coef = expf(-1.0f / (samplerate * (release_ms * 0.001f))); - comp.envelope_level = 0.f; + comp.attack_coef = expf(-1.0f / (attack_ms * 1e-6 * samplerate)); + comp.release_coef = expf(-1.0f / (release_ms * 1e-3 * samplerate)); + comp.envelope_level = -60.f; comp.sidechain_in = 0.f; } template inline void oneknob_process_block(oneknob_comp& comp, sample** audio, int frames) { - const float threshold = -18.f; - const float min_ratio = 1.0f; - const float max_ratio = 10.0f; - float ratio = min_ratio + comp.amount * (max_ratio - min_ratio); + const float min_user_ratio = 1.0f; + const float max_user_ratio = 20.0f; + const float threshold_db = -9.f; + + const float amount = fmaxf(0.0f, fminf(powf(comp.amount, 2.f), 1.0f)); // clamp to [0, 1] + float ratio = min_user_ratio + amount * (max_user_ratio - min_user_ratio); for (int i = 0; i < frames; ++i) { float rms_value = rms_process(comp.detector, comp.sidechain_in); - float absolute_rms_db = lin_2_db(fabs(rms_value)); + float envelope_in = lin_2_db(fmaxf(fabs(rms_value), 1e-20f)); - // cut envelope below threshold - float overshoot = absolute_rms_db - threshold; - if (overshoot < 0.f) overshoot = 0.f; - - if (overshoot > comp.envelope_level) { - comp.envelope_level = overshoot + comp.attack_coef * (comp.envelope_level - overshoot); - } else { - comp.envelope_level = overshoot + comp.release_coef * (comp.envelope_level - overshoot); + // attack + if (envelope_in > comp.envelope_level) { + comp.envelope_level = envelope_in + comp.attack_coef * (comp.envelope_level - envelope_in); + } + // release + else { + comp.envelope_level = envelope_in + comp.release_coef * (comp.envelope_level - envelope_in); } - if (comp.envelope_level < 0.f) comp.envelope_level = 0.f; + float x = comp.envelope_level; + float y; - float slope = 1.f / ratio; + if (x < threshold_db) y = x; + else y = threshold_db + (x - threshold_db) / ratio; - float gain_reduction_db = comp.envelope_level * (slope - 1.f); + float gain_reduction_db = y - x; float gain_reduction_lin = db_2_lin(gain_reduction_db); - sample input_l = audio[0][i]; - sample input_r = audio[1][i]; - audio[0][i] *= gain_reduction_lin; audio[1][i] *= gain_reduction_lin;