1use std::sync::atomic::Ordering;
2use std::sync::Arc;
3
4use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
5use crate::param::{AudioParam, AudioParamDescriptor};
6use crate::render::{
7    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
8};
9use crate::{AtomicF32, RENDER_QUANTUM_SIZE};
10
11use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelCountMode, ChannelInterpretation};
12
13fn db_to_lin(val: f32) -> f32 {
15    (10.0_f32).powf(val / 20.)
16}
17
18fn lin_to_db(val: f32) -> f32 {
22    if val == 0. {
23        -1000.
24    } else {
25        20. * val.log10() }
27}
28
29#[derive(Clone, Debug)]
39pub struct DynamicsCompressorOptions {
40    pub attack: f32,
41    pub knee: f32,
42    pub ratio: f32,
43    pub release: f32,
44    pub threshold: f32,
45    pub audio_node_options: AudioNodeOptions,
46}
47
48impl Default for DynamicsCompressorOptions {
49    fn default() -> Self {
50        Self {
51            attack: 0.003,   knee: 30.,       ratio: 12.,      release: 0.25,   threshold: -24., audio_node_options: AudioNodeOptions {
57                channel_count: 2,
58                channel_count_mode: ChannelCountMode::ClampedMax,
59                channel_interpretation: ChannelInterpretation::Speakers,
60            },
61        }
62    }
63}
64
65#[track_caller]
73#[inline(always)]
74fn assert_valid_channel_count(count: usize) {
75    assert!(
76        count <= 2,
77        "NotSupportedError - DynamicsCompressorNode channel count cannot be greater than two"
78    );
79}
80
81#[track_caller]
89#[inline(always)]
90fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
91    assert_ne!(
92        mode,
93        ChannelCountMode::Max,
94        "NotSupportedError - DynamicsCompressorNode channel count mode cannot be set to max"
95    );
96}
97
98#[derive(Debug)]
139pub struct DynamicsCompressorNode {
140    registration: AudioContextRegistration,
141    channel_config: ChannelConfig,
142    attack: AudioParam,
143    knee: AudioParam,
144    ratio: AudioParam,
145    release: AudioParam,
146    threshold: AudioParam,
147    reduction: Arc<AtomicF32>,
148}
149
150impl AudioNode for DynamicsCompressorNode {
151    fn registration(&self) -> &AudioContextRegistration {
152        &self.registration
153    }
154
155    fn channel_config(&self) -> &ChannelConfig {
156        &self.channel_config
157    }
158
159    fn number_of_inputs(&self) -> usize {
160        1
161    }
162
163    fn number_of_outputs(&self) -> usize {
164        1
165    }
166
167    fn set_channel_count(&self, count: usize) {
169        assert_valid_channel_count(count);
170        self.channel_config.set_count(count, self.registration());
171    }
172
173    fn set_channel_count_mode(&self, mode: ChannelCountMode) {
175        assert_valid_channel_count_mode(mode);
176        self.channel_config
177            .set_count_mode(mode, self.registration());
178    }
179}
180
181impl DynamicsCompressorNode {
182    pub fn new<C: BaseAudioContext>(context: &C, options: DynamicsCompressorOptions) -> Self {
183        context.base().register(move |registration| {
184            assert_valid_channel_count(options.audio_node_options.channel_count);
185            assert_valid_channel_count_mode(options.audio_node_options.channel_count_mode);
186
187            let attack_param_opts = AudioParamDescriptor {
190                name: String::new(),
191                min_value: 0.,
192                max_value: 1.,
193                default_value: 0.003,
194                automation_rate: crate::param::AutomationRate::K,
195            };
196            let (mut attack_param, attack_proc) =
197                context.create_audio_param(attack_param_opts, ®istration);
198            attack_param.set_automation_rate_constrained(true);
199            attack_param.set_value(options.attack);
200
201            let knee_param_opts = AudioParamDescriptor {
202                name: String::new(),
203                min_value: 0.,
204                max_value: 40.,
205                default_value: 30.,
206                automation_rate: crate::param::AutomationRate::K,
207            };
208            let (mut knee_param, knee_proc) =
209                context.create_audio_param(knee_param_opts, ®istration);
210            knee_param.set_automation_rate_constrained(true);
211            knee_param.set_value(options.knee);
212
213            let ratio_param_opts = AudioParamDescriptor {
214                name: String::new(),
215                min_value: 1.,
216                max_value: 20.,
217                default_value: 12.,
218                automation_rate: crate::param::AutomationRate::K,
219            };
220            let (mut ratio_param, ratio_proc) =
221                context.create_audio_param(ratio_param_opts, ®istration);
222            ratio_param.set_automation_rate_constrained(true);
223            ratio_param.set_value(options.ratio);
224
225            let release_param_opts = AudioParamDescriptor {
226                name: String::new(),
227                min_value: 0.,
228                max_value: 1.,
229                default_value: 0.25,
230                automation_rate: crate::param::AutomationRate::K,
231            };
232            let (mut release_param, release_proc) =
233                context.create_audio_param(release_param_opts, ®istration);
234            release_param.set_automation_rate_constrained(true);
235            release_param.set_value(options.release);
236
237            let threshold_param_opts = AudioParamDescriptor {
238                name: String::new(),
239                min_value: -100.,
240                max_value: 0.,
241                default_value: -24.,
242                automation_rate: crate::param::AutomationRate::K,
243            };
244            let (mut threshold_param, threshold_proc) =
245                context.create_audio_param(threshold_param_opts, ®istration);
246            threshold_param.set_automation_rate_constrained(true);
247            threshold_param.set_value(options.threshold);
248
249            let reduction = Arc::new(AtomicF32::new(0.));
250
251            let ring_buffer_size =
254                (context.sample_rate() * 0.006 / RENDER_QUANTUM_SIZE as f32).ceil() as usize + 1;
255            let ring_buffer = Vec::<AudioRenderQuantum>::with_capacity(ring_buffer_size);
256
257            let render = DynamicsCompressorRenderer {
258                attack: attack_proc,
259                knee: knee_proc,
260                ratio: ratio_proc,
261                release: release_proc,
262                threshold: threshold_proc,
263                reduction: Arc::clone(&reduction),
264                ring_buffer,
265                ring_index: 0,
266                prev_detector_value: 0.,
267            };
268
269            let node = DynamicsCompressorNode {
270                registration,
271                channel_config: options.audio_node_options.into(),
272                attack: attack_param,
273                knee: knee_param,
274                ratio: ratio_param,
275                release: release_param,
276                threshold: threshold_param,
277                reduction,
278            };
279
280            (node, Box::new(render))
281        })
282    }
283
284    pub fn attack(&self) -> &AudioParam {
285        &self.attack
286    }
287
288    pub fn knee(&self) -> &AudioParam {
289        &self.knee
290    }
291
292    pub fn ratio(&self) -> &AudioParam {
293        &self.ratio
294    }
295
296    pub fn release(&self) -> &AudioParam {
297        &self.release
298    }
299
300    pub fn threshold(&self) -> &AudioParam {
301        &self.threshold
302    }
303
304    pub fn reduction(&self) -> f32 {
305        self.reduction.load(Ordering::Relaxed)
306    }
307}
308
309struct DynamicsCompressorRenderer {
310    attack: AudioParamId,
311    knee: AudioParamId,
312    ratio: AudioParamId,
313    release: AudioParamId,
314    threshold: AudioParamId,
315    reduction: Arc<AtomicF32>,
316    ring_buffer: Vec<AudioRenderQuantum>,
317    ring_index: usize,
318    prev_detector_value: f32,
319}
320
321#[allow(clippy::non_send_fields_in_send_ty)]
325unsafe impl Send for DynamicsCompressorRenderer {}
326
327impl AudioProcessor for DynamicsCompressorRenderer {
331    fn process(
332        &mut self,
333        inputs: &[AudioRenderQuantum],
334        outputs: &mut [AudioRenderQuantum],
335        params: AudioParamValues<'_>,
336        scope: &AudioWorkletGlobalScope,
337    ) -> bool {
338        let input = inputs[0].clone();
340        let output = &mut outputs[0];
341        let sample_rate = scope.sample_rate;
342
343        let ring_size = self.ring_buffer.capacity();
344        if self.ring_buffer.len() < ring_size {
346            let mut silence = input.clone();
347            silence.make_silent();
348            self.ring_buffer.resize(ring_size, silence);
349        }
350
351        let threshold = params.get(&self.threshold)[0];
354        let knee = params.get(&self.knee)[0];
355        let ratio = params.get(&self.ratio)[0];
356        let threshold = if knee > 0. {
367            threshold + knee / 2.
368        } else {
369            threshold
370        };
371        let half_knee = knee / 2.;
372        let knee_partial = (1. / ratio - 1.) / (2. * knee);
374
375        let attack = params.get(&self.attack)[0];
377        let release = params.get(&self.release)[0];
378        let attack_tau = (-1. / (attack * sample_rate)).exp();
379        let release_tau = (-1. / (release * sample_rate)).exp();
380
381        let full_range_gain = threshold + (-threshold / ratio);
388        let full_range_makeup = 1. / db_to_lin(full_range_gain);
389        let makeup_gain = lin_to_db(full_range_makeup.powf(0.6));
390
391        let mut prev_detector_value = self.prev_detector_value;
392
393        let mut reduction_gain = 0.; let mut reduction_gains = [0.; 128]; let mut detector_values = [0.; 128]; for i in 0..RENDER_QUANTUM_SIZE {
398            let mut max = f32::MIN;
401
402            for channel in input.channels().iter() {
403                let sample = channel[i].abs();
404                if sample > max {
405                    max = sample;
406                }
407            }
408
409            let sample_db = lin_to_db(max);
412
413            let sample_attenuated = if sample_db <= threshold - half_knee {
418                sample_db
419            } else if sample_db <= threshold + half_knee {
420                sample_db + (sample_db - threshold + half_knee).powi(2) * knee_partial
421            } else {
422                threshold + (sample_db - threshold) / ratio
423            };
424            let sample_attenuation = sample_db - sample_attenuated;
426
427            let detector_value = if sample_attenuation > prev_detector_value {
432                attack_tau * prev_detector_value + (1. - attack_tau) * sample_attenuation
433            } else {
435                release_tau * prev_detector_value + (1. - release_tau) * sample_attenuation
436            };
437
438            detector_values[i] = detector_value;
439            reduction_gain = -1. * detector_value + makeup_gain;
441            reduction_gains[i] = db_to_lin(reduction_gain);
443            prev_detector_value = detector_value;
445        }
446
447        self.prev_detector_value = prev_detector_value;
449        self.reduction.store(reduction_gain, Ordering::Relaxed);
451
452        self.ring_buffer[self.ring_index] = input;
454
455        let read_index = (self.ring_index + 1) % ring_size;
457        let delayed = &self.ring_buffer[read_index];
458
459        self.ring_index = read_index;
460
461        *output = delayed.clone();
462
463        if output.is_silent() {
466            output.make_silent(); return false;
468        }
469
470        output.channels_mut().iter_mut().for_each(|channel| {
471            channel
472                .iter_mut()
473                .zip(reduction_gains.iter())
474                .for_each(|(o, g)| *o *= g);
475        });
476
477        true
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use float_eq::assert_float_eq;
484
485    use crate::context::OfflineAudioContext;
486    use crate::node::AudioScheduledSourceNode;
487
488    use super::*;
489
490    #[test]
491    fn test_constructor_default() {
492        let context = OfflineAudioContext::new(1, 1, 44_100.);
493        let compressor = DynamicsCompressorNode::new(&context, Default::default());
494
495        assert_float_eq!(compressor.attack().value(), 0.003, abs <= 0.);
496        assert_float_eq!(compressor.knee().value(), 30., abs <= 0.);
497        assert_float_eq!(compressor.ratio().value(), 12., abs <= 0.);
498        assert_float_eq!(compressor.release().value(), 0.25, abs <= 0.);
499        assert_float_eq!(compressor.threshold().value(), -24., abs <= 0.);
500    }
501
502    #[test]
503    fn test_constructor_non_default() {
504        let context = OfflineAudioContext::new(1, 1, 44_100.);
505        let compressor = DynamicsCompressorNode::new(
506            &context,
507            DynamicsCompressorOptions {
508                attack: 0.5,
509                knee: 12.,
510                ratio: 1.,
511                release: 0.75,
512                threshold: -60.,
513                ..DynamicsCompressorOptions::default()
514            },
515        );
516
517        assert_float_eq!(compressor.attack().value(), 0.5, abs <= 0.);
518        assert_float_eq!(compressor.knee().value(), 12., abs <= 0.);
519        assert_float_eq!(compressor.ratio().value(), 1., abs <= 0.);
520        assert_float_eq!(compressor.release().value(), 0.75, abs <= 0.);
521        assert_float_eq!(compressor.threshold().value(), -60., abs <= 0.);
522    }
523
524    #[test]
525    fn test_inner_delay() {
526        let sample_rate = 44_100.;
527        let compressor_delay = 0.006;
528        let non_zero_index = (compressor_delay * sample_rate / RENDER_QUANTUM_SIZE as f32).ceil()
531            as usize
532            * RENDER_QUANTUM_SIZE;
533
534        let mut context = OfflineAudioContext::new(1, 128 * 8, sample_rate);
535
536        let compressor = DynamicsCompressorNode::new(&context, Default::default());
537        compressor.connect(&context.destination());
538
539        let mut buffer = context.create_buffer(1, 128 * 5, sample_rate);
540        let signal = [1.; 128 * 5];
541        buffer.copy_to_channel(&signal, 0);
542
543        let mut src = context.create_buffer_source();
544        src.set_buffer(buffer);
545        src.connect(&compressor);
546        src.start();
547
548        let res = context.start_rendering_sync();
549        let chan = res.channel_data(0).as_slice();
550
551        assert_float_eq!(
553            chan[0..non_zero_index],
554            vec![0.; non_zero_index][..],
555            abs_all <= 0.
556        );
557
558        for sample in chan.iter().take(128 * 8).skip(non_zero_index) {
560            assert!(*sample != 0.);
561        }
562    }
563
564    #[test]
565    fn test_db_to_lin() {
566        assert_float_eq!(db_to_lin(0.), 1., abs <= 0.);
567        assert_float_eq!(db_to_lin(-20.), 0.1, abs <= 1e-8);
568        assert_float_eq!(db_to_lin(-40.), 0.01, abs <= 1e-8);
569        assert_float_eq!(db_to_lin(-60.), 0.001, abs <= 1e-8);
570    }
571
572    #[test]
573    fn test_lin_to_db() {
574        assert_float_eq!(lin_to_db(1.), 0., abs <= 0.);
575        assert_float_eq!(lin_to_db(0.1), -20., abs <= 0.);
576        assert_float_eq!(lin_to_db(0.01), -40., abs <= 0.);
577        assert_float_eq!(lin_to_db(0.001), -60., abs <= 0.);
578        assert_float_eq!(lin_to_db(0.), -1000., abs <= 0.);
580    }
581
582    }