atagen

headroom

public
Star0
Watch0
c454b10854660e0f55220e43b21a60913b505716
1
ssh://brewery@pls.lobotomise.me:33/atagen/headroom.git
crates/headroom-dsp/src/util.rsRust54 lines1.4 KB
1
//! db <-> linear conversions, time constants.
2
3
/// lower bound to avoid `log10(0)`.
4
pub const PEAK_FLOOR: f32 = 1e-20;
5
6
/// inputs at or below [`PEAK_FLOOR`] clamp to `-200 dB`.
7
#[must_use]
8
pub fn lin_to_db(x: f32) -> f32 {
9
    if x <= PEAK_FLOOR {
10
        -200.0
11
    } else {
12
        20.0 * x.log10()
13
    }
14
}
15
16
#[must_use]
17
pub fn db_to_lin(db: f32) -> f32 {
18
    10.0_f32.powf(db / 20.0)
19
}
20
21
/// one-pole smoother coefficient for `y[n] = y[n-1] + alpha * (x[n] - y[n-1])`.
22
/// alpha = `1 - exp(-1 / (tau * fs))`, tau = `time_ms / 1000`. time_ms <= 0 → 1.0 (instant).
23
#[must_use]
24
pub fn time_to_alpha(time_ms: f32, sample_rate: f32) -> f32 {
25
    if time_ms <= 0.0 || sample_rate <= 0.0 {
26
        1.0
27
    } else {
28
        let tau_samples = (time_ms * 1e-3) * sample_rate;
29
        1.0 - (-1.0 / tau_samples).exp()
30
    }
31
}
32
33
#[cfg(test)]
34
mod tests {
35
    use super::*;
36
37
    #[test]
38
    fn db_round_trips() {
39
        for db in [-60.0, -20.0, -6.0, -0.1, 0.0, 3.0, 6.0_f32] {
40
            let lin = db_to_lin(db);
41
            let back = lin_to_db(lin);
42
            assert!((back - db).abs() < 1e-3, "db={db} back={back}");
43
        }
44
    }
45
46
    #[test]
47
    fn time_to_alpha_endpoints() {
48
        assert_eq!(time_to_alpha(0.0, 48_000.0), 1.0);
49
        assert!(time_to_alpha(1000.0, 48_000.0) < 0.01);
50
        // Very fast attack: alpha approaches 1.
51
        let a_fast = time_to_alpha(0.01, 48_000.0);
52
        assert!(a_fast > 0.05, "fast alpha was {a_fast}");
53
    }
54
}