1#![allow(clippy::cast_precision_loss)]
2
3use crate::{ProcessCtx, Processor, StateBlob, StateError};
4
5#[derive(Copy, Clone, Debug, PartialEq)]
7pub struct EnvelopeFollower {
8 attack_ms: f32,
9 release_ms: f32,
10 attack: f32,
11 release: f32,
12 value: f32,
13}
14
15impl EnvelopeFollower {
16 #[must_use]
19 pub fn new(attack_ms: f32, release_ms: f32) -> Self {
20 Self {
21 attack_ms: positive(attack_ms, 5.0),
22 release_ms: positive(release_ms, 100.0),
23 attack: 0.0,
24 release: 0.0,
25 value: 0.0,
26 }
27 }
28 pub fn prepare(&mut self, sample_rate: u32) {
30 let rate = sample_rate.max(1) as f32;
31 self.attack = libm::expf(-1.0 / (self.attack_ms * 0.001 * rate));
32 self.release = libm::expf(-1.0 / (self.release_ms * 0.001 * rate));
33 }
34 pub fn next(&mut self, sample: f32) -> f32 {
36 if !self.value.is_finite() {
37 self.value = 0.0;
38 }
39 let peak = finite_or_zero(sample).abs();
40 let coefficient = if peak > self.value {
41 self.attack
42 } else {
43 self.release
44 };
45 self.value = coefficient * self.value + (1.0 - coefficient) * peak;
46 if self.value.abs() < 1.0e-30 {
47 self.value = 0.0;
48 }
49 self.value
50 }
51 pub fn reset(&mut self) {
53 self.value = 0.0;
54 }
55}
56
57#[derive(Clone, Debug)]
61pub struct Ducker {
62 follower: EnvelopeFollower,
63 threshold: f32,
64 depth: f32,
65 bypass: bool,
66}
67
68impl Ducker {
69 #[must_use]
74 pub fn new(threshold: f32, depth_db: f32, attack_ms: f32, release_ms: f32) -> Self {
75 Self {
76 follower: EnvelopeFollower::new(attack_ms, release_ms),
77 threshold: positive(threshold, 0.1),
78 depth: libm::powf(10.0, -depth_db.abs().min(96.0) / 20.0),
79 bypass: false,
80 }
81 }
82 pub fn set_bypass(&mut self, bypass: bool) {
84 self.bypass = bypass;
85 }
86}
87
88impl Processor for Ducker {
89 fn inputs_per_output(&self) -> usize {
91 2
92 }
93
94 fn history_samples(&self) -> u32 {
95 crate::processor::settling_samples(f64::from(
96 self.follower.attack.max(self.follower.release),
97 ))
98 }
99
100 fn prepare(&mut self, sample_rate: u32, _max_block: usize) {
101 self.follower.prepare(sample_rate);
102 }
103 fn process(&mut self, _ctx: &ProcessCtx, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
104 let channels = outputs.len().min(inputs.len() / 2).min(2);
105 let frames = outputs
106 .iter()
107 .take(channels)
108 .map(|v| v.len())
109 .chain(inputs.iter().take(channels * 2).map(|v| v.len()))
110 .min()
111 .unwrap_or(0);
112 for frame in 0..frames {
113 let sidechain = inputs[channels..channels * 2]
114 .iter()
115 .map(|channel| channel[frame].abs())
116 .fold(0.0_f32, f32::max);
117 let envelope = self.follower.next(sidechain);
118 let mix = ((envelope - self.threshold) / self.threshold.max(1.0e-9)).clamp(0.0, 1.0);
119 let gain = if self.bypass {
120 1.0
121 } else {
122 1.0 + (self.depth - 1.0) * mix
123 };
124 for channel in 0..channels {
125 outputs[channel][frame] = finite_or_zero(inputs[channel][frame]) * gain;
126 }
127 }
128 }
129 fn reset(&mut self) {
130 self.follower.reset();
131 }
132 fn save_state(&self) -> StateBlob {
133 StateBlob::new(self.follower.value.to_le_bytes().to_vec())
134 }
135 fn load_state(&mut self, state: &StateBlob) -> Result<(), StateError> {
136 let bytes = <[u8; 4]>::try_from(state.as_bytes()).map_err(|_| StateError::Mismatch)?;
137 let value = f32::from_le_bytes(bytes);
138 if !value.is_finite() || value < 0.0 {
139 return Err(StateError::Invalid);
140 }
141 self.follower.value = value;
142 Ok(())
143 }
144}
145
146fn finite_or_zero(value: f32) -> f32 {
147 if value.is_finite() { value } else { 0.0 }
148}
149
150fn positive(value: f32, fallback: f32) -> f32 {
151 if value.is_finite() && value > 0.0 {
152 value
153 } else {
154 fallback
155 }
156}