atagen

headroom

public
Star0
Watch0
b4f9722db37afb8ec0881327edf1a085c821d6a9
1
ssh://brewery@pls.lobotomise.me:33/atagen/headroom.git

Blame: crates/headroom-dsp/src/util.rs

614b4e43f9f3atagen2026-05-27 15:49 UTC1
//! db <-> linear conversions, time constants.
614b4e43f9f3atagen2026-05-27 15:49 UTC2
614b4e43f9f3atagen2026-05-27 15:49 UTC3
/// lower bound to avoid `log10(0)`.
614b4e43f9f3atagen2026-05-27 15:49 UTC4
pub const PEAK_FLOOR: f32 = 1e-20;
614b4e43f9f3atagen2026-05-27 15:49 UTC5
614b4e43f9f3atagen2026-05-27 15:49 UTC6
/// inputs at or below [`PEAK_FLOOR`] clamp to `-200 dB`.
614b4e43f9f3atagen2026-05-27 15:49 UTC7
#[must_use]
614b4e43f9f3atagen2026-05-27 15:49 UTC8
pub fn lin_to_db(x: f32) -> f32 {
614b4e43f9f3atagen2026-05-27 15:49 UTC9
    if x <= PEAK_FLOOR {
614b4e43f9f3atagen2026-05-27 15:49 UTC10
        -200.0
614b4e43f9f3atagen2026-05-27 15:49 UTC11
    } else {
614b4e43f9f3atagen2026-05-27 15:49 UTC12
        20.0 * x.log10()
614b4e43f9f3atagen2026-05-27 15:49 UTC13
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC14
}
614b4e43f9f3atagen2026-05-27 15:49 UTC15
614b4e43f9f3atagen2026-05-27 15:49 UTC16
#[must_use]
614b4e43f9f3atagen2026-05-27 15:49 UTC17
pub fn db_to_lin(db: f32) -> f32 {
614b4e43f9f3atagen2026-05-27 15:49 UTC18
    10.0_f32.powf(db / 20.0)
614b4e43f9f3atagen2026-05-27 15:49 UTC19
}
614b4e43f9f3atagen2026-05-27 15:49 UTC20
614b4e43f9f3atagen2026-05-27 15:49 UTC21
/// one-pole smoother coefficient for `y[n] = y[n-1] + alpha * (x[n] - y[n-1])`.
614b4e43f9f3atagen2026-05-27 15:49 UTC22
/// alpha = `1 - exp(-1 / (tau * fs))`, tau = `time_ms / 1000`. time_ms <= 0 → 1.0 (instant).
614b4e43f9f3atagen2026-05-27 15:49 UTC23
#[must_use]
614b4e43f9f3atagen2026-05-27 15:49 UTC24
pub fn time_to_alpha(time_ms: f32, sample_rate: f32) -> f32 {
614b4e43f9f3atagen2026-05-27 15:49 UTC25
    if time_ms <= 0.0 || sample_rate <= 0.0 {
614b4e43f9f3atagen2026-05-27 15:49 UTC26
        1.0
614b4e43f9f3atagen2026-05-27 15:49 UTC27
    } else {
614b4e43f9f3atagen2026-05-27 15:49 UTC28
        let tau_samples = (time_ms * 1e-3) * sample_rate;
614b4e43f9f3atagen2026-05-27 15:49 UTC29
        1.0 - (-1.0 / tau_samples).exp()
614b4e43f9f3atagen2026-05-27 15:49 UTC30
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC31
}
614b4e43f9f3atagen2026-05-27 15:49 UTC32
614b4e43f9f3atagen2026-05-27 15:49 UTC33
#[cfg(test)]
614b4e43f9f3atagen2026-05-27 15:49 UTC34
mod tests {
614b4e43f9f3atagen2026-05-27 15:49 UTC35
    use super::*;
614b4e43f9f3atagen2026-05-27 15:49 UTC36
614b4e43f9f3atagen2026-05-27 15:49 UTC37
    #[test]
614b4e43f9f3atagen2026-05-27 15:49 UTC38
    fn db_round_trips() {
614b4e43f9f3atagen2026-05-27 15:49 UTC39
        for db in [-60.0, -20.0, -6.0, -0.1, 0.0, 3.0, 6.0_f32] {
614b4e43f9f3atagen2026-05-27 15:49 UTC40
            let lin = db_to_lin(db);
614b4e43f9f3atagen2026-05-27 15:49 UTC41
            let back = lin_to_db(lin);
614b4e43f9f3atagen2026-05-27 15:49 UTC42
            assert!((back - db).abs() < 1e-3, "db={db} back={back}");
614b4e43f9f3atagen2026-05-27 15:49 UTC43
        }
614b4e43f9f3atagen2026-05-27 15:49 UTC44
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC45
614b4e43f9f3atagen2026-05-27 15:49 UTC46
    #[test]
614b4e43f9f3atagen2026-05-27 15:49 UTC47
    fn time_to_alpha_endpoints() {
614b4e43f9f3atagen2026-05-27 15:49 UTC48
        assert_eq!(time_to_alpha(0.0, 48_000.0), 1.0);
614b4e43f9f3atagen2026-05-27 15:49 UTC49
        assert!(time_to_alpha(1000.0, 48_000.0) < 0.01);
614b4e43f9f3atagen2026-05-27 15:49 UTC50
        // Very fast attack: alpha approaches 1.
614b4e43f9f3atagen2026-05-27 15:49 UTC51
        let a_fast = time_to_alpha(0.01, 48_000.0);
614b4e43f9f3atagen2026-05-27 15:49 UTC52
        assert!(a_fast > 0.05, "fast alpha was {a_fast}");
614b4e43f9f3atagen2026-05-27 15:49 UTC53
    }
614b4e43f9f3atagen2026-05-27 15:49 UTC54
}