Skip to main content

rescale_core/drc/
compressor.rs

1//! Dynamic Range Audio Compressor dengan Soft-Knee dan Makeup Gain.
2
3use super::envelope::{DetectionMode, EnvelopeFollower};
4use crate::gain::Gain;
5
6/// Konfigurasi parameter kompresor audio
7#[derive(Debug, Clone)]
8pub struct CompressorConfig {
9    pub threshold_db: f32,   // Titik ambang aktivasi (dBFS, misal: -20.0)
10    pub ratio: f32,          // Rasio kompresi (misal: 4.0 untuk 4:1)
11    pub knee_db: f32,        // Lebar kurva transisi halus (dB)
12    pub attack_ms: f32,      // Waktu respon attack (ms)
13    pub release_ms: f32,     // Waktu pemulihan release (ms)
14    pub makeup_gain_db: f32, // Kompensasi level output (dB)
15    pub mode: DetectionMode, // Mode deteksi sinyal (Peak / RMS)
16}
17
18impl Default for CompressorConfig {
19    fn default() -> Self {
20        Self {
21            threshold_db: -20.0,
22            ratio: 4.0,
23            knee_db: 6.0,
24            attack_ms: 10.0,
25            release_ms: 100.0,
26            makeup_gain_db: 0.0,
27            mode: DetectionMode::Peak,
28        }
29    }
30}
31
32#[derive(Debug, Clone)]
33pub struct Compressor {
34    config: CompressorConfig,
35    envelope: EnvelopeFollower,
36}
37
38impl Compressor {
39    pub fn new(sample_rate: f32, config: CompressorConfig) -> Self {
40        let envelope = EnvelopeFollower::new(
41            sample_rate,
42            config.attack_ms,
43            config.release_ms,
44            config.mode,
45        );
46        Self { config, envelope }
47    }
48
49    pub fn set_config(&mut self, sample_rate: f32, config: CompressorConfig) {
50        self.envelope
51            .set_time_constants(sample_rate, config.attack_ms, config.release_ms);
52        self.envelope.set_mode(config.mode);
53        self.config = config;
54    }
55
56    pub fn config(&self) -> &CompressorConfig {
57        &self.config
58    }
59
60    /// Menghitung kurva penguatan (Gain Computer) dengan karakteristik Soft-Knee
61    #[inline(always)]
62    fn compute_gain_reduction_db(&self, env_db: f32) -> f32 {
63        let t = self.config.threshold_db;
64        let r = self.config.ratio.max(1.0);
65        let w = self.config.knee_db;
66
67        if w <= 0.0 {
68            // Hard knee
69            if env_db <= t {
70                0.0
71            } else {
72                (t - env_db) * (1.0 - 1.0 / r)
73            }
74        } else if 2.0 * (env_db - t) < -w {
75            // Sinyal di bawah knee: tidak ada kompresi
76            0.0
77        } else if 2.0 * (env_db - t).abs() <= w {
78            // Sinyal berada dalam transisi kuadratik Soft-Knee
79            let diff = env_db - t + w / 2.0;
80            -(diff * diff) / (2.0 * w) * (1.0 - 1.0 / r)
81        } else {
82            // Sinyal di atas knee: kompresi penuh
83            (t - env_db) * (1.0 - 1.0 / r)
84        }
85    }
86
87    /// Memproses single sample audio
88    #[inline(always)]
89    pub fn process_sample(&mut self, input: f32) -> f32 {
90        let env_linear = self.envelope.process_sample(input);
91        let env_db = Gain::linear_to_db(env_linear);
92
93        let gain_reduction_db = self.compute_gain_reduction_db(env_db);
94        let total_gain_db = gain_reduction_db + self.config.makeup_gain_db;
95        let total_gain_linear = Gain::db_to_linear(total_gain_db);
96
97        input * total_gain_linear
98    }
99
100    /// Memproses seluruh buffer sample in-place
101    pub fn process_buffer(&mut self, buffer: &mut [f32]) {
102        for sample in buffer.iter_mut() {
103            *sample = self.process_sample(*sample);
104        }
105    }
106
107    pub fn reset(&mut self) {
108        self.envelope.reset();
109    }
110}