audio_processor_dynamics/
lib.rs

1// Augmented Audio: Audio libraries and applications
2// Copyright (c) 2022 Pedro Tacla Yamada
3//
4// The MIT License (MIT)
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22// THE SOFTWARE.
23
24//! Implements a compressor [`audio_processor_traits::AudioProcessor`].
25//!
26//! # Background
27//! * [Digital Dynamic Range Compressor Design — A Tutorial and Analysis](https://www.eecs.qmul.ac.uk/~josh/documents/2012/GiannoulisMassbergReiss-dynamicrangecompression-JAES2012.pdf)
28
29use audio_garbage_collector::{make_shared, Shared};
30use audio_processor_traits::{AudioBuffer, AudioContext, AudioProcessor};
31use augmented_audio_volume::db_to_amplitude;
32use handle::CompressorHandle;
33
34type FloatT = augmented_audio_volume::Float;
35
36mod handle {
37    #[cfg(not(feature = "f64"))]
38    use audio_processor_traits::AtomicF32 as AtomicFloat;
39    #[cfg(feature = "f64")]
40    use audio_processor_traits::AtomicF64 as AtomicFloat;
41
42    use super::FloatT;
43
44    pub fn calculate_multiplier(sample_rate: FloatT, duration_ms: FloatT) -> FloatT {
45        let attack_secs = duration_ms * 0.001;
46        let attack_samples = sample_rate * attack_secs;
47        FloatT::exp2(-1.0 / attack_samples)
48    }
49
50    pub struct CompressorHandle {
51        make_up_gain_db: AtomicFloat,
52        knee_width_db: AtomicFloat,
53        threshold_db: AtomicFloat,
54        ratio: AtomicFloat,
55        attack_ms: AtomicFloat,
56        release_ms: AtomicFloat,
57        sample_rate: AtomicFloat,
58    }
59
60    impl Default for CompressorHandle {
61        fn default() -> Self {
62            Self {
63                make_up_gain_db: AtomicFloat::new(0.0),
64                knee_width_db: AtomicFloat::new(0.1),
65                threshold_db: AtomicFloat::new(-10.0),
66                ratio: AtomicFloat::new(2.0),
67                attack_ms: AtomicFloat::new(3.0),
68                release_ms: AtomicFloat::new(10.0),
69                sample_rate: AtomicFloat::new(44100.0),
70            }
71        }
72    }
73
74    impl CompressorHandle {
75        pub fn attack_mult(&self) -> FloatT {
76            calculate_multiplier(self.sample_rate.get(), self.attack_ms.get())
77        }
78
79        pub fn release_mult(&self) -> FloatT {
80            calculate_multiplier(self.sample_rate.get(), self.release_ms.get())
81        }
82
83        pub fn set_attack_ms(&self, value: FloatT) {
84            self.attack_ms.set(value);
85        }
86
87        pub fn set_make_up_gain(&self, value: FloatT) {
88            self.make_up_gain_db.set(value);
89        }
90
91        pub fn set_release_ms(&self, value: FloatT) {
92            self.release_ms.set(value);
93        }
94
95        pub fn set_sample_rate(&self, sample_rate: FloatT) {
96            self.sample_rate.set(sample_rate);
97        }
98
99        pub fn set_threshold(&self, threshold: FloatT) {
100            self.threshold_db.set(threshold)
101        }
102
103        pub fn set_knee_width(&self, width: FloatT) {
104            self.knee_width_db.set(width)
105        }
106
107        pub fn set_ratio(&self, ratio: FloatT) {
108            self.ratio.set(ratio)
109        }
110
111        pub fn ratio(&self) -> FloatT {
112            self.ratio.get()
113        }
114
115        pub fn make_up_gain(&self) -> FloatT {
116            self.make_up_gain_db.get()
117        }
118
119        pub fn threshold(&self) -> FloatT {
120            self.threshold_db.get()
121        }
122
123        pub fn knee_width(&self) -> FloatT {
124            self.knee_width_db.get()
125        }
126    }
127}
128
129pub struct CompressorProcessor {
130    peak_detector_state: PeakDetector,
131    handle: Shared<CompressorHandle>,
132}
133
134impl Default for CompressorProcessor {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140impl CompressorProcessor {
141    pub fn new() -> Self {
142        Self {
143            peak_detector_state: PeakDetector::default(),
144            handle: make_shared(CompressorHandle::default()),
145        }
146    }
147
148    pub fn handle(&self) -> &Shared<CompressorHandle> {
149        &self.handle
150    }
151}
152
153impl AudioProcessor for CompressorProcessor {
154    type SampleType = FloatT;
155
156    fn prepare(&mut self, context: &mut AudioContext) {
157        self.handle
158            .set_sample_rate(context.settings.sample_rate() as FloatT);
159    }
160
161    fn process(&mut self, _context: &mut AudioContext, data: &mut AudioBuffer<Self::SampleType>) {
162        for sample_num in 0..data.num_samples() {
163            let input = data.get_mono(sample_num);
164            self.peak_detector_state.accept_frame(
165                self.handle.attack_mult(),
166                self.handle.release_mult(),
167                input,
168            );
169
170            let gain = self.compute_gain();
171            for channel in data.channels_mut() {
172                channel[sample_num] *= gain;
173            }
174        }
175    }
176}
177
178impl CompressorProcessor {
179    fn compute_gain(&self) -> FloatT {
180        let level = self.peak_detector_state.value;
181        let ratio = self.handle.ratio();
182        let make_up_gain = db_to_amplitude(self.handle.make_up_gain(), 1.0);
183        let threshold = db_to_amplitude(self.handle.threshold(), 1.0);
184        let width = db_to_amplitude(self.handle.knee_width(), 1.0);
185
186        let delta = level - threshold;
187        let output = if (2.0 * delta) < -width {
188            1.0
189        } else if (2.0 * delta.abs()) <= width {
190            1.0 + (1.0 / ratio - 1.0) * (delta + width / 2.0).powf(2.0) / 2.0 * width
191        } else {
192            1.0 + delta * (1.0 / ratio - 1.0)
193        };
194
195        make_up_gain + output
196    }
197}
198
199struct PeakDetector {
200    value: FloatT,
201}
202
203impl Default for PeakDetector {
204    fn default() -> Self {
205        Self { value: 0.0 }
206    }
207}
208
209impl PeakDetector {
210    fn accept_frame(&mut self, attack_mult: FloatT, release_mult: FloatT, new: FloatT) {
211        let new = new.abs();
212        let curr_slope = if self.value > new {
213            release_mult
214        } else {
215            attack_mult
216        };
217        self.value = (self.value * curr_slope) + ((1.0 - curr_slope) * new);
218    }
219}
220
221#[cfg(test)]
222mod test {
223    use audio_processor_testing_helpers::charts::{
224        draw_multi_vec_charts, draw_vec_chart, BLUE, RED,
225    };
226    use audio_processor_testing_helpers::relative_path;
227
228    use audio_processor_file::AudioFileProcessor;
229    use audio_processor_traits::{audio_buffer, AudioProcessorSettings};
230    use augmented_audio_volume::amplitude_to_db;
231
232    use super::*;
233
234    #[test]
235    fn test_peak_detector() {
236        let mut peak = PeakDetector::default();
237        peak.accept_frame(0.01, 0.02, 1.0);
238        assert!(peak.value > 0.0);
239    }
240
241    #[test]
242    fn test_create_compressor() {
243        let _ = CompressorProcessor::new();
244    }
245
246    #[test]
247    fn test_knee_widths() {
248        let amp = db_to_amplitude(0.1, 1.0);
249        assert!(amp > 0.0);
250        assert!(amp < 2.0);
251    }
252
253    #[cfg(target_os = "macos")]
254    #[test]
255    fn test_peak_detector_output() {
256        let output_path = relative_path!("src/peak-detector");
257        let settings = AudioProcessorSettings::default();
258        let mut context = AudioContext::default();
259        let mut input = setup_input_processor(settings);
260        let mut processor = PeakDetector::default();
261        let attack_multi = handle::calculate_multiplier(settings.sample_rate(), 1.0);
262        let release_mult = handle::calculate_multiplier(settings.sample_rate(), 5.0);
263
264        let mut input_vec = vec![];
265        let mut output_vec = vec![];
266        {
267            let mut buffer = AudioBuffer::empty();
268            buffer.resize(2, settings.block_size());
269            let num_chunks = (input.num_samples() / 8) / settings.block_size();
270            for _chunk in 0..num_chunks {
271                audio_buffer::clear(&mut buffer);
272                input.process(&mut context, &mut buffer);
273                for sample_num in 0..buffer.num_samples() {
274                    let input = buffer.get_mono(sample_num);
275                    input_vec.push(input);
276                    processor.accept_frame(attack_multi, release_mult, input);
277                    output_vec.push(processor.value * 2.0);
278                }
279            }
280        }
281
282        draw_multi_vec_charts(
283            &output_path,
284            "Peak Detector",
285            vec![(RED, input_vec), (BLUE, output_vec)],
286        );
287    }
288
289    #[test]
290    fn test_compress_synth_loop() {
291        let output_path = relative_path!("src/compressor");
292        let settings = AudioProcessorSettings::default();
293        let mut context = AudioContext::from(settings);
294        let mut input = setup_input_processor(settings);
295        let mut processor = CompressorProcessor::new();
296        processor.prepare(&mut context);
297        processor.handle.set_ratio(30.0);
298        processor.handle.set_threshold(-10.0);
299        processor.handle.set_attack_ms(1.0);
300        processor.handle.set_release_ms(5.0);
301        processor.handle.set_knee_width(-1.0);
302        processor
303            .handle
304            .set_make_up_gain(amplitude_to_db(0.25, 1.0));
305
306        let mut input_vec = vec![];
307        let mut output_vec = vec![];
308        let mut gain_vec = vec![];
309
310        {
311            let mut buffer = AudioBuffer::empty();
312            buffer.resize(1, settings.block_size());
313            let num_chunks = (input.num_samples() / 8) / settings.block_size();
314            for _chunk in 0..num_chunks {
315                audio_buffer::clear(&mut buffer);
316                input.process(&mut context, &mut buffer);
317                for sample_num in 0..buffer.num_samples() {
318                    let input = buffer.get_mono(sample_num);
319                    input_vec.push(input)
320                }
321
322                for sample in buffer.slice_mut() {
323                    let buf = vec![vec![*sample]];
324                    let mut one_sample = AudioBuffer::new(buf);
325                    processor.process(&mut context, &mut one_sample);
326                    *sample = *one_sample.get(0, 0);
327                    output_vec.push(*sample);
328                    gain_vec.push(processor.compute_gain());
329                }
330            }
331        }
332
333        draw_vec_chart(&output_path, "Input", input_vec);
334        draw_vec_chart(&output_path, "Gain", gain_vec);
335        draw_vec_chart(&output_path, "Output", output_vec);
336    }
337
338    fn setup_input_processor(settings: AudioProcessorSettings) -> AudioFileProcessor {
339        let input_file_path = relative_path!("../../../../input-files/C3-loop.mp3");
340        let mut input = AudioFileProcessor::from_path(
341            audio_garbage_collector::handle(),
342            settings,
343            &input_file_path,
344        )
345        .unwrap();
346        let mut context = AudioContext::from(settings);
347        input.prepare(&mut context);
348        input
349    }
350}