audio_processor_dynamics/
lib.rs1use 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}